--- linux-2.4.18-14orig/Documentation/Configure.help 2003-03-27 15:37:28.000000000 -0500 +++ linux-2.4.18-14/Documentation/Configure.help 2003-03-27 15:32:58.000000000 -0500 @@ -25590,6 +25590,17 @@ CONFIG_IPMI_WATCHDOG This enables the IPMI watchdog timer. +Emulate Radisys IPMI driver +CONFIG_IPMI_EMULATE_RADISYS + This enables emulation of the Radisys IPMI device driver. + +Emulate Intel IBM driver +CONFIG_IPMI_EMULATE_IMB + This enables emulation of the Intel IMB device driver. Note that you + MUST have the IPMI watchdog timer enabled to use this. This code + uses some of the watchdog code, but the dependency is not enforced + by config. + # # A couple of things I keep forgetting: # capitalize: AppleTalk, Ethernet, DOS, DMA, FAT, FTP, Internet, --- linux-2.4.18-14orig/drivers/char/Config.in 2003-03-27 15:37:28.000000000 -0500 +++ linux-2.4.18-14/drivers/char/Config.in 2003-03-27 15:32:58.000000000 -0500 @@ -180,6 +180,8 @@ dep_tristate ' Device interface for IPMI' CONFIG_IPMI_DEVICE_INTERFACE $CONFIG_IPMI_HANDLER dep_tristate ' IPMI KCS handler' CONFIG_IPMI_KCS $CONFIG_IPMI_HANDLER dep_tristate ' IPMI Watchdog Timer' CONFIG_IPMI_WATCHDOG $CONFIG_IPMI_HANDLER +dep_tristate ' Emulate Radisys IPMI driver' CONFIG_IPMI_EMULATE_RADISYS $CONFIG_IPMI_HANDLER +dep_tristate ' Emulate Intel IMB driver' CONFIG_IPMI_EMULATE_IMB $CONFIG_IPMI_WATCHDOG mainmenu_option next_comment comment 'Watchdog Cards' --- linux-2.4.18-14orig/drivers/char/ipmi/Makefile 2003-03-27 15:37:28.000000000 -0500 +++ linux-2.4.18-14/drivers/char/ipmi/Makefile 2003-03-27 15:32:58.000000000 -0500 @@ -13,6 +13,8 @@ obj-$(CONFIG_IPMI_DEVICE_INTERFACE) += ipmi_devintf.o obj-$(CONFIG_IPMI_KCS) += ipmi_kcs_drv.o obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o +obj-$(CONFIG_IPMI_EMULATE_RADISYS) += ipmi_radisys.o +obj-$(CONFIG_IPMI_EMULATE_IMB) += ipmi_imb.o include $(TOPDIR)/Rules.make --- /dev/null 2002-08-30 19:31:37.000000000 -0400 +++ linux-2.4.18-14/include/linux/ipmi_imb.h 2003-03-27 15:33:11.000000000 -0500 @@ -0,0 +1,144 @@ +/* + * ipmi_imb.h + * + * Intels IMB emulation on the MontaVista IPMI interface + * + * Author: MontaVista Software, Inc. + * Corey Minyard + * source@mvista.com + * + * Copyright 2002 MontaVista Software Inc. + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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. + */ + +#ifndef __LINUX_IPMI_IMB_H +#define __LINUX_IPMI_IMB_H + +typedef struct overlapped_s { + unsigned long Internal; + unsigned long InternalHigh; + unsigned long Offset; + unsigned long OffsetHigh; +} overlapped_t; + +struct smi { + unsigned long smi_VersionNo; + unsigned long smi_Reserved1; + unsigned long smi_Reserved2; + void *ntstatus; /* address of NT status block*/ + void *lpvInBuffer; /* address of buffer for input data*/ + unsigned long cbInBuffer; /* size of input buffer*/ + void *lpvOutBuffer; /* address of output buffer*/ + unsigned long cbOutBuffer; /* size of output buffer*/ + unsigned long *lpcbBytesReturned; /* address of actual bytes of output*/ + overlapped_t *lpoOverlapped; /* address of overlapped structure*/ +}; + + +#define MAX_IMB_PACKET_SIZE 33 + +typedef struct { + unsigned char rsSa; + unsigned char cmd; + unsigned char netFn; + unsigned char rsLun; + unsigned char dataLength; + unsigned char data[1]; +} ImbRequest; + +typedef struct { + unsigned long flags; +#define NO_RESPONSE_EXPECTED 0x01 + + unsigned long timeOut; + ImbRequest req; +} ImbRequestBuffer; + +#define MIN_IMB_REQ_BUF_SIZE 13 + + +typedef struct { + unsigned char cCode; + unsigned char data[1]; +} ImbResponseBuffer; + +#define MIN_IMB_RESP_BUF_SIZE 1 // a buffer without any request data +#define MAX_IMB_RESP_SIZE (MIN_IMB_RESP_BUF_SIZE + MAX_IMB_RESPONSE_SIZE) + +#define MIN_IMB_RESPONSE_SIZE 7 +#define MAX_IMB_RESPONSE_SIZE MAX_IMB_PACKET_SIZE + +typedef struct { + unsigned long timeOut; + unsigned long lastSeq; +} ImbAsyncRequest; + +typedef struct { + unsigned long thisSeq; + unsigned char data[1]; +} ImbAsyncResponse; + +#define MIN_ASYNC_RESP_SIZE sizeof(unsigned long) +#define MAX_ASYNC_RESP_SIZE (MIN_ASYNC_RESP_SIZE + MAX_IMB_PACKET_SIZE) + +#define STATUS_SUCCESS (0x00000000U) +#define IMB_NO_ASYNC_MSG ((unsigned long)0xE0070012L) +#define IMB_SEND_REQUEST_FAILED ((unsigned long)0xE0070013L) +#define INVALID_ARGUMENTS ((unsigned long)0xE0070002L) + + +#define FILE_DEVICE_IMB 0x00008010 +#define IOCTL_IMB_BASE 0x00000880 + +#define CTL_CODE(DeviceType, Function, Method, Access)\ + _IO(DeviceType & 0x00FF, Function & 0x00FF) + +#define FILE_DEVICE_IMB 0x00008010 +#define IOCTL_IMB_BASE 0x00000880 +#define METHOD_BUFFERED 0 +#define FILE_ANY_ACCESS 0 + + +typedef struct { + int code; +#define SD_NO_ACTION 0 +#define SD_RESET 1 +#define SD_POWER_OFF 2 + + int delayTime; /* in units of 100 millisecond */ +} ShutdownCmdBuffer; + + +/* BMC added parentheses around IOCTL_IMB_BASE + 2 */ +#define IOCTL_IMB_SEND_MESSAGE CTL_CODE(FILE_DEVICE_IMB, (IOCTL_IMB_BASE + 2), METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_IMB_GET_ASYNC_MSG CTL_CODE(FILE_DEVICE_IMB, (IOCTL_IMB_BASE + 8), METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_IMB_MAP_MEMORY CTL_CODE(FILE_DEVICE_IMB, (IOCTL_IMB_BASE + 14),METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_IMB_UNMAP_MEMORY CTL_CODE(FILE_DEVICE_IMB, (IOCTL_IMB_BASE + 16),METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_IMB_SHUTDOWN_CODE CTL_CODE(FILE_DEVICE_IMB, (IOCTL_IMB_BASE + 18),METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_IMB_REGISTER_ASYNC_OBJ CTL_CODE(FILE_DEVICE_IMB, (IOCTL_IMB_BASE + 24),METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_IMB_DEREGISTER_ASYNC_OBJ CTL_CODE(FILE_DEVICE_IMB, (IOCTL_IMB_BASE + 26),METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_IMB_CHECK_EVENT CTL_CODE(FILE_DEVICE_IMB, (IOCTL_IMB_BASE + 28),METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_IMB_POLL_ASYNC CTL_CODE(FILE_DEVICE_IMB, (IOCTL_IMB_BASE + 20),METHOD_BUFFERED, FILE_ANY_ACCESS) + + +#endif /* __LINUX_IPMI_IMB_H */ --- /dev/null 2002-08-30 19:31:37.000000000 -0400 +++ linux-2.4.18-14/include/linux/ipmi_radisys.h 2003-03-27 15:33:11.000000000 -0500 @@ -0,0 +1,128 @@ +/* + * ipmi_radisys.h + * + * An emulation of the Radisys IPMI interface on top of the MontaVista + * interface. + * + * Author: MontaVista Software, Inc. + * Corey Minyard + * source@mvista.com + * + * Copyright 2002 MontaVista Software Inc. + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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. + */ + +#ifndef __LINUX_IPMI_RADISYS_H +#define __LINUX_IPMI_RADISYS_H + +/****************************************************************************** + * This is the old IPMI interface defined by Radisys. We are + * compliant with that. Don't use it for new designs, though. + */ +#define IOCTL_IPMI_RCV ( IPMI_IOC_MAGIC<<8 | 1 ) +#define IOCTL_IPMI_SEND ( IPMI_IOC_MAGIC<<8 | 2 ) +#define IOCTL_IPMI_EVENT ( IPMI_IOC_MAGIC<<8 | 3 ) +#define IOCTL_IPMI_REGISTER ( IPMI_IOC_MAGIC<<8 | 4 ) +#define IOCTL_IPMI_UNREGISTER ( IPMI_IOC_MAGIC<<8 | 5 ) +#define IOCTL_IPMI_CLEAR ( IPMI_IOC_MAGIC<<8 | 9 ) + +/* These don't seem to be implemented in the Radisys driver. +#define IOCTL_IPMI_RESET_BMC ( IPMI_IOC_MAGIC<<8 | 6 ) +#define IOCTL_IPMI_GET_BMC_ADDR ( IPMI_IOC_MAGIC<<8 | 7 ) +#define IOCTL_IPMI_SET_BMC_ADDR ( IPMI_IOC_MAGIC<<8 | 8 ) +*/ + +/* + * Network Function Codes + */ +#define IPMI_NETFN_CHASSIS 0x00 /* Chassis - 0x00 << 2 */ +#define IPMI_NETFN_CHASSIS_RESP 0x04 /* Chassis - 0x01 << 2 */ + +#define IPMI_NETFN_BRIDGE 0x08 /* Bridge - 0x02 << 2 */ +#define IPMI_NETFN_BRIDGE_RESP 0x0c /* Bridge - 0x03 << 2 */ + +#define IPMI_NETFN_SENSOR_EVT 0x10 /* Sensor/Event - 0x04 << 2 */ +#define IPMI_NETFN_SENSOR_EVT_RESP 0x14 /* Sensor/Event - 0x05 << 2 */ + +#define IPMI_NETFN_APP 0x18 /* Application - 0x06 << 2 */ +#define IPMI_NETFN_APP_RESP 0x1c /* Application - 0x07 << 2 */ + +#define IPMI_NETFN_FIRMWARE 0x20 /* Firmware - 0x08 << 2 */ +#define IPMI_NETFN_FIRMWARE_RESP 0x24 /* Firmware - 0x09 << 2 */ + +#define IPMI_NETFN_STORAGE 0x28 /* Storage - 0x0a << 2 */ +#define IPMI_NETFN_STORAGE_RESP 0x2c /* Storage - 0x0b << 2 */ + +#define IPMI_NETFN_OEM_1 0xC0 /* Storage - 0x30 << 2 */ +#define IPMI_NETFN_OEM_1_RESP 0xC4 /* Storage - 0x31 << 2 */ + +/* there are 15 other OEM netfn pairs (OEM - 0x30-0x3f) */ + +typedef struct _IPMI_LIST_ENTRY { + struct _IPMI_LIST_ENTRY * volatile Flink; + struct _IPMI_LIST_ENTRY * volatile Blink; +} IPMI_LIST_ENTRY, *PIPMI_LIST_ENTRY; + +typedef struct IPMI_semaphore IPMI_KSEMAPHORE; +typedef struct IPMI_semaphore * IPMI_PKSEMAPHORE; + +/* IPMI Address structure */ +typedef struct _IPMI_ADDR { + unsigned char uchSlave; /* Slave Address */ + unsigned char uchLun; /* Logical Unit Number */ +} IPMI_ADDR, *PIPMI_ADDR; + +#define IPMI_MAX_MSG_SIZE 36 + +/* IPMI Message Descriptor structure */ +typedef struct _IPMI_MSGDESC { + /************************************/ + /* Device Driver Specific Elements */ + /************************************/ + IPMI_LIST_ENTRY Entry; /* Linked list element */ + void *pIRPacket; /* Pointer to IRP object */ + IPMI_PKSEMAPHORE pSema; /* Semaphore Object */ + long lTimeout; /* Timeout value */ + /************************************/ + /* Shared elements */ + /************************************/ + unsigned char auchBuffer[IPMI_MAX_MSG_SIZE]; /* Message buffer */ + unsigned long ulLength; /* Length of message in bytes */ + int fDefer; /* TRUE - Defer I/O + operation, doesn't seem + to be used in the + Radisys driver. */ + IPMI_ADDR Dest; /* Destination IPM Address */ + unsigned char uchNetFn; /* Network Function */ + unsigned char uchCmd; /* Command */ + unsigned char uchSeq; /* Sequence Number */ + unsigned char uchComplete; /* Completion Code */ +} IPMI_MSGDESC, *PIPMI_MSGDESC; + +/* Byte return codes for some things. */ +#define LOWLRC_SUCCESS 0x00 /* routine completed successfully */ +#define LOWLRC_ERROR 0xff /* routine did not complete */ +#define LOWLRC_INVALID_PARAMETERS 0xfe /* invalid parameters */ +#define LOWLRC_INVALID_REQUEST_DATA 0xfd /* invalid request data */ + +#endif /* __LINUX_IPMI_RADISYS_H */ --- /dev/null 2002-08-30 19:31:37.000000000 -0400 +++ linux-2.4.18-14/drivers/char/ipmi/ipmi_imb.c 2003-03-27 15:32:59.000000000 -0500 @@ -0,0 +1,744 @@ +/* + * ipmi_imb.c + * + * Intel IMB emulation for the IPMI interface. + * + * Author: MontaVista Software, Inc. + * Corey Minyard + * source@mvista.com + * + * Copyright 2002 MontaVista Software Inc. + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +#define MAX_BUFFER_SIZE 64 +#define BMC_SA 0x20 + +struct priv_data +{ + /* This is for supporting the old Imb interface. */ + ipmi_user_t imb_user; + spinlock_t imb_lock; + + unsigned long curr_msgid; + + /* A list of responses in the queue. */ + struct list_head imb_waiting_rsps; + + /* A list of things waiting for responses. We wake them all up + when a response comes in. */ + wait_queue_head_t imb_waiting_rsp_rcvrs; + + /* A list of commands that have come in. */ + struct list_head imb_waiting_cmds; + + /* A list of thing waiting for commands. We wake them all up + when a command comes in. */ + wait_queue_head_t imb_waiting_cmd_rcvrs; + + /* The registered command receiver value. This only allows someone + with the "magic number" to issue commands. */ + unsigned long imb_cmd_receiver; + + /* Is someone already waiting for a command? The Imb driver + only allows one waiter, this enforces that. */ + int imb_cmd_waiting; + + /* A list of IPMI events waiting to be delivered. (not that + the Imb driver calls incoming commands "events", this + variable is actual IPMI events, not incoming commands). */ + struct list_head imb_waiting_events; + +#define IMB_EVENT_QUEUE_LIMIT 16 /* Allow up to 16 events. */ + /* The number of events in the event queue. */ + unsigned int imb_waiting_event_count; +}; + +static devfs_handle_t devfs_handle; + + +/* We cheat and use a piece of the address as the timeout. */ +static long *imb_timeout(struct ipmi_recv_msg *msg) +{ + char *base = (char *) &(msg->addr); + base += sizeof(struct ipmi_ipmb_addr); + return (long *) base; +} + +static void imb_msg_recv(struct ipmi_recv_msg *msg, + void *data) +{ + struct priv_data *priv = data; + unsigned long flags; + + spin_lock_irqsave(&(priv->imb_lock), flags); + if (msg->recv_type == IPMI_RESPONSE_RECV_TYPE) { + *imb_timeout(msg) = 5000; + list_add_tail(&(msg->link), &(priv->imb_waiting_rsps)); + wake_up_all(&(priv->imb_waiting_rsp_rcvrs)); + } else if (msg->recv_type == IPMI_CMD_RECV_TYPE) { + *imb_timeout(msg) = 5000; + list_add_tail(&(msg->link), &(priv->imb_waiting_cmds)); + wake_up_all(&(priv->imb_waiting_cmd_rcvrs)); + } else if (msg->recv_type == IPMI_ASYNC_EVENT_RECV_TYPE) { + if (priv->imb_waiting_event_count > IMB_EVENT_QUEUE_LIMIT) { + ipmi_free_recv_msg(msg); + } else { + list_add_tail(&(msg->link),&(priv->imb_waiting_events)); + (priv->imb_waiting_event_count)++; + } + } else { + ipmi_free_recv_msg(msg); + } + spin_unlock_irqrestore(&(priv->imb_lock), flags); +} + +/* We emulate the event queue in the driver for the imb emulation. */ +static int imb_handle_event_request(struct priv_data *priv, + struct ipmi_recv_msg **rsp) +{ + struct list_head *entry; + unsigned long flags; + struct ipmi_recv_msg *msg = NULL; + int rv = 0; + + spin_lock_irqsave(&(priv->imb_lock), flags); + if (list_empty(&(priv->imb_waiting_events))) { + /* Nothing in the event queue, just return an error. */ + msg = ipmi_alloc_recv_msg(); + if (msg == NULL) { + rv = -EAGAIN; + goto out_err; + } + msg->addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + msg->addr.channel = IPMI_BMC_CHANNEL; + msg->msg.cmd = IPMI_READ_EVENT_MSG_BUFFER_CMD; + msg->msgid = 0; + msg->recv_type = IPMI_ASYNC_EVENT_RECV_TYPE; + msg->msg.netfn = IPMI_NETFN_APP_RESPONSE; + msg->msg.data = msg->msg_data; + msg->msg.data[0] = 0x80; /* Data not available. */ + msg->msg.data_len = 1; + } else { + /* Pull an item from the event queue . */ + entry = priv->imb_waiting_events.next; + list_del(entry); + msg = list_entry(entry, struct ipmi_recv_msg, link); + (priv->imb_waiting_event_count)--; + } + + *rsp = msg; + + out_err: + spin_unlock_irqrestore(&(priv->imb_lock), flags); + return rv; +} + +static struct priv_data *ipmi_user; +static unsigned int user_count = 0; /* How many users have this open. */ +static spinlock_t dev_lock = SPIN_LOCK_UNLOCKED; + +static int ipmi_imb_open(struct inode *inode, struct file *file) +{ + int rv; + + if (user_count == 0) { + rv = ipmi_register_all_cmd_rcvr(ipmi_user->imb_user); + if (rv) { + return rv; + } + } + + file->private_data = ipmi_user; + spin_lock(&dev_lock); + if (user_count == 0) + ipmi_set_gets_events(ipmi_user->imb_user, 1); + user_count++; + spin_unlock(&dev_lock); + + return 0; +} + +static int ipmi_imb_release(struct inode *inode, struct file *file) +{ + spin_lock(&dev_lock); + user_count--; + if (user_count == 0) { + ipmi_set_gets_events(ipmi_user->imb_user, 0); + ipmi_unregister_all_cmd_rcvr(ipmi_user->imb_user); + } + spin_unlock(&dev_lock); + return 0; +} + +static unsigned char +ipmb_checksum(unsigned char *data, int size) +{ + unsigned char csum = 0; + + for (; size > 0; size--, data++) + csum += *data; + + return -csum; +} + +extern void ipmi_delayed_shutdown(long delay, int power_off); + +static int ipmi_imb_ioctl(struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long data) +{ + struct priv_data *priv = file->private_data; + int rv = -EINVAL; + struct smi smi; + unsigned long flags; + + if (copy_from_user((caddr_t)&smi, (caddr_t)data, sizeof(smi))) { + return -EFAULT; + } + + switch(cmd) { + case IOCTL_IMB_POLL_ASYNC: + /* + * No-op for this, the low-level driver polls. + */ + break; + + case IOCTL_IMB_GET_ASYNC_MSG: + { + unsigned char resp[MAX_ASYNC_RESP_SIZE]; + struct ipmi_recv_msg *msg = NULL; + ImbAsyncResponse *pAsyncResp = (ImbAsyncResponse *) resp; + unsigned long length = 0; + + if (smi.cbInBuffer < sizeof(ImbAsyncRequest)) + return -EINVAL; + if (smi.cbOutBuffer < MIN_ASYNC_RESP_SIZE) + return -EINVAL; + + spin_lock_irqsave(&(priv->imb_lock), flags); + + if (list_empty(&(priv->imb_waiting_cmds))) { + /* No command waiting, just return an error. */ + rv = IMB_NO_ASYNC_MSG; + } else { + struct list_head *entry; + + /* Got a command, pull it out and handle it. */ + entry = priv->imb_waiting_cmds.next; + list_del(entry); + msg = list_entry(entry, struct ipmi_recv_msg, link); + rv = STATUS_SUCCESS; + } + spin_unlock_irqrestore(&(priv->imb_lock), flags); + + if (msg != NULL) { + struct ipmi_ipmb_addr *ipmb_addr; + + ipmb_addr = (struct ipmi_ipmb_addr *) &(msg->addr); + pAsyncResp->thisSeq = msg->msgid; + pAsyncResp->data[0] = IPMI_NETFN_APP_REQUEST << 2; + pAsyncResp->data[1] = IPMI_SEND_MSG_CMD; + pAsyncResp->data[2] = 0; + pAsyncResp->data[3] = msg->addr.channel; + pAsyncResp->data[4] = ((msg->msg.netfn << 2) + | 2); + pAsyncResp->data[5] + = ipmb_checksum(&(pAsyncResp->data[3]), + 2); + pAsyncResp->data[6] = ipmb_addr->slave_addr; + pAsyncResp->data[7] = ((msg->msgid << 2) + | ipmb_addr->lun); + pAsyncResp->data[8] = msg->msg.cmd; + + memcpy(&(pAsyncResp->data[9]), + &(msg->msg.data[0]), + msg->msg.data_len); + + length = msg->msg.data_len + MIN_ASYNC_RESP_SIZE; + + ipmi_free_recv_msg(msg); + + if (copy_to_user(smi.lpvOutBuffer, pAsyncResp, length)) + { + return -EFAULT; + } + } + + if (copy_to_user(smi.lpcbBytesReturned, + &length, + sizeof(length))) + { + return -EFAULT; + } + rv = 0; + break; + } + + case IOCTL_IMB_SEND_MESSAGE: + { + unsigned char imbReqBuffer[MAX_IMB_RESPONSE_SIZE + 8]; + unsigned char imbRespBuffer[MAX_IMB_RESPONSE_SIZE + 8]; + ImbRequestBuffer *pImbReq=(ImbRequestBuffer *)imbReqBuffer; + ImbResponseBuffer *pImbResp=(ImbResponseBuffer*)imbRespBuffer; + struct ipmi_addr addr; + struct ipmi_msg msg; + unsigned long msgid; + struct ipmi_recv_msg *rsp; + unsigned long length; + wait_queue_t wait; + struct list_head *entry; + + + if ((smi.cbInBuffer < MIN_IMB_REQ_BUF_SIZE) + || (smi.cbOutBuffer < MIN_IMB_RESP_BUF_SIZE)) + { + return -EINVAL; + } + + if (smi.cbInBuffer > (MAX_IMB_RESPONSE_SIZE + 8)) { + /* Input buffer is too large */ + return -EINVAL; + } + + if (copy_from_user(pImbReq, smi.lpvInBuffer, smi.cbInBuffer)) { + return -EFAULT; + } + if ((pImbReq->req.dataLength + MIN_IMB_REQ_BUF_SIZE) + > smi.cbInBuffer) + { + return -EINVAL; + } + if (pImbReq->req.dataLength > MAX_BUFFER_SIZE) { + return -EINVAL; + } + + if (pImbReq->req.rsSa == BMC_SA) { + struct ipmi_system_interface_addr *smi_addr + = (struct ipmi_system_interface_addr *) &addr; + + if ((pImbReq->req.netFn + == (IPMI_NETFN_APP_REQUEST << 2)) + && (pImbReq->req.cmd + == IPMI_READ_EVENT_MSG_BUFFER_CMD)) + { + /* The driver gets event messages + automatically, so we emulate + this. */ + rv = imb_handle_event_request(priv, &rsp); + goto copy_resp; + } else { + smi_addr->addr_type + = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + smi_addr->channel = IPMI_BMC_CHANNEL; + smi_addr->lun = 0; + } + } else { + struct ipmi_ipmb_addr *ipmb_addr = + (struct ipmi_ipmb_addr *) &addr; + + ipmb_addr->addr_type = IPMI_IPMB_ADDR_TYPE; + ipmb_addr->slave_addr = pImbReq->req.rsSa; + ipmb_addr->lun = pImbReq->req.rsLun; + ipmb_addr->channel = 0; + } + + if (pImbReq->flags & NO_RESPONSE_EXPECTED) { + spin_lock(&priv->imb_lock); + msgid = priv->curr_msgid; + (priv->curr_msgid)++; + spin_unlock(&priv->imb_lock); + } else { + msgid = 0; + } + + msg.netfn = pImbReq->req.netFn; + msg.cmd = pImbReq->req.cmd; + msg.data = pImbReq->req.data; + msg.data_len = pImbReq->req.dataLength; + rv = ipmi_request(priv->imb_user, + &addr, + msgid, + &msg, + 0); + if (rv) { + rv = IMB_SEND_REQUEST_FAILED; + goto copy_resp; + } + + if (pImbReq->flags & NO_RESPONSE_EXPECTED) + goto no_response; + + /* Now wait for the response to come back. */ + spin_lock_irqsave(&(priv->imb_lock), flags); + init_waitqueue_entry(&wait, current); + add_wait_queue(&(priv->imb_waiting_rsp_rcvrs), + &wait); + for (;;) { + /* Check to see if it's there. */ + if (!list_empty(&(priv->imb_waiting_rsps))) { + entry = priv->imb_waiting_rsps.next; + list_del(entry); + rsp = list_entry(entry, + struct ipmi_recv_msg, + link); + if (rsp->msgid != msgid) { + ipmi_free_recv_msg(rsp); + rsp = NULL; + } else { + break; + } + } + + set_current_state(TASK_INTERRUPTIBLE); + if (!signal_pending(current)) { + spin_unlock_irqrestore + (&(priv->imb_lock), flags); + schedule(); + spin_lock_irqsave + (&(priv->imb_lock), flags); + } else { + rv = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&(priv->imb_waiting_rsp_rcvrs), + &wait); + spin_unlock_irqrestore(&(priv->imb_lock), flags); + + copy_resp: + if (rsp != NULL) { + pImbResp->cCode = rsp->msg.data[0]; + memcpy(pImbResp->data, + rsp->msg.data+1, + rsp->msg.data_len-1); + length = (rsp->msg.data_len - 1 + + MIN_IMB_RESP_BUF_SIZE); + + if (copy_to_user(smi.lpvOutBuffer, pImbResp, length)) { + return -EFAULT; + } + + if (copy_to_user(smi.lpcbBytesReturned, + &length, + sizeof(length))) + { + return -EFAULT; + } + } + no_response: + break; + } + + case IOCTL_IMB_SHUTDOWN_CODE: + { + ShutdownCmdBuffer shutdownCmd; + + if (copy_from_user(&shutdownCmd, + smi.lpvInBuffer, + sizeof(ShutdownCmdBuffer))) + { + return -EFAULT; + } + + if (smi.cbInBuffer < sizeof(ShutdownCmdBuffer)) + { + return -EINVAL; + } + + rv = 0; + switch (shutdownCmd.code) { + case SD_POWER_OFF: + ipmi_delayed_shutdown(shutdownCmd.delayTime / 10, 1); + break; + + case SD_RESET: + ipmi_delayed_shutdown(shutdownCmd.delayTime / 10, 0); + break; + + case SD_NO_ACTION: + break; + + default: + rv = INVALID_ARGUMENTS; + } + + } + + case IOCTL_IMB_REGISTER_ASYNC_OBJ: + rv = STATUS_SUCCESS; + break; + + + case IOCTL_IMB_CHECK_EVENT: + { + wait_queue_t wait; + + spin_lock_irqsave(&(priv->imb_lock), flags); + init_waitqueue_entry(&wait, current); + add_wait_queue(&(priv->imb_waiting_cmd_rcvrs), + &wait); + while (!list_empty(&(priv->imb_waiting_cmds))) { + set_current_state(TASK_INTERRUPTIBLE); + if (!signal_pending(current)) { + spin_unlock_irqrestore + (&(priv->imb_lock), flags); + schedule(); + spin_lock_irqsave + (&(priv->imb_lock), flags); + } else { + rv = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&(priv->imb_waiting_cmd_rcvrs), + &wait); + spin_unlock_irqrestore(&(priv->imb_lock), flags); + rv = 0; + break; + } + } + + return rv; +} + +static int ipmi_imb_mmap(struct file *file, struct vm_area_struct *vma) +{ + off_t offset = vma->vm_pgoff << PAGE_SHIFT; + + if (offset < 0) + return -EINVAL; + + if (remap_page_range(vma->vm_start, offset, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) + { + return -EAGAIN; + } + + /*vma->vm_inode = what_goes_here; */ + + return 0; + +} + + +static struct file_operations ipmi_fops = { + owner: THIS_MODULE, + ioctl: ipmi_imb_ioctl, + open: ipmi_imb_open, + release: ipmi_imb_release, + mmap: ipmi_imb_mmap +}; + +static struct timer_list ipmi_imb_timer; + +/* Call every 100 ms. */ +#define IPMI_TIMEOUT_TIME 100 +#define IPMI_TIMEOUT_JIFFIES (IPMI_TIMEOUT_TIME/(1000/HZ)) + +static volatile int stop_operation = 0; +static volatile int timer_stopped = 0; + +static void ipmi_imb_timeout(unsigned long data) +{ + struct list_head *entry, *entry2; + struct priv_data *priv = (struct priv_data *) data; + int timeout_period = IPMI_TIMEOUT_TIME; + struct ipmi_recv_msg *msg; + + if (stop_operation) { + timer_stopped = 1; + return; + } + + /* Now time out any messages in the Imb message queue. */ + spin_lock(&(priv->imb_lock)); + list_for_each_safe(entry, entry2, &(priv->imb_waiting_rsps)) { + long *timeout; + msg = list_entry(entry, struct ipmi_recv_msg, link); + timeout = imb_timeout(msg); + *timeout -= timeout_period; + if ((*timeout) <= 0) { + list_del(entry); + ipmi_free_recv_msg(msg); + } + } + list_for_each_safe(entry, entry2, &(priv->imb_waiting_cmds)) { + long *timeout; + msg = list_entry(entry, struct ipmi_recv_msg, link); + timeout = imb_timeout(msg); + *timeout -= timeout_period; + if ((*timeout) <= 0) { + list_del(entry); + ipmi_free_recv_msg(msg); + } + } + spin_unlock(&priv->imb_lock); + + ipmi_imb_timer.expires += IPMI_TIMEOUT_JIFFIES; + add_timer(&ipmi_imb_timer); +} + +#define DEVICE_NAME "imb" + +static int ipmi_imb_major = 0; +MODULE_PARM(ipmi_imb_major, "i"); + +static struct ipmi_user_hndl ipmi_hndlrs = +{ + ipmi_recv_hndl : imb_msg_recv +}; + +static int init_ipmi_imb(void) +{ + int rv; + + if (ipmi_imb_major < 0) + return -EINVAL; + + ipmi_user = kmalloc(sizeof(*ipmi_user), GFP_KERNEL); + if (!ipmi_user) { + return -ENOMEM; + } + + /* Create the Imb interface user. */ + spin_lock_init(&(ipmi_user->imb_lock)); + INIT_LIST_HEAD(&(ipmi_user->imb_waiting_rsps)); + init_waitqueue_head(&(ipmi_user->imb_waiting_rsp_rcvrs)); + INIT_LIST_HEAD(&(ipmi_user->imb_waiting_cmds)); + init_waitqueue_head(&(ipmi_user->imb_waiting_cmd_rcvrs)); + ipmi_user->imb_cmd_waiting = 0; + INIT_LIST_HEAD(&(ipmi_user->imb_waiting_events)); + + rv = ipmi_create_user(0, + &ipmi_hndlrs, + ipmi_user, + &(ipmi_user->imb_user)); + if (rv) { + kfree(ipmi_user); + ipmi_user = NULL; + return rv; + } + + rv = register_chrdev(ipmi_imb_major, DEVICE_NAME, &ipmi_fops); + if (rv < 0) + { + kfree(ipmi_user); + ipmi_user = NULL; + printk(KERN_ERR "ipmi: can't get major %d\n", + ipmi_imb_major); + return rv; + } + + if (ipmi_imb_major == 0) + { + ipmi_imb_major = rv; + } + + devfs_handle = devfs_register(NULL, DEVICE_NAME, DEVFS_FL_NONE, + ipmi_imb_major, 0, + S_IFCHR | S_IRUSR | S_IWUSR, + &ipmi_fops, NULL); + + ipmi_imb_timer.data = (long) ipmi_user; + ipmi_imb_timer.function = ipmi_imb_timeout; + ipmi_imb_timer.expires = jiffies + IPMI_TIMEOUT_JIFFIES; + add_timer(&ipmi_imb_timer); + + printk(KERN_INFO "ipmi_imb: driver initialized at char major %d\n", + ipmi_imb_major); + + return 0; +} + +#ifdef MODULE +static void free_recv_msg_list(struct list_head *q) +{ + struct list_head *entry, *entry2; + struct ipmi_recv_msg *msg; + + list_for_each_safe(entry, entry2, q) { + msg = list_entry(entry, struct ipmi_recv_msg, link); + list_del(entry); + ipmi_free_recv_msg(msg); + } +} + +static void cleanup_ipmi_imb(void) +{ + devfs_unregister(devfs_handle); + + /* Tell the timer to stop, then wait for it to stop. This avoids + problems with race conditions removing the timer here. */ + stop_operation = 1; + while (!timer_stopped) { + schedule_timeout(1); + } + + ipmi_destroy_user(ipmi_user->imb_user); + + free_recv_msg_list(&(ipmi_user->imb_waiting_rsps)); + free_recv_msg_list(&(ipmi_user->imb_waiting_cmds)); + free_recv_msg_list(&(ipmi_user->imb_waiting_events)); + + kfree(ipmi_user); + ipmi_user = NULL; + + unregister_chrdev(ipmi_imb_major, DEVICE_NAME); +} +module_exit(cleanup_ipmi_imb); +#else +static int __init ipmi_imb_setup (char *str) +{ + int x; + + if (get_option (&str, &x)) { + /* ipmi=x sets the major number to x. */ + ipmi_imb_major = x; + } else if (!strcmp(str, "off")) { + ipmi_imb_major = -1; + } + + return 1; +} +__setup("ipmi_imb=", ipmi_imb_setup); +#endif + +module_init(init_ipmi_imb); +MODULE_LICENSE("GPL"); --- /dev/null 2002-08-30 19:31:37.000000000 -0400 +++ linux-2.4.18-14/drivers/char/ipmi/ipmi_radisys.c 2003-03-27 15:32:59.000000000 -0500 @@ -0,0 +1,834 @@ +/* + * ipmi_radisys.c + * + * Radisys emulation for the IPMI interface. + * + * Author: MontaVista Software, Inc. + * Corey Minyard + * source@mvista.com + * + * Copyright 2002 MontaVista Software Inc. + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct priv_data +{ + /* This is for supporting the old Radisys interface. */ + ipmi_user_t rs_user; + spinlock_t rs_lock; + + /* A list of responses in the queue. */ + struct list_head rs_waiting_rsps; + + /* A list of things waiting for responses. We wake them all up + when a response comes in. */ + wait_queue_head_t rs_waiting_rsp_rcvrs; + + /* A list of commands that have come in. */ + struct list_head rs_waiting_cmds; + + /* A list of thing waiting for commands. We wake them all up + when a command comes in. */ + wait_queue_head_t rs_waiting_cmd_rcvrs; + + /* The registered command receiver value. This only allows someone + with the "magic number" to issue commands. */ + unsigned long rs_cmd_receiver; + + /* Is someone already waiting for a command? The Radisys driver + only allows one waiter, this enforces that. */ + int rs_cmd_waiting; + + /* A list of IPMI events waiting to be delivered. (not that + the Radisys driver calls incoming commands "events", this + variable is actual IPMI events, not incoming commands). */ + struct list_head rs_waiting_events; + +#define RS_EVENT_QUEUE_LIMIT 16 /* Allow up to 16 events. */ + /* The number of events in the event queue. */ + unsigned int rs_waiting_event_count; +}; + + +static devfs_handle_t devfs_handle; + +/* We cheat and use a piece of the address as the timeout. */ +static long *rs_timeout(struct ipmi_recv_msg *msg) +{ + char *base = (char *) &(msg->addr); + base += sizeof(struct ipmi_ipmb_addr); + return (long *) base; +} + +static void rs_msg_recv(struct ipmi_recv_msg *msg, + void *data) +{ + struct priv_data *priv = data; + unsigned long flags; + + spin_lock_irqsave(&(priv->rs_lock), flags); + if (msg->recv_type == IPMI_RESPONSE_RECV_TYPE) { + *rs_timeout(msg) = 5000; + list_add_tail(&(msg->link), &(priv->rs_waiting_rsps)); + wake_up_all(&(priv->rs_waiting_rsp_rcvrs)); + } else if (msg->recv_type == IPMI_CMD_RECV_TYPE) { + *rs_timeout(msg) = 5000; + list_add_tail(&(msg->link), &(priv->rs_waiting_cmds)); + wake_up_all(&(priv->rs_waiting_cmd_rcvrs)); + } else if (msg->recv_type == IPMI_ASYNC_EVENT_RECV_TYPE) { + if (priv->rs_waiting_event_count > RS_EVENT_QUEUE_LIMIT) { + ipmi_free_recv_msg(msg); + } else { + list_add_tail(&(msg->link),&(priv->rs_waiting_events)); + (priv->rs_waiting_event_count)++; + } + } else { + ipmi_free_recv_msg(msg); + } + spin_unlock_irqrestore(&(priv->rs_lock), flags); +} + +/* We emulate the event queue in the driver for the Radisys emulation. */ +static int rs_handle_event_request(struct priv_data *priv) +{ + struct list_head *entry; + unsigned long flags; + struct ipmi_recv_msg *msg = NULL; + int rv = 0; + + spin_lock_irqsave(&(priv->rs_lock), flags); + if (list_empty(&(priv->rs_waiting_events))) { + /* Nothing in the event queue, just return an error. */ + msg = ipmi_alloc_recv_msg(); + if (msg == NULL) { + rv = -EAGAIN; + goto out_err; + } + msg->addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + msg->addr.channel = IPMI_BMC_CHANNEL; + msg->msg.cmd = IPMI_READ_EVENT_MSG_BUFFER_CMD; + msg->msgid = 0; + msg->recv_type = IPMI_ASYNC_EVENT_RECV_TYPE; + msg->msg.netfn = IPMI_NETFN_APP_RESPONSE; + msg->msg.data = msg->msg_data; + msg->msg.data[0] = 0x80; /* Data not available. */ + msg->msg.data_len = 1; + } else { + /* Pull an item from the event queue . */ + entry = priv->rs_waiting_events.next; + list_del(entry); + msg = list_entry(entry, struct ipmi_recv_msg, link); + (priv->rs_waiting_event_count)--; + } + + /* Put the response into the list of waiting responses and + wake all the response waiters up. */ + *rs_timeout(msg) = 5000; + list_add_tail(&(msg->link), &(priv->rs_waiting_rsps)); + wake_up_all(&(priv->rs_waiting_rsp_rcvrs)); + + out_err: + spin_unlock_irqrestore(&(priv->rs_lock), flags); + return rv; +} + +static struct ipmi_recv_msg *rs_find_in_list(struct list_head *q, + unsigned char slave_addr, + unsigned char lun, + unsigned char netfn, + unsigned char cmd, + unsigned char seq) +{ + struct list_head *entry; + struct ipmi_recv_msg *msg; + struct ipmi_addr addr; + unsigned char msg_seq; + + if (slave_addr == 1) { + struct ipmi_system_interface_addr *smi_addr; + smi_addr = (struct ipmi_system_interface_addr *) &addr; + smi_addr->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + smi_addr->lun = lun; + /* Slave address 1 means no matching sequence in the + Radisys driver. */ + } else { + struct ipmi_ipmb_addr *ipmb_addr; + ipmb_addr = (struct ipmi_ipmb_addr *) &addr; + ipmb_addr->addr_type = IPMI_IPMB_ADDR_TYPE; + ipmb_addr->slave_addr = slave_addr; + ipmb_addr->lun = lun; + } + + list_for_each(entry, q) { + msg = list_entry(entry, struct ipmi_recv_msg, link); + if (msg->addr.channel == IPMI_BMC_CHANNEL) + msg_seq = 0; + else + msg_seq = msg->msgid; + + /* We ignore the channel for these comparisons, since the + Radisys driver seems to ignore it. */ + addr.channel = msg->addr.channel; + + if ((msg_seq == seq) + && (msg->msg.cmd == cmd) + && (msg->msg.netfn == (netfn >> 2)) + && ipmi_addr_equal(&addr, &(msg->addr))) + { + list_del(entry); + return msg; + } + } + + return NULL; +} + +static struct priv_data *ipmi_user; +static unsigned int user_count = 0; /* How many users have this open. */ +static spinlock_t dev_lock = SPIN_LOCK_UNLOCKED; + +static int ipmi_open(struct inode *inode, struct file *file) +{ + file->private_data = ipmi_user; + spin_lock(&dev_lock); + if (user_count == 0) + ipmi_set_gets_events(ipmi_user->rs_user, 1); + user_count++; + spin_unlock(&dev_lock); + + return 0; +} + +static int ipmi_release(struct inode *inode, struct file *file) +{ + spin_lock(&dev_lock); + user_count--; + if (user_count == 0) + ipmi_set_gets_events(ipmi_user->rs_user, 0); + spin_unlock(&dev_lock); + return 0; +} + +static unsigned char +ipmb_checksum(unsigned char *data, int size) +{ + unsigned char csum = 0; + + for (; size > 0; size--, data++) + csum += *data; + + return -csum; +} + +static int ipmi_ioctl(struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long data) +{ + struct priv_data *priv = file->private_data; + int rv = -EINVAL; + + switch(cmd) { + case IOCTL_IPMI_RCV: /* get ipmi message */ + { + IPMI_MSGDESC rsp; + struct ipmi_recv_msg *msg; + unsigned long flags; + long timeout; + wait_queue_t wait; + + if (copy_from_user(&rsp, (void *) data, sizeof(rsp))) { + rv = -EFAULT; + break; + } + + rv = 0; + + spin_lock_irqsave(&(priv->rs_lock), flags); + + msg = rs_find_in_list(&(priv->rs_waiting_rsps), + rsp.Dest.uchSlave, + rsp.Dest.uchLun, + rsp.uchNetFn, + rsp.uchCmd, + rsp.uchSeq); + init_waitqueue_entry(&wait, current); + add_wait_queue(&(priv->rs_waiting_rsp_rcvrs), + &wait); + timeout = 5000 / (1000 / HZ); + while (msg == NULL) { + set_current_state(TASK_INTERRUPTIBLE); + if (!signal_pending(current)) { + spin_unlock_irqrestore + (&(priv->rs_lock), flags); + timeout = schedule_timeout(timeout); + spin_lock_irqsave + (&(priv->rs_lock), flags); + } else { + rv = -ERESTARTSYS; + break; + } + if (timeout <= 0) { + rsp.uchComplete = IPMI_TIMEOUT_COMPLETION_CODE; + break; + } else { + msg = rs_find_in_list + (&(priv->rs_waiting_rsps), + rsp.Dest.uchSlave, + rsp.Dest.uchLun, + rsp.uchNetFn, + rsp.uchCmd, + rsp.uchSeq); + } + } + remove_wait_queue(&(priv->rs_waiting_rsp_rcvrs), + &wait); + spin_unlock_irqrestore(&(priv->rs_lock), flags); + + if (msg != NULL) { + rsp.uchComplete = msg->msg.data[0]; + /* The Radisys driver expects all the data to + be there in the data, even the stuff we + already have processed for it. So make is + so. */ + if (msg->addr.channel == IPMI_BMC_CHANNEL) { + struct ipmi_system_interface_addr *smi_addr; + + smi_addr = ((struct ipmi_system_interface_addr *) + &(msg->addr)); + memcpy(&(rsp.auchBuffer[2]), + &(msg->msg.data[0]), + msg->msg.data_len); + rsp.ulLength = msg->msg.data_len+2; + rsp.auchBuffer[0] = ((msg->msg.netfn << 2) + | (smi_addr->lun)); + rsp.auchBuffer[1] = msg->msg.cmd; + } else { + struct ipmi_ipmb_addr *ipmb_addr; + + ipmb_addr = (struct ipmi_ipmb_addr *) &msg->addr; + memcpy(&(rsp.auchBuffer[9]), + &(msg->msg.data[0]), + msg->msg.data_len); + rsp.ulLength = msg->msg.data_len+10; + rsp.auchBuffer[0] = IPMI_NETFN_APP_REQUEST << 2; + rsp.auchBuffer[1] = IPMI_GET_MSG_CMD; + rsp.auchBuffer[2] = 0; + rsp.auchBuffer[3] = msg->addr.channel; + rsp.auchBuffer[4] = ((msg->msg.netfn << 2) + | 2); + rsp.auchBuffer[5] + = ipmb_checksum(&(rsp.auchBuffer[3]), + 2); + rsp.auchBuffer[6] = ipmb_addr->slave_addr; + rsp.auchBuffer[7] = ((msg->msgid << 2) + | ipmb_addr->lun); + rsp.auchBuffer[8] = msg->msg.cmd; + rsp.auchBuffer[msg->msg.data_len+9] + = ipmb_checksum(&(rsp.auchBuffer[6]), + msg->msg.data_len+3); + } + ipmi_free_recv_msg(msg); + } + + if (copy_to_user((void *) data, &rsp, sizeof(rsp))) { + rv = -EFAULT; + break; + } + + break; + } + + case IOCTL_IPMI_SEND: /* send ipmi message */ + { + IPMI_MSGDESC req; + struct ipmi_addr addr; + struct ipmi_msg msg; + unsigned char source_address; + unsigned char source_lun; + unsigned int start_offset; + + if (copy_from_user(&req, (void *) data, sizeof(req))) { + rv = -EFAULT; + break; + } + + if (req.Dest.uchSlave == 1) { + struct ipmi_system_interface_addr *smi_addr + = (struct ipmi_system_interface_addr *) &addr; + if ((req.uchNetFn == (IPMI_NETFN_APP_REQUEST << 2)) + && (req.uchCmd == IPMI_READ_EVENT_MSG_BUFFER_CMD)) + { + /* The driver gets event messages + automatically, so we emulate + this. */ + rv = rs_handle_event_request(priv); + break; + } + + smi_addr->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + smi_addr->channel = IPMI_BMC_CHANNEL; + smi_addr->lun = req.uchNetFn & 0x3; + source_address = 0; + source_lun = 0; + start_offset = 2; + } else { + struct ipmi_ipmb_addr *ipmb_addr = + (struct ipmi_ipmb_addr *) &addr; + + ipmb_addr->addr_type = IPMI_IPMB_ADDR_TYPE; + ipmb_addr->channel = 0; + ipmb_addr->slave_addr = req.Dest.uchSlave; + ipmb_addr->lun = req.Dest.uchLun; + source_address = req.auchBuffer[6]; + source_lun = req.auchBuffer[7] & 0x3; + start_offset = 9; + req.ulLength--; /* Remove the checksum the userland + process adds. */ + } + + msg.netfn = req.uchNetFn >> 2; + msg.cmd = req.uchCmd; + msg.data = req.auchBuffer + start_offset; + msg.data_len = req.ulLength - start_offset; + + rv = ipmi_request_with_source(priv->rs_user, + &addr, + req.uchSeq, + &msg, + 0, + source_address, + source_lun); + if (rv) + req.uchComplete = IPMI_UNKNOWN_ERR_COMPLETION_CODE; + else + req.uchComplete = 0; + /* The Radisys driver does no error checking here. */ + copy_to_user((void *) data, &req, sizeof(req)); + rv = 0; + break; + } + + case IOCTL_IPMI_EVENT: /* get an incoming command. Don't be + fooled by the name, these are + commands, not IPMI events. */ + { + IPMI_MSGDESC rsp; + struct ipmi_recv_msg *msg = NULL; + struct list_head *entry; + unsigned long flags; + long timeout; + unsigned long receiver; + wait_queue_t wait; + + if (copy_from_user(&receiver, (void *) data, sizeof(receiver))) + { + rv = -EFAULT; + break; + } + + if (copy_from_user(&timeout, + (void *) (data + sizeof(receiver)), + sizeof(timeout))) + { + rv = -EFAULT; + break; + } + + rv = 0; + + spin_lock_irqsave(&(priv->rs_lock), flags); + + /* If someone else is already waiting, the Radisys driver + returns EFAULT, so we do to. */ + if (priv->rs_cmd_waiting) { + spin_unlock_irqrestore(&(priv->rs_lock), flags); + rv = -EFAULT; + break; + } + + /* If the user thread doesn't match up, abort. */ + if (receiver != priv->rs_cmd_receiver) { + spin_unlock_irqrestore(&(priv->rs_lock), flags); + rsp.uchComplete = IPMI_INVALID_CMD_COMPLETION_CODE; + break; + } + + init_waitqueue_entry(&wait, current); + add_wait_queue(&(priv->rs_waiting_cmd_rcvrs), + &wait); + priv->rs_cmd_waiting = 1; + timeout = timeout / (1000 / HZ); /* from ms to jiffies */ + while ((timeout > 0) && + list_empty(&(priv->rs_waiting_cmds))) + { + set_current_state(TASK_INTERRUPTIBLE); + if (!signal_pending(current)) { + spin_unlock_irqrestore + (&(priv->rs_lock), flags); + timeout = schedule_timeout(timeout); + spin_lock_irqsave + (&(priv->rs_lock), flags); + } else { + rv = -ERESTARTSYS; + break; + } + } + if (!list_empty(&(priv->rs_waiting_cmds))) { + entry = priv->rs_waiting_cmds.next; + list_del(entry); + msg = list_entry(entry, struct ipmi_recv_msg, link); + } + priv->rs_cmd_waiting = 0; + remove_wait_queue(&(priv->rs_waiting_cmd_rcvrs), + &wait); + spin_unlock_irqrestore(&(priv->rs_lock), flags); + + if (msg != NULL) { + /* The Radisys driver expects all the data to + be there in the data, even the stuff we + already have processed for it. So make is + so. */ + struct ipmi_ipmb_addr *ipmb_addr; + + ipmb_addr = (struct ipmi_ipmb_addr *) &msg->addr; + memcpy(&(rsp.auchBuffer[9]), + &(msg->msg.data[0]), + msg->msg.data_len); + rsp.ulLength = msg->msg.data_len+9; + rsp.auchBuffer[0] = IPMI_NETFN_APP_REQUEST << 2; + rsp.auchBuffer[1] = IPMI_SEND_MSG_CMD; + rsp.auchBuffer[2] = 0; + rsp.auchBuffer[3] = msg->addr.channel; + rsp.auchBuffer[4] = ((msg->msg.netfn << 2) + | 2); + rsp.auchBuffer[5] + = ipmb_checksum(&(rsp.auchBuffer[3]), + 2); + rsp.auchBuffer[6] = ipmb_addr->slave_addr; + rsp.auchBuffer[7] = ((msg->msgid << 2) + | ipmb_addr->lun); + rsp.auchBuffer[8] = msg->msg.cmd; + + rsp.uchNetFn = (msg->msg.netfn << 2); + rsp.uchCmd = msg->msg.cmd; + rsp.uchSeq = msg->msgid; + rsp.ulLength = msg->msg.data_len + 9; + ipmi_free_recv_msg(msg); + } else if (!rv) { + /* On a time out, the Radisys driver returns + IPMIRC_ERROR in the completion code, for + some wierd reason. */ + rsp.uchComplete = IPMI_UNKNOWN_ERR_COMPLETION_CODE; + } + + /* The Radisys driver does no error checking here. */ + copy_to_user((void *) data, &rsp, sizeof(rsp)); + rv = 0; + break; + } + + case IOCTL_IPMI_REGISTER: /* register as event receiver */ + { + unsigned long receiver; + unsigned char rc = LOWLRC_SUCCESS; + unsigned long flags; + + if (copy_from_user(&receiver, (void *) data, sizeof(receiver))) + { + rv = -EFAULT; + break; + } + + spin_lock_irqsave(&(priv->rs_lock), flags); + if (priv->rs_cmd_receiver == 0) { + rv = ipmi_register_all_cmd_rcvr(priv->rs_user); + if (rv) { + priv->rs_cmd_receiver = receiver; + } else { + rc = LOWLRC_ERROR; + } + } else if (priv->rs_cmd_receiver != receiver) { + rc = LOWLRC_ERROR; + } + spin_unlock_irqrestore(&(priv->rs_lock), flags); + + /* The Radisys driver does no error checking here. */ + copy_to_user((void *) data, &rc, sizeof(rc)); + rv = 0; + break; + } + + case IOCTL_IPMI_UNREGISTER: /* unregister as event receiver */ + { + unsigned long receiver; + unsigned char rc = LOWLRC_SUCCESS; + unsigned long flags; + + if (copy_from_user(&receiver, (void *) data, sizeof(receiver))) + { + rv = -EFAULT; + break; + } + + spin_lock_irqsave(&(priv->rs_lock), flags); + if (priv->rs_cmd_receiver == receiver) { + ipmi_unregister_all_cmd_rcvr(priv->rs_user); + priv->rs_cmd_receiver = 0; + } else { + rc = LOWLRC_ERROR; + } + spin_unlock_irqrestore(&(priv->rs_lock), flags); + + /* The Radisys driver does no error checking here. */ + copy_to_user((void *) data, &rc, sizeof(rc)); + rv = 0; + break; + } + + case IOCTL_IPMI_CLEAR: /* clear registered event receiver */ + { + unsigned char rc = LOWLRC_SUCCESS; + unsigned long flags; + + spin_lock_irqsave(&(priv->rs_lock), flags); + ipmi_unregister_all_cmd_rcvr(priv->rs_user); + priv->rs_cmd_receiver = 0; + spin_unlock_irqrestore(&(priv->rs_lock), flags); + + /* The Radisys driver does no error checking here. */ + copy_to_user((void *) data, &rc, sizeof(rc)); + rv = 0; + break; + } + } + + return rv; +} + +static struct file_operations ipmi_fops = { + owner: THIS_MODULE, + ioctl: ipmi_ioctl, + open: ipmi_open, + release: ipmi_release +}; + +static struct timer_list ipmi_radisys_timer; + +/* Call every 100 ms. */ +#define IPMI_TIMEOUT_TIME 100 +#define IPMI_TIMEOUT_JIFFIES (IPMI_TIMEOUT_TIME/(1000/HZ)) + +/* Request events from the queue every second. Hopefully, in the + future, IPMI will add a way to know immediately if an event is + in the queue. */ +#define IPMI_REQUEST_EV_TIME (1000 / (IPMI_TIMEOUT_TIME)) + +static volatile int stop_operation = 0; +static volatile int timer_stopped = 0; + +static void ipmi_radisys_timeout(unsigned long data) +{ + struct list_head *entry, *entry2; + struct priv_data *priv = (struct priv_data *) data; + int timeout_period = IPMI_TIMEOUT_TIME; + struct ipmi_recv_msg *msg; + + if (stop_operation) { + timer_stopped = 1; + return; + } + + /* Now time out any messages in the Radisys message queue. */ + spin_lock(&(priv->rs_lock)); + list_for_each_safe(entry, entry2, &(priv->rs_waiting_rsps)) { + long *timeout; + msg = list_entry(entry, struct ipmi_recv_msg, link); + timeout = rs_timeout(msg); + *timeout -= timeout_period; + if ((*timeout) <= 0) { + list_del(entry); + ipmi_free_recv_msg(msg); + } + } + list_for_each_safe(entry, entry2, &(priv->rs_waiting_cmds)) { + long *timeout; + msg = list_entry(entry, struct ipmi_recv_msg, link); + timeout = rs_timeout(msg); + *timeout -= timeout_period; + if ((*timeout) <= 0) { + list_del(entry); + ipmi_free_recv_msg(msg); + } + } + spin_unlock(&priv->rs_lock); + + ipmi_radisys_timer.expires += IPMI_TIMEOUT_JIFFIES; + add_timer(&ipmi_radisys_timer); +} + +#define DEVICE_NAME "ipmi_radisys" + +static int ipmi_radisys_major = 0; +MODULE_PARM(ipmi_radisys_major, "i"); + +static struct ipmi_user_hndl ipmi_hndlrs = +{ + ipmi_recv_hndl : rs_msg_recv +}; + + +static int init_ipmi_radisys(void) +{ + int rv; + + if (ipmi_radisys_major < 0) + return -EINVAL; + + ipmi_user = kmalloc(sizeof(*ipmi_user), GFP_KERNEL); + if (!ipmi_user) { + printk("ipmi_radisys: Unable to allocate memory\n"); + return -ENOMEM; + } + + /* Create the Radisys interface user. */ + spin_lock_init(&(ipmi_user->rs_lock)); + INIT_LIST_HEAD(&(ipmi_user->rs_waiting_rsps)); + init_waitqueue_head(&(ipmi_user->rs_waiting_rsp_rcvrs)); + INIT_LIST_HEAD(&(ipmi_user->rs_waiting_cmds)); + init_waitqueue_head(&(ipmi_user->rs_waiting_cmd_rcvrs)); + ipmi_user->rs_cmd_waiting = 0; + INIT_LIST_HEAD(&(ipmi_user->rs_waiting_events)); + + rv = ipmi_create_user(0, + &ipmi_hndlrs, + ipmi_user, + &(ipmi_user->rs_user)); + if (rv) { + printk("ipmi_radisys: Unable to create an IPMI user, probably" + " no physical devices present.\n"); + kfree(ipmi_user); + ipmi_user = NULL; + return rv; + } + + rv = register_chrdev(ipmi_radisys_major, DEVICE_NAME, &ipmi_fops); + if (rv < 0) + { + printk("ipmi_radisys: Unable to create the character device\n"); + kfree(ipmi_user); + ipmi_user = NULL; + printk(KERN_ERR "ipmi: can't get major %d\n", + ipmi_radisys_major); + return rv; + } + + if (ipmi_radisys_major == 0) + { + ipmi_radisys_major = rv; + } + + devfs_handle = devfs_register(NULL, DEVICE_NAME, DEVFS_FL_NONE, + ipmi_radisys_major, 0, + S_IFCHR | S_IRUSR | S_IWUSR, + &ipmi_fops, NULL); + + ipmi_radisys_timer.data = (long) ipmi_user; + ipmi_radisys_timer.function = ipmi_radisys_timeout; + ipmi_radisys_timer.expires = jiffies + IPMI_TIMEOUT_JIFFIES; + add_timer(&ipmi_radisys_timer); + + printk(KERN_INFO "ipmi_radisys: driver initialized at char major %d\n", + ipmi_radisys_major); + + return 0; +} + +#ifdef MODULE +static void free_recv_msg_list(struct list_head *q) +{ + struct list_head *entry, *entry2; + struct ipmi_recv_msg *msg; + + list_for_each_safe(entry, entry2, q) { + msg = list_entry(entry, struct ipmi_recv_msg, link); + list_del(entry); + ipmi_free_recv_msg(msg); + } +} + +static void cleanup_ipmi_radisys(void) +{ + devfs_unregister(devfs_handle); + + /* Tell the timer to stop, then wait for it to stop. This avoids + problems with race conditions removing the timer here. */ + stop_operation = 1; + while (!timer_stopped) { + schedule_timeout(1); + } + + ipmi_destroy_user(ipmi_user->rs_user); + + free_recv_msg_list(&(ipmi_user->rs_waiting_rsps)); + free_recv_msg_list(&(ipmi_user->rs_waiting_cmds)); + free_recv_msg_list(&(ipmi_user->rs_waiting_events)); + + kfree(ipmi_user); + ipmi_user = NULL; + + unregister_chrdev(ipmi_radisys_major, DEVICE_NAME); +} +module_exit(cleanup_ipmi_radisys); +#else +static int __init ipmi_radisys_setup (char *str) +{ + int x; + + if (get_option (&str, &x)) { + /* ipmi=x sets the major number to x. */ + ipmi_radisys_major = x; + } else if (!strcmp(str, "off")) { + ipmi_radisys_major = -1; + } + + return 1; +} +__setup("ipmi_radisys=", ipmi_radisys_setup); +#endif + +module_init(init_ipmi_radisys); +MODULE_LICENSE("GPL");