Creation of Cybook 2416 (actually Gen4) repository

This commit is contained in:
mlt
2009-12-18 17:10:00 +00:00
committed by godzil
commit 76f20f4d40
13791 changed files with 6812321 additions and 0 deletions

125
drivers/misc/Kconfig Normal file
View File

@@ -0,0 +1,125 @@
#
# Misc strange devices
#
menu "Misc devices"
config IBM_ASM
tristate "Device driver for IBM RSA service processor"
depends on X86 && PCI && EXPERIMENTAL
---help---
This option enables device driver support for in-band access to the
IBM RSA (Condor) service processor in eServer xSeries systems.
The ibmasm device driver allows user space application to access
ASM (Advanced Systems Management) functions on the service
processor. The driver is meant to be used in conjunction with
a user space API.
The ibmasm driver also enables the OS to use the UART on the
service processor board as a regular serial port. To make use of
this feature serial driver support (CONFIG_SERIAL_8250) must be
enabled.
WARNING: This software may not be supported or function
correctly on your IBM server. Please consult the IBM ServerProven
website <http://www.pc.ibm.com/ww/eserver/xseries/serverproven> for
information on the specific driver level and support statement
for your IBM server.
If unsure, say N.
config SGI_IOC4
tristate "SGI IOC4 Base IO support"
depends on PCI
---help---
This option enables basic support for the IOC4 chip on certain
SGI IO controller cards (IO9, IO10, and PCI-RT). This option
does not enable any specific functions on such a card, but provides
necessary infrastructure for other drivers to utilize.
If you have an SGI Altix with an IOC4-based card say Y.
Otherwise say N.
config TIFM_CORE
tristate "TI Flash Media interface support (EXPERIMENTAL)"
depends on EXPERIMENTAL && PCI
help
If you want support for Texas Instruments(R) Flash Media adapters
you should select this option and then also choose an appropriate
host adapter, such as 'TI Flash Media PCI74xx/PCI76xx host adapter
support', if you have a TI PCI74xx compatible card reader, for
example.
You will also have to select some flash card format drivers. MMC/SD
cards are supported via 'MMC/SD Card support: TI Flash Media MMC/SD
Interface support (MMC_TIFM_SD)'.
To compile this driver as a module, choose M here: the module will
be called tifm_core.
config TIFM_7XX1
tristate "TI Flash Media PCI74xx/PCI76xx host adapter support (EXPERIMENTAL)"
depends on PCI && TIFM_CORE && EXPERIMENTAL
default TIFM_CORE
help
This option enables support for Texas Instruments(R) PCI74xx and
PCI76xx families of Flash Media adapters, found in many laptops.
To make actual use of the device, you will have to select some
flash card format drivers, as outlined in the TIFM_CORE Help.
To compile this driver as a module, choose M here: the module will
be called tifm_7xx1.
config ASUS_LAPTOP
tristate "Asus Laptop Extras (EXPERIMENTAL)"
depends on X86
depends on ACPI
depends on EXPERIMENTAL && !ACPI_ASUS
depends on LEDS_CLASS
depends on BACKLIGHT_CLASS_DEVICE
---help---
This is the new Linux driver for Asus laptops. It may also support some
MEDION, JVC or VICTOR laptops. It makes all the extra buttons generate
standard ACPI events that go through /proc/acpi/events. It also adds
support for video output switching, LCD backlight control, Bluetooth and
Wlan control, and most importantly, allows you to blink those fancy LEDs.
For more information and a userspace daemon for handling the extra
buttons see <http://acpi4asus.sf.net/>.
If you have an ACPI-compatible ASUS laptop, say Y or M here.
config MSI_LAPTOP
tristate "MSI Laptop Extras"
depends on X86
depends on ACPI_EC
depends on BACKLIGHT_CLASS_DEVICE
---help---
This is a driver for laptops built by MSI (MICRO-STAR
INTERNATIONAL):
MSI MegaBook S270 (MS-1013)
Cytron/TCM/Medion/Tchibo MD96100/SAM2000
It adds support for Bluetooth, WLAN and LCD brightness control.
More information about this driver is available at
<http://0pointer.de/lennart/tchibo.html>.
If you have an MSI S270 laptop, say Y or M here.
config SONY_LAPTOP
tristate "Sony Laptop Extras"
depends on X86 && ACPI
select BACKLIGHT_CLASS_DEVICE
---help---
This mini-driver drives the SNC device present in the ACPI BIOS of
the Sony Vaio laptops.
It gives access to some extra laptop functionalities. In its current
form, this driver let the user set or query the screen brightness
through the backlight subsystem and remove/apply power to some
devices.
Read <file:Documentation/sony-laptop.txt> for more information.
endmenu

14
drivers/misc/Makefile Normal file
View File

@@ -0,0 +1,14 @@
#
# Makefile for misc devices that really don't fit anywhere else.
#
obj- := misc.o # Dummy rule to force built-in.o to be made
obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/
obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o
obj-$(CONFIG_LKDTM) += lkdtm.o
obj-$(CONFIG_TIFM_CORE) += tifm_core.o
obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o
obj-$(CONFIG_SGI_IOC4) += ioc4.o
obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o

1149
drivers/misc/asus-laptop.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
obj-$(CONFIG_HDPU_FEATURES) := hdpu_cpustate.o hdpu_nexus.o

View File

@@ -0,0 +1,248 @@
/*
* Sky CPU State Driver
*
* Copyright (C) 2002 Brian Waite
*
* This driver allows use of the CPU state bits
* It exports the /dev/sky_cpustate and also
* /proc/sky_cpustate pseudo-file for status information.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/miscdevice.h>
#include <linux/pci.h>
#include <linux/proc_fs.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#include <linux/hdpu_features.h>
#define SKY_CPUSTATE_VERSION "1.1"
static int hdpu_cpustate_probe(struct platform_device *pdev);
static int hdpu_cpustate_remove(struct platform_device *pdev);
struct cpustate_t cpustate;
static int cpustate_get_ref(int excl)
{
int retval = -EBUSY;
spin_lock(&cpustate.lock);
if (cpustate.excl)
goto out_busy;
if (excl) {
if (cpustate.open_count)
goto out_busy;
cpustate.excl = 1;
}
cpustate.open_count++;
retval = 0;
out_busy:
spin_unlock(&cpustate.lock);
return retval;
}
static int cpustate_free_ref(void)
{
spin_lock(&cpustate.lock);
cpustate.excl = 0;
cpustate.open_count--;
spin_unlock(&cpustate.lock);
return 0;
}
unsigned char cpustate_get_state(void)
{
return cpustate.cached_val;
}
void cpustate_set_state(unsigned char new_state)
{
unsigned int state = (new_state << 21);
#ifdef DEBUG_CPUSTATE
printk("CPUSTATE -> 0x%x\n", new_state);
#endif
spin_lock(&cpustate.lock);
cpustate.cached_val = new_state;
writel((0xff << 21), cpustate.clr_addr);
writel(state, cpustate.set_addr);
spin_unlock(&cpustate.lock);
}
/*
* Now all the various file operations that we export.
*/
static ssize_t cpustate_read(struct file *file, char *buf,
size_t count, loff_t * ppos)
{
unsigned char data;
if (count < 0)
return -EFAULT;
if (count == 0)
return 0;
data = cpustate_get_state();
if (copy_to_user(buf, &data, sizeof(unsigned char)))
return -EFAULT;
return sizeof(unsigned char);
}
static ssize_t cpustate_write(struct file *file, const char *buf,
size_t count, loff_t * ppos)
{
unsigned char data;
if (count < 0)
return -EFAULT;
if (count == 0)
return 0;
if (copy_from_user((unsigned char *)&data, buf, sizeof(unsigned char)))
return -EFAULT;
cpustate_set_state(data);
return sizeof(unsigned char);
}
static int cpustate_open(struct inode *inode, struct file *file)
{
return cpustate_get_ref((file->f_flags & O_EXCL));
}
static int cpustate_release(struct inode *inode, struct file *file)
{
return cpustate_free_ref();
}
/*
* Info exported via "/proc/sky_cpustate".
*/
static int cpustate_read_proc(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
char *p = page;
int len = 0;
p += sprintf(p, "CPU State: %04x\n", cpustate_get_state());
len = p - page;
if (len <= off + count)
*eof = 1;
*start = page + off;
len -= off;
if (len > count)
len = count;
if (len < 0)
len = 0;
return len;
}
static struct platform_driver hdpu_cpustate_driver = {
.probe = hdpu_cpustate_probe,
.remove = hdpu_cpustate_remove,
.driver = {
.name = HDPU_CPUSTATE_NAME,
},
};
/*
* The various file operations we support.
*/
static const struct file_operations cpustate_fops = {
owner:THIS_MODULE,
open:cpustate_open,
release:cpustate_release,
read:cpustate_read,
write:cpustate_write,
fasync:NULL,
poll:NULL,
ioctl:NULL,
llseek:no_llseek,
};
static struct miscdevice cpustate_dev = {
MISC_DYNAMIC_MINOR,
"sky_cpustate",
&cpustate_fops
};
static int hdpu_cpustate_probe(struct platform_device *pdev)
{
struct resource *res;
struct proc_dir_entry *proc_de;
int ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
cpustate.set_addr = (unsigned long *)res->start;
cpustate.clr_addr = (unsigned long *)res->end - 1;
ret = misc_register(&cpustate_dev);
if (ret) {
printk(KERN_WARNING "sky_cpustate: Unable to register misc "
"device.\n");
cpustate.set_addr = NULL;
cpustate.clr_addr = NULL;
return ret;
}
proc_de = create_proc_read_entry("sky_cpustate", 0, 0,
cpustate_read_proc, NULL);
if (proc_de == NULL)
printk(KERN_WARNING "sky_cpustate: Unable to create proc "
"dir entry\n");
printk(KERN_INFO "Sky CPU State Driver v" SKY_CPUSTATE_VERSION "\n");
return 0;
}
static int hdpu_cpustate_remove(struct platform_device *pdev)
{
cpustate.set_addr = NULL;
cpustate.clr_addr = NULL;
remove_proc_entry("sky_cpustate", NULL);
misc_deregister(&cpustate_dev);
return 0;
}
static int __init cpustate_init(void)
{
int rc;
rc = platform_driver_register(&hdpu_cpustate_driver);
return rc;
}
static void __exit cpustate_exit(void)
{
platform_driver_unregister(&hdpu_cpustate_driver);
}
module_init(cpustate_init);
module_exit(cpustate_exit);
MODULE_AUTHOR("Brian Waite");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,108 @@
/*
* Sky Nexus Register Driver
*
* Copyright (C) 2002 Brian Waite
*
* This driver allows reading the Nexus register
* It exports the /proc/sky_chassis_id and also
* /proc/sky_slot_id pseudo-file for status information.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/hdpu_features.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
static int hdpu_nexus_probe(struct platform_device *pdev);
static int hdpu_nexus_remove(struct platform_device *pdev);
static struct proc_dir_entry *hdpu_slot_id;
static struct proc_dir_entry *hdpu_chassis_id;
static int slot_id = -1;
static int chassis_id = -1;
static struct platform_driver hdpu_nexus_driver = {
.probe = hdpu_nexus_probe,
.remove = hdpu_nexus_remove,
.driver = {
.name = HDPU_NEXUS_NAME,
},
};
int hdpu_slot_id_read(char *buffer, char **buffer_location, off_t offset,
int buffer_length, int *zero, void *ptr)
{
if (offset > 0)
return 0;
return sprintf(buffer, "%d\n", slot_id);
}
int hdpu_chassis_id_read(char *buffer, char **buffer_location, off_t offset,
int buffer_length, int *zero, void *ptr)
{
if (offset > 0)
return 0;
return sprintf(buffer, "%d\n", chassis_id);
}
static int hdpu_nexus_probe(struct platform_device *pdev)
{
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
int *nexus_id_addr;
nexus_id_addr =
ioremap(res->start, (unsigned long)(res->end - res->start));
if (nexus_id_addr) {
slot_id = (*nexus_id_addr >> 8) & 0x1f;
chassis_id = *nexus_id_addr & 0xff;
iounmap(nexus_id_addr);
} else
printk("Could not map slot id\n");
hdpu_slot_id = create_proc_entry("sky_slot_id", 0666, &proc_root);
hdpu_slot_id->read_proc = hdpu_slot_id_read;
hdpu_chassis_id = create_proc_entry("sky_chassis_id", 0666, &proc_root);
hdpu_chassis_id->read_proc = hdpu_chassis_id_read;
return 0;
}
static int hdpu_nexus_remove(struct platform_device *pdev)
{
slot_id = -1;
chassis_id = -1;
remove_proc_entry("sky_slot_id", &proc_root);
remove_proc_entry("sky_chassis_id", &proc_root);
hdpu_slot_id = 0;
hdpu_chassis_id = 0;
return 0;
}
static int __init nexus_init(void)
{
int rc;
rc = platform_driver_register(&hdpu_nexus_driver);
return rc;
}
static void __exit nexus_exit(void)
{
platform_driver_unregister(&hdpu_nexus_driver);
}
module_init(nexus_init);
module_exit(nexus_exit);
MODULE_AUTHOR("Brian Waite");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,15 @@
obj-$(CONFIG_IBM_ASM) := ibmasm.o
ibmasm-y := module.o \
ibmasmfs.o \
event.o \
command.o \
remote.o \
heartbeat.o \
r_heartbeat.o \
dot_command.o \
lowlevel.o
ibmasm-$(CONFIG_SERIAL_8250) += uart.o

View File

@@ -0,0 +1,193 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
#include "ibmasm.h"
#include "lowlevel.h"
static void exec_next_command(struct service_processor *sp);
static void free_command(struct kobject *kobj);
static struct kobj_type ibmasm_cmd_kobj_type = {
.release = free_command,
};
static atomic_t command_count = ATOMIC_INIT(0);
struct command *ibmasm_new_command(struct service_processor *sp, size_t buffer_size)
{
struct command *cmd;
if (buffer_size > IBMASM_CMD_MAX_BUFFER_SIZE)
return NULL;
cmd = kmalloc(sizeof(struct command), GFP_KERNEL);
if (cmd == NULL)
return NULL;
memset(cmd, 0, sizeof(*cmd));
cmd->buffer = kmalloc(buffer_size, GFP_KERNEL);
if (cmd->buffer == NULL) {
kfree(cmd);
return NULL;
}
memset(cmd->buffer, 0, buffer_size);
cmd->buffer_size = buffer_size;
kobject_init(&cmd->kobj);
cmd->kobj.ktype = &ibmasm_cmd_kobj_type;
cmd->lock = &sp->lock;
cmd->status = IBMASM_CMD_PENDING;
init_waitqueue_head(&cmd->wait);
INIT_LIST_HEAD(&cmd->queue_node);
atomic_inc(&command_count);
dbg("command count: %d\n", atomic_read(&command_count));
return cmd;
}
static void free_command(struct kobject *kobj)
{
struct command *cmd = to_command(kobj);
list_del(&cmd->queue_node);
atomic_dec(&command_count);
dbg("command count: %d\n", atomic_read(&command_count));
kfree(cmd->buffer);
kfree(cmd);
}
static void enqueue_command(struct service_processor *sp, struct command *cmd)
{
list_add_tail(&cmd->queue_node, &sp->command_queue);
}
static struct command *dequeue_command(struct service_processor *sp)
{
struct command *cmd;
struct list_head *next;
if (list_empty(&sp->command_queue))
return NULL;
next = sp->command_queue.next;
list_del_init(next);
cmd = list_entry(next, struct command, queue_node);
return cmd;
}
static inline void do_exec_command(struct service_processor *sp)
{
char tsbuf[32];
dbg("%s:%d at %s\n", __FUNCTION__, __LINE__, get_timestamp(tsbuf));
if (ibmasm_send_i2o_message(sp)) {
sp->current_command->status = IBMASM_CMD_FAILED;
wake_up(&sp->current_command->wait);
command_put(sp->current_command);
exec_next_command(sp);
}
}
/**
* exec_command
* send a command to a service processor
* Commands are executed sequentially. One command (sp->current_command)
* is sent to the service processor. Once the interrupt handler gets a
* message of type command_response, the message is copied into
* the current commands buffer,
*/
void ibmasm_exec_command(struct service_processor *sp, struct command *cmd)
{
unsigned long flags;
char tsbuf[32];
dbg("%s:%d at %s\n", __FUNCTION__, __LINE__, get_timestamp(tsbuf));
spin_lock_irqsave(&sp->lock, flags);
if (!sp->current_command) {
sp->current_command = cmd;
command_get(sp->current_command);
spin_unlock_irqrestore(&sp->lock, flags);
do_exec_command(sp);
} else {
enqueue_command(sp, cmd);
spin_unlock_irqrestore(&sp->lock, flags);
}
}
static void exec_next_command(struct service_processor *sp)
{
unsigned long flags;
char tsbuf[32];
dbg("%s:%d at %s\n", __FUNCTION__, __LINE__, get_timestamp(tsbuf));
spin_lock_irqsave(&sp->lock, flags);
sp->current_command = dequeue_command(sp);
if (sp->current_command) {
command_get(sp->current_command);
spin_unlock_irqrestore(&sp->lock, flags);
do_exec_command(sp);
} else {
spin_unlock_irqrestore(&sp->lock, flags);
}
}
/**
* Sleep until a command has failed or a response has been received
* and the command status been updated by the interrupt handler.
* (see receive_response).
*/
void ibmasm_wait_for_response(struct command *cmd, int timeout)
{
wait_event_interruptible_timeout(cmd->wait,
cmd->status == IBMASM_CMD_COMPLETE ||
cmd->status == IBMASM_CMD_FAILED,
timeout * HZ);
}
/**
* receive_command_response
* called by the interrupt handler when a dot command of type command_response
* was received.
*/
void ibmasm_receive_command_response(struct service_processor *sp, void *response, size_t size)
{
struct command *cmd = sp->current_command;
if (!sp->current_command)
return;
memcpy_fromio(cmd->buffer, response, min(size, cmd->buffer_size));
cmd->status = IBMASM_CMD_COMPLETE;
wake_up(&sp->current_command->wait);
command_put(sp->current_command);
exec_next_command(sp);
}

View File

@@ -0,0 +1,152 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
#include "ibmasm.h"
#include "dot_command.h"
/**
* Dispatch an incoming message to the specific handler for the message.
* Called from interrupt context.
*/
void ibmasm_receive_message(struct service_processor *sp, void *message, int message_size)
{
u32 size;
struct dot_command_header *header = (struct dot_command_header *)message;
if (message_size == 0)
return;
size = get_dot_command_size(message);
if (size == 0)
return;
if (size > message_size)
size = message_size;
switch (header->type) {
case sp_event:
ibmasm_receive_event(sp, message, size);
break;
case sp_command_response:
ibmasm_receive_command_response(sp, message, size);
break;
case sp_heartbeat:
ibmasm_receive_heartbeat(sp, message, size);
break;
default:
dev_err(sp->dev, "Received unknown message from service processor\n");
}
}
#define INIT_BUFFER_SIZE 32
/**
* send the 4.3.5.10 dot command (driver VPD) to the service processor
*/
int ibmasm_send_driver_vpd(struct service_processor *sp)
{
struct command *command;
struct dot_command_header *header;
u8 *vpd_command;
u8 *vpd_data;
int result = 0;
command = ibmasm_new_command(sp, INIT_BUFFER_SIZE);
if (command == NULL)
return -ENOMEM;
header = (struct dot_command_header *)command->buffer;
header->type = sp_write;
header->command_size = 4;
header->data_size = 16;
header->status = 0;
header->reserved = 0;
vpd_command = command->buffer + sizeof(struct dot_command_header);
vpd_command[0] = 0x4;
vpd_command[1] = 0x3;
vpd_command[2] = 0x5;
vpd_command[3] = 0xa;
vpd_data = vpd_command + header->command_size;
vpd_data[0] = 0;
strcat(vpd_data, IBMASM_DRIVER_VPD);
vpd_data[10] = 0;
vpd_data[15] = 0;
ibmasm_exec_command(sp, command);
ibmasm_wait_for_response(command, IBMASM_CMD_TIMEOUT_NORMAL);
if (command->status != IBMASM_CMD_COMPLETE)
result = -ENODEV;
command_put(command);
return result;
}
struct os_state_command {
struct dot_command_header header;
unsigned char command[3];
unsigned char data;
};
/**
* send the 4.3.6 dot command (os state) to the service processor
* During driver init this function is called with os state "up".
* This causes the service processor to start sending heartbeats the
* driver.
* During driver exit the function is called with os state "down",
* causing the service processor to stop the heartbeats.
*/
int ibmasm_send_os_state(struct service_processor *sp, int os_state)
{
struct command *cmd;
struct os_state_command *os_state_cmd;
int result = 0;
cmd = ibmasm_new_command(sp, sizeof(struct os_state_command));
if (cmd == NULL)
return -ENOMEM;
os_state_cmd = (struct os_state_command *)cmd->buffer;
os_state_cmd->header.type = sp_write;
os_state_cmd->header.command_size = 3;
os_state_cmd->header.data_size = 1;
os_state_cmd->header.status = 0;
os_state_cmd->command[0] = 4;
os_state_cmd->command[1] = 3;
os_state_cmd->command[2] = 6;
os_state_cmd->data = os_state;
ibmasm_exec_command(sp, cmd);
ibmasm_wait_for_response(cmd, IBMASM_CMD_TIMEOUT_NORMAL);
if (cmd->status != IBMASM_CMD_COMPLETE)
result = -ENODEV;
command_put(cmd);
return result;
}

View File

@@ -0,0 +1,78 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
#ifndef __DOT_COMMAND_H__
#define __DOT_COMMAND_H__
/*
* dot commands are the protocol used to communicate with the service
* processor.
* They consist of header, a command of variable length and data of
* variable length.
*/
/* dot command types */
#define sp_write 0
#define sp_write_next 1
#define sp_read 2
#define sp_read_next 3
#define sp_command_response 4
#define sp_event 5
#define sp_heartbeat 6
#pragma pack(1)
struct dot_command_header {
u8 type;
u8 command_size;
u16 data_size;
u8 status;
u8 reserved;
};
#pragma pack()
static inline size_t get_dot_command_size(void *buffer)
{
struct dot_command_header *cmd = (struct dot_command_header *)buffer;
return sizeof(struct dot_command_header) + cmd->command_size + cmd->data_size;
}
static inline unsigned int get_dot_command_timeout(void *buffer)
{
struct dot_command_header *header = (struct dot_command_header *)buffer;
unsigned char *cmd = buffer + sizeof(struct dot_command_header);
/* dot commands 6.3.1, 7.1 and 8.x need a longer timeout */
if (header->command_size == 3) {
if ((cmd[0] == 6) && (cmd[1] == 3) && (cmd[2] == 1))
return IBMASM_CMD_TIMEOUT_EXTRA;
} else if (header->command_size == 2) {
if ((cmd[0] == 7) && (cmd[1] == 1))
return IBMASM_CMD_TIMEOUT_EXTRA;
if (cmd[0] == 8)
return IBMASM_CMD_TIMEOUT_EXTRA;
}
return IBMASM_CMD_TIMEOUT_NORMAL;
}
#endif /* __DOT_COMMAND_H__ */

175
drivers/misc/ibmasm/event.c Normal file
View File

@@ -0,0 +1,175 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
#include "ibmasm.h"
#include "lowlevel.h"
/*
* ASM service processor event handling routines.
*
* Events are signalled to the device drivers through interrupts.
* They have the format of dot commands, with the type field set to
* sp_event.
* The driver does not interpret the events, it simply stores them in a
* circular buffer.
*/
static void wake_up_event_readers(struct service_processor *sp)
{
struct event_reader *reader;
list_for_each_entry(reader, &sp->event_buffer->readers, node)
wake_up_interruptible(&reader->wait);
}
/**
* receive_event
* Called by the interrupt handler when a dot command of type sp_event is
* received.
* Store the event in the circular event buffer, wake up any sleeping
* event readers.
* There is no reader marker in the buffer, therefore readers are
* responsible for keeping up with the writer, or they will loose events.
*/
void ibmasm_receive_event(struct service_processor *sp, void *data, unsigned int data_size)
{
struct event_buffer *buffer = sp->event_buffer;
struct ibmasm_event *event;
unsigned long flags;
data_size = min(data_size, IBMASM_EVENT_MAX_SIZE);
spin_lock_irqsave(&sp->lock, flags);
/* copy the event into the next slot in the circular buffer */
event = &buffer->events[buffer->next_index];
memcpy_fromio(event->data, data, data_size);
event->data_size = data_size;
event->serial_number = buffer->next_serial_number;
/* advance indices in the buffer */
buffer->next_index = (buffer->next_index + 1) % IBMASM_NUM_EVENTS;
buffer->next_serial_number++;
spin_unlock_irqrestore(&sp->lock, flags);
wake_up_event_readers(sp);
}
static inline int event_available(struct event_buffer *b, struct event_reader *r)
{
return (r->next_serial_number < b->next_serial_number);
}
/**
* get_next_event
* Called by event readers (initiated from user space through the file
* system).
* Sleeps until a new event is available.
*/
int ibmasm_get_next_event(struct service_processor *sp, struct event_reader *reader)
{
struct event_buffer *buffer = sp->event_buffer;
struct ibmasm_event *event;
unsigned int index;
unsigned long flags;
reader->cancelled = 0;
if (wait_event_interruptible(reader->wait,
event_available(buffer, reader) || reader->cancelled))
return -ERESTARTSYS;
if (!event_available(buffer, reader))
return 0;
spin_lock_irqsave(&sp->lock, flags);
index = buffer->next_index;
event = &buffer->events[index];
while (event->serial_number < reader->next_serial_number) {
index = (index + 1) % IBMASM_NUM_EVENTS;
event = &buffer->events[index];
}
memcpy(reader->data, event->data, event->data_size);
reader->data_size = event->data_size;
reader->next_serial_number = event->serial_number + 1;
spin_unlock_irqrestore(&sp->lock, flags);
return event->data_size;
}
void ibmasm_cancel_next_event(struct event_reader *reader)
{
reader->cancelled = 1;
wake_up_interruptible(&reader->wait);
}
void ibmasm_event_reader_register(struct service_processor *sp, struct event_reader *reader)
{
unsigned long flags;
reader->next_serial_number = sp->event_buffer->next_serial_number;
init_waitqueue_head(&reader->wait);
spin_lock_irqsave(&sp->lock, flags);
list_add(&reader->node, &sp->event_buffer->readers);
spin_unlock_irqrestore(&sp->lock, flags);
}
void ibmasm_event_reader_unregister(struct service_processor *sp, struct event_reader *reader)
{
unsigned long flags;
spin_lock_irqsave(&sp->lock, flags);
list_del(&reader->node);
spin_unlock_irqrestore(&sp->lock, flags);
}
int ibmasm_event_buffer_init(struct service_processor *sp)
{
struct event_buffer *buffer;
struct ibmasm_event *event;
int i;
buffer = kmalloc(sizeof(struct event_buffer), GFP_KERNEL);
if (!buffer)
return 1;
buffer->next_index = 0;
buffer->next_serial_number = 1;
event = buffer->events;
for (i=0; i<IBMASM_NUM_EVENTS; i++, event++)
event->serial_number = 0;
INIT_LIST_HEAD(&buffer->readers);
sp->event_buffer = buffer;
return 0;
}
void ibmasm_event_buffer_exit(struct service_processor *sp)
{
kfree(sp->event_buffer);
}

View File

@@ -0,0 +1,101 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
#include <linux/notifier.h>
#include "ibmasm.h"
#include "dot_command.h"
#include "lowlevel.h"
static int suspend_heartbeats = 0;
/*
* Once the driver indicates to the service processor that it is running
* - see send_os_state() - the service processor sends periodic heartbeats
* to the driver. The driver must respond to the heartbeats or else the OS
* will be rebooted.
* In the case of a panic the interrupt handler continues to work and thus
* continues to respond to heartbeats, making the service processor believe
* the OS is still running and thus preventing a reboot.
* To prevent this from happening a callback is added the panic_notifier_list.
* Before responding to a heartbeat the driver checks if a panic has happened,
* if yes it suspends heartbeat, causing the service processor to reboot as
* expected.
*/
static int panic_happened(struct notifier_block *n, unsigned long val, void *v)
{
suspend_heartbeats = 1;
return 0;
}
static struct notifier_block panic_notifier = { panic_happened, NULL, 1 };
void ibmasm_register_panic_notifier(void)
{
atomic_notifier_chain_register(&panic_notifier_list, &panic_notifier);
}
void ibmasm_unregister_panic_notifier(void)
{
atomic_notifier_chain_unregister(&panic_notifier_list,
&panic_notifier);
}
int ibmasm_heartbeat_init(struct service_processor *sp)
{
sp->heartbeat = ibmasm_new_command(sp, HEARTBEAT_BUFFER_SIZE);
if (sp->heartbeat == NULL)
return -ENOMEM;
return 0;
}
void ibmasm_heartbeat_exit(struct service_processor *sp)
{
char tsbuf[32];
dbg("%s:%d at %s\n", __FUNCTION__, __LINE__, get_timestamp(tsbuf));
ibmasm_wait_for_response(sp->heartbeat, IBMASM_CMD_TIMEOUT_NORMAL);
dbg("%s:%d at %s\n", __FUNCTION__, __LINE__, get_timestamp(tsbuf));
suspend_heartbeats = 1;
command_put(sp->heartbeat);
}
void ibmasm_receive_heartbeat(struct service_processor *sp, void *message, size_t size)
{
struct command *cmd = sp->heartbeat;
struct dot_command_header *header = (struct dot_command_header *)cmd->buffer;
char tsbuf[32];
dbg("%s:%d at %s\n", __FUNCTION__, __LINE__, get_timestamp(tsbuf));
if (suspend_heartbeats)
return;
/* return the received dot command to sender */
cmd->status = IBMASM_CMD_PENDING;
size = min(size, cmd->buffer_size);
memcpy_fromio(cmd->buffer, message, size);
header->type = sp_write;
ibmasm_exec_command(sp, cmd);
}

77
drivers/misc/ibmasm/i2o.h Normal file
View File

@@ -0,0 +1,77 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
#pragma pack(1)
struct i2o_header {
u8 version;
u8 message_flags;
u16 message_size;
u8 target;
u8 initiator_and_target;
u8 initiator;
u8 function;
u32 initiator_context;
};
#pragma pack()
#define I2O_HEADER_TEMPLATE \
{ .version = 0x01, \
.message_flags = 0x00, \
.function = 0xFF, \
.initiator = 0x00, \
.initiator_and_target = 0x40, \
.target = 0x00, \
.initiator_context = 0x0 }
#define I2O_MESSAGE_SIZE 0x1000
#define I2O_COMMAND_SIZE (I2O_MESSAGE_SIZE - sizeof(struct i2o_header))
#pragma pack(1)
struct i2o_message {
struct i2o_header header;
void *data;
};
#pragma pack()
static inline unsigned short outgoing_message_size(unsigned int data_size)
{
unsigned int size;
unsigned short i2o_size;
if (data_size > I2O_COMMAND_SIZE)
data_size = I2O_COMMAND_SIZE;
size = sizeof(struct i2o_header) + data_size;
i2o_size = size / sizeof(u32);
if (size % sizeof(u32))
i2o_size++;
return i2o_size;
}
static inline u32 incoming_data_size(struct i2o_message *i2o_message)
{
return (sizeof(u32) * i2o_message->header.message_size);
}

View File

@@ -0,0 +1,218 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/input.h>
/* Driver identification */
#define DRIVER_NAME "ibmasm"
#define DRIVER_VERSION "1.0"
#define DRIVER_AUTHOR "Max Asbock <masbock@us.ibm.com>, Vernon Mauery <vernux@us.ibm.com>"
#define DRIVER_DESC "IBM ASM Service Processor Driver"
#define err(msg) printk(KERN_ERR "%s: " msg "\n", DRIVER_NAME)
#define info(msg) printk(KERN_INFO "%s: " msg "\n", DRIVER_NAME)
extern int ibmasm_debug;
#define dbg(STR, ARGS...) \
do { \
if (ibmasm_debug) \
printk(KERN_DEBUG STR , ##ARGS); \
} while (0)
static inline char *get_timestamp(char *buf)
{
struct timeval now;
do_gettimeofday(&now);
sprintf(buf, "%lu.%lu", now.tv_sec, now.tv_usec);
return buf;
}
#define IBMASM_CMD_PENDING 0
#define IBMASM_CMD_COMPLETE 1
#define IBMASM_CMD_FAILED 2
#define IBMASM_CMD_TIMEOUT_NORMAL 45
#define IBMASM_CMD_TIMEOUT_EXTRA 240
#define IBMASM_CMD_MAX_BUFFER_SIZE 0x8000
#define REVERSE_HEARTBEAT_TIMEOUT 120
#define HEARTBEAT_BUFFER_SIZE 0x400
#ifdef IA64
#define IBMASM_DRIVER_VPD "Lin64 6.08 "
#else
#define IBMASM_DRIVER_VPD "Lin32 6.08 "
#endif
#define SYSTEM_STATE_OS_UP 5
#define SYSTEM_STATE_OS_DOWN 4
#define IBMASM_NAME_SIZE 16
#define IBMASM_NUM_EVENTS 10
#define IBMASM_EVENT_MAX_SIZE 2048u
struct command {
struct list_head queue_node;
wait_queue_head_t wait;
unsigned char *buffer;
size_t buffer_size;
int status;
struct kobject kobj;
spinlock_t *lock;
};
#define to_command(c) container_of(c, struct command, kobj)
static inline void command_put(struct command *cmd)
{
unsigned long flags;
spinlock_t *lock = cmd->lock;
spin_lock_irqsave(lock, flags);
kobject_put(&cmd->kobj);
spin_unlock_irqrestore(lock, flags);
}
static inline void command_get(struct command *cmd)
{
kobject_get(&cmd->kobj);
}
struct ibmasm_event {
unsigned int serial_number;
unsigned int data_size;
unsigned char data[IBMASM_EVENT_MAX_SIZE];
};
struct event_buffer {
struct ibmasm_event events[IBMASM_NUM_EVENTS];
unsigned int next_serial_number;
unsigned int next_index;
struct list_head readers;
};
struct event_reader {
int cancelled;
unsigned int next_serial_number;
wait_queue_head_t wait;
struct list_head node;
unsigned int data_size;
unsigned char data[IBMASM_EVENT_MAX_SIZE];
};
struct reverse_heartbeat {
wait_queue_head_t wait;
unsigned int stopped;
};
struct ibmasm_remote {
struct input_dev *keybd_dev;
struct input_dev *mouse_dev;
};
struct service_processor {
struct list_head node;
spinlock_t lock;
void __iomem *base_address;
unsigned int irq;
struct command *current_command;
struct command *heartbeat;
struct list_head command_queue;
struct event_buffer *event_buffer;
char dirname[IBMASM_NAME_SIZE];
char devname[IBMASM_NAME_SIZE];
unsigned int number;
struct ibmasm_remote remote;
int serial_line;
struct device *dev;
};
/* command processing */
extern struct command *ibmasm_new_command(struct service_processor *sp, size_t buffer_size);
extern void ibmasm_exec_command(struct service_processor *sp, struct command *cmd);
extern void ibmasm_wait_for_response(struct command *cmd, int timeout);
extern void ibmasm_receive_command_response(struct service_processor *sp, void *response, size_t size);
/* event processing */
extern int ibmasm_event_buffer_init(struct service_processor *sp);
extern void ibmasm_event_buffer_exit(struct service_processor *sp);
extern void ibmasm_receive_event(struct service_processor *sp, void *data, unsigned int data_size);
extern void ibmasm_event_reader_register(struct service_processor *sp, struct event_reader *reader);
extern void ibmasm_event_reader_unregister(struct service_processor *sp, struct event_reader *reader);
extern int ibmasm_get_next_event(struct service_processor *sp, struct event_reader *reader);
extern void ibmasm_cancel_next_event(struct event_reader *reader);
/* heartbeat - from SP to OS */
extern void ibmasm_register_panic_notifier(void);
extern void ibmasm_unregister_panic_notifier(void);
extern int ibmasm_heartbeat_init(struct service_processor *sp);
extern void ibmasm_heartbeat_exit(struct service_processor *sp);
extern void ibmasm_receive_heartbeat(struct service_processor *sp, void *message, size_t size);
/* reverse heartbeat - from OS to SP */
extern void ibmasm_init_reverse_heartbeat(struct service_processor *sp, struct reverse_heartbeat *rhb);
extern int ibmasm_start_reverse_heartbeat(struct service_processor *sp, struct reverse_heartbeat *rhb);
extern void ibmasm_stop_reverse_heartbeat(struct reverse_heartbeat *rhb);
/* dot commands */
extern void ibmasm_receive_message(struct service_processor *sp, void *data, int data_size);
extern int ibmasm_send_driver_vpd(struct service_processor *sp);
extern int ibmasm_send_os_state(struct service_processor *sp, int os_state);
/* low level message processing */
extern int ibmasm_send_i2o_message(struct service_processor *sp);
extern irqreturn_t ibmasm_interrupt_handler(int irq, void * dev_id);
/* remote console */
extern void ibmasm_handle_mouse_interrupt(struct service_processor *sp);
extern int ibmasm_init_remote_input_dev(struct service_processor *sp);
extern void ibmasm_free_remote_input_dev(struct service_processor *sp);
/* file system */
extern int ibmasmfs_register(void);
extern void ibmasmfs_unregister(void);
extern void ibmasmfs_add_sp(struct service_processor *sp);
/* uart */
#ifdef CONFIG_SERIAL_8250
extern void ibmasm_register_uart(struct service_processor *sp);
extern void ibmasm_unregister_uart(struct service_processor *sp);
#else
#define ibmasm_register_uart(sp) do { } while(0)
#define ibmasm_unregister_uart(sp) do { } while(0)
#endif

View File

@@ -0,0 +1,638 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
/*
* Parts of this code are based on an article by Jonathan Corbet
* that appeared in Linux Weekly News.
*/
/*
* The IBMASM file virtual filesystem. It creates the following hierarchy
* dymamically when mounted from user space:
*
* /ibmasm
* |-- 0
* | |-- command
* | |-- event
* | |-- reverse_heartbeat
* | `-- remote_video
* | |-- depth
* | |-- height
* | `-- width
* .
* .
* .
* `-- n
* |-- command
* |-- event
* |-- reverse_heartbeat
* `-- remote_video
* |-- depth
* |-- height
* `-- width
*
* For each service processor the following files are created:
*
* command: execute dot commands
* write: execute a dot command on the service processor
* read: return the result of a previously executed dot command
*
* events: listen for service processor events
* read: sleep (interruptible) until an event occurs
* write: wakeup sleeping event listener
*
* reverse_heartbeat: send a heartbeat to the service processor
* read: sleep (interruptible) until the reverse heartbeat fails
* write: wakeup sleeping heartbeat listener
*
* remote_video/width
* remote_video/height
* remote_video/width: control remote display settings
* write: set value
* read: read value
*/
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ibmasm.h"
#include "remote.h"
#include "dot_command.h"
#define IBMASMFS_MAGIC 0x66726f67
static LIST_HEAD(service_processors);
static struct inode *ibmasmfs_make_inode(struct super_block *sb, int mode);
static void ibmasmfs_create_files (struct super_block *sb, struct dentry *root);
static int ibmasmfs_fill_super (struct super_block *sb, void *data, int silent);
static int ibmasmfs_get_super(struct file_system_type *fst,
int flags, const char *name, void *data,
struct vfsmount *mnt)
{
return get_sb_single(fst, flags, data, ibmasmfs_fill_super, mnt);
}
static struct super_operations ibmasmfs_s_ops = {
.statfs = simple_statfs,
.drop_inode = generic_delete_inode,
};
static const struct file_operations *ibmasmfs_dir_ops = &simple_dir_operations;
static struct file_system_type ibmasmfs_type = {
.owner = THIS_MODULE,
.name = "ibmasmfs",
.get_sb = ibmasmfs_get_super,
.kill_sb = kill_litter_super,
};
static int ibmasmfs_fill_super (struct super_block *sb, void *data, int silent)
{
struct inode *root;
struct dentry *root_dentry;
sb->s_blocksize = PAGE_CACHE_SIZE;
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
sb->s_magic = IBMASMFS_MAGIC;
sb->s_op = &ibmasmfs_s_ops;
sb->s_time_gran = 1;
root = ibmasmfs_make_inode (sb, S_IFDIR | 0500);
if (!root)
return -ENOMEM;
root->i_op = &simple_dir_inode_operations;
root->i_fop = ibmasmfs_dir_ops;
root_dentry = d_alloc_root(root);
if (!root_dentry) {
iput(root);
return -ENOMEM;
}
sb->s_root = root_dentry;
ibmasmfs_create_files(sb, root_dentry);
return 0;
}
static struct inode *ibmasmfs_make_inode(struct super_block *sb, int mode)
{
struct inode *ret = new_inode(sb);
if (ret) {
ret->i_mode = mode;
ret->i_uid = ret->i_gid = 0;
ret->i_blocks = 0;
ret->i_atime = ret->i_mtime = ret->i_ctime = CURRENT_TIME;
}
return ret;
}
static struct dentry *ibmasmfs_create_file (struct super_block *sb,
struct dentry *parent,
const char *name,
const struct file_operations *fops,
void *data,
int mode)
{
struct dentry *dentry;
struct inode *inode;
dentry = d_alloc_name(parent, name);
if (!dentry)
return NULL;
inode = ibmasmfs_make_inode(sb, S_IFREG | mode);
if (!inode) {
dput(dentry);
return NULL;
}
inode->i_fop = fops;
inode->i_private = data;
d_add(dentry, inode);
return dentry;
}
static struct dentry *ibmasmfs_create_dir (struct super_block *sb,
struct dentry *parent,
const char *name)
{
struct dentry *dentry;
struct inode *inode;
dentry = d_alloc_name(parent, name);
if (!dentry)
return NULL;
inode = ibmasmfs_make_inode(sb, S_IFDIR | 0500);
if (!inode) {
dput(dentry);
return NULL;
}
inode->i_op = &simple_dir_inode_operations;
inode->i_fop = ibmasmfs_dir_ops;
d_add(dentry, inode);
return dentry;
}
int ibmasmfs_register(void)
{
return register_filesystem(&ibmasmfs_type);
}
void ibmasmfs_unregister(void)
{
unregister_filesystem(&ibmasmfs_type);
}
void ibmasmfs_add_sp(struct service_processor *sp)
{
list_add(&sp->node, &service_processors);
}
/* struct to save state between command file operations */
struct ibmasmfs_command_data {
struct service_processor *sp;
struct command *command;
};
/* struct to save state between event file operations */
struct ibmasmfs_event_data {
struct service_processor *sp;
struct event_reader reader;
int active;
};
/* struct to save state between reverse heartbeat file operations */
struct ibmasmfs_heartbeat_data {
struct service_processor *sp;
struct reverse_heartbeat heartbeat;
int active;
};
static int command_file_open(struct inode *inode, struct file *file)
{
struct ibmasmfs_command_data *command_data;
if (!inode->i_private)
return -ENODEV;
command_data = kmalloc(sizeof(struct ibmasmfs_command_data), GFP_KERNEL);
if (!command_data)
return -ENOMEM;
command_data->command = NULL;
command_data->sp = inode->i_private;
file->private_data = command_data;
return 0;
}
static int command_file_close(struct inode *inode, struct file *file)
{
struct ibmasmfs_command_data *command_data = file->private_data;
if (command_data->command)
command_put(command_data->command);
kfree(command_data);
return 0;
}
static ssize_t command_file_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
struct ibmasmfs_command_data *command_data = file->private_data;
struct command *cmd;
int len;
unsigned long flags;
if (*offset < 0)
return -EINVAL;
if (count == 0 || count > IBMASM_CMD_MAX_BUFFER_SIZE)
return 0;
if (*offset != 0)
return 0;
spin_lock_irqsave(&command_data->sp->lock, flags);
cmd = command_data->command;
if (cmd == NULL) {
spin_unlock_irqrestore(&command_data->sp->lock, flags);
return 0;
}
command_data->command = NULL;
spin_unlock_irqrestore(&command_data->sp->lock, flags);
if (cmd->status != IBMASM_CMD_COMPLETE) {
command_put(cmd);
return -EIO;
}
len = min(count, cmd->buffer_size);
if (copy_to_user(buf, cmd->buffer, len)) {
command_put(cmd);
return -EFAULT;
}
command_put(cmd);
return len;
}
static ssize_t command_file_write(struct file *file, const char __user *ubuff, size_t count, loff_t *offset)
{
struct ibmasmfs_command_data *command_data = file->private_data;
struct command *cmd;
unsigned long flags;
if (*offset < 0)
return -EINVAL;
if (count == 0 || count > IBMASM_CMD_MAX_BUFFER_SIZE)
return 0;
if (*offset != 0)
return 0;
/* commands are executed sequentially, only one command at a time */
if (command_data->command)
return -EAGAIN;
cmd = ibmasm_new_command(command_data->sp, count);
if (!cmd)
return -ENOMEM;
if (copy_from_user(cmd->buffer, ubuff, count)) {
command_put(cmd);
return -EFAULT;
}
spin_lock_irqsave(&command_data->sp->lock, flags);
if (command_data->command) {
spin_unlock_irqrestore(&command_data->sp->lock, flags);
command_put(cmd);
return -EAGAIN;
}
command_data->command = cmd;
spin_unlock_irqrestore(&command_data->sp->lock, flags);
ibmasm_exec_command(command_data->sp, cmd);
ibmasm_wait_for_response(cmd, get_dot_command_timeout(cmd->buffer));
return count;
}
static int event_file_open(struct inode *inode, struct file *file)
{
struct ibmasmfs_event_data *event_data;
struct service_processor *sp;
if (!inode->i_private)
return -ENODEV;
sp = inode->i_private;
event_data = kmalloc(sizeof(struct ibmasmfs_event_data), GFP_KERNEL);
if (!event_data)
return -ENOMEM;
ibmasm_event_reader_register(sp, &event_data->reader);
event_data->sp = sp;
event_data->active = 0;
file->private_data = event_data;
return 0;
}
static int event_file_close(struct inode *inode, struct file *file)
{
struct ibmasmfs_event_data *event_data = file->private_data;
ibmasm_event_reader_unregister(event_data->sp, &event_data->reader);
kfree(event_data);
return 0;
}
static ssize_t event_file_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
struct ibmasmfs_event_data *event_data = file->private_data;
struct event_reader *reader = &event_data->reader;
struct service_processor *sp = event_data->sp;
int ret;
unsigned long flags;
if (*offset < 0)
return -EINVAL;
if (count == 0 || count > IBMASM_EVENT_MAX_SIZE)
return 0;
if (*offset != 0)
return 0;
spin_lock_irqsave(&sp->lock, flags);
if (event_data->active) {
spin_unlock_irqrestore(&sp->lock, flags);
return -EBUSY;
}
event_data->active = 1;
spin_unlock_irqrestore(&sp->lock, flags);
ret = ibmasm_get_next_event(sp, reader);
if (ret <= 0)
goto out;
if (count < reader->data_size) {
ret = -EINVAL;
goto out;
}
if (copy_to_user(buf, reader->data, reader->data_size)) {
ret = -EFAULT;
goto out;
}
ret = reader->data_size;
out:
event_data->active = 0;
return ret;
}
static ssize_t event_file_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
struct ibmasmfs_event_data *event_data = file->private_data;
if (*offset < 0)
return -EINVAL;
if (count != 1)
return 0;
if (*offset != 0)
return 0;
ibmasm_cancel_next_event(&event_data->reader);
return 0;
}
static int r_heartbeat_file_open(struct inode *inode, struct file *file)
{
struct ibmasmfs_heartbeat_data *rhbeat;
if (!inode->i_private)
return -ENODEV;
rhbeat = kmalloc(sizeof(struct ibmasmfs_heartbeat_data), GFP_KERNEL);
if (!rhbeat)
return -ENOMEM;
rhbeat->sp = inode->i_private;
rhbeat->active = 0;
ibmasm_init_reverse_heartbeat(rhbeat->sp, &rhbeat->heartbeat);
file->private_data = rhbeat;
return 0;
}
static int r_heartbeat_file_close(struct inode *inode, struct file *file)
{
struct ibmasmfs_heartbeat_data *rhbeat = file->private_data;
kfree(rhbeat);
return 0;
}
static ssize_t r_heartbeat_file_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
struct ibmasmfs_heartbeat_data *rhbeat = file->private_data;
unsigned long flags;
int result;
if (*offset < 0)
return -EINVAL;
if (count == 0 || count > 1024)
return 0;
if (*offset != 0)
return 0;
/* allow only one reverse heartbeat per process */
spin_lock_irqsave(&rhbeat->sp->lock, flags);
if (rhbeat->active) {
spin_unlock_irqrestore(&rhbeat->sp->lock, flags);
return -EBUSY;
}
rhbeat->active = 1;
spin_unlock_irqrestore(&rhbeat->sp->lock, flags);
result = ibmasm_start_reverse_heartbeat(rhbeat->sp, &rhbeat->heartbeat);
rhbeat->active = 0;
return result;
}
static ssize_t r_heartbeat_file_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
struct ibmasmfs_heartbeat_data *rhbeat = file->private_data;
if (*offset < 0)
return -EINVAL;
if (count != 1)
return 0;
if (*offset != 0)
return 0;
if (rhbeat->active)
ibmasm_stop_reverse_heartbeat(&rhbeat->heartbeat);
return 1;
}
static int remote_settings_file_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
static int remote_settings_file_close(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t remote_settings_file_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
void __iomem *address = (void __iomem *)file->private_data;
unsigned char *page;
int retval;
int len = 0;
unsigned int value;
if (*offset < 0)
return -EINVAL;
if (count == 0 || count > 1024)
return 0;
if (*offset != 0)
return 0;
page = (unsigned char *)__get_free_page(GFP_KERNEL);
if (!page)
return -ENOMEM;
value = readl(address);
len = sprintf(page, "%d\n", value);
if (copy_to_user(buf, page, len)) {
retval = -EFAULT;
goto exit;
}
*offset += len;
retval = len;
exit:
free_page((unsigned long)page);
return retval;
}
static ssize_t remote_settings_file_write(struct file *file, const char __user *ubuff, size_t count, loff_t *offset)
{
void __iomem *address = (void __iomem *)file->private_data;
char *buff;
unsigned int value;
if (*offset < 0)
return -EINVAL;
if (count == 0 || count > 1024)
return 0;
if (*offset != 0)
return 0;
buff = kmalloc (count + 1, GFP_KERNEL);
if (!buff)
return -ENOMEM;
memset(buff, 0x0, count + 1);
if (copy_from_user(buff, ubuff, count)) {
kfree(buff);
return -EFAULT;
}
value = simple_strtoul(buff, NULL, 10);
writel(value, address);
kfree(buff);
return count;
}
static const struct file_operations command_fops = {
.open = command_file_open,
.release = command_file_close,
.read = command_file_read,
.write = command_file_write,
};
static const struct file_operations event_fops = {
.open = event_file_open,
.release = event_file_close,
.read = event_file_read,
.write = event_file_write,
};
static const struct file_operations r_heartbeat_fops = {
.open = r_heartbeat_file_open,
.release = r_heartbeat_file_close,
.read = r_heartbeat_file_read,
.write = r_heartbeat_file_write,
};
static const struct file_operations remote_settings_fops = {
.open = remote_settings_file_open,
.release = remote_settings_file_close,
.read = remote_settings_file_read,
.write = remote_settings_file_write,
};
static void ibmasmfs_create_files (struct super_block *sb, struct dentry *root)
{
struct list_head *entry;
struct service_processor *sp;
list_for_each(entry, &service_processors) {
struct dentry *dir;
struct dentry *remote_dir;
sp = list_entry(entry, struct service_processor, node);
dir = ibmasmfs_create_dir(sb, root, sp->dirname);
if (!dir)
continue;
ibmasmfs_create_file(sb, dir, "command", &command_fops, sp, S_IRUSR|S_IWUSR);
ibmasmfs_create_file(sb, dir, "event", &event_fops, sp, S_IRUSR|S_IWUSR);
ibmasmfs_create_file(sb, dir, "reverse_heartbeat", &r_heartbeat_fops, sp, S_IRUSR|S_IWUSR);
remote_dir = ibmasmfs_create_dir(sb, dir, "remote_video");
if (!remote_dir)
continue;
ibmasmfs_create_file(sb, remote_dir, "width", &remote_settings_fops, (void *)display_width(sp), S_IRUSR|S_IWUSR);
ibmasmfs_create_file(sb, remote_dir, "height", &remote_settings_fops, (void *)display_height(sp), S_IRUSR|S_IWUSR);
ibmasmfs_create_file(sb, remote_dir, "depth", &remote_settings_fops, (void *)display_depth(sp), S_IRUSR|S_IWUSR);
}
}

View File

@@ -0,0 +1,85 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
#include "ibmasm.h"
#include "lowlevel.h"
#include "i2o.h"
#include "dot_command.h"
#include "remote.h"
static struct i2o_header header = I2O_HEADER_TEMPLATE;
int ibmasm_send_i2o_message(struct service_processor *sp)
{
u32 mfa;
unsigned int command_size;
struct i2o_message *message;
struct command *command = sp->current_command;
mfa = get_mfa_inbound(sp->base_address);
if (!mfa)
return 1;
command_size = get_dot_command_size(command->buffer);
header.message_size = outgoing_message_size(command_size);
message = get_i2o_message(sp->base_address, mfa);
memcpy_toio(&message->header, &header, sizeof(struct i2o_header));
memcpy_toio(&message->data, command->buffer, command_size);
set_mfa_inbound(sp->base_address, mfa);
return 0;
}
irqreturn_t ibmasm_interrupt_handler(int irq, void * dev_id)
{
u32 mfa;
struct service_processor *sp = (struct service_processor *)dev_id;
void __iomem *base_address = sp->base_address;
char tsbuf[32];
if (!sp_interrupt_pending(base_address))
return IRQ_NONE;
dbg("respond to interrupt at %s\n", get_timestamp(tsbuf));
if (mouse_interrupt_pending(sp)) {
ibmasm_handle_mouse_interrupt(sp);
clear_mouse_interrupt(sp);
}
mfa = get_mfa_outbound(base_address);
if (valid_mfa(mfa)) {
struct i2o_message *msg = get_i2o_message(base_address, mfa);
ibmasm_receive_message(sp, &msg->data, incoming_data_size(msg));
} else
dbg("didn't get a valid MFA\n");
set_mfa_outbound(base_address, mfa);
dbg("finished interrupt at %s\n", get_timestamp(tsbuf));
return IRQ_HANDLED;
}

View File

@@ -0,0 +1,137 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
/* Condor service processor specific hardware definitions */
#ifndef __IBMASM_CONDOR_H__
#define __IBMASM_CONDOR_H__
#include <asm/io.h>
#define VENDORID_IBM 0x1014
#define DEVICEID_RSA 0x010F
#define GET_MFA_ADDR(x) (x & 0xFFFFFF00)
#define MAILBOX_FULL(x) (x & 0x00000001)
#define NO_MFAS_AVAILABLE 0xFFFFFFFF
#define INBOUND_QUEUE_PORT 0x40 /* contains address of next free MFA */
#define OUTBOUND_QUEUE_PORT 0x44 /* contains address of posted MFA */
#define SP_INTR_MASK 0x00000008
#define UART_INTR_MASK 0x00000010
#define INTR_STATUS_REGISTER 0x13A0
#define INTR_CONTROL_REGISTER 0x13A4
#define SCOUT_COM_A_BASE 0x0000
#define SCOUT_COM_B_BASE 0x0100
#define SCOUT_COM_C_BASE 0x0200
#define SCOUT_COM_D_BASE 0x0300
static inline int sp_interrupt_pending(void __iomem *base_address)
{
return SP_INTR_MASK & readl(base_address + INTR_STATUS_REGISTER);
}
static inline int uart_interrupt_pending(void __iomem *base_address)
{
return UART_INTR_MASK & readl(base_address + INTR_STATUS_REGISTER);
}
static inline void ibmasm_enable_interrupts(void __iomem *base_address, int mask)
{
void __iomem *ctrl_reg = base_address + INTR_CONTROL_REGISTER;
writel( readl(ctrl_reg) & ~mask, ctrl_reg);
}
static inline void ibmasm_disable_interrupts(void __iomem *base_address, int mask)
{
void __iomem *ctrl_reg = base_address + INTR_CONTROL_REGISTER;
writel( readl(ctrl_reg) | mask, ctrl_reg);
}
static inline void enable_sp_interrupts(void __iomem *base_address)
{
ibmasm_enable_interrupts(base_address, SP_INTR_MASK);
}
static inline void disable_sp_interrupts(void __iomem *base_address)
{
ibmasm_disable_interrupts(base_address, SP_INTR_MASK);
}
static inline void enable_uart_interrupts(void __iomem *base_address)
{
ibmasm_enable_interrupts(base_address, UART_INTR_MASK);
}
static inline void disable_uart_interrupts(void __iomem *base_address)
{
ibmasm_disable_interrupts(base_address, UART_INTR_MASK);
}
#define valid_mfa(mfa) ( (mfa) != NO_MFAS_AVAILABLE )
static inline u32 get_mfa_outbound(void __iomem *base_address)
{
int retry;
u32 mfa;
for (retry=0; retry<=10; retry++) {
mfa = readl(base_address + OUTBOUND_QUEUE_PORT);
if (valid_mfa(mfa))
break;
}
return mfa;
}
static inline void set_mfa_outbound(void __iomem *base_address, u32 mfa)
{
writel(mfa, base_address + OUTBOUND_QUEUE_PORT);
}
static inline u32 get_mfa_inbound(void __iomem *base_address)
{
u32 mfa = readl(base_address + INBOUND_QUEUE_PORT);
if (MAILBOX_FULL(mfa))
return 0;
return mfa;
}
static inline void set_mfa_inbound(void __iomem *base_address, u32 mfa)
{
writel(mfa, base_address + INBOUND_QUEUE_PORT);
}
static inline struct i2o_message *get_i2o_message(void __iomem *base_address, u32 mfa)
{
return (struct i2o_message *)(GET_MFA_ADDR(mfa) + base_address);
}
#endif /* __IBMASM_CONDOR_H__ */

View File

@@ -0,0 +1,239 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
* This driver is based on code originally written by Pete Reynolds
* and others.
*
*/
/*
* The ASM device driver does the following things:
*
* 1) When loaded it sends a message to the service processor,
* indicating that an OS is * running. This causes the service processor
* to send periodic heartbeats to the OS.
*
* 2) Answers the periodic heartbeats sent by the service processor.
* Failure to do so would result in system reboot.
*
* 3) Acts as a pass through for dot commands sent from user applications.
* The interface for this is the ibmasmfs file system.
*
* 4) Allows user applications to register for event notification. Events
* are sent to the driver through interrupts. They can be read from user
* space through the ibmasmfs file system.
*
* 5) Allows user space applications to send heartbeats to the service
* processor (aka reverse heartbeats). Again this happens through ibmasmfs.
*
* 6) Handles remote mouse and keyboard event interrupts and makes them
* available to user applications through ibmasmfs.
*
*/
#include <linux/pci.h>
#include <linux/init.h>
#include "ibmasm.h"
#include "lowlevel.h"
#include "remote.h"
int ibmasm_debug = 0;
module_param(ibmasm_debug, int , S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(ibmasm_debug, " Set debug mode on or off");
static int __devinit ibmasm_init_one(struct pci_dev *pdev, const struct pci_device_id *id)
{
int result;
struct service_processor *sp;
if ((result = pci_enable_device(pdev))) {
dev_err(&pdev->dev, "Failed to enable PCI device\n");
return result;
}
if ((result = pci_request_regions(pdev, DRIVER_NAME))) {
dev_err(&pdev->dev, "Failed to allocate PCI resources\n");
goto error_resources;
}
/* vnc client won't work without bus-mastering */
pci_set_master(pdev);
sp = kmalloc(sizeof(struct service_processor), GFP_KERNEL);
if (sp == NULL) {
dev_err(&pdev->dev, "Failed to allocate memory\n");
result = -ENOMEM;
goto error_kmalloc;
}
memset(sp, 0, sizeof(struct service_processor));
spin_lock_init(&sp->lock);
INIT_LIST_HEAD(&sp->command_queue);
pci_set_drvdata(pdev, (void *)sp);
sp->dev = &pdev->dev;
sp->number = pdev->bus->number;
snprintf(sp->dirname, IBMASM_NAME_SIZE, "%d", sp->number);
snprintf(sp->devname, IBMASM_NAME_SIZE, "%s%d", DRIVER_NAME, sp->number);
if (ibmasm_event_buffer_init(sp)) {
dev_err(sp->dev, "Failed to allocate event buffer\n");
goto error_eventbuffer;
}
if (ibmasm_heartbeat_init(sp)) {
dev_err(sp->dev, "Failed to allocate heartbeat command\n");
goto error_heartbeat;
}
sp->irq = pdev->irq;
sp->base_address = ioremap(pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0));
if (sp->base_address == 0) {
dev_err(sp->dev, "Failed to ioremap pci memory\n");
result = -ENODEV;
goto error_ioremap;
}
result = request_irq(sp->irq, ibmasm_interrupt_handler, IRQF_SHARED, sp->devname, (void*)sp);
if (result) {
dev_err(sp->dev, "Failed to register interrupt handler\n");
goto error_request_irq;
}
enable_sp_interrupts(sp->base_address);
result = ibmasm_init_remote_input_dev(sp);
if (result) {
dev_err(sp->dev, "Failed to initialize remote queue\n");
goto error_send_message;
}
result = ibmasm_send_driver_vpd(sp);
if (result) {
dev_err(sp->dev, "Failed to send driver VPD to service processor\n");
goto error_send_message;
}
result = ibmasm_send_os_state(sp, SYSTEM_STATE_OS_UP);
if (result) {
dev_err(sp->dev, "Failed to send OS state to service processor\n");
goto error_send_message;
}
ibmasmfs_add_sp(sp);
ibmasm_register_uart(sp);
return 0;
error_send_message:
disable_sp_interrupts(sp->base_address);
ibmasm_free_remote_input_dev(sp);
free_irq(sp->irq, (void *)sp);
error_request_irq:
iounmap(sp->base_address);
error_ioremap:
ibmasm_heartbeat_exit(sp);
error_heartbeat:
ibmasm_event_buffer_exit(sp);
error_eventbuffer:
pci_set_drvdata(pdev, NULL);
kfree(sp);
error_kmalloc:
pci_release_regions(pdev);
error_resources:
pci_disable_device(pdev);
return result;
}
static void __devexit ibmasm_remove_one(struct pci_dev *pdev)
{
struct service_processor *sp = (struct service_processor *)pci_get_drvdata(pdev);
dbg("Unregistering UART\n");
ibmasm_unregister_uart(sp);
dbg("Sending OS down message\n");
if (ibmasm_send_os_state(sp, SYSTEM_STATE_OS_DOWN))
err("failed to get repsonse to 'Send OS State' command\n");
dbg("Disabling heartbeats\n");
ibmasm_heartbeat_exit(sp);
dbg("Disabling interrupts\n");
disable_sp_interrupts(sp->base_address);
dbg("Freeing SP irq\n");
free_irq(sp->irq, (void *)sp);
dbg("Cleaning up\n");
ibmasm_free_remote_input_dev(sp);
iounmap(sp->base_address);
ibmasm_event_buffer_exit(sp);
pci_set_drvdata(pdev, NULL);
kfree(sp);
pci_release_regions(pdev);
pci_disable_device(pdev);
}
static struct pci_device_id ibmasm_pci_table[] =
{
{ PCI_DEVICE(VENDORID_IBM, DEVICEID_RSA) },
{},
};
static struct pci_driver ibmasm_driver = {
.name = DRIVER_NAME,
.id_table = ibmasm_pci_table,
.probe = ibmasm_init_one,
.remove = __devexit_p(ibmasm_remove_one),
};
static void __exit ibmasm_exit (void)
{
ibmasm_unregister_panic_notifier();
ibmasmfs_unregister();
pci_unregister_driver(&ibmasm_driver);
info(DRIVER_DESC " version " DRIVER_VERSION " unloaded");
}
static int __init ibmasm_init(void)
{
int result;
result = ibmasmfs_register();
if (result) {
err("Failed to register ibmasmfs file system");
return result;
}
result = pci_register_driver(&ibmasm_driver);
if (result) {
ibmasmfs_unregister();
return result;
}
ibmasm_register_panic_notifier();
info(DRIVER_DESC " version " DRIVER_VERSION " loaded");
return 0;
}
module_init(ibmasm_init);
module_exit(ibmasm_exit);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(pci, ibmasm_pci_table);

View File

@@ -0,0 +1,98 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
#include "ibmasm.h"
#include "dot_command.h"
/*
* Reverse Heartbeat, i.e. heartbeats sent from the driver to the
* service processor.
* These heartbeats are initiated by user level programs.
*/
/* the reverse heartbeat dot command */
#pragma pack(1)
static struct {
struct dot_command_header header;
unsigned char command[3];
} rhb_dot_cmd = {
.header = {
.type = sp_read,
.command_size = 3,
.data_size = 0,
.status = 0
},
.command = { 4, 3, 6 }
};
#pragma pack()
void ibmasm_init_reverse_heartbeat(struct service_processor *sp, struct reverse_heartbeat *rhb)
{
init_waitqueue_head(&rhb->wait);
rhb->stopped = 0;
}
/**
* start_reverse_heartbeat
* Loop forever, sending a reverse heartbeat dot command to the service
* processor, then sleeping. The loop comes to an end if the service
* processor fails to respond 3 times or we were interrupted.
*/
int ibmasm_start_reverse_heartbeat(struct service_processor *sp, struct reverse_heartbeat *rhb)
{
struct command *cmd;
int times_failed = 0;
int result = 1;
cmd = ibmasm_new_command(sp, sizeof rhb_dot_cmd);
if (!cmd)
return -ENOMEM;
while (times_failed < 3) {
memcpy(cmd->buffer, (void *)&rhb_dot_cmd, sizeof rhb_dot_cmd);
cmd->status = IBMASM_CMD_PENDING;
ibmasm_exec_command(sp, cmd);
ibmasm_wait_for_response(cmd, IBMASM_CMD_TIMEOUT_NORMAL);
if (cmd->status != IBMASM_CMD_COMPLETE)
times_failed++;
wait_event_interruptible_timeout(rhb->wait,
rhb->stopped,
REVERSE_HEARTBEAT_TIMEOUT * HZ);
if (signal_pending(current) || rhb->stopped) {
result = -EINTR;
break;
}
}
command_put(cmd);
rhb->stopped = 0;
return result;
}
void ibmasm_stop_reverse_heartbeat(struct reverse_heartbeat *rhb)
{
rhb->stopped = 1;
wake_up_interruptible(&rhb->wait);
}

View File

@@ -0,0 +1,283 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Authors: Max Asb<73>ck <amax@us.ibm.com>
* Vernon Mauery <vernux@us.ibm.com>
*
*/
/* Remote mouse and keyboard event handling functions */
#include <linux/pci.h>
#include "ibmasm.h"
#include "remote.h"
static int xmax = 1600;
static int ymax = 1200;
static unsigned short xlate_high[XLATE_SIZE] = {
[KEY_SYM_ENTER & 0xff] = KEY_ENTER,
[KEY_SYM_KPSLASH & 0xff] = KEY_KPSLASH,
[KEY_SYM_KPSTAR & 0xff] = KEY_KPASTERISK,
[KEY_SYM_KPMINUS & 0xff] = KEY_KPMINUS,
[KEY_SYM_KPDOT & 0xff] = KEY_KPDOT,
[KEY_SYM_KPPLUS & 0xff] = KEY_KPPLUS,
[KEY_SYM_KP0 & 0xff] = KEY_KP0,
[KEY_SYM_KP1 & 0xff] = KEY_KP1,
[KEY_SYM_KP2 & 0xff] = KEY_KP2, [KEY_SYM_KPDOWN & 0xff] = KEY_KP2,
[KEY_SYM_KP3 & 0xff] = KEY_KP3,
[KEY_SYM_KP4 & 0xff] = KEY_KP4, [KEY_SYM_KPLEFT & 0xff] = KEY_KP4,
[KEY_SYM_KP5 & 0xff] = KEY_KP5,
[KEY_SYM_KP6 & 0xff] = KEY_KP6, [KEY_SYM_KPRIGHT & 0xff] = KEY_KP6,
[KEY_SYM_KP7 & 0xff] = KEY_KP7,
[KEY_SYM_KP8 & 0xff] = KEY_KP8, [KEY_SYM_KPUP & 0xff] = KEY_KP8,
[KEY_SYM_KP9 & 0xff] = KEY_KP9,
[KEY_SYM_BK_SPC & 0xff] = KEY_BACKSPACE,
[KEY_SYM_TAB & 0xff] = KEY_TAB,
[KEY_SYM_CTRL & 0xff] = KEY_LEFTCTRL,
[KEY_SYM_ALT & 0xff] = KEY_LEFTALT,
[KEY_SYM_INSERT & 0xff] = KEY_INSERT,
[KEY_SYM_DELETE & 0xff] = KEY_DELETE,
[KEY_SYM_SHIFT & 0xff] = KEY_LEFTSHIFT,
[KEY_SYM_UARROW & 0xff] = KEY_UP,
[KEY_SYM_DARROW & 0xff] = KEY_DOWN,
[KEY_SYM_LARROW & 0xff] = KEY_LEFT,
[KEY_SYM_RARROW & 0xff] = KEY_RIGHT,
[KEY_SYM_ESCAPE & 0xff] = KEY_ESC,
[KEY_SYM_PAGEUP & 0xff] = KEY_PAGEUP,
[KEY_SYM_PAGEDOWN & 0xff] = KEY_PAGEDOWN,
[KEY_SYM_HOME & 0xff] = KEY_HOME,
[KEY_SYM_END & 0xff] = KEY_END,
[KEY_SYM_F1 & 0xff] = KEY_F1,
[KEY_SYM_F2 & 0xff] = KEY_F2,
[KEY_SYM_F3 & 0xff] = KEY_F3,
[KEY_SYM_F4 & 0xff] = KEY_F4,
[KEY_SYM_F5 & 0xff] = KEY_F5,
[KEY_SYM_F6 & 0xff] = KEY_F6,
[KEY_SYM_F7 & 0xff] = KEY_F7,
[KEY_SYM_F8 & 0xff] = KEY_F8,
[KEY_SYM_F9 & 0xff] = KEY_F9,
[KEY_SYM_F10 & 0xff] = KEY_F10,
[KEY_SYM_F11 & 0xff] = KEY_F11,
[KEY_SYM_F12 & 0xff] = KEY_F12,
[KEY_SYM_CAP_LOCK & 0xff] = KEY_CAPSLOCK,
[KEY_SYM_NUM_LOCK & 0xff] = KEY_NUMLOCK,
[KEY_SYM_SCR_LOCK & 0xff] = KEY_SCROLLLOCK,
};
static unsigned short xlate[XLATE_SIZE] = {
[NO_KEYCODE] = KEY_RESERVED,
[KEY_SYM_SPACE] = KEY_SPACE,
[KEY_SYM_TILDE] = KEY_GRAVE, [KEY_SYM_BKTIC] = KEY_GRAVE,
[KEY_SYM_ONE] = KEY_1, [KEY_SYM_BANG] = KEY_1,
[KEY_SYM_TWO] = KEY_2, [KEY_SYM_AT] = KEY_2,
[KEY_SYM_THREE] = KEY_3, [KEY_SYM_POUND] = KEY_3,
[KEY_SYM_FOUR] = KEY_4, [KEY_SYM_DOLLAR] = KEY_4,
[KEY_SYM_FIVE] = KEY_5, [KEY_SYM_PERCENT] = KEY_5,
[KEY_SYM_SIX] = KEY_6, [KEY_SYM_CARAT] = KEY_6,
[KEY_SYM_SEVEN] = KEY_7, [KEY_SYM_AMPER] = KEY_7,
[KEY_SYM_EIGHT] = KEY_8, [KEY_SYM_STAR] = KEY_8,
[KEY_SYM_NINE] = KEY_9, [KEY_SYM_LPAREN] = KEY_9,
[KEY_SYM_ZERO] = KEY_0, [KEY_SYM_RPAREN] = KEY_0,
[KEY_SYM_MINUS] = KEY_MINUS, [KEY_SYM_USCORE] = KEY_MINUS,
[KEY_SYM_EQUAL] = KEY_EQUAL, [KEY_SYM_PLUS] = KEY_EQUAL,
[KEY_SYM_LBRKT] = KEY_LEFTBRACE, [KEY_SYM_LCURLY] = KEY_LEFTBRACE,
[KEY_SYM_RBRKT] = KEY_RIGHTBRACE, [KEY_SYM_RCURLY] = KEY_RIGHTBRACE,
[KEY_SYM_SLASH] = KEY_BACKSLASH, [KEY_SYM_PIPE] = KEY_BACKSLASH,
[KEY_SYM_TIC] = KEY_APOSTROPHE, [KEY_SYM_QUOTE] = KEY_APOSTROPHE,
[KEY_SYM_SEMIC] = KEY_SEMICOLON, [KEY_SYM_COLON] = KEY_SEMICOLON,
[KEY_SYM_COMMA] = KEY_COMMA, [KEY_SYM_LT] = KEY_COMMA,
[KEY_SYM_PERIOD] = KEY_DOT, [KEY_SYM_GT] = KEY_DOT,
[KEY_SYM_BSLASH] = KEY_SLASH, [KEY_SYM_QMARK] = KEY_SLASH,
[KEY_SYM_A] = KEY_A, [KEY_SYM_a] = KEY_A,
[KEY_SYM_B] = KEY_B, [KEY_SYM_b] = KEY_B,
[KEY_SYM_C] = KEY_C, [KEY_SYM_c] = KEY_C,
[KEY_SYM_D] = KEY_D, [KEY_SYM_d] = KEY_D,
[KEY_SYM_E] = KEY_E, [KEY_SYM_e] = KEY_E,
[KEY_SYM_F] = KEY_F, [KEY_SYM_f] = KEY_F,
[KEY_SYM_G] = KEY_G, [KEY_SYM_g] = KEY_G,
[KEY_SYM_H] = KEY_H, [KEY_SYM_h] = KEY_H,
[KEY_SYM_I] = KEY_I, [KEY_SYM_i] = KEY_I,
[KEY_SYM_J] = KEY_J, [KEY_SYM_j] = KEY_J,
[KEY_SYM_K] = KEY_K, [KEY_SYM_k] = KEY_K,
[KEY_SYM_L] = KEY_L, [KEY_SYM_l] = KEY_L,
[KEY_SYM_M] = KEY_M, [KEY_SYM_m] = KEY_M,
[KEY_SYM_N] = KEY_N, [KEY_SYM_n] = KEY_N,
[KEY_SYM_O] = KEY_O, [KEY_SYM_o] = KEY_O,
[KEY_SYM_P] = KEY_P, [KEY_SYM_p] = KEY_P,
[KEY_SYM_Q] = KEY_Q, [KEY_SYM_q] = KEY_Q,
[KEY_SYM_R] = KEY_R, [KEY_SYM_r] = KEY_R,
[KEY_SYM_S] = KEY_S, [KEY_SYM_s] = KEY_S,
[KEY_SYM_T] = KEY_T, [KEY_SYM_t] = KEY_T,
[KEY_SYM_U] = KEY_U, [KEY_SYM_u] = KEY_U,
[KEY_SYM_V] = KEY_V, [KEY_SYM_v] = KEY_V,
[KEY_SYM_W] = KEY_W, [KEY_SYM_w] = KEY_W,
[KEY_SYM_X] = KEY_X, [KEY_SYM_x] = KEY_X,
[KEY_SYM_Y] = KEY_Y, [KEY_SYM_y] = KEY_Y,
[KEY_SYM_Z] = KEY_Z, [KEY_SYM_z] = KEY_Z,
};
static char remote_mouse_name[] = "ibmasm RSA I remote mouse";
static char remote_keybd_name[] = "ibmasm RSA I remote keyboard";
static void print_input(struct remote_input *input)
{
if (input->type == INPUT_TYPE_MOUSE) {
unsigned char buttons = input->mouse_buttons;
dbg("remote mouse movement: (x,y)=(%d,%d)%s%s%s%s\n",
input->data.mouse.x, input->data.mouse.y,
(buttons)?" -- buttons:":"",
(buttons & REMOTE_BUTTON_LEFT)?"left ":"",
(buttons & REMOTE_BUTTON_MIDDLE)?"middle ":"",
(buttons & REMOTE_BUTTON_RIGHT)?"right":""
);
} else {
dbg("remote keypress (code, flag, down):"
"%d (0x%x) [0x%x] [0x%x]\n",
input->data.keyboard.key_code,
input->data.keyboard.key_code,
input->data.keyboard.key_flag,
input->data.keyboard.key_down
);
}
}
static void send_mouse_event(struct input_dev *dev, struct remote_input *input)
{
unsigned char buttons = input->mouse_buttons;
input_report_abs(dev, ABS_X, input->data.mouse.x);
input_report_abs(dev, ABS_Y, input->data.mouse.y);
input_report_key(dev, BTN_LEFT, buttons & REMOTE_BUTTON_LEFT);
input_report_key(dev, BTN_MIDDLE, buttons & REMOTE_BUTTON_MIDDLE);
input_report_key(dev, BTN_RIGHT, buttons & REMOTE_BUTTON_RIGHT);
input_sync(dev);
}
static void send_keyboard_event(struct input_dev *dev,
struct remote_input *input)
{
unsigned int key;
unsigned short code = input->data.keyboard.key_code;
if (code & 0xff00)
key = xlate_high[code & 0xff];
else
key = xlate[code];
input_report_key(dev, key, (input->data.keyboard.key_down) ? 1 : 0);
input_sync(dev);
}
void ibmasm_handle_mouse_interrupt(struct service_processor *sp)
{
unsigned long reader;
unsigned long writer;
struct remote_input input;
reader = get_queue_reader(sp);
writer = get_queue_writer(sp);
while (reader != writer) {
memcpy_fromio(&input, get_queue_entry(sp, reader),
sizeof(struct remote_input));
print_input(&input);
if (input.type == INPUT_TYPE_MOUSE) {
send_mouse_event(sp->remote.mouse_dev, &input);
} else if (input.type == INPUT_TYPE_KEYBOARD) {
send_keyboard_event(sp->remote.keybd_dev, &input);
} else
break;
reader = advance_queue_reader(sp, reader);
writer = get_queue_writer(sp);
}
}
int ibmasm_init_remote_input_dev(struct service_processor *sp)
{
/* set up the mouse input device */
struct input_dev *mouse_dev, *keybd_dev;
struct pci_dev *pdev = to_pci_dev(sp->dev);
int error = -ENOMEM;
int i;
sp->remote.mouse_dev = mouse_dev = input_allocate_device();
sp->remote.keybd_dev = keybd_dev = input_allocate_device();
if (!mouse_dev || !keybd_dev)
goto err_free_devices;
mouse_dev->id.bustype = BUS_PCI;
mouse_dev->id.vendor = pdev->vendor;
mouse_dev->id.product = pdev->device;
mouse_dev->id.version = 1;
mouse_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);
mouse_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) |
BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);
set_bit(BTN_TOUCH, mouse_dev->keybit);
mouse_dev->name = remote_mouse_name;
input_set_abs_params(mouse_dev, ABS_X, 0, xmax, 0, 0);
input_set_abs_params(mouse_dev, ABS_Y, 0, ymax, 0, 0);
mouse_dev->id.bustype = BUS_PCI;
keybd_dev->id.vendor = pdev->vendor;
keybd_dev->id.product = pdev->device;
mouse_dev->id.version = 2;
keybd_dev->evbit[0] = BIT(EV_KEY);
keybd_dev->name = remote_keybd_name;
for (i = 0; i < XLATE_SIZE; i++) {
if (xlate_high[i])
set_bit(xlate_high[i], keybd_dev->keybit);
if (xlate[i])
set_bit(xlate[i], keybd_dev->keybit);
}
error = input_register_device(mouse_dev);
if (error)
goto err_free_devices;
error = input_register_device(keybd_dev);
if (error)
goto err_unregister_mouse_dev;
enable_mouse_interrupts(sp);
printk(KERN_INFO "ibmasm remote responding to events on RSA card %d\n", sp->number);
return 0;
err_unregister_mouse_dev:
input_unregister_device(mouse_dev);
mouse_dev = NULL; /* so we don't try to free it again below */
err_free_devices:
input_free_device(mouse_dev);
input_free_device(keybd_dev);
return error;
}
void ibmasm_free_remote_input_dev(struct service_processor *sp)
{
disable_mouse_interrupts(sp);
input_unregister_device(sp->remote.mouse_dev);
input_unregister_device(sp->remote.keybd_dev);
}

View File

@@ -0,0 +1,270 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
* Orignally written by Pete Reynolds
*/
#ifndef _IBMASM_REMOTE_H_
#define _IBMASM_REMOTE_H_
#include <asm/io.h>
/* pci offsets */
#define CONDOR_MOUSE_DATA 0x000AC000
#define CONDOR_MOUSE_ISR_CONTROL 0x00
#define CONDOR_MOUSE_ISR_STATUS 0x04
#define CONDOR_MOUSE_Q_READER 0x08
#define CONDOR_MOUSE_Q_WRITER 0x0C
#define CONDOR_MOUSE_Q_BEGIN 0x10
#define CONDOR_MOUSE_MAX_X 0x14
#define CONDOR_MOUSE_MAX_Y 0x18
#define CONDOR_INPUT_DESKTOP_INFO 0x1F0
#define CONDOR_INPUT_DISPLAY_RESX 0x1F4
#define CONDOR_INPUT_DISPLAY_RESY 0x1F8
#define CONDOR_INPUT_DISPLAY_BITS 0x1FC
#define CONDOR_OUTPUT_VNC_STATUS 0x200
#define CONDOR_MOUSE_INTR_STATUS_MASK 0x00000001
#define INPUT_TYPE_MOUSE 0x1
#define INPUT_TYPE_KEYBOARD 0x2
/* mouse button states received from SP */
#define REMOTE_DOUBLE_CLICK 0xF0
#define REMOTE_BUTTON_LEFT 0x01
#define REMOTE_BUTTON_MIDDLE 0x02
#define REMOTE_BUTTON_RIGHT 0x04
/* size of keysym/keycode translation matricies */
#define XLATE_SIZE 256
struct mouse_input {
unsigned short y;
unsigned short x;
};
struct keyboard_input {
unsigned short key_code;
unsigned char key_flag;
unsigned char key_down;
};
struct remote_input {
union {
struct mouse_input mouse;
struct keyboard_input keyboard;
} data;
unsigned char type;
unsigned char pad1;
unsigned char mouse_buttons;
unsigned char pad3;
};
#define mouse_addr(sp) (sp->base_address + CONDOR_MOUSE_DATA)
#define display_width(sp) (mouse_addr(sp) + CONDOR_INPUT_DISPLAY_RESX)
#define display_height(sp) (mouse_addr(sp) + CONDOR_INPUT_DISPLAY_RESY)
#define display_depth(sp) (mouse_addr(sp) + CONDOR_INPUT_DISPLAY_BITS)
#define desktop_info(sp) (mouse_addr(sp) + CONDOR_INPUT_DESKTOP_INFO)
#define vnc_status(sp) (mouse_addr(sp) + CONDOR_OUTPUT_VNC_STATUS)
#define isr_control(sp) (mouse_addr(sp) + CONDOR_MOUSE_ISR_CONTROL)
#define mouse_interrupt_pending(sp) readl(mouse_addr(sp) + CONDOR_MOUSE_ISR_STATUS)
#define clear_mouse_interrupt(sp) writel(0, mouse_addr(sp) + CONDOR_MOUSE_ISR_STATUS)
#define enable_mouse_interrupts(sp) writel(1, mouse_addr(sp) + CONDOR_MOUSE_ISR_CONTROL)
#define disable_mouse_interrupts(sp) writel(0, mouse_addr(sp) + CONDOR_MOUSE_ISR_CONTROL)
/* remote input queue operations */
#define REMOTE_QUEUE_SIZE 60
#define get_queue_writer(sp) readl(mouse_addr(sp) + CONDOR_MOUSE_Q_WRITER)
#define get_queue_reader(sp) readl(mouse_addr(sp) + CONDOR_MOUSE_Q_READER)
#define set_queue_reader(sp, reader) writel(reader, mouse_addr(sp) + CONDOR_MOUSE_Q_READER)
#define queue_begin (mouse_addr(sp) + CONDOR_MOUSE_Q_BEGIN)
#define get_queue_entry(sp, read_index) \
((void*)(queue_begin + read_index * sizeof(struct remote_input)))
static inline int advance_queue_reader(struct service_processor *sp, unsigned long reader)
{
reader++;
if (reader == REMOTE_QUEUE_SIZE)
reader = 0;
set_queue_reader(sp, reader);
return reader;
}
#define NO_KEYCODE 0
#define KEY_SYM_BK_SPC 0xFF08
#define KEY_SYM_TAB 0xFF09
#define KEY_SYM_ENTER 0xFF0D
#define KEY_SYM_SCR_LOCK 0xFF14
#define KEY_SYM_ESCAPE 0xFF1B
#define KEY_SYM_HOME 0xFF50
#define KEY_SYM_LARROW 0xFF51
#define KEY_SYM_UARROW 0xFF52
#define KEY_SYM_RARROW 0xFF53
#define KEY_SYM_DARROW 0xFF54
#define KEY_SYM_PAGEUP 0xFF55
#define KEY_SYM_PAGEDOWN 0xFF56
#define KEY_SYM_END 0xFF57
#define KEY_SYM_INSERT 0xFF63
#define KEY_SYM_NUM_LOCK 0xFF7F
#define KEY_SYM_KPSTAR 0xFFAA
#define KEY_SYM_KPPLUS 0xFFAB
#define KEY_SYM_KPMINUS 0xFFAD
#define KEY_SYM_KPDOT 0xFFAE
#define KEY_SYM_KPSLASH 0xFFAF
#define KEY_SYM_KPRIGHT 0xFF96
#define KEY_SYM_KPUP 0xFF97
#define KEY_SYM_KPLEFT 0xFF98
#define KEY_SYM_KPDOWN 0xFF99
#define KEY_SYM_KP0 0xFFB0
#define KEY_SYM_KP1 0xFFB1
#define KEY_SYM_KP2 0xFFB2
#define KEY_SYM_KP3 0xFFB3
#define KEY_SYM_KP4 0xFFB4
#define KEY_SYM_KP5 0xFFB5
#define KEY_SYM_KP6 0xFFB6
#define KEY_SYM_KP7 0xFFB7
#define KEY_SYM_KP8 0xFFB8
#define KEY_SYM_KP9 0xFFB9
#define KEY_SYM_F1 0xFFBE // 1B 5B 5B 41
#define KEY_SYM_F2 0xFFBF // 1B 5B 5B 42
#define KEY_SYM_F3 0xFFC0 // 1B 5B 5B 43
#define KEY_SYM_F4 0xFFC1 // 1B 5B 5B 44
#define KEY_SYM_F5 0xFFC2 // 1B 5B 5B 45
#define KEY_SYM_F6 0xFFC3 // 1B 5B 31 37 7E
#define KEY_SYM_F7 0xFFC4 // 1B 5B 31 38 7E
#define KEY_SYM_F8 0xFFC5 // 1B 5B 31 39 7E
#define KEY_SYM_F9 0xFFC6 // 1B 5B 32 30 7E
#define KEY_SYM_F10 0xFFC7 // 1B 5B 32 31 7E
#define KEY_SYM_F11 0xFFC8 // 1B 5B 32 33 7E
#define KEY_SYM_F12 0xFFC9 // 1B 5B 32 34 7E
#define KEY_SYM_SHIFT 0xFFE1
#define KEY_SYM_CTRL 0xFFE3
#define KEY_SYM_ALT 0xFFE9
#define KEY_SYM_CAP_LOCK 0xFFE5
#define KEY_SYM_DELETE 0xFFFF
#define KEY_SYM_TILDE 0x60
#define KEY_SYM_BKTIC 0x7E
#define KEY_SYM_ONE 0x31
#define KEY_SYM_BANG 0x21
#define KEY_SYM_TWO 0x32
#define KEY_SYM_AT 0x40
#define KEY_SYM_THREE 0x33
#define KEY_SYM_POUND 0x23
#define KEY_SYM_FOUR 0x34
#define KEY_SYM_DOLLAR 0x24
#define KEY_SYM_FIVE 0x35
#define KEY_SYM_PERCENT 0x25
#define KEY_SYM_SIX 0x36
#define KEY_SYM_CARAT 0x5E
#define KEY_SYM_SEVEN 0x37
#define KEY_SYM_AMPER 0x26
#define KEY_SYM_EIGHT 0x38
#define KEY_SYM_STAR 0x2A
#define KEY_SYM_NINE 0x39
#define KEY_SYM_LPAREN 0x28
#define KEY_SYM_ZERO 0x30
#define KEY_SYM_RPAREN 0x29
#define KEY_SYM_MINUS 0x2D
#define KEY_SYM_USCORE 0x5F
#define KEY_SYM_EQUAL 0x2B
#define KEY_SYM_PLUS 0x3D
#define KEY_SYM_LBRKT 0x5B
#define KEY_SYM_LCURLY 0x7B
#define KEY_SYM_RBRKT 0x5D
#define KEY_SYM_RCURLY 0x7D
#define KEY_SYM_SLASH 0x5C
#define KEY_SYM_PIPE 0x7C
#define KEY_SYM_TIC 0x27
#define KEY_SYM_QUOTE 0x22
#define KEY_SYM_SEMIC 0x3B
#define KEY_SYM_COLON 0x3A
#define KEY_SYM_COMMA 0x2C
#define KEY_SYM_LT 0x3C
#define KEY_SYM_PERIOD 0x2E
#define KEY_SYM_GT 0x3E
#define KEY_SYM_BSLASH 0x2F
#define KEY_SYM_QMARK 0x3F
#define KEY_SYM_A 0x41
#define KEY_SYM_B 0x42
#define KEY_SYM_C 0x43
#define KEY_SYM_D 0x44
#define KEY_SYM_E 0x45
#define KEY_SYM_F 0x46
#define KEY_SYM_G 0x47
#define KEY_SYM_H 0x48
#define KEY_SYM_I 0x49
#define KEY_SYM_J 0x4A
#define KEY_SYM_K 0x4B
#define KEY_SYM_L 0x4C
#define KEY_SYM_M 0x4D
#define KEY_SYM_N 0x4E
#define KEY_SYM_O 0x4F
#define KEY_SYM_P 0x50
#define KEY_SYM_Q 0x51
#define KEY_SYM_R 0x52
#define KEY_SYM_S 0x53
#define KEY_SYM_T 0x54
#define KEY_SYM_U 0x55
#define KEY_SYM_V 0x56
#define KEY_SYM_W 0x57
#define KEY_SYM_X 0x58
#define KEY_SYM_Y 0x59
#define KEY_SYM_Z 0x5A
#define KEY_SYM_a 0x61
#define KEY_SYM_b 0x62
#define KEY_SYM_c 0x63
#define KEY_SYM_d 0x64
#define KEY_SYM_e 0x65
#define KEY_SYM_f 0x66
#define KEY_SYM_g 0x67
#define KEY_SYM_h 0x68
#define KEY_SYM_i 0x69
#define KEY_SYM_j 0x6A
#define KEY_SYM_k 0x6B
#define KEY_SYM_l 0x6C
#define KEY_SYM_m 0x6D
#define KEY_SYM_n 0x6E
#define KEY_SYM_o 0x6F
#define KEY_SYM_p 0x70
#define KEY_SYM_q 0x71
#define KEY_SYM_r 0x72
#define KEY_SYM_s 0x73
#define KEY_SYM_t 0x74
#define KEY_SYM_u 0x75
#define KEY_SYM_v 0x76
#define KEY_SYM_w 0x77
#define KEY_SYM_x 0x78
#define KEY_SYM_y 0x79
#define KEY_SYM_z 0x7A
#define KEY_SYM_SPACE 0x20
#endif /* _IBMASM_REMOTE_H_ */

View File

@@ -0,0 +1,72 @@
/*
* IBM ASM Service Processor Device Driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2004
*
* Author: Max Asb<73>ck <amax@us.ibm.com>
*
*/
#include <linux/termios.h>
#include <linux/tty.h>
#include <linux/serial_core.h>
#include <linux/serial_reg.h>
#include <linux/serial_8250.h>
#include "ibmasm.h"
#include "lowlevel.h"
void ibmasm_register_uart(struct service_processor *sp)
{
struct uart_port uport;
void __iomem *iomem_base;
iomem_base = sp->base_address + SCOUT_COM_B_BASE;
/* read the uart scratch register to determine if the UART
* is dedicated to the service processor or if the OS can use it
*/
if (0 == readl(iomem_base + UART_SCR)) {
dev_info(sp->dev, "IBM SP UART not registered, owned by service processor\n");
sp->serial_line = -1;
return;
}
memset(&uport, 0, sizeof(struct uart_port));
uport.irq = sp->irq;
uport.uartclk = 3686400;
uport.flags = UPF_SHARE_IRQ;
uport.iotype = UPIO_MEM;
uport.membase = iomem_base;
sp->serial_line = serial8250_register_port(&uport);
if (sp->serial_line < 0) {
dev_err(sp->dev, "Failed to register serial port\n");
return;
}
enable_uart_interrupts(sp->base_address);
}
void ibmasm_unregister_uart(struct service_processor *sp)
{
if (sp->serial_line < 0)
return;
disable_uart_interrupts(sp->base_address);
serial8250_unregister_port(sp->serial_line);
}

474
drivers/misc/ioc4.c Normal file
View File

@@ -0,0 +1,474 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2005-2006 Silicon Graphics, Inc. All Rights Reserved.
*/
/* This file contains the master driver module for use by SGI IOC4 subdrivers.
*
* It allocates any resources shared between multiple subdevices, and
* provides accessor functions (where needed) and the like for those
* resources. It also provides a mechanism for the subdevice modules
* to support loading and unloading.
*
* Non-shared resources (e.g. external interrupt A_INT_OUT register page
* alias, serial port and UART registers) are handled by the subdevice
* modules themselves.
*
* This is all necessary because IOC4 is not implemented as a multi-function
* PCI device, but an amalgamation of disparate registers for several
* types of device (ATA, serial, external interrupts). The normal
* resource management in the kernel doesn't have quite the right interfaces
* to handle this situation (e.g. multiple modules can't claim the same
* PCI ID), thus this IOC4 master module.
*/
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/ioc4.h>
#include <linux/ktime.h>
#include <linux/mutex.h>
#include <linux/time.h>
#include <asm/io.h>
/***************
* Definitions *
***************/
/* Tweakable values */
/* PCI bus speed detection/calibration */
#define IOC4_CALIBRATE_COUNT 63 /* Calibration cycle period */
#define IOC4_CALIBRATE_CYCLES 256 /* Average over this many cycles */
#define IOC4_CALIBRATE_DISCARD 2 /* Discard first few cycles */
#define IOC4_CALIBRATE_LOW_MHZ 25 /* Lower bound on bus speed sanity */
#define IOC4_CALIBRATE_HIGH_MHZ 75 /* Upper bound on bus speed sanity */
#define IOC4_CALIBRATE_DEFAULT_MHZ 66 /* Assumed if sanity check fails */
/************************
* Submodule management *
************************/
static DEFINE_MUTEX(ioc4_mutex);
static LIST_HEAD(ioc4_devices);
static LIST_HEAD(ioc4_submodules);
/* Register an IOC4 submodule */
int
ioc4_register_submodule(struct ioc4_submodule *is)
{
struct ioc4_driver_data *idd;
mutex_lock(&ioc4_mutex);
list_add(&is->is_list, &ioc4_submodules);
/* Initialize submodule for each IOC4 */
if (!is->is_probe)
goto out;
list_for_each_entry(idd, &ioc4_devices, idd_list) {
if (is->is_probe(idd)) {
printk(KERN_WARNING
"%s: IOC4 submodule %s probe failed "
"for pci_dev %s",
__FUNCTION__, module_name(is->is_owner),
pci_name(idd->idd_pdev));
}
}
out:
mutex_unlock(&ioc4_mutex);
return 0;
}
/* Unregister an IOC4 submodule */
void
ioc4_unregister_submodule(struct ioc4_submodule *is)
{
struct ioc4_driver_data *idd;
mutex_lock(&ioc4_mutex);
list_del(&is->is_list);
/* Remove submodule for each IOC4 */
if (!is->is_remove)
goto out;
list_for_each_entry(idd, &ioc4_devices, idd_list) {
if (is->is_remove(idd)) {
printk(KERN_WARNING
"%s: IOC4 submodule %s remove failed "
"for pci_dev %s.\n",
__FUNCTION__, module_name(is->is_owner),
pci_name(idd->idd_pdev));
}
}
out:
mutex_unlock(&ioc4_mutex);
}
/*********************
* Device management *
*********************/
#define IOC4_CALIBRATE_LOW_LIMIT \
(1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_LOW_MHZ)
#define IOC4_CALIBRATE_HIGH_LIMIT \
(1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_HIGH_MHZ)
#define IOC4_CALIBRATE_DEFAULT \
(1000*IOC4_EXTINT_COUNT_DIVISOR/IOC4_CALIBRATE_DEFAULT_MHZ)
#define IOC4_CALIBRATE_END \
(IOC4_CALIBRATE_CYCLES + IOC4_CALIBRATE_DISCARD)
#define IOC4_INT_OUT_MODE_TOGGLE 0x7 /* Toggle INT_OUT every COUNT+1 ticks */
/* Determines external interrupt output clock period of the PCI bus an
* IOC4 is attached to. This value can be used to determine the PCI
* bus speed.
*
* IOC4 has a design feature that various internal timers are derived from
* the PCI bus clock. This causes IOC4 device drivers to need to take the
* bus speed into account when setting various register values (e.g. INT_OUT
* register COUNT field, UART divisors, etc). Since this information is
* needed by several subdrivers, it is determined by the main IOC4 driver,
* even though the following code utilizes external interrupt registers
* to perform the speed calculation.
*/
static void
ioc4_clock_calibrate(struct ioc4_driver_data *idd)
{
union ioc4_int_out int_out;
union ioc4_gpcr gpcr;
unsigned int state, last_state = 1;
struct timespec start_ts, end_ts;
uint64_t start, end, period;
unsigned int count = 0;
/* Enable output */
gpcr.raw = 0;
gpcr.fields.dir = IOC4_GPCR_DIR_0;
gpcr.fields.int_out_en = 1;
writel(gpcr.raw, &idd->idd_misc_regs->gpcr_s.raw);
/* Reset to power-on state */
writel(0, &idd->idd_misc_regs->int_out.raw);
mmiowb();
/* Set up square wave */
int_out.raw = 0;
int_out.fields.count = IOC4_CALIBRATE_COUNT;
int_out.fields.mode = IOC4_INT_OUT_MODE_TOGGLE;
int_out.fields.diag = 0;
writel(int_out.raw, &idd->idd_misc_regs->int_out.raw);
mmiowb();
/* Check square wave period averaged over some number of cycles */
do {
int_out.raw = readl(&idd->idd_misc_regs->int_out.raw);
state = int_out.fields.int_out;
if (!last_state && state) {
count++;
if (count == IOC4_CALIBRATE_END) {
ktime_get_ts(&end_ts);
break;
} else if (count == IOC4_CALIBRATE_DISCARD)
ktime_get_ts(&start_ts);
}
last_state = state;
} while (1);
/* Calculation rearranged to preserve intermediate precision.
* Logically:
* 1. "end - start" gives us the measurement period over all
* the square wave cycles.
* 2. Divide by number of square wave cycles to get the period
* of a square wave cycle.
* 3. Divide by 2*(int_out.fields.count+1), which is the formula
* by which the IOC4 generates the square wave, to get the
* period of an IOC4 INT_OUT count.
*/
end = end_ts.tv_sec * NSEC_PER_SEC + end_ts.tv_nsec;
start = start_ts.tv_sec * NSEC_PER_SEC + start_ts.tv_nsec;
period = (end - start) /
(IOC4_CALIBRATE_CYCLES * 2 * (IOC4_CALIBRATE_COUNT + 1));
/* Bounds check the result. */
if (period > IOC4_CALIBRATE_LOW_LIMIT ||
period < IOC4_CALIBRATE_HIGH_LIMIT) {
printk(KERN_INFO
"IOC4 %s: Clock calibration failed. Assuming"
"PCI clock is %d ns.\n",
pci_name(idd->idd_pdev),
IOC4_CALIBRATE_DEFAULT / IOC4_EXTINT_COUNT_DIVISOR);
period = IOC4_CALIBRATE_DEFAULT;
} else {
u64 ns = period;
do_div(ns, IOC4_EXTINT_COUNT_DIVISOR);
printk(KERN_DEBUG
"IOC4 %s: PCI clock is %llu ns.\n",
pci_name(idd->idd_pdev), (unsigned long long)ns);
}
/* Remember results. We store the extint clock period rather
* than the PCI clock period so that greater precision is
* retained. Divide by IOC4_EXTINT_COUNT_DIVISOR to get
* PCI clock period.
*/
idd->count_period = period;
}
/* There are three variants of IOC4 cards: IO9, IO10, and PCI-RT.
* Each brings out different combinations of IOC4 signals, thus.
* the IOC4 subdrivers need to know to which we're attached.
*
* We look for the presence of a SCSI (IO9) or SATA (IO10) controller
* on the same PCI bus at slot number 3 to differentiate IO9 from IO10.
* If neither is present, it's a PCI-RT.
*/
static unsigned int
ioc4_variant(struct ioc4_driver_data *idd)
{
struct pci_dev *pdev = NULL;
int found = 0;
/* IO9: Look for a QLogic ISP 12160 at the same bus and slot 3. */
do {
pdev = pci_get_device(PCI_VENDOR_ID_QLOGIC,
PCI_DEVICE_ID_QLOGIC_ISP12160, pdev);
if (pdev &&
idd->idd_pdev->bus->number == pdev->bus->number &&
3 == PCI_SLOT(pdev->devfn))
found = 1;
pci_dev_put(pdev);
} while (pdev && !found);
if (NULL != pdev)
return IOC4_VARIANT_IO9;
/* IO10: Look for a Vitesse VSC 7174 at the same bus and slot 3. */
pdev = NULL;
do {
pdev = pci_get_device(PCI_VENDOR_ID_VITESSE,
PCI_DEVICE_ID_VITESSE_VSC7174, pdev);
if (pdev &&
idd->idd_pdev->bus->number == pdev->bus->number &&
3 == PCI_SLOT(pdev->devfn))
found = 1;
pci_dev_put(pdev);
} while (pdev && !found);
if (NULL != pdev)
return IOC4_VARIANT_IO10;
/* PCI-RT: No SCSI/SATA controller will be present */
return IOC4_VARIANT_PCI_RT;
}
/* Adds a new instance of an IOC4 card */
static int
ioc4_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id)
{
struct ioc4_driver_data *idd;
struct ioc4_submodule *is;
uint32_t pcmd;
int ret;
/* Enable IOC4 and take ownership of it */
if ((ret = pci_enable_device(pdev))) {
printk(KERN_WARNING
"%s: Failed to enable IOC4 device for pci_dev %s.\n",
__FUNCTION__, pci_name(pdev));
goto out;
}
pci_set_master(pdev);
/* Set up per-IOC4 data */
idd = kmalloc(sizeof(struct ioc4_driver_data), GFP_KERNEL);
if (!idd) {
printk(KERN_WARNING
"%s: Failed to allocate IOC4 data for pci_dev %s.\n",
__FUNCTION__, pci_name(pdev));
ret = -ENODEV;
goto out_idd;
}
idd->idd_pdev = pdev;
idd->idd_pci_id = pci_id;
/* Map IOC4 misc registers. These are shared between subdevices
* so the main IOC4 module manages them.
*/
idd->idd_bar0 = pci_resource_start(idd->idd_pdev, 0);
if (!idd->idd_bar0) {
printk(KERN_WARNING
"%s: Unable to find IOC4 misc resource "
"for pci_dev %s.\n",
__FUNCTION__, pci_name(idd->idd_pdev));
ret = -ENODEV;
goto out_pci;
}
if (!request_mem_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs),
"ioc4_misc")) {
printk(KERN_WARNING
"%s: Unable to request IOC4 misc region "
"for pci_dev %s.\n",
__FUNCTION__, pci_name(idd->idd_pdev));
ret = -ENODEV;
goto out_pci;
}
idd->idd_misc_regs = ioremap(idd->idd_bar0,
sizeof(struct ioc4_misc_regs));
if (!idd->idd_misc_regs) {
printk(KERN_WARNING
"%s: Unable to remap IOC4 misc region "
"for pci_dev %s.\n",
__FUNCTION__, pci_name(idd->idd_pdev));
ret = -ENODEV;
goto out_misc_region;
}
/* Failsafe portion of per-IOC4 initialization */
/* Detect card variant */
idd->idd_variant = ioc4_variant(idd);
printk(KERN_INFO "IOC4 %s: %s card detected.\n", pci_name(pdev),
idd->idd_variant == IOC4_VARIANT_IO9 ? "IO9" :
idd->idd_variant == IOC4_VARIANT_PCI_RT ? "PCI-RT" :
idd->idd_variant == IOC4_VARIANT_IO10 ? "IO10" : "unknown");
/* Initialize IOC4 */
pci_read_config_dword(idd->idd_pdev, PCI_COMMAND, &pcmd);
pci_write_config_dword(idd->idd_pdev, PCI_COMMAND,
pcmd | PCI_COMMAND_PARITY | PCI_COMMAND_SERR);
/* Determine PCI clock */
ioc4_clock_calibrate(idd);
/* Disable/clear all interrupts. Need to do this here lest
* one submodule request the shared IOC4 IRQ, but interrupt
* is generated by a different subdevice.
*/
/* Disable */
writel(~0, &idd->idd_misc_regs->other_iec.raw);
writel(~0, &idd->idd_misc_regs->sio_iec);
/* Clear (i.e. acknowledge) */
writel(~0, &idd->idd_misc_regs->other_ir.raw);
writel(~0, &idd->idd_misc_regs->sio_ir);
/* Track PCI-device specific data */
idd->idd_serial_data = NULL;
pci_set_drvdata(idd->idd_pdev, idd);
mutex_lock(&ioc4_mutex);
list_add_tail(&idd->idd_list, &ioc4_devices);
/* Add this IOC4 to all submodules */
list_for_each_entry(is, &ioc4_submodules, is_list) {
if (is->is_probe && is->is_probe(idd)) {
printk(KERN_WARNING
"%s: IOC4 submodule 0x%s probe failed "
"for pci_dev %s.\n",
__FUNCTION__, module_name(is->is_owner),
pci_name(idd->idd_pdev));
}
}
mutex_unlock(&ioc4_mutex);
return 0;
out_misc_region:
release_mem_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs));
out_pci:
kfree(idd);
out_idd:
pci_disable_device(pdev);
out:
return ret;
}
/* Removes a particular instance of an IOC4 card. */
static void
ioc4_remove(struct pci_dev *pdev)
{
struct ioc4_submodule *is;
struct ioc4_driver_data *idd;
idd = pci_get_drvdata(pdev);
/* Remove this IOC4 from all submodules */
mutex_lock(&ioc4_mutex);
list_for_each_entry(is, &ioc4_submodules, is_list) {
if (is->is_remove && is->is_remove(idd)) {
printk(KERN_WARNING
"%s: IOC4 submodule 0x%s remove failed "
"for pci_dev %s.\n",
__FUNCTION__, module_name(is->is_owner),
pci_name(idd->idd_pdev));
}
}
mutex_unlock(&ioc4_mutex);
/* Release resources */
iounmap(idd->idd_misc_regs);
if (!idd->idd_bar0) {
printk(KERN_WARNING
"%s: Unable to get IOC4 misc mapping for pci_dev %s. "
"Device removal may be incomplete.\n",
__FUNCTION__, pci_name(idd->idd_pdev));
}
release_mem_region(idd->idd_bar0, sizeof(struct ioc4_misc_regs));
/* Disable IOC4 and relinquish */
pci_disable_device(pdev);
/* Remove and free driver data */
mutex_lock(&ioc4_mutex);
list_del(&idd->idd_list);
mutex_unlock(&ioc4_mutex);
kfree(idd);
}
static struct pci_device_id ioc4_id_table[] = {
{PCI_VENDOR_ID_SGI, PCI_DEVICE_ID_SGI_IOC4, PCI_ANY_ID,
PCI_ANY_ID, 0x0b4000, 0xFFFFFF},
{0}
};
static struct pci_driver ioc4_driver = {
.name = "IOC4",
.id_table = ioc4_id_table,
.probe = ioc4_probe,
.remove = ioc4_remove,
};
MODULE_DEVICE_TABLE(pci, ioc4_id_table);
/*********************
* Module management *
*********************/
/* Module load */
static int __devinit
ioc4_init(void)
{
return pci_register_driver(&ioc4_driver);
}
/* Module unload */
static void __devexit
ioc4_exit(void)
{
pci_unregister_driver(&ioc4_driver);
}
module_init(ioc4_init);
module_exit(ioc4_exit);
MODULE_AUTHOR("Brent Casavant - Silicon Graphics, Inc. <bcasavan@sgi.com>");
MODULE_DESCRIPTION("PCI driver master module for SGI IOC4 Base-IO Card");
MODULE_LICENSE("GPL");
EXPORT_SYMBOL(ioc4_register_submodule);
EXPORT_SYMBOL(ioc4_unregister_submodule);

343
drivers/misc/lkdtm.c Normal file
View File

@@ -0,0 +1,343 @@
/*
* Kprobe module for testing crash dumps
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (C) IBM Corporation, 2006
*
* Author: Ankita Garg <ankita@in.ibm.com>
*
* This module induces system failures at predefined crashpoints to
* evaluate the reliability of crash dumps obtained using different dumping
* solutions.
*
* It is adapted from the Linux Kernel Dump Test Tool by
* Fernando Luis Vazquez Cao <http://lkdtt.sourceforge.net>
*
* Usage : insmod lkdtm.ko [recur_count={>0}] cpoint_name=<> cpoint_type=<>
* [cpoint_count={>0}]
*
* recur_count : Recursion level for the stack overflow test. Default is 10.
*
* cpoint_name : Crash point where the kernel is to be crashed. It can be
* one of INT_HARDWARE_ENTRY, INT_HW_IRQ_EN, INT_TASKLET_ENTRY,
* FS_DEVRW, MEM_SWAPOUT, TIMERADD, SCSI_DISPATCH_CMD,
* IDE_CORE_CP
*
* cpoint_type : Indicates the action to be taken on hitting the crash point.
* It can be one of PANIC, BUG, EXCEPTION, LOOP, OVERFLOW
*
* cpoint_count : Indicates the number of times the crash point is to be hit
* to trigger an action. The default is 10.
*/
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/buffer_head.h>
#include <linux/kprobes.h>
#include <linux/list.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/hrtimer.h>
#include <scsi/scsi_cmnd.h>
#ifdef CONFIG_IDE
#include <linux/ide.h>
#endif
#define NUM_CPOINTS 8
#define NUM_CPOINT_TYPES 5
#define DEFAULT_COUNT 10
#define REC_NUM_DEFAULT 10
enum cname {
INVALID,
INT_HARDWARE_ENTRY,
INT_HW_IRQ_EN,
INT_TASKLET_ENTRY,
FS_DEVRW,
MEM_SWAPOUT,
TIMERADD,
SCSI_DISPATCH_CMD,
IDE_CORE_CP
};
enum ctype {
NONE,
PANIC,
BUG,
EXCEPTION,
LOOP,
OVERFLOW
};
static char* cp_name[] = {
"INT_HARDWARE_ENTRY",
"INT_HW_IRQ_EN",
"INT_TASKLET_ENTRY",
"FS_DEVRW",
"MEM_SWAPOUT",
"TIMERADD",
"SCSI_DISPATCH_CMD",
"IDE_CORE_CP"
};
static char* cp_type[] = {
"PANIC",
"BUG",
"EXCEPTION",
"LOOP",
"OVERFLOW"
};
static struct jprobe lkdtm;
static int lkdtm_parse_commandline(void);
static void lkdtm_handler(void);
static char* cpoint_name;
static char* cpoint_type;
static int cpoint_count = DEFAULT_COUNT;
static int recur_count = REC_NUM_DEFAULT;
static enum cname cpoint = INVALID;
static enum ctype cptype = NONE;
static int count = DEFAULT_COUNT;
module_param(recur_count, int, 0644);
MODULE_PARM_DESC(recur_count, " Recursion level for the stack overflow test, "\
"default is 10");
module_param(cpoint_name, charp, 0644);
MODULE_PARM_DESC(cpoint_name, " Crash Point, where kernel is to be crashed");
module_param(cpoint_type, charp, 0644);
MODULE_PARM_DESC(cpoint_type, " Crash Point Type, action to be taken on "\
"hitting the crash point");
module_param(cpoint_count, int, 0644);
MODULE_PARM_DESC(cpoint_count, " Crash Point Count, number of times the "\
"crash point is to be hit to trigger action");
unsigned int jp_do_irq(unsigned int irq)
{
lkdtm_handler();
jprobe_return();
return 0;
}
irqreturn_t jp_handle_irq_event(unsigned int irq, struct irqaction *action)
{
lkdtm_handler();
jprobe_return();
return 0;
}
void jp_tasklet_action(struct softirq_action *a)
{
lkdtm_handler();
jprobe_return();
}
void jp_ll_rw_block(int rw, int nr, struct buffer_head *bhs[])
{
lkdtm_handler();
jprobe_return();
}
struct scan_control;
unsigned long jp_shrink_inactive_list(unsigned long max_scan,
struct zone *zone, struct scan_control *sc)
{
lkdtm_handler();
jprobe_return();
return 0;
}
int jp_hrtimer_start(struct hrtimer *timer, ktime_t tim,
const enum hrtimer_mode mode)
{
lkdtm_handler();
jprobe_return();
return 0;
}
int jp_scsi_dispatch_cmd(struct scsi_cmnd *cmd)
{
lkdtm_handler();
jprobe_return();
return 0;
}
#ifdef CONFIG_IDE
int jp_generic_ide_ioctl(ide_drive_t *drive, struct file *file,
struct block_device *bdev, unsigned int cmd,
unsigned long arg)
{
lkdtm_handler();
jprobe_return();
return 0;
}
#endif
static int lkdtm_parse_commandline(void)
{
int i;
if (cpoint_name == INVALID || cpoint_type == NONE ||
cpoint_count < 1 || recur_count < 1)
return -EINVAL;
for (i = 0; i < NUM_CPOINTS; ++i) {
if (!strcmp(cpoint_name, cp_name[i])) {
cpoint = i + 1;
break;
}
}
for (i = 0; i < NUM_CPOINT_TYPES; ++i) {
if (!strcmp(cpoint_type, cp_type[i])) {
cptype = i + 1;
break;
}
}
if (cpoint == INVALID || cptype == NONE)
return -EINVAL;
count = cpoint_count;
return 0;
}
static int recursive_loop(int a)
{
char buf[1024];
memset(buf,0xFF,1024);
recur_count--;
if (!recur_count)
return 0;
else
return recursive_loop(a);
}
void lkdtm_handler(void)
{
printk(KERN_INFO "lkdtm : Crash point %s of type %s hit\n",
cpoint_name, cpoint_type);
--count;
if (count == 0) {
switch (cptype) {
case NONE:
break;
case PANIC:
printk(KERN_INFO "lkdtm : PANIC\n");
panic("dumptest");
break;
case BUG:
printk(KERN_INFO "lkdtm : BUG\n");
BUG();
break;
case EXCEPTION:
printk(KERN_INFO "lkdtm : EXCEPTION\n");
*((int *) 0) = 0;
break;
case LOOP:
printk(KERN_INFO "lkdtm : LOOP\n");
for (;;);
break;
case OVERFLOW:
printk(KERN_INFO "lkdtm : OVERFLOW\n");
(void) recursive_loop(0);
break;
default:
break;
}
count = cpoint_count;
}
}
int lkdtm_module_init(void)
{
int ret;
if (lkdtm_parse_commandline() == -EINVAL) {
printk(KERN_INFO "lkdtm : Invalid command\n");
return -EINVAL;
}
switch (cpoint) {
case INT_HARDWARE_ENTRY:
lkdtm.kp.symbol_name = "__do_IRQ";
lkdtm.entry = (kprobe_opcode_t*) jp_do_irq;
break;
case INT_HW_IRQ_EN:
lkdtm.kp.symbol_name = "handle_IRQ_event";
lkdtm.entry = (kprobe_opcode_t*) jp_handle_irq_event;
break;
case INT_TASKLET_ENTRY:
lkdtm.kp.symbol_name = "tasklet_action";
lkdtm.entry = (kprobe_opcode_t*) jp_tasklet_action;
break;
case FS_DEVRW:
lkdtm.kp.symbol_name = "ll_rw_block";
lkdtm.entry = (kprobe_opcode_t*) jp_ll_rw_block;
break;
case MEM_SWAPOUT:
lkdtm.kp.symbol_name = "shrink_inactive_list";
lkdtm.entry = (kprobe_opcode_t*) jp_shrink_inactive_list;
break;
case TIMERADD:
lkdtm.kp.symbol_name = "hrtimer_start";
lkdtm.entry = (kprobe_opcode_t*) jp_hrtimer_start;
break;
case SCSI_DISPATCH_CMD:
lkdtm.kp.symbol_name = "scsi_dispatch_cmd";
lkdtm.entry = (kprobe_opcode_t*) jp_scsi_dispatch_cmd;
break;
case IDE_CORE_CP:
#ifdef CONFIG_IDE
lkdtm.kp.symbol_name = "generic_ide_ioctl";
lkdtm.entry = (kprobe_opcode_t*) jp_generic_ide_ioctl;
#else
printk(KERN_INFO "lkdtm : Crash point not available\n");
#endif
break;
default:
printk(KERN_INFO "lkdtm : Invalid Crash Point\n");
break;
}
if ((ret = register_jprobe(&lkdtm)) < 0) {
printk(KERN_INFO "lkdtm : Couldn't register jprobe\n");
return ret;
}
printk(KERN_INFO "lkdtm : Crash point %s of type %s registered\n",
cpoint_name, cpoint_type);
return 0;
}
void lkdtm_module_exit(void)
{
unregister_jprobe(&lkdtm);
printk(KERN_INFO "lkdtm : Crash point unregistered\n");
}
module_init(lkdtm_module_init);
module_exit(lkdtm_module_exit);
MODULE_LICENSE("GPL");

396
drivers/misc/msi-laptop.c Normal file
View File

@@ -0,0 +1,396 @@
/*-*-linux-c-*-*/
/*
Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
/*
* msi-laptop.c - MSI S270 laptop support. This laptop is sold under
* various brands, including "Cytron/TCM/Medion/Tchibo MD96100".
*
* This driver exports a few files in /sys/devices/platform/msi-laptop-pf/:
*
* lcd_level - Screen brightness: contains a single integer in the
* range 0..8. (rw)
*
* auto_brightness - Enable automatic brightness control: contains
* either 0 or 1. If set to 1 the hardware adjusts the screen
* brightness automatically when the power cord is
* plugged/unplugged. (rw)
*
* wlan - WLAN subsystem enabled: contains either 0 or 1. (ro)
*
* bluetooth - Bluetooth subsystem enabled: contains either 0 or 1
* Please note that this file is constantly 0 if no Bluetooth
* hardware is available. (ro)
*
* In addition to these platform device attributes the driver
* registers itself in the Linux backlight control subsystem and is
* available to userspace under /sys/class/backlight/msi-laptop-bl/.
*
* This driver might work on other laptops produced by MSI. If you
* want to try it you can pass force=1 as argument to the module which
* will force it to load even when the DMI data doesn't identify the
* laptop as MSI S270. YMMV.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/backlight.h>
#include <linux/platform_device.h>
#include <linux/autoconf.h>
#define MSI_DRIVER_VERSION "0.5"
#define MSI_LCD_LEVEL_MAX 9
#define MSI_EC_COMMAND_WIRELESS 0x10
#define MSI_EC_COMMAND_LCD_LEVEL 0x11
static int force;
module_param(force, bool, 0);
MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
static int auto_brightness;
module_param(auto_brightness, int, 0);
MODULE_PARM_DESC(auto_brightness, "Enable automatic brightness control (0: disabled; 1: enabled; 2: don't touch)");
/* Hardware access */
static int set_lcd_level(int level)
{
u8 buf[2];
if (level < 0 || level >= MSI_LCD_LEVEL_MAX)
return -EINVAL;
buf[0] = 0x80;
buf[1] = (u8) (level*31);
return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, buf, sizeof(buf), NULL, 0);
}
static int get_lcd_level(void)
{
u8 wdata = 0, rdata;
int result;
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1);
if (result < 0)
return result;
return (int) rdata / 31;
}
static int get_auto_brightness(void)
{
u8 wdata = 4, rdata;
int result;
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, &wdata, 1, &rdata, 1);
if (result < 0)
return result;
return !!(rdata & 8);
}
static int set_auto_brightness(int enable)
{
u8 wdata[2], rdata;
int result;
wdata[0] = 4;
result = ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 1, &rdata, 1);
if (result < 0)
return result;
wdata[0] = 0x84;
wdata[1] = (rdata & 0xF7) | (enable ? 8 : 0);
return ec_transaction(MSI_EC_COMMAND_LCD_LEVEL, wdata, 2, NULL, 0);
}
static int get_wireless_state(int *wlan, int *bluetooth)
{
u8 wdata = 0, rdata;
int result;
result = ec_transaction(MSI_EC_COMMAND_WIRELESS, &wdata, 1, &rdata, 1);
if (result < 0)
return -1;
if (wlan)
*wlan = !!(rdata & 8);
if (bluetooth)
*bluetooth = !!(rdata & 128);
return 0;
}
/* Backlight device stuff */
static int bl_get_brightness(struct backlight_device *b)
{
return get_lcd_level();
}
static int bl_update_status(struct backlight_device *b)
{
return set_lcd_level(b->props.brightness);
}
static struct backlight_ops msibl_ops = {
.get_brightness = bl_get_brightness,
.update_status = bl_update_status,
};
static struct backlight_device *msibl_device;
/* Platform device */
static ssize_t show_wlan(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret, enabled;
ret = get_wireless_state(&enabled, NULL);
if (ret < 0)
return ret;
return sprintf(buf, "%i\n", enabled);
}
static ssize_t show_bluetooth(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret, enabled;
ret = get_wireless_state(NULL, &enabled);
if (ret < 0)
return ret;
return sprintf(buf, "%i\n", enabled);
}
static ssize_t show_lcd_level(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
ret = get_lcd_level();
if (ret < 0)
return ret;
return sprintf(buf, "%i\n", ret);
}
static ssize_t store_lcd_level(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int level, ret;
if (sscanf(buf, "%i", &level) != 1 || (level < 0 || level >= MSI_LCD_LEVEL_MAX))
return -EINVAL;
ret = set_lcd_level(level);
if (ret < 0)
return ret;
return count;
}
static ssize_t show_auto_brightness(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
ret = get_auto_brightness();
if (ret < 0)
return ret;
return sprintf(buf, "%i\n", ret);
}
static ssize_t store_auto_brightness(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int enable, ret;
if (sscanf(buf, "%i", &enable) != 1 || (enable != (enable & 1)))
return -EINVAL;
ret = set_auto_brightness(enable);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR(lcd_level, 0644, show_lcd_level, store_lcd_level);
static DEVICE_ATTR(auto_brightness, 0644, show_auto_brightness, store_auto_brightness);
static DEVICE_ATTR(bluetooth, 0444, show_bluetooth, NULL);
static DEVICE_ATTR(wlan, 0444, show_wlan, NULL);
static struct attribute *msipf_attributes[] = {
&dev_attr_lcd_level.attr,
&dev_attr_auto_brightness.attr,
&dev_attr_bluetooth.attr,
&dev_attr_wlan.attr,
NULL
};
static struct attribute_group msipf_attribute_group = {
.attrs = msipf_attributes
};
static struct platform_driver msipf_driver = {
.driver = {
.name = "msi-laptop-pf",
.owner = THIS_MODULE,
}
};
static struct platform_device *msipf_device;
/* Initialization */
static struct dmi_system_id __initdata msi_dmi_table[] = {
{
.ident = "MSI S270",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT'L CO.,LTD"),
DMI_MATCH(DMI_PRODUCT_NAME, "MS-1013"),
}
},
{
.ident = "Medion MD96100",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "NOTEBOOK"),
DMI_MATCH(DMI_PRODUCT_NAME, "SAM2000"),
}
},
{ }
};
static int __init msi_init(void)
{
int ret;
if (acpi_disabled)
return -ENODEV;
if (!force && !dmi_check_system(msi_dmi_table))
return -ENODEV;
if (auto_brightness < 0 || auto_brightness > 2)
return -EINVAL;
/* Register backlight stuff */
msibl_device = backlight_device_register("msi-laptop-bl", NULL, NULL,
&msibl_ops);
if (IS_ERR(msibl_device))
return PTR_ERR(msibl_device);
msibl_device->props.max_brightness = MSI_LCD_LEVEL_MAX-1,
ret = platform_driver_register(&msipf_driver);
if (ret)
goto fail_backlight;
/* Register platform stuff */
msipf_device = platform_device_alloc("msi-laptop-pf", -1);
if (!msipf_device) {
ret = -ENOMEM;
goto fail_platform_driver;
}
ret = platform_device_add(msipf_device);
if (ret)
goto fail_platform_device1;
ret = sysfs_create_group(&msipf_device->dev.kobj, &msipf_attribute_group);
if (ret)
goto fail_platform_device2;
/* Disable automatic brightness control by default because
* this module was probably loaded to do brightness control in
* software. */
if (auto_brightness != 2)
set_auto_brightness(auto_brightness);
printk(KERN_INFO "msi-laptop: driver "MSI_DRIVER_VERSION" successfully loaded.\n");
return 0;
fail_platform_device2:
platform_device_del(msipf_device);
fail_platform_device1:
platform_device_put(msipf_device);
fail_platform_driver:
platform_driver_unregister(&msipf_driver);
fail_backlight:
backlight_device_unregister(msibl_device);
return ret;
}
static void __exit msi_cleanup(void)
{
sysfs_remove_group(&msipf_device->dev.kobj, &msipf_attribute_group);
platform_device_unregister(msipf_device);
platform_driver_unregister(&msipf_driver);
backlight_device_unregister(msibl_device);
/* Enable automatic brightness control again */
if (auto_brightness != 2)
set_auto_brightness(1);
printk(KERN_INFO "msi-laptop: driver unloaded.\n");
}
module_init(msi_init);
module_exit(msi_cleanup);
MODULE_AUTHOR("Lennart Poettering");
MODULE_DESCRIPTION("MSI Laptop Support");
MODULE_VERSION(MSI_DRIVER_VERSION);
MODULE_LICENSE("GPL");

564
drivers/misc/sony-laptop.c Normal file
View File

@@ -0,0 +1,564 @@
/*
* ACPI Sony Notebook Control Driver (SNC)
*
* Copyright (C) 2004-2005 Stelian Pop <stelian@popies.net>
* Copyright (C) 2007 Mattia Dongili <malattia@linux.it>
*
* Parts of this driver inspired from asus_acpi.c and ibm_acpi.c
* which are copyrighted by their respective authors.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/backlight.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <acpi/acpi_drivers.h>
#include <acpi/acpi_bus.h>
#include <asm/uaccess.h>
#define ACPI_SNC_CLASS "sony"
#define ACPI_SNC_HID "SNY5001"
#define ACPI_SNC_DRIVER_NAME "ACPI Sony Notebook Control Driver v0.4"
/* the device uses 1-based values, while the backlight subsystem uses
0-based values */
#define SONY_MAX_BRIGHTNESS 8
#define LOG_PFX KERN_WARNING "sony-laptop: "
MODULE_AUTHOR("Stelian Pop, Mattia Dongili");
MODULE_DESCRIPTION(ACPI_SNC_DRIVER_NAME);
MODULE_LICENSE("GPL");
static int debug;
module_param(debug, int, 0);
MODULE_PARM_DESC(debug, "set this to 1 (and RTFM) if you want to help "
"the development of this driver");
static ssize_t sony_acpi_show(struct device *, struct device_attribute *,
char *);
static ssize_t sony_acpi_store(struct device *, struct device_attribute *,
const char *, size_t);
static int boolean_validate(const int, const int);
static int brightness_default_validate(const int, const int);
#define SNC_VALIDATE_IN 0
#define SNC_VALIDATE_OUT 1
struct sony_acpi_value {
char *name; /* name of the entry */
char **acpiget; /* names of the ACPI get function */
char **acpiset; /* names of the ACPI set function */
int (*validate)(const int, const int); /* input/output validation */
int value; /* current setting */
int valid; /* Has ever been set */
int debug; /* active only in debug mode ? */
struct device_attribute devattr; /* sysfs atribute */
};
#define HANDLE_NAMES(_name, _values...) \
static char *snc_##_name[] = { _values, NULL }
#define SONY_ACPI_VALUE(_name, _getters, _setters, _validate, _debug) \
{ \
.name = __stringify(_name), \
.acpiget = _getters, \
.acpiset = _setters, \
.validate = _validate, \
.debug = _debug, \
.devattr = __ATTR(_name, 0, sony_acpi_show, sony_acpi_store), \
}
#define SONY_ACPI_VALUE_NULL { .name = NULL }
HANDLE_NAMES(fnkey_get, "GHKE");
HANDLE_NAMES(brightness_def_get, "GPBR");
HANDLE_NAMES(brightness_def_set, "SPBR");
HANDLE_NAMES(cdpower_get, "GCDP");
HANDLE_NAMES(cdpower_set, "SCDP", "CDPW");
HANDLE_NAMES(audiopower_get, "GAZP");
HANDLE_NAMES(audiopower_set, "AZPW");
HANDLE_NAMES(lanpower_get, "GLNP");
HANDLE_NAMES(lanpower_set, "LNPW");
HANDLE_NAMES(PID_get, "GPID");
HANDLE_NAMES(CTR_get, "GCTR");
HANDLE_NAMES(CTR_set, "SCTR");
HANDLE_NAMES(PCR_get, "GPCR");
HANDLE_NAMES(PCR_set, "SPCR");
HANDLE_NAMES(CMI_get, "GCMI");
HANDLE_NAMES(CMI_set, "SCMI");
static struct sony_acpi_value sony_acpi_values[] = {
SONY_ACPI_VALUE(brightness_default, snc_brightness_def_get,
snc_brightness_def_set, brightness_default_validate, 0),
SONY_ACPI_VALUE(fnkey, snc_fnkey_get, NULL, NULL, 0),
SONY_ACPI_VALUE(cdpower, snc_cdpower_get, snc_cdpower_set, boolean_validate, 0),
SONY_ACPI_VALUE(audiopower, snc_audiopower_get, snc_audiopower_set,
boolean_validate, 0),
SONY_ACPI_VALUE(lanpower, snc_lanpower_get, snc_lanpower_set,
boolean_validate, 1),
/* unknown methods */
SONY_ACPI_VALUE(PID, snc_PID_get, NULL, NULL, 1),
SONY_ACPI_VALUE(CTR, snc_CTR_get, snc_CTR_set, NULL, 1),
SONY_ACPI_VALUE(PCR, snc_PCR_get, snc_PCR_set, NULL, 1),
SONY_ACPI_VALUE(CMI, snc_CMI_get, snc_CMI_set, NULL, 1),
SONY_ACPI_VALUE_NULL
};
static acpi_handle sony_acpi_handle;
static struct acpi_device *sony_acpi_acpi_device = NULL;
/*
* acpi_evaluate_object wrappers
*/
static int acpi_callgetfunc(acpi_handle handle, char *name, int *result)
{
struct acpi_buffer output;
union acpi_object out_obj;
acpi_status status;
output.length = sizeof(out_obj);
output.pointer = &out_obj;
status = acpi_evaluate_object(handle, name, NULL, &output);
if ((status == AE_OK) && (out_obj.type == ACPI_TYPE_INTEGER)) {
*result = out_obj.integer.value;
return 0;
}
printk(LOG_PFX "acpi_callreadfunc failed\n");
return -1;
}
static int acpi_callsetfunc(acpi_handle handle, char *name, int value,
int *result)
{
struct acpi_object_list params;
union acpi_object in_obj;
struct acpi_buffer output;
union acpi_object out_obj;
acpi_status status;
params.count = 1;
params.pointer = &in_obj;
in_obj.type = ACPI_TYPE_INTEGER;
in_obj.integer.value = value;
output.length = sizeof(out_obj);
output.pointer = &out_obj;
status = acpi_evaluate_object(handle, name, &params, &output);
if (status == AE_OK) {
if (result != NULL) {
if (out_obj.type != ACPI_TYPE_INTEGER) {
printk(LOG_PFX "acpi_evaluate_object bad "
"return type\n");
return -1;
}
*result = out_obj.integer.value;
}
return 0;
}
printk(LOG_PFX "acpi_evaluate_object failed\n");
return -1;
}
/*
* sony_acpi_values input/output validate functions
*/
/* brightness_default_validate:
*
* manipulate input output values to keep consistency with the
* backlight framework for which brightness values are 0-based.
*/
static int brightness_default_validate(const int direction, const int value)
{
switch (direction) {
case SNC_VALIDATE_OUT:
return value - 1;
case SNC_VALIDATE_IN:
if (value >= 0 && value < SONY_MAX_BRIGHTNESS)
return value + 1;
}
return -EINVAL;
}
/* boolean_validate:
*
* on input validate boolean values 0/1, on output just pass the
* received value.
*/
static int boolean_validate(const int direction, const int value)
{
if (direction == SNC_VALIDATE_IN) {
if (value != 0 && value != 1)
return -EINVAL;
}
return value;
}
/*
* Sysfs show/store common to all sony_acpi_values
*/
static ssize_t sony_acpi_show(struct device *dev, struct device_attribute *attr,
char *buffer)
{
int value;
struct sony_acpi_value *item =
container_of(attr, struct sony_acpi_value, devattr);
if (!*item->acpiget)
return -EIO;
if (acpi_callgetfunc(sony_acpi_handle, *item->acpiget, &value) < 0)
return -EIO;
if (item->validate)
value = item->validate(SNC_VALIDATE_OUT, value);
return snprintf(buffer, PAGE_SIZE, "%d\n", value);
}
static ssize_t sony_acpi_store(struct device *dev,
struct device_attribute *attr,
const char *buffer, size_t count)
{
int value;
struct sony_acpi_value *item =
container_of(attr, struct sony_acpi_value, devattr);
if (!item->acpiset)
return -EIO;
if (count > 31)
return -EINVAL;
value = simple_strtoul(buffer, NULL, 10);
if (item->validate)
value = item->validate(SNC_VALIDATE_IN, value);
if (value < 0)
return value;
if (acpi_callsetfunc(sony_acpi_handle, *item->acpiset, value, NULL) < 0)
return -EIO;
item->value = value;
item->valid = 1;
return count;
}
/*
* Platform device
*/
static struct platform_driver sncpf_driver = {
.driver = {
.name = "sony-laptop",
.owner = THIS_MODULE,
}
};
static struct platform_device *sncpf_device;
static int sony_snc_pf_add(void)
{
acpi_handle handle;
struct sony_acpi_value *item;
int ret = 0;
ret = platform_driver_register(&sncpf_driver);
if (ret)
goto out;
sncpf_device = platform_device_alloc("sony-laptop", -1);
if (!sncpf_device) {
ret = -ENOMEM;
goto out_platform_registered;
}
ret = platform_device_add(sncpf_device);
if (ret)
goto out_platform_alloced;
for (item = sony_acpi_values; item->name; ++item) {
if (!debug && item->debug)
continue;
/* find the available acpiget as described in the DSDT */
for (; item->acpiget && *item->acpiget; ++item->acpiget) {
if (ACPI_SUCCESS(acpi_get_handle(sony_acpi_handle,
*item->acpiget,
&handle))) {
if (debug)
printk(LOG_PFX "Found %s getter: %s\n",
item->name, *item->acpiget);
item->devattr.attr.mode |= S_IRUGO;
break;
}
}
/* find the available acpiset as described in the DSDT */
for (; item->acpiset && *item->acpiset; ++item->acpiset) {
if (ACPI_SUCCESS(acpi_get_handle(sony_acpi_handle,
*item->acpiset,
&handle))) {
if (debug)
printk(LOG_PFX "Found %s setter: %s\n",
item->name, *item->acpiset);
item->devattr.attr.mode |= S_IWUSR;
break;
}
}
if (item->devattr.attr.mode != 0) {
ret =
device_create_file(&sncpf_device->dev,
&item->devattr);
if (ret)
goto out_sysfs;
}
}
return 0;
out_sysfs:
for (item = sony_acpi_values; item->name; ++item) {
device_remove_file(&sncpf_device->dev, &item->devattr);
}
platform_device_del(sncpf_device);
out_platform_alloced:
platform_device_put(sncpf_device);
out_platform_registered:
platform_driver_unregister(&sncpf_driver);
out:
return ret;
}
static void sony_snc_pf_remove(void)
{
struct sony_acpi_value *item;
for (item = sony_acpi_values; item->name; ++item) {
device_remove_file(&sncpf_device->dev, &item->devattr);
}
platform_device_del(sncpf_device);
platform_device_put(sncpf_device);
platform_driver_unregister(&sncpf_driver);
}
/*
* Backlight device
*/
static int sony_backlight_update_status(struct backlight_device *bd)
{
return acpi_callsetfunc(sony_acpi_handle, "SBRT",
bd->props.brightness + 1, NULL);
}
static int sony_backlight_get_brightness(struct backlight_device *bd)
{
int value;
if (acpi_callgetfunc(sony_acpi_handle, "GBRT", &value))
return 0;
/* brightness levels are 1-based, while backlight ones are 0-based */
return value - 1;
}
static struct backlight_device *sony_backlight_device;
static struct backlight_ops sony_backlight_ops = {
.update_status = sony_backlight_update_status,
.get_brightness = sony_backlight_get_brightness,
};
/*
* ACPI callbacks
*/
static void sony_acpi_notify(acpi_handle handle, u32 event, void *data)
{
if (debug)
printk(LOG_PFX "sony_acpi_notify, event: %d\n", event);
acpi_bus_generate_event(sony_acpi_acpi_device, 1, event);
}
static acpi_status sony_walk_callback(acpi_handle handle, u32 level,
void *context, void **return_value)
{
struct acpi_namespace_node *node;
union acpi_operand_object *operand;
node = (struct acpi_namespace_node *)handle;
operand = (union acpi_operand_object *)node->object;
printk(LOG_PFX "method: name: %4.4s, args %X\n", node->name.ascii,
(u32) operand->method.param_count);
return AE_OK;
}
/*
* ACPI device
*/
static int sony_acpi_resume(struct acpi_device *device)
{
struct sony_acpi_value *item;
for (item = sony_acpi_values; item->name; item++) {
int ret;
if (!item->valid)
continue;
ret = acpi_callsetfunc(sony_acpi_handle, *item->acpiset,
item->value, NULL);
if (ret < 0) {
printk("%s: %d\n", __FUNCTION__, ret);
break;
}
}
return 0;
}
static int sony_acpi_add(struct acpi_device *device)
{
acpi_status status;
int result = 0;
acpi_handle handle;
sony_acpi_acpi_device = device;
sony_acpi_handle = device->handle;
if (debug) {
status = acpi_walk_namespace(ACPI_TYPE_METHOD, sony_acpi_handle,
1, sony_walk_callback, NULL, NULL);
if (ACPI_FAILURE(status)) {
printk(LOG_PFX "unable to walk acpi resources\n");
result = -ENODEV;
goto outwalk;
}
}
status = acpi_install_notify_handler(sony_acpi_handle,
ACPI_DEVICE_NOTIFY,
sony_acpi_notify, NULL);
if (ACPI_FAILURE(status)) {
printk(LOG_PFX "unable to install notify handler\n");
result = -ENODEV;
goto outwalk;
}
if (ACPI_SUCCESS(acpi_get_handle(sony_acpi_handle, "GBRT", &handle))) {
sony_backlight_device = backlight_device_register("sony", NULL,
NULL,
&sony_backlight_ops);
if (IS_ERR(sony_backlight_device)) {
printk(LOG_PFX "unable to register backlight device\n");
sony_backlight_device = NULL;
} else {
sony_backlight_device->props.brightness =
sony_backlight_get_brightness
(sony_backlight_device);
sony_backlight_device->props.max_brightness =
SONY_MAX_BRIGHTNESS - 1;
}
}
if (sony_snc_pf_add())
goto outbacklight;
printk(KERN_INFO ACPI_SNC_DRIVER_NAME " successfully installed\n");
return 0;
outbacklight:
if (sony_backlight_device)
backlight_device_unregister(sony_backlight_device);
status = acpi_remove_notify_handler(sony_acpi_handle,
ACPI_DEVICE_NOTIFY,
sony_acpi_notify);
if (ACPI_FAILURE(status))
printk(LOG_PFX "unable to remove notify handler\n");
outwalk:
return result;
}
static int sony_acpi_remove(struct acpi_device *device, int type)
{
acpi_status status;
if (sony_backlight_device)
backlight_device_unregister(sony_backlight_device);
sony_acpi_acpi_device = NULL;
status = acpi_remove_notify_handler(sony_acpi_handle,
ACPI_DEVICE_NOTIFY,
sony_acpi_notify);
if (ACPI_FAILURE(status))
printk(LOG_PFX "unable to remove notify handler\n");
sony_snc_pf_remove();
printk(KERN_INFO ACPI_SNC_DRIVER_NAME " successfully removed\n");
return 0;
}
static struct acpi_driver sony_acpi_driver = {
.name = ACPI_SNC_DRIVER_NAME,
.class = ACPI_SNC_CLASS,
.ids = ACPI_SNC_HID,
.ops = {
.add = sony_acpi_add,
.remove = sony_acpi_remove,
.resume = sony_acpi_resume,
},
};
static int __init sony_acpi_init(void)
{
return acpi_bus_register_driver(&sony_acpi_driver);
}
static void __exit sony_acpi_exit(void)
{
acpi_bus_unregister_driver(&sony_acpi_driver);
}
module_init(sony_acpi_init);
module_exit(sony_acpi_exit);

464
drivers/misc/tifm_7xx1.c Normal file
View File

@@ -0,0 +1,464 @@
/*
* tifm_7xx1.c - TI FlashMedia driver
*
* Copyright (C) 2006 Alex Dubov <oakad@yahoo.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/tifm.h>
#include <linux/dma-mapping.h>
#include <linux/freezer.h>
#define DRIVER_NAME "tifm_7xx1"
#define DRIVER_VERSION "0.7"
static void tifm_7xx1_eject(struct tifm_adapter *fm, struct tifm_dev *sock)
{
unsigned long flags;
spin_lock_irqsave(&fm->lock, flags);
fm->socket_change_set |= 1 << sock->socket_id;
wake_up_all(&fm->change_set_notify);
spin_unlock_irqrestore(&fm->lock, flags);
}
static irqreturn_t tifm_7xx1_isr(int irq, void *dev_id)
{
struct tifm_adapter *fm = dev_id;
struct tifm_dev *sock;
unsigned int irq_status;
unsigned int sock_irq_status, cnt;
spin_lock(&fm->lock);
irq_status = readl(fm->addr + FM_INTERRUPT_STATUS);
if (irq_status == 0 || irq_status == (~0)) {
spin_unlock(&fm->lock);
return IRQ_NONE;
}
if (irq_status & TIFM_IRQ_ENABLE) {
writel(TIFM_IRQ_ENABLE, fm->addr + FM_CLEAR_INTERRUPT_ENABLE);
for (cnt = 0; cnt < fm->num_sockets; cnt++) {
sock = fm->sockets[cnt];
sock_irq_status = (irq_status >> cnt)
& (TIFM_IRQ_FIFOMASK(1)
| TIFM_IRQ_CARDMASK(1));
if (sock && sock_irq_status)
sock->signal_irq(sock, sock_irq_status);
}
fm->socket_change_set |= irq_status
& ((1 << fm->num_sockets) - 1);
}
writel(irq_status, fm->addr + FM_INTERRUPT_STATUS);
if (!fm->socket_change_set)
writel(TIFM_IRQ_ENABLE, fm->addr + FM_SET_INTERRUPT_ENABLE);
else
wake_up_all(&fm->change_set_notify);
spin_unlock(&fm->lock);
return IRQ_HANDLED;
}
static tifm_media_id tifm_7xx1_toggle_sock_power(char __iomem *sock_addr,
int is_x2)
{
unsigned int s_state;
int cnt;
writel(0x0e00, sock_addr + SOCK_CONTROL);
for (cnt = 0; cnt < 100; cnt++) {
if (!(TIFM_SOCK_STATE_POWERED
& readl(sock_addr + SOCK_PRESENT_STATE)))
break;
msleep(10);
}
s_state = readl(sock_addr + SOCK_PRESENT_STATE);
if (!(TIFM_SOCK_STATE_OCCUPIED & s_state))
return FM_NULL;
if (is_x2) {
writel((s_state & 7) | 0x0c00, sock_addr + SOCK_CONTROL);
} else {
// SmartMedia cards need extra 40 msec
if (((readl(sock_addr + SOCK_PRESENT_STATE) >> 4) & 7) == 1)
msleep(40);
writel(readl(sock_addr + SOCK_CONTROL) | TIFM_CTRL_LED,
sock_addr + SOCK_CONTROL);
msleep(10);
writel((s_state & 0x7) | 0x0c00 | TIFM_CTRL_LED,
sock_addr + SOCK_CONTROL);
}
for (cnt = 0; cnt < 100; cnt++) {
if ((TIFM_SOCK_STATE_POWERED
& readl(sock_addr + SOCK_PRESENT_STATE)))
break;
msleep(10);
}
if (!is_x2)
writel(readl(sock_addr + SOCK_CONTROL) & (~TIFM_CTRL_LED),
sock_addr + SOCK_CONTROL);
return (readl(sock_addr + SOCK_PRESENT_STATE) >> 4) & 7;
}
inline static char __iomem *
tifm_7xx1_sock_addr(char __iomem *base_addr, unsigned int sock_num)
{
return base_addr + ((sock_num + 1) << 10);
}
static int tifm_7xx1_switch_media(void *data)
{
struct tifm_adapter *fm = data;
unsigned long flags;
tifm_media_id media_id;
char *card_name = "xx";
int cnt, rc;
struct tifm_dev *sock;
unsigned int socket_change_set;
while (1) {
rc = wait_event_interruptible(fm->change_set_notify,
fm->socket_change_set);
if (rc == -ERESTARTSYS)
try_to_freeze();
spin_lock_irqsave(&fm->lock, flags);
socket_change_set = fm->socket_change_set;
fm->socket_change_set = 0;
dev_dbg(fm->dev, "checking media set %x\n",
socket_change_set);
if (kthread_should_stop())
socket_change_set = (1 << fm->num_sockets) - 1;
spin_unlock_irqrestore(&fm->lock, flags);
if (!socket_change_set)
continue;
spin_lock_irqsave(&fm->lock, flags);
for (cnt = 0; cnt < fm->num_sockets; cnt++) {
if (!(socket_change_set & (1 << cnt)))
continue;
sock = fm->sockets[cnt];
if (sock) {
printk(KERN_INFO DRIVER_NAME
": demand removing card from socket %d\n",
cnt);
fm->sockets[cnt] = NULL;
spin_unlock_irqrestore(&fm->lock, flags);
device_unregister(&sock->dev);
spin_lock_irqsave(&fm->lock, flags);
writel(0x0e00,
tifm_7xx1_sock_addr(fm->addr, cnt)
+ SOCK_CONTROL);
}
if (kthread_should_stop())
continue;
spin_unlock_irqrestore(&fm->lock, flags);
media_id = tifm_7xx1_toggle_sock_power(
tifm_7xx1_sock_addr(fm->addr, cnt),
fm->num_sockets == 2);
if (media_id) {
sock = tifm_alloc_device(fm);
if (sock) {
sock->addr = tifm_7xx1_sock_addr(fm->addr,
cnt);
sock->media_id = media_id;
sock->socket_id = cnt;
switch (media_id) {
case 1:
card_name = "xd";
break;
case 2:
card_name = "ms";
break;
case 3:
card_name = "sd";
break;
default:
tifm_free_device(&sock->dev);
spin_lock_irqsave(&fm->lock, flags);
continue;
}
snprintf(sock->dev.bus_id, BUS_ID_SIZE,
"tifm_%s%u:%u", card_name,
fm->id, cnt);
printk(KERN_INFO DRIVER_NAME
": %s card detected in socket %d\n",
card_name, cnt);
if (!device_register(&sock->dev)) {
spin_lock_irqsave(&fm->lock, flags);
if (!fm->sockets[cnt]) {
fm->sockets[cnt] = sock;
sock = NULL;
}
spin_unlock_irqrestore(&fm->lock, flags);
}
if (sock)
tifm_free_device(&sock->dev);
}
spin_lock_irqsave(&fm->lock, flags);
}
}
if (!kthread_should_stop()) {
writel(TIFM_IRQ_FIFOMASK(socket_change_set)
| TIFM_IRQ_CARDMASK(socket_change_set),
fm->addr + FM_CLEAR_INTERRUPT_ENABLE);
writel(TIFM_IRQ_FIFOMASK(socket_change_set)
| TIFM_IRQ_CARDMASK(socket_change_set),
fm->addr + FM_SET_INTERRUPT_ENABLE);
writel(TIFM_IRQ_ENABLE,
fm->addr + FM_SET_INTERRUPT_ENABLE);
spin_unlock_irqrestore(&fm->lock, flags);
} else {
for (cnt = 0; cnt < fm->num_sockets; cnt++) {
if (fm->sockets[cnt])
fm->socket_change_set |= 1 << cnt;
}
if (!fm->socket_change_set) {
spin_unlock_irqrestore(&fm->lock, flags);
return 0;
} else {
spin_unlock_irqrestore(&fm->lock, flags);
}
}
}
return 0;
}
#ifdef CONFIG_PM
static int tifm_7xx1_suspend(struct pci_dev *dev, pm_message_t state)
{
dev_dbg(&dev->dev, "suspending host\n");
pci_save_state(dev);
pci_enable_wake(dev, pci_choose_state(dev, state), 0);
pci_disable_device(dev);
pci_set_power_state(dev, pci_choose_state(dev, state));
return 0;
}
static int tifm_7xx1_resume(struct pci_dev *dev)
{
struct tifm_adapter *fm = pci_get_drvdata(dev);
int cnt, rc;
unsigned long flags;
tifm_media_id new_ids[fm->num_sockets];
pci_set_power_state(dev, PCI_D0);
pci_restore_state(dev);
rc = pci_enable_device(dev);
if (rc)
return rc;
pci_set_master(dev);
dev_dbg(&dev->dev, "resuming host\n");
for (cnt = 0; cnt < fm->num_sockets; cnt++)
new_ids[cnt] = tifm_7xx1_toggle_sock_power(
tifm_7xx1_sock_addr(fm->addr, cnt),
fm->num_sockets == 2);
spin_lock_irqsave(&fm->lock, flags);
fm->socket_change_set = 0;
for (cnt = 0; cnt < fm->num_sockets; cnt++) {
if (fm->sockets[cnt]) {
if (fm->sockets[cnt]->media_id == new_ids[cnt])
fm->socket_change_set |= 1 << cnt;
fm->sockets[cnt]->media_id = new_ids[cnt];
}
}
writel(TIFM_IRQ_ENABLE | TIFM_IRQ_SOCKMASK((1 << fm->num_sockets) - 1),
fm->addr + FM_SET_INTERRUPT_ENABLE);
if (!fm->socket_change_set) {
spin_unlock_irqrestore(&fm->lock, flags);
return 0;
} else {
fm->socket_change_set = 0;
spin_unlock_irqrestore(&fm->lock, flags);
}
wait_event_timeout(fm->change_set_notify, fm->socket_change_set, HZ);
spin_lock_irqsave(&fm->lock, flags);
writel(TIFM_IRQ_FIFOMASK(fm->socket_change_set)
| TIFM_IRQ_CARDMASK(fm->socket_change_set),
fm->addr + FM_CLEAR_INTERRUPT_ENABLE);
writel(TIFM_IRQ_FIFOMASK(fm->socket_change_set)
| TIFM_IRQ_CARDMASK(fm->socket_change_set),
fm->addr + FM_SET_INTERRUPT_ENABLE);
writel(TIFM_IRQ_ENABLE,
fm->addr + FM_SET_INTERRUPT_ENABLE);
fm->socket_change_set = 0;
spin_unlock_irqrestore(&fm->lock, flags);
return 0;
}
#else
#define tifm_7xx1_suspend NULL
#define tifm_7xx1_resume NULL
#endif /* CONFIG_PM */
static int tifm_7xx1_probe(struct pci_dev *dev,
const struct pci_device_id *dev_id)
{
struct tifm_adapter *fm;
int pci_dev_busy = 0;
int rc;
rc = pci_set_dma_mask(dev, DMA_32BIT_MASK);
if (rc)
return rc;
rc = pci_enable_device(dev);
if (rc)
return rc;
pci_set_master(dev);
rc = pci_request_regions(dev, DRIVER_NAME);
if (rc) {
pci_dev_busy = 1;
goto err_out;
}
pci_intx(dev, 1);
fm = tifm_alloc_adapter();
if (!fm) {
rc = -ENOMEM;
goto err_out_int;
}
fm->dev = &dev->dev;
fm->num_sockets = (dev->device == PCI_DEVICE_ID_TI_XX21_XX11_FM)
? 4 : 2;
fm->sockets = kzalloc(sizeof(struct tifm_dev*) * fm->num_sockets,
GFP_KERNEL);
if (!fm->sockets)
goto err_out_free;
fm->eject = tifm_7xx1_eject;
pci_set_drvdata(dev, fm);
fm->addr = ioremap(pci_resource_start(dev, 0),
pci_resource_len(dev, 0));
if (!fm->addr)
goto err_out_free;
rc = request_irq(dev->irq, tifm_7xx1_isr, IRQF_SHARED, DRIVER_NAME, fm);
if (rc)
goto err_out_unmap;
init_waitqueue_head(&fm->change_set_notify);
rc = tifm_add_adapter(fm, tifm_7xx1_switch_media);
if (rc)
goto err_out_irq;
writel(TIFM_IRQ_SETALL, fm->addr + FM_CLEAR_INTERRUPT_ENABLE);
writel(TIFM_IRQ_ENABLE | TIFM_IRQ_SOCKMASK((1 << fm->num_sockets) - 1),
fm->addr + FM_SET_INTERRUPT_ENABLE);
wake_up_process(fm->media_switcher);
return 0;
err_out_irq:
free_irq(dev->irq, fm);
err_out_unmap:
iounmap(fm->addr);
err_out_free:
pci_set_drvdata(dev, NULL);
tifm_free_adapter(fm);
err_out_int:
pci_intx(dev, 0);
pci_release_regions(dev);
err_out:
if (!pci_dev_busy)
pci_disable_device(dev);
return rc;
}
static void tifm_7xx1_remove(struct pci_dev *dev)
{
struct tifm_adapter *fm = pci_get_drvdata(dev);
unsigned long flags;
writel(TIFM_IRQ_SETALL, fm->addr + FM_CLEAR_INTERRUPT_ENABLE);
mmiowb();
free_irq(dev->irq, fm);
spin_lock_irqsave(&fm->lock, flags);
fm->socket_change_set = (1 << fm->num_sockets) - 1;
spin_unlock_irqrestore(&fm->lock, flags);
kthread_stop(fm->media_switcher);
tifm_remove_adapter(fm);
pci_set_drvdata(dev, NULL);
iounmap(fm->addr);
pci_intx(dev, 0);
pci_release_regions(dev);
pci_disable_device(dev);
tifm_free_adapter(fm);
}
static struct pci_device_id tifm_7xx1_pci_tbl [] = {
{ PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_XX21_XX11_FM, PCI_ANY_ID,
PCI_ANY_ID, 0, 0, 0 }, /* xx21 - the one I have */
{ PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_XX12_FM, PCI_ANY_ID,
PCI_ANY_ID, 0, 0, 0 },
{ PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_XX20_FM, PCI_ANY_ID,
PCI_ANY_ID, 0, 0, 0 },
{ }
};
static struct pci_driver tifm_7xx1_driver = {
.name = DRIVER_NAME,
.id_table = tifm_7xx1_pci_tbl,
.probe = tifm_7xx1_probe,
.remove = tifm_7xx1_remove,
.suspend = tifm_7xx1_suspend,
.resume = tifm_7xx1_resume,
};
static int __init tifm_7xx1_init(void)
{
return pci_register_driver(&tifm_7xx1_driver);
}
static void __exit tifm_7xx1_exit(void)
{
pci_unregister_driver(&tifm_7xx1_driver);
}
MODULE_AUTHOR("Alex Dubov");
MODULE_DESCRIPTION("TI FlashMedia host driver");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(pci, tifm_7xx1_pci_tbl);
MODULE_VERSION(DRIVER_VERSION);
module_init(tifm_7xx1_init);
module_exit(tifm_7xx1_exit);

306
drivers/misc/tifm_core.c Normal file
View File

@@ -0,0 +1,306 @@
/*
* tifm_core.c - TI FlashMedia driver
*
* Copyright (C) 2006 Alex Dubov <oakad@yahoo.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/tifm.h>
#include <linux/init.h>
#include <linux/idr.h>
#define DRIVER_NAME "tifm_core"
#define DRIVER_VERSION "0.7"
static DEFINE_IDR(tifm_adapter_idr);
static DEFINE_SPINLOCK(tifm_adapter_lock);
static tifm_media_id *tifm_device_match(tifm_media_id *ids,
struct tifm_dev *dev)
{
while (*ids) {
if (dev->media_id == *ids)
return ids;
ids++;
}
return NULL;
}
static int tifm_match(struct device *dev, struct device_driver *drv)
{
struct tifm_dev *fm_dev = container_of(dev, struct tifm_dev, dev);
struct tifm_driver *fm_drv;
fm_drv = container_of(drv, struct tifm_driver, driver);
if (!fm_drv->id_table)
return -EINVAL;
if (tifm_device_match(fm_drv->id_table, fm_dev))
return 1;
return -ENODEV;
}
static int tifm_uevent(struct device *dev, char **envp, int num_envp,
char *buffer, int buffer_size)
{
struct tifm_dev *fm_dev;
int i = 0;
int length = 0;
const char *card_type_name[] = {"INV", "SM", "MS", "SD"};
if (!dev || !(fm_dev = container_of(dev, struct tifm_dev, dev)))
return -ENODEV;
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length,
"TIFM_CARD_TYPE=%s", card_type_name[fm_dev->media_id]))
return -ENOMEM;
return 0;
}
#ifdef CONFIG_PM
static int tifm_device_suspend(struct device *dev, pm_message_t state)
{
struct tifm_dev *fm_dev = container_of(dev, struct tifm_dev, dev);
struct tifm_driver *drv = fm_dev->drv;
if (drv && drv->suspend)
return drv->suspend(fm_dev, state);
return 0;
}
static int tifm_device_resume(struct device *dev)
{
struct tifm_dev *fm_dev = container_of(dev, struct tifm_dev, dev);
struct tifm_driver *drv = fm_dev->drv;
if (drv && drv->resume)
return drv->resume(fm_dev);
return 0;
}
#else
#define tifm_device_suspend NULL
#define tifm_device_resume NULL
#endif /* CONFIG_PM */
static struct bus_type tifm_bus_type = {
.name = "tifm",
.match = tifm_match,
.uevent = tifm_uevent,
.suspend = tifm_device_suspend,
.resume = tifm_device_resume
};
static void tifm_free(struct class_device *cdev)
{
struct tifm_adapter *fm = container_of(cdev, struct tifm_adapter, cdev);
kfree(fm->sockets);
kfree(fm);
}
static struct class tifm_adapter_class = {
.name = "tifm_adapter",
.release = tifm_free
};
struct tifm_adapter *tifm_alloc_adapter(void)
{
struct tifm_adapter *fm;
fm = kzalloc(sizeof(struct tifm_adapter), GFP_KERNEL);
if (fm) {
fm->cdev.class = &tifm_adapter_class;
spin_lock_init(&fm->lock);
class_device_initialize(&fm->cdev);
}
return fm;
}
EXPORT_SYMBOL(tifm_alloc_adapter);
void tifm_free_adapter(struct tifm_adapter *fm)
{
class_device_put(&fm->cdev);
}
EXPORT_SYMBOL(tifm_free_adapter);
int tifm_add_adapter(struct tifm_adapter *fm,
int (*mediathreadfn)(void *data))
{
int rc;
if (!idr_pre_get(&tifm_adapter_idr, GFP_KERNEL))
return -ENOMEM;
spin_lock(&tifm_adapter_lock);
rc = idr_get_new(&tifm_adapter_idr, fm, &fm->id);
spin_unlock(&tifm_adapter_lock);
if (!rc) {
snprintf(fm->cdev.class_id, BUS_ID_SIZE, "tifm%u", fm->id);
fm->media_switcher = kthread_create(mediathreadfn,
fm, "tifm/%u", fm->id);
if (!IS_ERR(fm->media_switcher))
return class_device_add(&fm->cdev);
spin_lock(&tifm_adapter_lock);
idr_remove(&tifm_adapter_idr, fm->id);
spin_unlock(&tifm_adapter_lock);
rc = -ENOMEM;
}
return rc;
}
EXPORT_SYMBOL(tifm_add_adapter);
void tifm_remove_adapter(struct tifm_adapter *fm)
{
class_device_del(&fm->cdev);
spin_lock(&tifm_adapter_lock);
idr_remove(&tifm_adapter_idr, fm->id);
spin_unlock(&tifm_adapter_lock);
}
EXPORT_SYMBOL(tifm_remove_adapter);
void tifm_free_device(struct device *dev)
{
struct tifm_dev *fm_dev = container_of(dev, struct tifm_dev, dev);
kfree(fm_dev);
}
EXPORT_SYMBOL(tifm_free_device);
static void tifm_dummy_signal_irq(struct tifm_dev *sock,
unsigned int sock_irq_status)
{
return;
}
struct tifm_dev *tifm_alloc_device(struct tifm_adapter *fm)
{
struct tifm_dev *dev = kzalloc(sizeof(struct tifm_dev), GFP_KERNEL);
if (dev) {
spin_lock_init(&dev->lock);
dev->dev.parent = fm->dev;
dev->dev.bus = &tifm_bus_type;
dev->dev.release = tifm_free_device;
dev->signal_irq = tifm_dummy_signal_irq;
}
return dev;
}
EXPORT_SYMBOL(tifm_alloc_device);
void tifm_eject(struct tifm_dev *sock)
{
struct tifm_adapter *fm = dev_get_drvdata(sock->dev.parent);
fm->eject(fm, sock);
}
EXPORT_SYMBOL(tifm_eject);
int tifm_map_sg(struct tifm_dev *sock, struct scatterlist *sg, int nents,
int direction)
{
return pci_map_sg(to_pci_dev(sock->dev.parent), sg, nents, direction);
}
EXPORT_SYMBOL(tifm_map_sg);
void tifm_unmap_sg(struct tifm_dev *sock, struct scatterlist *sg, int nents,
int direction)
{
pci_unmap_sg(to_pci_dev(sock->dev.parent), sg, nents, direction);
}
EXPORT_SYMBOL(tifm_unmap_sg);
static int tifm_device_probe(struct device *dev)
{
struct tifm_driver *drv;
struct tifm_dev *fm_dev;
int rc = 0;
const tifm_media_id *id;
drv = container_of(dev->driver, struct tifm_driver, driver);
fm_dev = container_of(dev, struct tifm_dev, dev);
get_device(dev);
if (!fm_dev->drv && drv->probe && drv->id_table) {
rc = -ENODEV;
id = tifm_device_match(drv->id_table, fm_dev);
if (id)
rc = drv->probe(fm_dev);
if (rc >= 0) {
rc = 0;
fm_dev->drv = drv;
}
}
if (rc)
put_device(dev);
return rc;
}
static int tifm_device_remove(struct device *dev)
{
struct tifm_dev *fm_dev = container_of(dev, struct tifm_dev, dev);
struct tifm_driver *drv = fm_dev->drv;
if (drv) {
fm_dev->signal_irq = tifm_dummy_signal_irq;
if (drv->remove)
drv->remove(fm_dev);
fm_dev->drv = NULL;
}
put_device(dev);
return 0;
}
int tifm_register_driver(struct tifm_driver *drv)
{
drv->driver.bus = &tifm_bus_type;
drv->driver.probe = tifm_device_probe;
drv->driver.remove = tifm_device_remove;
drv->driver.suspend = tifm_device_suspend;
drv->driver.resume = tifm_device_resume;
return driver_register(&drv->driver);
}
EXPORT_SYMBOL(tifm_register_driver);
void tifm_unregister_driver(struct tifm_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL(tifm_unregister_driver);
static int __init tifm_init(void)
{
int rc = bus_register(&tifm_bus_type);
if (!rc) {
rc = class_register(&tifm_adapter_class);
if (rc)
bus_unregister(&tifm_bus_type);
}
return rc;
}
static void __exit tifm_exit(void)
{
class_unregister(&tifm_adapter_class);
bus_unregister(&tifm_bus_type);
}
subsys_initcall(tifm_init);
module_exit(tifm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alex Dubov");
MODULE_DESCRIPTION("TI FlashMedia core driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);