Add a IPMI serial driver interface. Requires the serial layered interface patch. Signed-off-by: Corey Minyard Documentation/IPMI.txt | 146 + drivers/char/ipmi/Kconfig | 50 drivers/char/ipmi/Makefile | 4 drivers/char/ipmi/ipmi_serial.c | 2305 ++++++++++++++++++++++++++ drivers/char/ipmi/ipmi_serial_direct.c | 643 +++++++ drivers/char/ipmi/ipmi_serial_radisys_ascii.c | 611 ++++++ drivers/char/ipmi/ipmi_serial_terminal_mode.c | 880 +++++++++ include/linux/ipmi_msgdefs.h | 3 include/linux/ipmi_serial_sm.h | 306 +++ 9 files changed, 4948 insertions(+) Index: linux-2.6.30/drivers/char/ipmi/ipmi_serial.c =================================================================== --- /dev/null +++ linux-2.6.30/drivers/char/ipmi/ipmi_serial.c @@ -0,0 +1,2305 @@ +/* + * ipmi_serial.c + * + * The interface to the IPMI driver for the serial system interface + * + * Author: MontaVista Software, Inc. + * David Griego + * Corey Minyard + * source@mvista.com + * + * Copyright 2005,2006,2007 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 +#include +#include +#include +#include +#include +#include + +#define PFX "ipmi_serial(%s%d): " /* %s%d is for info->name, info->line */ +#define NPFX "ipmi_serial: " + +#define MAX_SERIAL_SETUP_STR 100 +static char setup_str[MAX_SERIAL_SETUP_STR] = CONFIG_SERIAL_IPMI_SETUP; +module_param_string(port_info, setup_str, MAX_SERIAL_SETUP_STR, 0444); +MODULE_PARM_DESC(port_info, "Defines the perameters for the serial interface" + " i.e. port_info=ttyS1,38400,Direct:ttyS2,9600n81r," + "TerminalMode,pp"); + +static int hotmod_handler(const char *val, struct kernel_param *kp); + +module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200); +MODULE_PARM_DESC(hotmod, "Add and remove interfaces. See" + " Documentation/IPMI.txt in the kernel sources for the" + " gory details."); + +static int unload_when_empty; +module_param(unload_when_empty, int, 0); +MODULE_PARM_DESC(unload_when_empty, "Unload the module if no interfaces are" + " specified or found, default is false (0). Setting to 1" + " is useful for not loading the module if nothing is" + " found."); + +#define IPMI_SER_DEBUG_STATE 1 +#define IPMI_SER_DEBUG_MSG 2 +#define IPMI_SER_DEBUG_DATA 4 +#define IPMI_SER_DEBUG_TIMING 8 +#define IPMI_SER_DEBUG_CHAR_TIMING 16 +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Set bit 0 to enable state debugging, bit 1 to" + " enable data debugging, bit 2 to enable message debugging," + " bit 3 to enable message timing, bit 4 to enable" + " character timing."); + +/* List of registered codecs */ +static LIST_HEAD(codec_list); + +/* List of interfaces that are configured. */ +static LIST_HEAD(info_list); + +/* Lock for the above two lists. */ +static DEFINE_MUTEX(list_lock); + +/* Call every 10 ms. */ +#define IPMI_SERIAL_TIMEOUT_TIME_USEC 10000 +#define IPMI_SERIAL_USEC_PER_JIFFY (1000000 / HZ) +#define IPMI_SERIAL_TIMEOUT_JIFFIES (IPMI_SERIAL_TIMEOUT_TIME_USEC / \ + IPMI_SERIAL_USEC_PER_JIFFY) + + +/* Timeouts in microseconds. */ +#define IPMI_SERIAL_INIT_TIMEOUT 1000000 +#define IPMI_SERIAL_INIT_RETRIES 10 +#define IPMI_SERIAL_RETRY_TIMEOUT 1000000 +#define IPMI_SERIAL_MAX_ERROR_RETRIES 10 + +/* + * Polling for flags. Note that event if the interface doesn't + * request polling, we still poll once a second. + */ +#define NO_POLLING_FLAG_TIMEOUT 1000000 +#define POLLING_FLAG_TIMEOUT 100000 + +enum ipmi_serial_state { + SERIAL_EMPTY, + SERIAL_HANDLING_MSG, + SERIAL_GETTING_FLAGS, + SERIAL_GETTING_EVENTS, + SERIAL_CLEARING_FLAGS, + SERIAL_GETTING_MESSAGES, + /* FIXME - add watchdog stuff. */ +}; + +enum ipmi_serial_states { + SERIAL_IDLE, /* The serial interface is currently + doing nothing. */ + SERIAL_WRITE, /* We are writing bytes to the + interface. */ + SERIAL_READ, /* We are waiting to read data from + the interface. */ + SERIAL_ASYNC_MSG, /* Getting async message from BMC */ + SERIAL_COMPLETE, /* Allows get message to complete + correctly */ + SERIAL_ERROR0, /* State to transition to the error + handler, this was added to the + state machine in the spec to be + sure IBF was there. */ + SERIAL_HOSED /* The hardware failed to follow the + state machine. */ +}; + +#define TTY_NAME_LEN 8 + +/* + * Indexes into stats[] in smb_info below. + */ +enum serial_stat_indexes { + /* Number of watchdog pretimeouts */ + SERIAL_STAT_watchdog_pretimeouts = 0, + + /* Number of asyncronous messages received. */ + SERIAL_STAT_incoming_messages, + + /* Number of events received. */ + SERIAL_STAT_events, + + /* Number of completed requests. */ + SERIAL_STAT_complete_transactions, + + /* Number of times flags were fetched. */ + SERIAL_STAT_flag_fetches, + + /* Number of ATTN characters receved. */ + SERIAL_STAT_attentions, + + /* Number of times the timer went off. */ + SERIAL_STAT_timer_ticks, + + /* Number of transmitted characters. */ + SERIAL_STAT_xmit_chars, + + /* Number of received characters. */ + SERIAL_STAT_recv_chars, + + /* Number of times the protocol was violated. */ + SERIAL_STAT_protocol_violations, + + /* Number of times the checksum was incorrect. */ + SERIAL_STAT_checksum_errors, + + /* Number of times a transaction timed out */ + SERIAL_STAT_timeouts, + + /* Number of times more data than allowed was received in a message. */ + SERIAL_STAT_overruns, + + + /* This *must* remain last, add new values above this. */ + SERIAL_NUM_STATS, +}; + +struct ipmi_serial_info { + ipmi_smi_t intf; + struct list_head link; + + spinlock_t lock; + + struct list_head xmit_msgs; + struct list_head hp_xmit_msgs; + struct ipmi_smi_msg *curr_msg; + struct ipmi_smi_msg *to_send; + + int msg_timeout; + int retries; + unsigned long last_timeout_jiffies; + struct timer_list timer; + + enum ipmi_serial_state state; + + int upper_layer_ready; + + unsigned char slave_addr; + + /* + * Used to deliver things to the upper layer when processing + * is complete. Instead of delivering immediately, we queue + * things up and deliver them when all processing is done. + * Makes locking a lot easier. + */ + int watchdog_pretimeouts_to_deliver; + int msg_delivery_in_progress; + struct list_head msgs_to_deliver; + + /* + * Sequence number for send messages. Note that we reserve + * zero for special messages (getting flags, etc.); it is + * never used for normal messages. + */ + unsigned int send_seq; + + /* + * If set to true, this will request events the next time the + * state machine is idle. Only do this if do_event_request is + * true. Otherwise the codec doesn't support this capability. + */ + int req_events; + int do_event_request; + int has_event_buffer; + int global_enable_valid; + + /* + * Used to handle automatic timing of getting flags. + */ + int flag_timeout; + int req_flags; + int flag_timer; + int supports_flags; + + /* + * If true, run the state machine to completion on every send + * call. Generally used after a panic to make sure stuff goes + * out. + */ + int run_to_completion; + + /* + * The driver is shutting down, don't start anything new. + */ + int stop_operation; + + /* Flags from the last GET_MSG_FLAGS command, used when an ATTN + is set to hold the flags until we are done handling everything + from the flags. */ +#define RECEIVE_MSG_AVAIL 0x01 +#define EVENT_MSG_BUFFER_FULL 0x02 +#define WDT_PRE_TIMEOUT_INT 0x08 +#define OEM0_DATA_AVAIL 0x20 +#define OEM1_DATA_AVAIL 0x40 +#define OEM2_DATA_AVAIL 0x80 +#define OEM_DATA_AVAIL (OEM0_DATA_AVAIL | \ + OEM1_DATA_AVAIL | \ + OEM2_DATA_AVAIL) + unsigned char msg_flags; + + /* Per-OEM handler, called from handle_flags(). + Returns 1 when handle_flags() needs to be re-run + or 0 indicating it set si_state itself. + */ + int (*oem_data_avail_handler)(struct ipmi_serial_info *info); + + /* + * CODEC information. + */ + struct ipmi_serial_codec *codec; + struct ipmi_serial_codec_data *codec_data; + struct ipmi_serial_codec_data *alloc_codec_data; + + /* Holds the parameter string. */ + char *keepstr; + + /* Serial port information. */ + char name[TTY_NAME_LEN]; /* Name of the serial driver + we are looking for. */ + int line; /* Serial line we are looking + for. */ + char *options; /* Options for serial port */ + char *codec_name; /* Name of codec to try */ + char *codec_options; /* Options for the codec */ + struct uart_direct direct; + unsigned long poll_state; + char uart_buffer[UART_XMIT_SIZE]; + struct uart_port *port; + struct ktermios termios, old_termios; + + /* Buffer for incoming serial data. */ +#define SBUF_NEXT(v) ((v + 1) % 128) + unsigned char sbuf[128]; + unsigned int sbuf_start, sbuf_next; + int delivering_char; + + /* From the get device id response... */ + int device_id_valid; + struct ipmi_device_id device_id; + + /* Used to return intialization status. */ + int init_status; + + /* If this is set, returned responses will go to this function. */ + void (*internal_msg_handler)(struct ipmi_serial_info *info, + const unsigned char *data, + unsigned int len); + + atomic_t stats[SERIAL_NUM_STATS]; +}; + +#define serial_inc_stat(serial, stat) \ + atomic_inc(&(serial)->stats[SERIAL_STAT_ ## stat]) +#define serial_add_stat(serial, stat, val) \ + atomic_add(val, &(serial)->stats[SERIAL_STAT_ ## stat]) +#define serial_get_stat(serial, stat) \ + ((unsigned int) atomic_read(&(serial)->stats[SERIAL_STAT_ ## stat])) + +static void ipmi_serial_cleanup_one(struct ipmi_serial_info *to_clean); + +struct baud_rates { + unsigned int rate; + unsigned int cflag; + char *name; +}; + +static struct baud_rates baud_rates[] = { + { 921600, B921600, "921600" }, + { 500000, B500000, "500000" }, + { 460800, B460800, "460800" }, + { 230400, B230400, "230400" }, + { 115200, B115200, "115200" }, + { 57600, B57600, "57600" }, + { 38400, B38400, "38400" }, + { 19200, B19200, "19200" }, + { 9600, B9600, "9600" }, + { 4800, B4800, "4800" }, + { 2400, B2400, "2400" }, + { 1200, B1200, "1200" }, + { 0, B38400, "?" } +}; + +/* + * If run_to_completion mode is on, return NULL to know the lock wasn't + * taken. Otherwise lock info->lock and return the flags. + */ +static unsigned long *ipmi_serial_lock_cond(struct ipmi_serial_info *info, + unsigned long *flags) +{ + if (info->run_to_completion) + return NULL; + spin_lock_irqsave(&info->lock, *flags); + return flags; +} + +static void ipmi_serial_unlock_cond(struct ipmi_serial_info *info, + unsigned long *flags) +{ + if (!flags) + return; + spin_unlock_irqrestore(&info->lock, *flags); +} + +static void queue_return_msg(struct ipmi_serial_info *info) +{ + if (debug & IPMI_SER_DEBUG_TIMING) { + struct timeval t; + do_gettimeofday(&t); + printk(KERN_DEBUG PFX "Recv msg at %ld.%6.6ld\n", + info->name, info->line, t.tv_sec, t.tv_usec); + } + if (debug & IPMI_SER_DEBUG_MSG) { + int i; + unsigned char *buf = info->curr_msg->rsp; + printk(KERN_DEBUG PFX "Received msg:", info->name, info->line); + for (i = 0; i < info->curr_msg->rsp_size; i++) + printk(" %2.2x(%c)", buf[i], + isprint(buf[i]) ? buf[i] : ' '); + printk("\n"); + } + + /* Queue the message for delivery when we release the lock. */ + list_add_tail(&info->curr_msg->link, &info->msgs_to_deliver); + info->curr_msg = NULL; +} + +static void queue_async_msg(struct ipmi_serial_info *info, + struct ipmi_smi_msg *msg) +{ + if (debug & IPMI_SER_DEBUG_TIMING) { + struct timeval t; + do_gettimeofday(&t); + printk(KERN_DEBUG PFX "Recv async msg at %ld.%6.6ld\n", + info->name, info->line, t.tv_sec, t.tv_usec); + } + if (debug & IPMI_SER_DEBUG_MSG) { + int i; + unsigned char *buf = msg->rsp; + printk(KERN_DEBUG PFX "Received async msg:", + info->name, info->line); + for (i = 0; i < msg->rsp_size; i++) + printk(" %2.2x(%c)", buf[i], + isprint(buf[i]) ? buf[i] : ' '); + printk("\n"); + } + + /* Queue the message for delivery when we release the lock. */ + list_add_tail(&msg->link, &info->msgs_to_deliver); +} + +static void queue_return_err_msg(struct ipmi_serial_info *info, int err) +{ + struct ipmi_smi_msg *msg = info->curr_msg; + + /* Make it a reponse */ + msg->rsp[0] = msg->data[0] | 4; + msg->rsp[1] = msg->data[1]; + msg->rsp[2] = err; + msg->rsp_size = 3; + queue_return_msg(info); +} + +static int send_curr_msg(struct ipmi_serial_info *info) +{ + if (!info->port) + return -ENODEV; + + WARN_ON(info->to_send); + if (info->to_send) + return -EBUSY; + + if (debug & IPMI_SER_DEBUG_TIMING) { + struct timeval t; + do_gettimeofday(&t); + printk(KERN_DEBUG PFX "Send msg at %ld.%6.6ld\n", + info->name, info->line, t.tv_sec, t.tv_usec); + } + if (debug & IPMI_SER_DEBUG_MSG) { + int i; + unsigned char *buf = info->curr_msg->data; + printk(KERN_DEBUG PFX "Sent msg:", info->name, info->line); + for (i = 0; i < info->curr_msg->data_size; i++) + printk(" %2.2x(%c)", buf[i], + isprint(buf[i]) ? buf[i] : ' '); + printk("\n"); + } + info->to_send = info->curr_msg; + return 0; +} + +static int start_clear_flags(struct ipmi_serial_info *info) +{ + struct ipmi_smi_msg *msg; + int rv; + + WARN_ON(info->curr_msg); + if (info->curr_msg) + return -EBUSY; + + msg = ipmi_alloc_smi_msg(); + if (!msg) + return -ENOMEM; + msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); + msg->data[1] = IPMI_CLEAR_MSG_FLAGS_CMD; + msg->data[2] = WDT_PRE_TIMEOUT_INT; + msg->data_size = 3; + info->curr_msg = msg; + rv = send_curr_msg(info); + if (!rv) + info->state = SERIAL_CLEARING_FLAGS; + else { + msg->done(msg); + info->curr_msg = NULL; + } + return rv; +} + +static int start_flag_fetch(struct ipmi_serial_info *info) +{ + struct ipmi_smi_msg *msg; + int rv; + + WARN_ON(info->curr_msg); + if (info->curr_msg) + return -EBUSY; + + msg = ipmi_alloc_smi_msg(); + if (!msg) + return -ENOMEM; + + msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); + msg->data[1] = IPMI_GET_MSG_FLAGS_CMD; + msg->data_size = 2; + info->curr_msg = msg; + rv = send_curr_msg(info); + if (!rv) + info->state = SERIAL_GETTING_FLAGS; + else { + msg->done(msg); + info->curr_msg = NULL; + } + return rv; +} + +static int start_event_fetch(struct ipmi_serial_info *info) +{ + struct ipmi_smi_msg *msg; + int rv; + + WARN_ON(info->curr_msg); + if (info->curr_msg) + return -EBUSY; + + msg = ipmi_alloc_smi_msg(); + if (!msg) + return -ENOMEM; + + msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); + msg->data[1] = IPMI_READ_EVENT_MSG_BUFFER_CMD; + msg->data_size = 2; + info->curr_msg = msg; + rv = send_curr_msg(info); + if (rv) { + msg->done(msg); + info->curr_msg = NULL; + } else + info->state = SERIAL_GETTING_EVENTS; + + return rv; +} + +static int start_msg_fetch(struct ipmi_serial_info *info) +{ + struct ipmi_smi_msg *msg; + int rv; + + WARN_ON(info->curr_msg); + if (info->curr_msg) + return -EBUSY; + + msg = ipmi_alloc_smi_msg(); + if (!msg) + return -ENOMEM; + + msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); + msg->data[1] = IPMI_GET_MSG_CMD; + msg->data_size = 2; + info->curr_msg = msg; + rv = send_curr_msg(info); + if (rv) { + info->curr_msg->done(info->curr_msg); + info->curr_msg = NULL; + } else + info->state = SERIAL_GETTING_MESSAGES; + + return rv; +} + +static void handle_flags(struct ipmi_serial_info *info) +{ + int rv; + + retry: + if (info->msg_flags & WDT_PRE_TIMEOUT_INT) { + /* Watchdog pre-timeout */ + serial_inc_stat(info, watchdog_pretimeouts); + + info->watchdog_pretimeouts_to_deliver++; + start_clear_flags(info); + info->msg_flags &= ~WDT_PRE_TIMEOUT_INT; + } else if (info->msg_flags & RECEIVE_MSG_AVAIL) { + /* + * Messages available. If for some reason we fail, + * just give up for now. It will be retried. + */ + info->msg_flags &= ~RECEIVE_MSG_AVAIL; + rv = start_msg_fetch(info); + if (rv) + goto retry; + } else if (info->msg_flags & EVENT_MSG_BUFFER_FULL) { + /* + * Messages available. If for some reason we fail, + * just give up for now. It will be retried. + */ + info->msg_flags &= ~EVENT_MSG_BUFFER_FULL; + rv = start_event_fetch(info); + if (rv) + goto retry; + } else if (info->msg_flags & OEM_DATA_AVAIL + && info->oem_data_avail_handler) { + if (info->oem_data_avail_handler(info)) + goto retry; + } else { + info->state = SERIAL_EMPTY; + } +} + +/* + * This routine starts the next thing to be processed. Called with + * the lock, and releases the lock. + * + * We delay handling of flags and transmissions of new messages here. + * We also delay the deliver of message to the upper and lower layer + * in a single-threaded but unlocked section below, so that we don't + * have worries of deadlocks calling back into this layer. + */ +static void start_next_msg(struct ipmi_serial_info *info, unsigned long *flags) +{ + struct list_head to_deliver; + int wdog_pretimeout_count; + int rv; + struct ipmi_smi_msg *msg, *s; + unsigned long oflags; + + restart: + if (debug & IPMI_SER_DEBUG_STATE) + printk(KERN_DEBUG PFX "start_next_message state = %d\n", + info->name, info->line, info->state); + + if (info->state == SERIAL_EMPTY) { + if (info->msg_flags) { + /* Handle flags we have first. */ + handle_flags(info); + if (info->state == SERIAL_EMPTY) { + info->msg_flags = 0; + goto restart; + } + info->msg_timeout = IPMI_SERIAL_RETRY_TIMEOUT; + info->retries = IPMI_SERIAL_MAX_ERROR_RETRIES; + } else if (info->req_events) { + /* We prefer fetching events over new messages. */ + info->req_events = 0; + rv = start_event_fetch(info); + if (rv) + goto restart; + info->msg_timeout = IPMI_SERIAL_RETRY_TIMEOUT; + info->retries = IPMI_SERIAL_MAX_ERROR_RETRIES; + } else if (likely(info->upper_layer_ready) && info->req_flags) { + /* We prefer fetching flags over new messages. */ + info->req_flags = 0; + rv = start_flag_fetch(info); + if (rv) + goto restart; + info->msg_timeout = IPMI_SERIAL_RETRY_TIMEOUT; + info->retries = IPMI_SERIAL_MAX_ERROR_RETRIES; + } else { + /* Look for messages to transmit. */ + if (!list_empty(&info->hp_xmit_msgs)) { + msg = list_entry(info->hp_xmit_msgs.next, + struct ipmi_smi_msg, + link); + } else if (!list_empty(&info->xmit_msgs)) { + msg = list_entry(info->xmit_msgs.next, + struct ipmi_smi_msg, + link); + } else + msg = NULL; + + info->curr_msg = msg; + if (msg) { + if (debug & IPMI_SER_DEBUG_TIMING) { + struct timeval t; + do_gettimeofday(&t); + printk(KERN_DEBUG PFX + "Start send at %ld.%6.6ld\n", + info->name, info->line, + t.tv_sec, t.tv_usec); + } + info->state = SERIAL_HANDLING_MSG; + list_del(&msg->link); + rv = send_curr_msg(info); + if (rv) { + queue_return_err_msg(info, + IPMI_ERR_UNSPECIFIED); + goto restart; + } + info->msg_timeout = IPMI_SERIAL_RETRY_TIMEOUT; + info->retries = IPMI_SERIAL_MAX_ERROR_RETRIES; + } + } + } + + restart_delivery: + if (info->watchdog_pretimeouts_to_deliver + || !list_empty(&info->msgs_to_deliver) + || info->to_send) { + if (info->msg_delivery_in_progress) { + /* + * Another thread is already delivering, tell + * it there are more things to do and + * leave. + */ + info->msg_delivery_in_progress = 2; + goto out_unlock; + } + + /* Pull the data we need to deliver. */ + wdog_pretimeout_count = info->watchdog_pretimeouts_to_deliver; + info->watchdog_pretimeouts_to_deliver = 0; + + INIT_LIST_HEAD(&to_deliver); + if (!list_empty(&info->msgs_to_deliver)) + list_splice_init(&info->msgs_to_deliver, &to_deliver); + + info->msg_delivery_in_progress = 1; + ipmi_serial_unlock_cond(info, flags); + while (wdog_pretimeout_count > 0) { + ipmi_smi_watchdog_pretimeout(info->intf); + wdog_pretimeout_count--; + } + + list_for_each_entry_safe(msg, s, &to_deliver, link) { + list_del(&msg->link); + ipmi_smi_msg_received(info->intf, msg); + } + if (info->to_send) { + if (debug & IPMI_SER_DEBUG_TIMING) { + struct timeval t; + do_gettimeofday(&t); + printk(KERN_DEBUG PFX + "send to codec at %ld.%6.6ld\n", + info->name, info->line, + t.tv_sec, t.tv_usec); + } + info->send_seq++; + if (info->send_seq == 0) + info->send_seq++; + msg = info->to_send; + info->to_send = NULL; + rv = info->codec->send_msg(info->codec_data, + msg->data, + msg->data_size, + info->send_seq); + if (rv) + printk(KERN_WARNING PFX + "Error from codec send_msg: %d\n", + info->name, info->line, rv); + } + flags = ipmi_serial_lock_cond(info, &oflags); + if (info->msg_delivery_in_progress > 1) { + /* Another thread put things in to be delivered, go + * ahead and do the delivery since it couldn't. */ + info->msg_delivery_in_progress = 0; + goto restart_delivery; + } + info->msg_delivery_in_progress = 0; + } + out_unlock: + ipmi_serial_unlock_cond(info, flags); +} + +unsigned int ipmi_serial_ll_xmit(struct ipmi_serial_info *info, + const unsigned char *buf, + unsigned int count) +{ + struct uart_port *port; + unsigned long oflags, *flags; + unsigned int rv; + + if (debug & IPMI_SER_DEBUG_CHAR_TIMING) { + struct timeval t; + do_gettimeofday(&t); + printk(KERN_DEBUG PFX "xmit chars at %ld.%6.6ld\n", + info->name, info->line, t.tv_sec, t.tv_usec); + } + + flags = ipmi_serial_lock_cond(info, &oflags); + port = info->port; + ipmi_serial_unlock_cond(info, flags); + if (!port) + return 0; + + rv = uart_direct_write(port, buf, count, flags != NULL); + if (rv > 0) + serial_add_stat(info, xmit_chars, rv); + + if (debug & IPMI_SER_DEBUG_DATA) { + int i; + printk(KERN_DEBUG PFX "Outgoing data:", + info->name, info->line); + for (i = 0; i < rv; i++) + printk(" %2.2x(%c)", buf[i], + isprint(buf[i]) ? buf[i] : ' '); + printk("\n"); + } + return rv; +} +EXPORT_SYMBOL(ipmi_serial_ll_xmit); + +void ipmi_serial_ll_attn(struct ipmi_serial_info *info) +{ + unsigned long oflags, *flags; + + if (info->stop_operation) + return; + + flags = ipmi_serial_lock_cond(info, &oflags); + serial_inc_stat(info, attentions); + if (info->port) + info->req_flags = 1; + start_next_msg(info, flags); +} +EXPORT_SYMBOL(ipmi_serial_ll_attn); + +void ipmi_serial_ll_recv(struct ipmi_serial_info *info, + const unsigned char *msg, + unsigned int len, + unsigned int seq) +{ + struct ipmi_smi_msg *rmsg; + int truncated = 0; + unsigned long oflags, *flags; + + if (info->internal_msg_handler) { + info->internal_msg_handler(info, msg, len); + return; + } + + flags = ipmi_serial_lock_cond(info, &oflags); + if (!info->intf) { + ipmi_serial_unlock_cond(info, flags); + return; + } + if (seq && (seq != info->send_seq)) { + /* Sequence doesn't match and is non-zero, just ignore. */ + ipmi_serial_unlock_cond(info, flags); + return; + } + if (debug & IPMI_SER_DEBUG_STATE) + printk(KERN_DEBUG PFX "ll_recv state = %d\n", + info->name, info->line, info->state); + switch (info->state) { + case SERIAL_EMPTY: + break; + + case SERIAL_HANDLING_MSG: + serial_inc_stat(info, complete_transactions); + rmsg = info->curr_msg; + if (len > IPMI_MAX_MSG_LENGTH) { + rmsg->rsp_size = IPMI_MAX_MSG_LENGTH; + truncated = 1; + } else + rmsg->rsp_size = len; + memcpy(rmsg->rsp, msg, rmsg->rsp_size); + if (truncated && (msg[2] == 0)) + rmsg->rsp[2] = IPMI_ERR_MSG_TRUNCATED; + queue_return_msg(info); + break; + + case SERIAL_GETTING_FLAGS: + info->curr_msg->done(info->curr_msg); + info->curr_msg = NULL; + /* We got the flags from the SMI, now fetch them. */ + if ((msg[2] == 0) && (len >= 4)) { + serial_inc_stat(info, flag_fetches); + info->msg_flags = msg[3]; + } + break; + + case SERIAL_GETTING_EVENTS: + rmsg = info->curr_msg; + if (len > IPMI_MAX_MSG_LENGTH) { + printk(KERN_WARNING PFX "got event response " + "that was too long: %d\n", info->name, + info->line, len); + break; + } + rmsg->rsp_size = len; + memcpy(rmsg->rsp, msg, rmsg->rsp_size); + if (rmsg->rsp[2] != 0) { + /* Error getting event, probably done. */ + rmsg->done(rmsg); + info->curr_msg = NULL; + } else { + serial_inc_stat(info, events); + + queue_return_msg(info); + } + break; + + case SERIAL_CLEARING_FLAGS: + /* We cleared the flags. */ + info->curr_msg->done(info->curr_msg); + info->curr_msg = NULL; + if (msg[2] != 0) { + /* Error clearing flags */ + printk(KERN_WARNING + PFX "Error clearing flags: %2.2x\n", + info->name, info->line, msg[2]); + } + info->state = SERIAL_EMPTY; + break; + + case SERIAL_GETTING_MESSAGES: + rmsg = info->curr_msg; + if (len > IPMI_MAX_MSG_LENGTH) { + printk(KERN_WARNING PFX "got incoming command " + "that was too long: %d\n", info->name, + info->line, len); + break; + } + rmsg->rsp_size = len; + memcpy(rmsg->rsp, msg, rmsg->rsp_size); + if (rmsg->rsp[2] != 0) { + /* Error getting message, probably done. */ + rmsg->done(rmsg); + info->curr_msg = NULL; + } else { + serial_inc_stat(info, incoming_messages); + queue_return_msg(info); + } + break; + } + info->state = SERIAL_EMPTY; /* Tell start_next_msg to do something. */ + start_next_msg(info, flags); +} +EXPORT_SYMBOL(ipmi_serial_ll_recv); + +void ipmi_serial_ll_async(struct ipmi_serial_info *info, + const unsigned char *msg, unsigned int len) +{ + struct ipmi_smi_msg *rmsg; + unsigned long *flags, oflags; + + if ((msg[0] >> 2) != IPMI_NETFN_APP_RESPONSE) { + printk(KERN_WARNING PFX "Got invalid async NETFN: 0x%x", + info->name, info->line, msg[0] >> 2); + return; + } + + flags = ipmi_serial_lock_cond(info, &oflags); + if (!info->intf) { + ipmi_serial_unlock_cond(info, flags); + return; + } + + /* Validate that the message is allowed and do stats. */ + switch (msg[1]) { + case IPMI_GET_MSG_CMD: + serial_inc_stat(info, incoming_messages); + break; + + case IPMI_READ_EVENT_MSG_BUFFER_CMD: + serial_inc_stat(info, events); + break; + + default: + printk(KERN_WARNING PFX "Got invalid async command: 0x%x", + info->name, info->line, msg[1]); + goto out; + } + + if (len > IPMI_MAX_MSG_LENGTH) { + /* FIXME - add peg */ + printk(KERN_WARNING PFX "got incoming async msg " + "that was too long: %d\n", info->name, info->line, len); + goto out; + } + rmsg = ipmi_alloc_smi_msg(); + if (!rmsg) { + /* FIXME - add peg */ + printk(KERN_WARNING PFX "Dropped incoming async msg, " + "could not allocate message\n", info->name, info->line); + goto out; + } + + /* Make it look like we sent the command. */ + rmsg->data[0] = msg[0] & ~(1 << 2); /* Convert response to request */ + rmsg->data[1] = msg[1]; /* command */ + rmsg->data_size = 2; + + rmsg->rsp_size = len; + memcpy(rmsg->rsp, msg, rmsg->rsp_size); + queue_async_msg(info, rmsg); + + out: + start_next_msg(info, flags); +} +EXPORT_SYMBOL(ipmi_serial_ll_async); + +void ipmi_serial_ll_protocol_violation(struct ipmi_serial_info *info) +{ + serial_inc_stat(info, protocol_violations); +} +EXPORT_SYMBOL(ipmi_serial_ll_protocol_violation); + +void ipmi_serial_ll_checksum_error(struct ipmi_serial_info *info) +{ + serial_inc_stat(info, checksum_errors); +} +EXPORT_SYMBOL(ipmi_serial_ll_checksum_error); + +static void ipmi_serial_handle_char(struct uart_port *port, + unsigned int status, + unsigned int overrun, unsigned int ch, + unsigned int flag) +{ + struct ipmi_serial_info *info = port->info->direct->direct_data; + unsigned int next; + + if (debug & IPMI_SER_DEBUG_CHAR_TIMING) { + struct timeval t; + do_gettimeofday(&t); + printk(KERN_DEBUG PFX "Got char %2.2x(%c) at %ld.%6.6ld\n", + info->name, info->line, + ch, isprint(ch) ? ch : ' ', + t.tv_sec, t.tv_usec); + } + + serial_inc_stat(info, recv_chars); + + if (status & overrun) + serial_inc_stat(info, overruns); + + if (debug & IPMI_SER_DEBUG_DATA) + printk(KERN_DEBUG PFX "Incoming char: %2.2x '%c'\n", + info->name, info->line, ch, isprint(ch) ? ch : ' '); + + /* + * No lock is needed. We are the only place that changes + * sbuf_next, and we are single-threaded here due to the port + * lock. + */ + next = SBUF_NEXT(info->sbuf_next); + if (next == info->sbuf_start) { + serial_inc_stat(info, overruns); + return; + } + info->sbuf[info->sbuf_next] = ch; + smp_wmb(); + info->sbuf_next = next; +} + +static void ipmi_serial_push(struct uart_port *port) +{ + struct ipmi_serial_info *info = port->info->direct->direct_data; + unsigned long oflags, *flags; + struct ipmi_serial_codec_data *cdata; + unsigned char ch; + + if (debug & IPMI_SER_DEBUG_CHAR_TIMING) { + struct timeval t; + do_gettimeofday(&t); + printk(KERN_DEBUG PFX "Got push at %ld.%6.6ld\n", + info->name, info->line, t.tv_sec, t.tv_usec); + } + + cdata = info->codec_data; + if (!cdata) + return; + + flags = ipmi_serial_lock_cond(info, &oflags); + if (info->delivering_char) + goto out_unlock; + info->delivering_char = 1; + while (info->sbuf_next != info->sbuf_start) { + ch = info->sbuf[info->sbuf_start]; + info->sbuf_start = SBUF_NEXT(info->sbuf_start); + ipmi_serial_unlock_cond(info, flags); + info->codec->handle_char(cdata, ch); + flags = ipmi_serial_lock_cond(info, &oflags); + } + info->delivering_char = 0; + out_unlock: + ipmi_serial_unlock_cond(info, flags); +} + +static void ipmi_serial_tx_ready(unsigned long data) +{ + struct ipmi_serial_info *info = (struct ipmi_serial_info *) data; + struct ipmi_serial_codec_data *cdata; + + cdata = info->codec_data; + if (cdata) + info->codec->tx_ready(cdata); +} + +static void timeout_handling(struct ipmi_serial_info *info, + unsigned int delay_since_last) +{ + unsigned long oflags, *flags; + + if (info->codec->timer_tick) + info->codec->timer_tick(info->codec_data, delay_since_last); + + flags = ipmi_serial_lock_cond(info, &oflags); + + info->flag_timer -= delay_since_last; + if (info->flag_timer <= 0) { + if (!info->stop_operation && info->supports_flags) + info->req_flags = 1; + info->flag_timer = info->flag_timeout; + } + + info->msg_timeout -= delay_since_last; + if (info->msg_timeout > 0) + /* Haven't timed out yet. */ + goto out_unlock; + + serial_inc_stat(info, timeouts); + + /* + * Note that if we are doing flags, events, or things of that + * nature, just ignore failures and continue on. The + * operation will be retried later, so no need to fret. + * Just retry messages. + */ + if (debug & IPMI_SER_DEBUG_STATE) + printk(KERN_DEBUG PFX "timeout state = %d\n", + info->name, info->line, info->state); + switch (info->state) { + case SERIAL_EMPTY: + break; + + case SERIAL_HANDLING_MSG: + info->retries--; + if (info->retries > 0) + send_curr_msg(info); /* Retry the send */ + else { + queue_return_err_msg(info, IPMI_TIMEOUT_ERR); + info->state = SERIAL_EMPTY; + } + break; + + case SERIAL_GETTING_FLAGS: + case SERIAL_CLEARING_FLAGS: + info->curr_msg->done(info->curr_msg); + info->curr_msg = NULL; + info->state = SERIAL_EMPTY; + break; + + case SERIAL_GETTING_EVENTS: + info->curr_msg->done(info->curr_msg); + info->curr_msg = NULL; + info->state = SERIAL_EMPTY; + break; + + case SERIAL_GETTING_MESSAGES: + info->curr_msg->done(info->curr_msg); + info->curr_msg = NULL; + info->state = SERIAL_EMPTY; + break; + } + + out_unlock: + start_next_msg(info, flags); +} + +static void ipmi_serial_timeout(unsigned long data) +{ + struct ipmi_serial_info *info = (struct ipmi_serial_info *) data; + unsigned long curr_jif = jiffies; + + serial_inc_stat(info, timer_ticks); + + timeout_handling(info, ((curr_jif - info->last_timeout_jiffies) + * IPMI_SERIAL_USEC_PER_JIFFY)); + + info->last_timeout_jiffies = curr_jif; + mod_timer(&info->timer, curr_jif + IPMI_SERIAL_TIMEOUT_JIFFIES); +} + +static int ipmi_serial_start_processing(void *send_info, ipmi_smi_t intf) +{ + struct ipmi_serial_info *info = send_info; + unsigned long curr_jif = jiffies; + + info->intf = intf; + smp_wmb(); /* Make sure intf is set before anything else. */ + + info->upper_layer_ready = 1; + info->last_timeout_jiffies = curr_jif; + mod_timer(&info->timer, curr_jif + IPMI_SERIAL_TIMEOUT_JIFFIES); + return 0; +} + +static void ipmi_serial_poll(void *send_info) +{ + struct ipmi_serial_info *info = send_info; + struct uart_port *port; + struct circ_buf *circ; + + port = info->port; + if (!port) + return; + + /* + * No need for locks here, that would result in a deadlock and + * the locks are unnecessary. + */ + udelay(10); + timeout_handling(info, 10); + if (info->run_to_completion) { + port->ops->poll(port, UART_POLL_FLAGS_TX | UART_POLL_FLAGS_RX); + } else { + unsigned long flags; + spin_lock_irqsave(&port->lock, flags); + port->ops->poll_startup(port, &info->poll_state); + port->ops->poll(port, UART_POLL_FLAGS_TX | UART_POLL_FLAGS_RX); + port->ops->poll_shutdown(port, info->poll_state); + spin_unlock_irqrestore(&port->lock, flags); + } + if (info->sbuf_next != info->sbuf_start) + ipmi_serial_push(port); + circ = uart_get_circ_buf(port); + if (uart_circ_chars_free(circ) > 0) + ipmi_serial_tx_ready((unsigned long) info); +} + +static void ipmi_serial_sender(void *send_info, + struct ipmi_smi_msg *msg, + int priority) +{ + struct ipmi_serial_info *info = send_info; + unsigned long oflags, *flags; + + flags = ipmi_serial_lock_cond(info, &oflags); + if (!info->port) { + info->curr_msg = msg; + queue_return_err_msg(info, IPMI_ERR_UNSPECIFIED); + } else if (priority > 0) + list_add_tail(&msg->link, &info->hp_xmit_msgs); + else + list_add_tail(&msg->link, &info->xmit_msgs); + start_next_msg(info, flags); + + if (info->run_to_completion) { + while (info->curr_msg) + ipmi_serial_poll(info); + } +} + +static void ipmi_serial_request_events(void *send_info) +{ + struct ipmi_serial_info *info = send_info; + unsigned long oflags, *flags; + + flags = ipmi_serial_lock_cond(info, &oflags); + if (!info->stop_operation && info->port && info->do_event_request + && info->has_event_buffer) + info->req_events = 1; + start_next_msg(info, flags); +} + +static void ipmi_serial_set_run_to_completion(void *send_info, int rtc_on) +{ + struct ipmi_serial_info *info = send_info; + struct uart_port *port; + + if (info->run_to_completion == rtc_on) + return; + + port = info->port; + if (!port) + return; + + info->run_to_completion = rtc_on; + if (rtc_on) { + port->ops->poll_startup(port, &info->poll_state); + while (info->curr_msg) + ipmi_serial_poll(info); + } else + port->ops->poll_shutdown(port, info->poll_state); +} + +static int ipmi_serial_inc_usecount(void *send_info) +{ + struct ipmi_serial_info *info = send_info; + + if (!try_module_get(info->codec->owner)) + return -ENODEV; + return 0; +} + +static void ipmi_serial_dec_usecount(void *send_info) +{ + struct ipmi_serial_info *info = send_info; + + module_put(info->codec->owner); +} + +static struct ipmi_smi_handlers handlers = { + .owner = THIS_MODULE, + .start_processing = ipmi_serial_start_processing, + .sender = ipmi_serial_sender, + .request_events = ipmi_serial_request_events, + .set_run_to_completion = ipmi_serial_set_run_to_completion, + .poll = ipmi_serial_poll, + .inc_usecount = ipmi_serial_inc_usecount, + .dec_usecount = ipmi_serial_dec_usecount +}; + +static int type_file_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + return sprintf(page, "serial\n"); +} + +static int param_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char *out = page; + struct ipmi_serial_info *info = data; + struct ktermios *t = &info->termios; + int i; + char parity, bits, stop, opts[4]; + + /* Translate from termios and mctrl. */ + for (i = 0; baud_rates[i].rate; i++) { + if ((t->c_cflag & CBAUD) == baud_rates[i].cflag) + break; + } + if (!(t->c_cflag & PARENB)) + parity = 'n'; + else if (t->c_cflag & PARODD) + parity = 'o'; + else + parity = 'e'; + switch (t->c_cflag & CSIZE) { + case CS5: + bits = '5'; + break; + case CS6: + bits = '6'; + break; + case CS7: + bits = '7'; + break; + case CS8: + bits = '8'; + break; + default: + bits = '?'; + } + if (t->c_cflag & CSTOPB) + stop = '2'; + else + stop = '1'; + opts[0] = '\0'; + if (t->c_cflag & CRTSCTS) + strcat(opts, "r"); + if (!(info->port->mctrl & TIOCM_RTS)) + strcat(opts, "R"); + if (!(info->port->mctrl & TIOCM_DTR)) + strcat(opts, "D"); + + out += sprintf(out, "%s%d,%s%c%c%c%s,%s", + info->name, info->line, + baud_rates[i].name, parity, bits, stop, opts, + info->codec->name); + out += info->codec->add_options(info->codec_data, out); + out += sprintf(out, "\n"); + + return out - page; +} + +static int stat_file_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char *out = page; + struct ipmi_serial_info *info = data; + + out += sprintf(out, "timer_ticks: %u\n", + serial_get_stat(info, timer_ticks)); + out += sprintf(out, "attentions: %u\n", + serial_get_stat(info, attentions)); + out += sprintf(out, "flag_fetches: %u\n", + serial_get_stat(info, flag_fetches)); + out += sprintf(out, "complete_transactions: %u\n", + serial_get_stat(info, complete_transactions)); + out += sprintf(out, "events: %u\n", + serial_get_stat(info, events)); + out += sprintf(out, "watchdog_pretimeouts: %u\n", + serial_get_stat(info, watchdog_pretimeouts)); + out += sprintf(out, "xmit_chars: %u\n", + serial_get_stat(info, xmit_chars)); + out += sprintf(out, "recv_chars: %u\n", + serial_get_stat(info, recv_chars)); + out += sprintf(out, "protocol_violations: %u\n", + serial_get_stat(info, protocol_violations)); + out += sprintf(out, "checksum_errors: %u\n", + serial_get_stat(info, checksum_errors)); + out += sprintf(out, "timeouts: %u\n", + serial_get_stat(info, timeouts)); + out += sprintf(out, "overruns: %u\n", + serial_get_stat(info, overruns)); + + return out - page; +} + +static void devid_handler(struct ipmi_serial_info *info, + const unsigned char *data, + unsigned int len) +{ + int rv; + + rv = ipmi_demangle_device_id(data, len, &info->device_id); + if (rv) { + info->device_id_valid = rv; + return; + } + + info->device_id_valid = 1; + + if (info->codec->check_dev_id) + info->codec->check_dev_id(info->codec_data, &info->device_id); +} + +static int try_get_dev_id(struct ipmi_serial_info *info) +{ + unsigned char msg[2]; + int rv; + int timeout; + + msg[0] = IPMI_NETFN_APP_REQUEST << 2; + msg[1] = IPMI_GET_DEVICE_ID_CMD; + + info->device_id_valid = 0; + info->internal_msg_handler = devid_handler; + rv = info->codec->send_msg(info->codec_data, msg, 2, 0); + if (rv) + goto out_err; + + timeout = IPMI_SERIAL_RETRY_TIMEOUT; + while (!info->device_id_valid && (timeout > 0)) { + msleep(10); + timeout -= 10000; + } + if (!info->device_id_valid) + rv = -ETIMEDOUT; + else if (info->device_id_valid < 0) + rv = info->device_id_valid; + + out_err: + info->internal_msg_handler = NULL; + return rv; +} + +static void set_global_enable_handler(struct ipmi_serial_info *info, + const unsigned char *data, + unsigned int len) +{ + if (len < 3 || + data[0] != (IPMI_NETFN_APP_REQUEST | 1) << 2 || + data[1] != IPMI_SET_BMC_GLOBAL_ENABLES_CMD) { + printk(KERN_WARNING PFX + "Invalid return from set global " + " enables command, cannot enable the event" + " buffer.\n", info->name, info->line); + info->global_enable_valid = -EINVAL; + return; + } + + if (data[2] != 0) + info->global_enable_valid = 1; + else + info->global_enable_valid = -ENOENT; +} + +static void get_global_enable_handler(struct ipmi_serial_info *info, + const unsigned char *data, + unsigned int len) +{ + unsigned char msg[3]; + int rv; + + if (len < 4 || + data[0] != (IPMI_NETFN_APP_REQUEST | 1) << 2 || + data[1] != IPMI_GET_BMC_GLOBAL_ENABLES_CMD || + data[2] != 0) { + printk(KERN_WARNING PFX + "Invalid return from get global" + " enables command, cannot enable the event" + " buffer.\n", info->name, info->line); + info->global_enable_valid = -EINVAL; + return; + } + + if (data[3] & IPMI_BMC_EVT_MSG_BUFF) { + /* Nothing to do, it's enabled. */ + info->global_enable_valid = 1; + return; + } + + msg[0] = IPMI_NETFN_APP_REQUEST << 2; + msg[1] = IPMI_SET_BMC_GLOBAL_ENABLES_CMD; + msg[2] = data[3] | IPMI_BMC_EVT_MSG_BUFF; + info->internal_msg_handler = set_global_enable_handler; + rv = info->codec->send_msg(info->codec_data, msg, 3, 0); + if (rv) + info->global_enable_valid = rv; +} + +static int try_enable_event_buffer(struct ipmi_serial_info *info) +{ + unsigned char msg[3]; + int timeout; + int rv = 0; + + msg[0] = IPMI_NETFN_APP_REQUEST << 2; + msg[1] = IPMI_GET_BMC_GLOBAL_ENABLES_CMD; + info->global_enable_valid = 0; + info->internal_msg_handler = get_global_enable_handler; + rv = info->codec->send_msg(info->codec_data, msg, 2, 0); + if (rv) + goto out_err; + + timeout = IPMI_SERIAL_RETRY_TIMEOUT; + while (!info->global_enable_valid && (timeout > 0)) { + msleep(10); + timeout -= 10000; + } + if (!info->global_enable_valid) + rv = -ETIMEDOUT; + else if (info->global_enable_valid < 0) + rv = info->global_enable_valid; + + out_err: + info->internal_msg_handler = NULL; + return rv; +} + +void ipmi_serial_ll_init_complete(struct ipmi_serial_info *info, + unsigned char slave_addr, + int err) +{ + if (err) + info->init_status = err; + else { + if (slave_addr) + info->slave_addr = slave_addr; + info->init_status = 1; + } +} +EXPORT_SYMBOL(ipmi_serial_ll_init_complete); + +static int setup_termios(struct ipmi_serial_info *info) +{ + char *o = info->options; + int baud = 0; + int i; + unsigned int mctrl = 0; + unsigned long flags; + int rv; + + /* Init some defaults. */ + info->termios.c_cflag = CLOCAL | CREAD; + info->termios.c_iflag = IGNPAR; + info->termios.c_oflag = 0; + info->termios.c_lflag = 0; + info->termios.c_cc[VTIME] = 0; /* inter-character timer used */ + + rv = info->codec->setup_termios(&info->termios); + if (rv) + return rv; + + if (o && *o) { + char *end; + baud = simple_strtoul(o, &end, 10); + if (end == o) { + printk(KERN_ERR PFX "no baud rate given\n", + info->name, info->line); + return -EINVAL; + } + o = end; + } + for (i = 0; baud_rates[i].rate != 0; i++) { + if (baud_rates[i].rate == baud) + break; + } + if (baud_rates[i].rate == 0) { + printk(KERN_ERR PFX "invalid baud rate\n", + info->name, info->line); + return -EINVAL; + } + + info->termios.c_cflag |= baud_rates[i].cflag; + + if (o && *o) { + switch (*o) { + case 'o': case 'O': + info->termios.c_cflag |= PARODD; + /*fall through*/ + case 'e': case 'E': + info->termios.c_cflag |= PARENB; + break; + case 'n': case 'N': + break; + default: + printk(KERN_ERR PFX "Invalid parity: '%c'\n", + info->name, info->line, *o); + return -EINVAL; + } + o++; + } + if (o && *o) { + switch (*o) { + case '5': + info->termios.c_cflag |= CS5; + break; + case '6': + info->termios.c_cflag |= CS6; + break; + case '7': + info->termios.c_cflag |= CS7; + break; + case '8': + info->termios.c_cflag |= CS8; + break; + default: + info->termios.c_cflag |= CS8; + printk(KERN_ERR PFX "Invalid bits: '%c'\n", + info->name, info->line, *o); + return -EINVAL; + } + o++; + } else { + info->termios.c_cflag |= CS8; + } + + if (o && *o) { + switch (*o) { + case '1': + break; + case '2': + info->termios.c_cflag |= CSTOPB; + break; + default: + printk(KERN_ERR PFX "Invalid stop bits: '%c'\n", + info->name, info->line, *o); + return -EINVAL; + } + o++; + } + + /* + * Note that unless overridden always enable DTR and sending + * RTS to the device. The device can ignore it, but it can't + * hurt to send it. It can be overridden with options. + */ + mctrl |= TIOCM_RTS | TIOCM_DTR; + + while (o && *o) { + switch (*o) { + case 'r': + info->termios.c_cflag |= CRTSCTS; + break; + case 'R': + mctrl &= ~TIOCM_RTS; + break; + case 'D': + mctrl &= ~TIOCM_DTR; + break; + default: + printk(KERN_ERR PFX "Invalid config option: '%c'\n", + info->name, info->line, *o); + return -EINVAL; + } + o++; + } + + spin_lock_irqsave(&info->port->lock, flags); + info->port->mctrl |= mctrl; + spin_unlock_irqrestore(&info->port->lock, flags); + info->port->ops->set_termios(info->port, &info->termios, + &info->old_termios); + return 0; +} + +/* + * Called when the serial layer says it found a serial driver that + * matches. We use this to kick things off. + */ +static int ipmi_serial_found(struct ipmi_serial_info *info) +{ + int rv; + int timeout; + int retries; + unsigned int capabilities; + char sysfs_name[sizeof(info->name) + 4]; + + printk(KERN_INFO PFX "Found a matching serial port\n", + info->name, info->line); + + info->stop_operation = 0; + + setup_termios(info); + + info->init_status = 0; + retries = IPMI_SERIAL_INIT_RETRIES; + retry: + rv = info->codec->init(info->alloc_codec_data, info, + info->codec_options); + if (rv) { + printk(KERN_ERR PFX "codec initialization failed, " + "interface is not usable: %d\n", info->name, + info->line, rv); + return rv; + } + + /* Allow the receiver to send to the codec. */ + info->codec_data = info->alloc_codec_data; + + if (info->codec->start) { + rv = info->codec->start(info->codec_data); + if (rv) { + printk(KERN_ERR PFX "codec start failed, " + "interface is not usable: %d\n", info->name, + info->line, rv); + goto out_err; + } + } else + ipmi_serial_ll_init_complete(info, 0, 0); + + timeout = IPMI_SERIAL_INIT_TIMEOUT; + while (!info->init_status && (timeout > 0)) { + msleep(10); + timeout -= 10000; + } + if (!info->init_status) { + retries--; + if (retries > 0) { + /* Turn the receiver off and let it clear out. */ + info->codec_data = NULL; + synchronize_sched(); + info->codec->cleanup(info->alloc_codec_data); + goto retry; + } + + printk(KERN_ERR PFX "codec initialization timed out, " + "interface is not usable\n", info->name, info->line); + rv = -ETIMEDOUT; + goto out_err; + } else if (info->init_status < 0) { + rv = info->init_status; + printk(KERN_ERR PFX "Initialization failed: %d\n", + info->name, info->line, rv); + goto out_err; + } + + retries = IPMI_SERIAL_MAX_ERROR_RETRIES; + retry_devid: + rv = try_get_dev_id(info); + if (rv) { + retries--; + if (retries > 0) + goto retry_devid; + printk(KERN_ERR PFX "Device ID fetch failed, " + "interface is not usable: %d\n", info->name, + info->line, rv); + goto out_err; + } + + capabilities = info->codec->capabilities(info->codec_data); + info->do_event_request = (capabilities + & IPMI_SERIAL_SUPPORTS_EVENT_BUFFER); + if (capabilities & IPMI_SERIAL_NEEDS_GET_FLAGS_POLLING) + info->flag_timeout = POLLING_FLAG_TIMEOUT; + else + info->flag_timeout = NO_POLLING_FLAG_TIMEOUT; + info->flag_timer = info->flag_timeout; + info->supports_flags = (capabilities + & IPMI_SERIAL_SUPPORTS_GET_FLAGS); + + if (info->do_event_request) { + if (try_enable_event_buffer(info) == 0) + info->has_event_buffer = 1; + } + + + snprintf(sysfs_name, sizeof(sysfs_name), "bmc:%s%d", info->name, + info->line); + + rv = ipmi_register_smi(&handlers, + info, + &info->device_id, + info->port->dev, + sysfs_name, + info->slave_addr); + if (rv) { + printk(KERN_ERR PFX "Unable to register the " + "interface with the IPMI message handler: %d\n", + info->name, info->line, rv); + if (info->intf) + del_timer_sync(&info->timer); + info->intf = NULL; + goto out_err_stop_timer; + } + + rv = ipmi_smi_add_proc_entry(info->intf, "type", + type_file_read_proc, + info); + if (rv) { + printk(KERN_ERR PFX + "Unable to create proc entry: %d\n", info->name, + info->line, rv); + goto out_err_stop_timer; + } + + rv = ipmi_smi_add_proc_entry(info->intf, "params", + param_read_proc, + info); + if (rv) { + printk(KERN_ERR PFX + "Unable to create proc entry: %d\n", info->name, + info->line, rv); + goto out_err_stop_timer; + } + + rv = ipmi_smi_add_proc_entry(info->intf, "serial_stats", + stat_file_read_proc, + info); + if (rv) { + printk(KERN_ERR PFX + "Unable to create proc entry: %d\n", info->name, + info->line, rv); + goto out_err_stop_timer; + } + + return 0; + + out_err_stop_timer: + if (info->intf) { + del_timer_sync(&info->timer); + ipmi_unregister_smi(info->intf); + info->intf = NULL; + } + out_err: + /* Turn the receiver off and let it clear out. */ + info->codec_data = NULL; + synchronize_sched(); + + info->codec->cleanup(info->alloc_codec_data); + return rv; +} + +static void setup_intf(struct ipmi_serial_info *info, + struct ipmi_serial_codec *codec) +{ + int rv; + + info->alloc_codec_data = kmalloc(codec->size(), GFP_KERNEL); + if (!info->alloc_codec_data) { + printk(KERN_ERR PFX "Unable to allocate codec data\n", + info->name, info->line); + return; + } + + info->codec = codec; + + /* + * Tell the serial layer that we want to take over a specific + * serial interface. + */ + info->port = uart_get_direct_port(info->name, info->line, 0); + if (!info->port) { + printk(KERN_ERR PFX "Unable to find serial port\n", + info->name, info->line); + return; + } + info->port->info->direct = &info->direct; + if (!info->port->info->xmit.buf) { + info->port->info->xmit.buf = info->uart_buffer; + uart_circ_clear(&info->port->info->xmit); + info->port->info->flags |= UIF_BOOT_ALLOCATED; + } + tasklet_init(&info->port->info->tlet, ipmi_serial_tx_ready, + (unsigned long) info); + info->port->info->flags |= UIF_TASKLET_SETUP; + + rv = info->port->ops->startup(info->port); + if (rv) { + tasklet_kill(&info->port->info->tlet); + info->port->info->flags &= ~UIF_TASKLET_SETUP; + if (info->port->info->xmit.buf == info->uart_buffer) { + info->port->info->xmit.buf = NULL; + info->port->info->flags &= ~UIF_BOOT_ALLOCATED; + } + uart_put_direct_port(info->port, 0); + info->port = NULL; + printk(KERN_ERR PFX "Unable setup serial port %d\n", + info->name, info->line, rv); + return; + } + + if (ipmi_serial_found(info)) { + tasklet_kill(&info->port->info->tlet); + info->port->info->flags &= ~UIF_TASKLET_SETUP; + if (info->port->info->xmit.buf == info->uart_buffer) { + info->port->info->xmit.buf = NULL; + info->port->info->flags &= ~UIF_BOOT_ALLOCATED; + } + info->port->ops->shutdown(info->port); + uart_put_direct_port(info->port, 0); + info->port = NULL; + } +} + +static void free_info_memory(struct ipmi_serial_info *info) +{ + kfree(info->keepstr); + kfree(info); +} + +static int name_line_from_str(const char *str, char *name, int len) +{ + if (str[0] >= '0' && str[0] <= '9') { + /* We convert a number to ttyS# */ + strncpy(name, "ttyS", len); + return simple_strtoul(str, NULL, 10); + } else { + int size; + const char *s; + s = str; + while (*s && *s != ',' && (*s < '0' || *s > '9')) + s++; + size = s - str; + if (size >= len) + size = len - 1; + memcpy(name, str, size); + name[size] = '\0'; + return simple_strtoul(s, NULL, 10); + } +} + +/* + * Setup a list of ipmi serial system interfaces. + */ +static void ipmi_serial_setup_one(const char *istr) +{ + struct ipmi_serial_codec *codec; + struct ipmi_serial_info *info; + struct ipmi_serial_info *cinfo; + char *str; + int i; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + printk(KERN_ERR NPFX "unable to allocate serial info: %s\n", + istr); + return; + } + info->keepstr = kstrdup(istr, GFP_KERNEL); + if (!info->keepstr) { + printk(KERN_ERR NPFX "unable to allocate serial string: %s\n", + istr); + kfree(info); + return; + } + str = info->keepstr; + + spin_lock_init(&info->lock); + setup_timer(&info->timer, ipmi_serial_timeout, (long) info); + INIT_LIST_HEAD(&info->xmit_msgs); + INIT_LIST_HEAD(&info->hp_xmit_msgs); + INIT_LIST_HEAD(&info->msgs_to_deliver); + info->slave_addr = 0x20; /* default */ + + for (i = 0; i < SERIAL_NUM_STATS; i++) + atomic_set(&info->stats[i], 0); + + /* + * Decode str into name, options, codec, codec options. + */ + if (strlen(str) == 0) { + printk(KERN_ERR NPFX "Empty serial port name specified\n"); + free_info_memory(info); + return; + } + + info->line = name_line_from_str(str, info->name, sizeof(info->name)); + + /* First ',' points to serial port options */ + info->options = strchr(str, ','); + if (info->options) { + *(info->options++) = '\0'; + + /* Second ',' points to the codec name. */ + info->codec_name = strchr(info->options, ','); + } + + if (info->codec_name) { + *(info->codec_name++) = '\0'; + + /* Third ',' points to the codec options. */ + info->codec_options = strchr(info->codec_name, ','); + } + + if (info->codec_options) + *(info->codec_options++) = 0; + +#ifdef __sparc__ + if (!strcmp(info->name, "ttya")) { + strcpy(info->name, "ttyS"); + info->line = 0; + } + if (!strcmp(info->name, "ttyb")) { + strcpy(info->name, "ttyS"); + info->line = 1; + } +#endif + + info->direct.direct_data = info; + info->direct.handle_char = ipmi_serial_handle_char; + info->direct.push = ipmi_serial_push; + + mutex_lock(&list_lock); + /* Check for dups. */ + list_for_each_entry(cinfo, &info_list, link) { + if ((strcmp(cinfo->name, info->name) == 0) && + (cinfo->line == info->line)) { + printk(KERN_ERR NPFX "Duplicate port given: %s\n", + str); + free_info_memory(info); + goto out_unlock; + } + } + list_add(&info->link, &info_list); + + /* Try to match up this new interface with any registered codecs. */ + list_for_each_entry(codec, &codec_list, link) { + if (strcmp(codec->name, info->codec_name) == 0) { + setup_intf(info, codec); + break; + } + } + out_unlock: + mutex_unlock(&list_lock); +} + +static void ipmi_serial_remove_one(const char *str) +{ + struct ipmi_serial_info *info; + char *s; + int line; + char name[TTY_NAME_LEN]; + + line = name_line_from_str(str, name, sizeof(name)); + + s = strchr(str, ','); + + mutex_lock(&list_lock); + list_for_each_entry(info, &info_list, link) { + if ((strcmp(name, info->name) == 0) && (info->line == line)) { + ipmi_serial_cleanup_one(info); + list_del(&info->link); + free_info_memory(info); + goto out_unlock; + } + } + printk(KERN_ERR NPFX "Could not find port to remove: %s.\n", str); + out_unlock: + mutex_unlock(&list_lock); +} + +static int hotmod_handler(const char *istr, struct kernel_param *kp) +{ + char *str = kstrdup(istr, GFP_KERNEL); + char *s; + int rv = -EINVAL; + int len, i; + char *next, *curr; + + if (!str) + return -ENOMEM; + + /* Kill any trailing spaces, as we can get a "\n" from echo. */ + len = strlen(str); + i = len - 1; + while ((i >= 0) && isspace(str[i])) { + str[i] = '\0'; + i--; + } + + for (curr = str; curr; curr = next) { + next = strchr(curr, ':'); + if (next) { + *next = '\0'; + next++; + } + + s = strchr(curr, ','); + if (!s) { + printk(KERN_WARNING NPFX + "No hotmod operation given.\n"); + break; + } + *s = '\0'; + s++; + + if (strcmp(curr, "add") == 0) { + ipmi_serial_setup_one(s); + rv = len; + } else if (strcmp(curr, "remove") == 0) { + ipmi_serial_remove_one(s); + rv = len; + } else { + printk(KERN_WARNING NPFX + "Invalid hotmod operation given: '%s'.\n", + curr); + break; + } + } + kfree(str); + + return rv; +} + +static __init int init_ipmi_serial(void) +{ + int count = 0; + char *next, *curr, *str; + + printk(KERN_INFO "IPMI Serial System Interface driver\n"); + + if (setup_str[0] == '\0') { + if (unload_when_empty) { + printk(KERN_WARNING NPFX "no interfaces specified\n"); + return -ENODEV; + } + return 0; + } + + curr = setup_str; + while (curr) { + next = strchr(curr, ':'); + if (!next) { + str = kstrdup(curr, GFP_KERNEL); + } else { + /* Duplicate up to (but not including) the ':'. */ + str = kstrndup(curr, next - curr, GFP_KERNEL); + next++; + } + if (!str) { + printk(KERN_ERR NPFX + "could not allocate string setup: %s\n", + curr); + if (count == 0) + return -ENOMEM; + else + /* We configured one, so don't error. */ + return 0; + } else { + count++; + ipmi_serial_setup_one(str); + kfree(str); + } + curr = next; + } + + return 0; +} +/* + * We have to initialize after the serial core has initialized because + * we needs its sysfs entries initialized. So delay initialization to + * the end. + */ +late_initcall(init_ipmi_serial); + +static __exit void cleanup_ipmi_serial(void) +{ + struct ipmi_serial_info *info, *s; + + list_for_each_entry_safe(info, s, &info_list, link) { + list_del(&info->link); + free_info_memory(info); + } +} +module_exit(cleanup_ipmi_serial); + +static void ipmi_serial_cleanup_one(struct ipmi_serial_info *to_clean) +{ + int rv; + unsigned long flags; + ipmi_smi_t intf; + + if (!to_clean) + return; + + intf = to_clean->intf; + + if (intf) { + to_clean->stop_operation = 1; /* Don't start anything new. */ + + del_timer_sync(&to_clean->timer); + + /* + * Interrupts and timeouts are stopped, now flush out + * all the messages. + */ + spin_lock_irqsave(&to_clean->lock, flags); + while ((to_clean->state != SERIAL_EMPTY) + || to_clean->msg_delivery_in_progress) { + spin_unlock_irqrestore(&to_clean->lock, flags); + msleep(10); + timeout_handling(to_clean, 10000); + spin_lock_irqsave(&to_clean->lock, flags); + } + to_clean->intf = NULL; /* No more messages allowed. */ + spin_unlock_irqrestore(&to_clean->lock, flags); + + /* + * At this point we won't send any messages up, so we can + * unregister the SMI. + */ + rv = ipmi_unregister_smi(intf); + if (rv) { + printk(KERN_ERR PFX + "Unable to unregister device: errno=%d\n", + to_clean->name, to_clean->line, rv); + } + } + + if (to_clean->codec_data) { + to_clean->codec_data = NULL; + synchronize_sched(); + to_clean->codec->cleanup(to_clean->alloc_codec_data); + } + + if (to_clean->port) { + tasklet_kill(&to_clean->port->info->tlet); + to_clean->port->info->flags &= ~UIF_TASKLET_SETUP; + to_clean->port->info->direct = NULL; + to_clean->port->ops->shutdown(to_clean->port); + if (to_clean->port->info->xmit.buf == to_clean->uart_buffer) { + to_clean->port->info->xmit.buf = NULL; + to_clean->port->info->flags &= ~UIF_BOOT_ALLOCATED; + } + uart_put_direct_port(to_clean->port, 0); + } +} + +int ipmi_serial_codec_register(struct ipmi_serial_codec *codec) +{ + struct ipmi_serial_codec *c; + struct ipmi_serial_info *info; + + printk(KERN_INFO NPFX "Registering %s codec\n", codec->name); + + mutex_lock(&list_lock); + /* Check for dups. */ + list_for_each_entry(c, &codec_list, link) { + if (strcmp(c->name, codec->name) == 0) { + printk(KERN_WARNING NPFX + "Registering duplicate codec: %s\n", + codec->name); + mutex_unlock(&list_lock); + return -EBUSY; + } + } + list_add(&codec->link, &codec_list); + + list_for_each_entry(info, &info_list, link) { + if (strcmp(info->codec_name, codec->name) == 0) + setup_intf(info, codec); + } + mutex_unlock(&list_lock); + return 0; +} +EXPORT_SYMBOL(ipmi_serial_codec_register); + +void ipmi_serial_codec_unregister(struct ipmi_serial_codec *codec) +{ + struct ipmi_serial_info *info; + + /* + * Guaranteed to be no users on any devices on this codec, it + * can't unregister unless it's module is being unloaded and + * the module's refcount is zero. + */ + + printk(KERN_INFO NPFX "Unregistering %s codec\n", codec->name); + + mutex_lock(&list_lock); + list_del(&codec->link); + list_for_each_entry(info, &info_list, link) { + if (info->codec == codec) + /* Does not remove the entry from the list. */ + ipmi_serial_cleanup_one(info); + } + mutex_unlock(&list_lock); +} +EXPORT_SYMBOL(ipmi_serial_codec_unregister); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Corey Minyard "); +MODULE_DESCRIPTION("Support for IPMI over serial."); Index: linux-2.6.30/include/linux/ipmi_serial_sm.h =================================================================== --- /dev/null +++ linux-2.6.30/include/linux/ipmi_serial_sm.h @@ -0,0 +1,306 @@ +/* + * ipmi_serial_sm.h + * + * State machine interface for low-level IPMI serial interface driver + * state machines. This code is the interface between + * the ipmi_serial code and the supported codec(s) + * + * Author: MontaVista Software, Inc. + * dgriego@mvista.com + * cminyard@mvista.com + * source@mvista.com + * + * Copyright 2006 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 _IPMI_SERIAL_SM_H +#define _IPMI_SERIAL_SM_H + +#include +#include + +/* + * This is the interface between the IPMI serial code and the codecs + * themselves. + * + * The IPMI serial code handles the interface to the IPMI message + * handler, the queuing of messages to transmit, the configuration of + * the serial port, and dealing directly with the serial port. It also + * requests and handles interface flags. + * + * The lower layer is concerned with transmission of one message at a + * time, the handling of received bytes to assemble into full received + * messages. + * + * All messages are formatted in the same manner as a standard KCS + * command: + * send: [(netfn << 2) | lun] [command] [data1] ... + * recv: [(netfn << 2) | lun] [command] [completion code] [data1] ... + * + * Note that some commands may have a special meaning: + * IPMI_GET_MSG_FLAGS_CMD - If the BMC does not handle this and transfers + * this information via a different mechanism, the lower layer may + * return -ENOTSUP for a send of this command. + * IPMI_READ_EVENT_MSG_BUFFER_CMD - Used to report a received event. + * The lower layer may send these asynchronously to the upper layer + * if it has a different mechanism to receive these. + * IPMI_GET_MSG_CMD - Used to report a received command. + * The lower layer may send these asynchronously to the upper layer + * if it has a different mechanism to receive these. + * The lower layer may not asynchronously send anything else with + * the async function except what is allowed above. + * + * See the end of this file for expected call flows. + */ + +/* + * This is defined by the codecs themselves, it is an opaque + * data type for them to use. + */ +struct ipmi_serial_codec_data; + +/* + * Opaque data used in the serial interface that the state machines + * must supply to the serial interface in callbacks. + */ +struct ipmi_serial_info; + +/* + * Handlers for the IPMI serial state machine. + */ +struct ipmi_serial_codec { + struct module *owner; + char *name; + +#define IPMI_SERIAL_NEEDS_GET_FLAGS_POLLING 0x00000001 +#define IPMI_SERIAL_SUPPORTS_GET_FLAGS 0x00000002 +#define IPMI_SERIAL_SUPPORTS_EVENT_BUFFER 0x00000004 +#define IPMI_SERIAL_HAS_ATTN 0x00000008 + unsigned int (*capabilities)(struct ipmi_serial_codec_data *data); + + /* + * Set up the basic termios for the serial port for this + * interface type. The upper layer will call this when + * setting up the serial port. The upper layer will change + * some of these values based upon information from the user, + * primarily baud, number of bits/char, stop bits, and parity. + */ + int (*setup_termios)(struct ktermios *t); + + /* + * Initialize the data structure for the codec. The info must + * be supplied to callbacks. Note that this shouldn't send or + * receive anything, it should just initialize. The user may + * pass options in on the serial configuration command line, + * these will be passed to this function. + */ + int (*init)(struct ipmi_serial_codec_data *data, + struct ipmi_serial_info *info, + const char *options); + + /* + * Start processing. When this is done, + * ipmi_serial_ll_init_complete() must be called by the codec. + * This function may send and receive data. If this is NULL, + * the codec is assumed to already be fully operational after + * the init call. + */ + int (*start)(struct ipmi_serial_codec_data *data); + + /* + * Cleanup anything that needs to be cleaned up before the codec + * is freed. + */ + void (*cleanup)(struct ipmi_serial_codec_data *data); + + /* + * Return the size of the ipmi_serial_codec_data structure in + * bytes. The upper layer will use this to allocate the data + * structure for the codec. + */ + int (*size)(void); + + /* + * Send a message. Only one message send at a time is + * allowed. When the send is complete the lower layer must + * call ipmi_serial_ll_send_complete(). May return an error + * code if there is already a message in progress. The response + * that comes back will have the given seq value in the receive + * call. + */ + int (*send_msg)(struct ipmi_serial_codec_data *data, + const unsigned char *msg, unsigned int msg_len, + unsigned int seq); + + /* + * Handle a single received character from the serial port. + * Note that this is called without the serial code claiming a + * lock, but will be single-threaded. + */ + void (*handle_char)(struct ipmi_serial_codec_data *data, + unsigned char ch); + + /* + * Called when the transmitter has space to take more + * characters. If ipmi_serial_xmit_data returns less queued + * characters than asked to transmit, the lower layer should + * wait for this call and then send more data. + */ + void (*tx_ready)(struct ipmi_serial_codec_data *data); + + + /* + * If not-NULL, this will be called periodically. The time + * since the last call will be passed, time is in microseconds. + */ + void (*timer_tick)(struct ipmi_serial_codec_data *data, + unsigned int time_since_last); + + /* + * Once the device id is fetched by the main serial code, this + * will be called if it is not NULL. This allows the codec to + * enable certain hacks for certain machines. + */ + void (*check_dev_id)(struct ipmi_serial_codec_data *data, + struct ipmi_device_id *dev_id); + + /* + * Print the current options in use by the codec. If there are + * options printed, the codec should prepend a ','. + */ + int (*add_options)(struct ipmi_serial_codec_data *data, char *out); + + /* + * Used by the serial interface, the codec shouldn't touch + * anything below. + */ + struct list_head link; +}; + +/* + * Called by the lower layer when initialization is complete. Pass + * in zero as the slave_addr if you are unable to compute it. + */ +extern void ipmi_serial_ll_init_complete(struct ipmi_serial_info *info, + unsigned char slave_addr, + int err); + +/* + * Called by the lower layer when it needs to send some data. Returns the + * actual number of bytes queued for transmit. + */ +extern unsigned int ipmi_serial_ll_xmit(struct ipmi_serial_info *info, + const unsigned char *data, + unsigned int len); + +/* + * Called by the lower layer when it detects that message flags are + * available. + */ +extern void ipmi_serial_ll_attn(struct ipmi_serial_info *info); + +/* + * Called by the lower layer when a full message response is received. + * The seq will be set to the value supplied to the state machine + * at send time. + */ +extern void ipmi_serial_ll_recv(struct ipmi_serial_info *info, + const unsigned char *data, + unsigned int len, + unsigned int seq); + +/* + * Called when the lower layer receives an async message (assuming it + * has a different way to do this than normal flag handling) These may + * be received commands or events based upon the rules specified + * above. + */ +extern void ipmi_serial_ll_async(struct ipmi_serial_info *info, + const unsigned char *data, + unsigned int len); + +/* + * Used to report various low-level errors. + */ +extern void ipmi_serial_ll_protocol_violation(struct ipmi_serial_info *info); +extern void ipmi_serial_ll_checksum_error(struct ipmi_serial_info *info); + +/* + * These functions allow codecs to register and unregister with the serial + * system interface layer. + */ +extern int ipmi_serial_codec_register(struct ipmi_serial_codec *codec); +extern void ipmi_serial_codec_unregister(struct ipmi_serial_codec *codec); + +/* + * Expected flow how how things happen on this interface: + * + * Upper Layer Lower Layer + * | | + * |----------------init----------------------->| + * | | + * |<---------------xmit------------------------| + * | | + * |-----------handle_char--------------------->| + * |-----------handle_char--------------------->| + * |-----------handle_char--------------------->| + * | | + * |<------------init_complete------------------| + * | | + * |--------------send_msg--------------------->| + * | | + * |<-----------xmit (return less)--------------| + * | | + * |------------tx_ready----------------------->| + * | | + * |<---------------xmit------------------------| + * | | + * |<-------------send_complete-----------------| + * | | + * |-----------handle_char--------------------->| + * |-----------handle_char--------------------->| + * |-----------handle_char--------------------->| + * | | + * |<---------------recv------------------------| + * | | + * |-----------handle_char--------------------->| + * | | + * |<---------------attn------------------------| + * | | + * |--------------send_msg(GET_FLAGS)---------->| + * | | + * |<---------------xmit------------------------| + * | | + * |<-------------send_complete-----------------| + * | | + * |-----------handle_char--------------------->| + * |-----------handle_char--------------------->| + * |-----------handle_char--------------------->| + * | | + * |<---------------recv------------------------| + * | | + * |--------------cleanup---------------------->| + * | | + */ +#endif /* _IPMI_SERIAL_SM_H */ Index: linux-2.6.30/include/linux/ipmi_msgdefs.h =================================================================== --- linux-2.6.30.orig/include/linux/ipmi_msgdefs.h +++ linux-2.6.30/include/linux/ipmi_msgdefs.h @@ -71,6 +71,9 @@ #define IPMI_NETFN_FIRMWARE_REQUEST 0x08 #define IPMI_NETFN_FIRMWARE_RESPONSE 0x09 +#define IPMI_NETFN_OEM_REQUEST 0x2e +#define IPMI_NETFN_OEM_RESPONSE 0x2f + /* The default slave address */ #define IPMI_BMC_SLAVE_ADDR 0x20 Index: linux-2.6.30/drivers/char/ipmi/Kconfig =================================================================== --- linux-2.6.30.orig/drivers/char/ipmi/Kconfig +++ linux-2.6.30/drivers/char/ipmi/Kconfig @@ -61,6 +61,56 @@ config IPMI_SMB enabled along with this, so the I2C driver knows to run to completion during sending a panic event. +config IPMI_SERIAL + tristate 'IPMI Serial Interface' + depends on SERIAL_CORE + help + System Interface handler to support serial interface + to the BMC. Unless your device has a serial interface to the BMC + you should probably say "n" here. + +config SERIAL_IPMI_SETUP + string 'Default serial IPMI interface setup' + depends on IPMI_SERIAL + default "" + help + Set the default Serial interface parameters in the format: + port,,, + * + * Copyright 2006 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 + +#define PFX "ipmi_serial_terminal_mode: " + +struct tm_options { +#define PIGEONPOINT_OPTION "pp" + int pigeonpoint; + int pigeonpoint_forced; /* Set by command line. */ +#define PIGEONPOINT_INVIANA_OPTION "ppInvIANA" + int pigeonpoint_iana_bad; + int pigeonpoint_iana_bad_forced; /* Set by command line. */ +#define ATTN_OPTION "attn" + int attn; + int attn_forced; /* Set by command line. */ + unsigned char attn_char; +}; + +/* + * Three bytes for every character plus the bridge/seqno field and add + * the [, ], and newline and perhaps an extra space + */ +#define TM_MAX_CHARS_SIZE (((IPMI_MAX_MSG_LENGTH + 1) * 3) + 4) + +/* + * Give the remote end this much time (100ms) to send a response + * before we give up and retry the given number of times. + */ +#define TM_HANDSHAKE_TIME 100000 +#define TM_HANDSHAKE_RETRIES 5 + + +struct ipmi_serial_codec_data { + struct ipmi_serial_info *info; + + unsigned char xmit_chars[TM_MAX_CHARS_SIZE]; + unsigned int xmit_chars_len; + unsigned int xmit_chars_pos; + + unsigned char recv_chars[TM_MAX_CHARS_SIZE]; + unsigned int recv_chars_len; + int recv_chars_too_many; + + unsigned char seqno; + unsigned int seqnum_table[0x40]; + + unsigned char xmit_msg[IPMI_MAX_MSG_LENGTH]; + unsigned int xmit_msg_len; + unsigned int xmit_msg_seq; + unsigned char recv_msg[IPMI_MAX_MSG_LENGTH]; + unsigned int recv_msg_len; + + char echo_on; + char pp_iana_retried; + + spinlock_t lock; + + /* + * Handle receipt of a message. This is a function var so it + * can be replaced at init time and when special handling is + * required for messages. + */ + void (*recv_msg_handler)(struct ipmi_serial_codec_data *data, + const unsigned char *msg, + unsigned int len, + unsigned int seq); + + struct ipmi_device_id id; + struct tm_options options; + + /* + * Used to time initialization messages. + */ + int handshake_time; + int handshake_retries_left; + int handshake_done; +}; + +#define MOTOROLA_MFG_ID 0x0000a1 +#define MOTOROLA_ATCA_F101_PROD_ID 0x0051 +#define MOTOROLA_ATCA_6101_PROD_ID 0x0053 +/* Table of BMCs that support the PigeonPoint echo handling. */ +static struct { unsigned int mfg_id, prod_id; } pp_bmcs[] = +{ + { MOTOROLA_MFG_ID, MOTOROLA_ATCA_F101_PROD_ID }, + { MOTOROLA_MFG_ID, MOTOROLA_ATCA_6101_PROD_ID }, + { 0, 0 } +}; + +static struct { unsigned int mfg_id, prod_id, val; } attn_bmcs[] = +{ + { MOTOROLA_MFG_ID, MOTOROLA_ATCA_F101_PROD_ID, 0x07 }, + { MOTOROLA_MFG_ID, MOTOROLA_ATCA_6101_PROD_ID, 0x07 }, + { 0, 0 } +}; + +static void check_devid_options(struct ipmi_serial_codec_data *data) +{ + int i; + + if (!data->options.pigeonpoint_forced) { + for (i = 0; pp_bmcs[i].mfg_id != 0; i++) { + if ((data->id.manufacturer_id == pp_bmcs[i].mfg_id) + && (data->id.product_id == pp_bmcs[i].prod_id)) { + data->options.pigeonpoint = 1; + break; + } + } + } + if (!data->options.attn_forced) { + for (i = 0; attn_bmcs[i].mfg_id != 0; i++) { + if ((data->id.manufacturer_id == attn_bmcs[i].mfg_id) + && (data->id.product_id == attn_bmcs[i].prod_id)) { + data->options.attn = 1; + data->options.attn_char = attn_bmcs[i].val; + break; + } + } + } +} + +static int check_options(struct ipmi_serial_codec_data *data, + const char *options) +{ + const char *s; + char *optval; + int optval_len; + char *next; + int len; + char *end; + + for (s = options; s; s = next) { + next = strchr(s, '+'); + if (next) { + len = next - s; + next++; + } else + len = strlen(s); + + optval = strchr(s, '='); + if (next && optval >= next) + optval = NULL; + if (optval) { + len = optval - s; + optval++; + if (next) { + optval_len = next - optval; + next++; + } else + optval_len = strlen(optval); + } else + optval_len = 0; + end = NULL; + if (strncmp(PIGEONPOINT_OPTION, s, len) == 0) { + data->options.pigeonpoint = 1; + data->options.pigeonpoint_forced = 1; + } else if (strncmp("no" PIGEONPOINT_OPTION, s, len) == 0) { + data->options.pigeonpoint = 0; + data->options.pigeonpoint_forced = 1; + } else if (strncmp(PIGEONPOINT_INVIANA_OPTION, s, len) == 0) { + data->options.pigeonpoint_iana_bad = 1; + data->options.pigeonpoint_iana_bad_forced = 1; + } else if (strncmp("no" PIGEONPOINT_INVIANA_OPTION, s, len) + == 0) { + data->options.pigeonpoint_iana_bad = 0; + data->options.pigeonpoint_iana_bad_forced = 1; + } else if (strncmp(ATTN_OPTION, s, len) == 0) { + data->options.attn = 1; + data->options.attn_forced = 1; + if (optval) + data->options.attn_char + = simple_strtoul(optval, &end, 0); + else + /* default to bell */ + data->options.attn_char = 0x07; + } else if (strncmp("no" ATTN_OPTION, s, len) == 0) { + data->options.attn = 0; + data->options.attn_forced = 1; + } else { + printk(KERN_WARNING PFX "Unknown options: %s\n", + options); + } + + if (optval && !end) { + printk(KERN_WARNING PFX "No value needed at: %s\n", s); + return -EINVAL; + } + if (optval && end != (optval + optval_len)) { + printk(KERN_WARNING PFX "Invalid value for: %s\n", s); + return -EINVAL; + } + } + + return 0; +} + +static int tm_add_options(struct ipmi_serial_codec_data *data, char *page) +{ + char pfx = ','; + char *out = page; + + if (data->options.pigeonpoint) { + out += sprintf(out, "%cpp", pfx); + pfx = '+'; + } + if (data->options.attn) { + out += sprintf(out, "%cattn", pfx); + if (data->options.attn_char != 0x07) + out += sprintf(out, "=0x%x", data->options.attn_char); + pfx = '+'; + } + + return out - page; +} + +static unsigned char hex2char[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' +}; + +static void format_msg(struct ipmi_serial_codec_data *data, + const unsigned char *msg, unsigned int msg_len, + unsigned int seq) +{ + int i; + int len; + unsigned char *c = data->xmit_chars; + unsigned char seqno; + + len = 0; + c[len] = '['; + len++; + + c[len] = hex2char[msg[0] >> 4]; + len++; + c[len] = hex2char[msg[0] & 0xf]; + len++; + + /* + * Insert the sequence number and bridge bits. Bridge bits + * are always zero. + */ + seqno = data->seqno; + data->seqno = (data->seqno + 1) & 0x3f; + data->seqnum_table[seqno] = seq; + seqno <<= 2; + c[len] = hex2char[seqno >> 4]; + len++; + c[len] = hex2char[seqno & 0xf]; + len++; + + /* Now the rest of the message. */ + for (i = 1; ; ) { + c[len] = hex2char[msg[i] >> 4]; + len++; + c[len] = hex2char[msg[i] & 0xf]; + len++; + i++; + if (i == msg_len) + break; + c[len] = ' '; + len++; + } + c[len] = ']'; + len++; + c[len] = 0x0a; + len++; + c[len] = 0x0d; /* We definitely have room for this byte. */ + len++; + + data->xmit_chars_pos = 0; + data->xmit_chars_len = len; +} + +static int fromhex(unsigned char c) +{ + if (isdigit(c)) + return c - '0'; + else if (isxdigit(c)) + return tolower(c) - 'a' + 10; + else + return -EINVAL; +} + +/* + * Called when the ']' is seen, the leading '[' is removed, too. We + * get this with a leading space and no more than one space between + * items. + */ +static int unformat_msg(struct ipmi_serial_codec_data *data) +{ + unsigned char *r = data->recv_chars; + unsigned char *o = data->recv_msg; + unsigned int len = data->recv_chars_len; + unsigned int p = 0; + unsigned int i = 0; + int rv; + + if (isspace(r[p])) + p++; + while (p < len) { + if (i >= sizeof(data->recv_msg)) + return -EFBIG; + if (p >= len) + return -EINVAL; + rv = fromhex(r[p]); + if (rv < 0) + return rv; + o[i] = rv << 4; + p++; + if (p >= len) + return -EINVAL; + rv = fromhex(r[p]); + if (rv < 0) + return rv; + o[i] |= rv; + p++; + i++; + if (isspace(r[p])) + p++; + } + data->recv_msg_len = i; + return 0; +} + +static void normal_recv(struct ipmi_serial_codec_data *data, + const unsigned char *msg, + unsigned int len, + unsigned int seq) +{ + if (((msg[0] >> 2) & 1) == 0) + /* + * If the bottom bit of the message is zero, then it + * is a command and most likely an echo. + */ + return; + + if (len < 3) { + /* Responses must be at least 3 bytes after processing. */ + ipmi_serial_ll_protocol_violation(data->info); + return; + } + + ipmi_serial_ll_recv(data->info, msg, len, seq); +} + + +static void handle_recv_msg(struct ipmi_serial_codec_data *data) +{ + unsigned int seq; + unsigned char *m = data->recv_msg; + unsigned int len = data->recv_msg_len; + int i; + + if (len < 3) { + /* Messages must be at least 3 bytes */ + ipmi_serial_ll_protocol_violation(data->info); + return; + } + + seq = m[1] >> 2; + + /* Now remove the seq# */ + for (i = 1; i < len-1; i++) + m[i] = m[i+1]; + len--; + + data->recv_msg_handler(data, m, len, data->seqnum_table[seq]); +} + +static void try_to_send_data(struct ipmi_serial_codec_data *data) +{ + unsigned char *c; + unsigned int left; + unsigned int sent; + + if (!data->xmit_chars_len) + return; + + restart: + c = data->xmit_chars + data->xmit_chars_pos; + left = data->xmit_chars_len - data->xmit_chars_pos; + sent = ipmi_serial_ll_xmit(data->info, c, left); + if (sent == left) { + /* We are done with this message. */ + if (data->xmit_msg_len) { + /* Send the next message we have waiting. */ + format_msg(data, data->xmit_msg, data->xmit_msg_len, + data->xmit_msg_seq); + data->xmit_msg_len = 0; + goto restart; + } else { + /* Nothing to do. */ + data->xmit_chars_len = 0; + } + } else { + data->xmit_chars_pos += sent; + } +} + +static void finish_init(struct ipmi_serial_codec_data *data, int err) +{ + ipmi_serial_ll_init_complete(data->info, 0, err); +} + +/* + * Handling for pigeonpoint-specific codec, we have special ways to + * turn on/off echo. + */ +#define PP_OEM_CHARS 0x0a, 0x40, 0x00 +#define PP_GET_SERIAL_INTF_CMD 0x01 +#define PP_SET_SERIAL_INTF_CMD 0x02 +#define PP_GET_SER_INTF_SIZE 6 +static unsigned char pp_get_ser_intf_cmd[PP_GET_SER_INTF_SIZE] + = {IPMI_NETFN_OEM_REQUEST << 2, PP_GET_SERIAL_INTF_CMD, + PP_OEM_CHARS, 0x01}; +#define PP_SET_SER_INTF_SIZE 7 +static unsigned char pp_set_ser_intf_cmd[PP_SET_SER_INTF_SIZE] + = {IPMI_NETFN_OEM_REQUEST << 2, PP_SET_SERIAL_INTF_CMD, + PP_OEM_CHARS, 0x01, 0x00}; + +static void handle_pp_set_ser(struct ipmi_serial_codec_data *data, + const unsigned char *msg, + unsigned int len, + unsigned int seq) +{ + unsigned long flags; + + if (((msg[0] >> 2) & 1) == 0) + /* Got the echo */ + return; + + if (((msg[0] >> 2) != IPMI_NETFN_OEM_RESPONSE) + || (msg[1] != PP_SET_SERIAL_INTF_CMD)) + /* Not what we were expecting */ + return; + + spin_lock_irqsave(&data->lock, flags); + if (data->handshake_done) { + spin_unlock_irqrestore(&data->lock, flags); + return; + } + + if (msg[2] != 0) { + data->handshake_done = 1; + spin_unlock_irqrestore(&data->lock, flags); + printk(KERN_WARNING PFX + "Error setting pigeonpoint serial parms: 0x%x\n", + msg[2]); + finish_init(data, -EINVAL); + } + + data->handshake_done = 1; + data->recv_msg_handler = normal_recv; + spin_unlock_irqrestore(&data->lock, flags); + finish_init(data, 0); +} + +static void pp_start_disable_echo(struct ipmi_serial_codec_data *data); + +static void handle_pp_get_ser(struct ipmi_serial_codec_data *data, + const unsigned char *msg, + unsigned int len, + unsigned int seq) +{ + unsigned long flags; + + if (((msg[0] >> 2) & 1) == 0) + /* Got the echo */ + return; + + if (((msg[0] >> 2) != IPMI_NETFN_OEM_RESPONSE) + || (msg[1] != PP_GET_SERIAL_INTF_CMD)) + /* Not what we were expecting */ + return; + + spin_lock_irqsave(&data->lock, flags); + if (data->handshake_done) { + spin_unlock_irqrestore(&data->lock, flags); + return; + } + + if (msg[2] != 0) { + if (!data->options.pigeonpoint_iana_bad_forced && + !data->pp_iana_retried) { + /* Try swapping the IANA and see if that helps. */ + data->pp_iana_retried = 1; + data->options.pigeonpoint_iana_bad + = !data->options.pigeonpoint_iana_bad; + pp_start_disable_echo(data); + spin_unlock_irqrestore(&data->lock, flags); + return; + } + data->handshake_done = 1; + spin_unlock_irqrestore(&data->lock, flags); + printk(KERN_WARNING PFX + "Error getting pigeonpoint serial parms: 0x%x\n", + msg[2]); + finish_init(data, -EINVAL); + return; + } + + if (len < 6) { + data->handshake_done = 1; + spin_unlock_irqrestore(&data->lock, flags); + printk(KERN_WARNING PFX + "Pigeonpoint serial parms too short: %d\n", len); + finish_init(data, -EINVAL); + return; + } + + memcpy(data->xmit_msg, pp_set_ser_intf_cmd, + sizeof(pp_set_ser_intf_cmd)); + if (data->options.pigeonpoint_iana_bad) { + data->xmit_msg[2] = 0x00; + data->xmit_msg[4] = 0x0a; + } + data->xmit_msg[6] &= 0x7f; /* And off echo bit */ + + data->recv_msg_handler = handle_pp_set_ser; + data->handshake_time = TM_HANDSHAKE_TIME; + data->handshake_retries_left = TM_HANDSHAKE_RETRIES; + format_msg(data, data->xmit_msg, sizeof(pp_set_ser_intf_cmd), 0); + data->xmit_msg_len = 0; + try_to_send_data(data); + + spin_unlock_irqrestore(&data->lock, flags); +} + + +static void pp_start_disable_echo(struct ipmi_serial_codec_data *data) +{ + memcpy(data->xmit_msg, pp_get_ser_intf_cmd, + sizeof(pp_get_ser_intf_cmd)); + if (data->options.pigeonpoint_iana_bad) { + data->xmit_msg[2] = 0x00; + data->xmit_msg[4] = 0x0a; + } + + data->recv_msg_handler = handle_pp_get_ser; + data->handshake_time = TM_HANDSHAKE_TIME; + data->handshake_retries_left = TM_HANDSHAKE_RETRIES; + format_msg(data, data->xmit_msg, sizeof(pp_get_ser_intf_cmd), 0); + data->xmit_msg_len = 0; + try_to_send_data(data); +} + + +static int tm_setup_termios(struct ktermios *t) +{ + /* Nothing to do, the default is fine. */ + return 0; +} + +static unsigned char devid_msg[] = { IPMI_NETFN_APP_REQUEST << 2, + IPMI_GET_DEVICE_ID_CMD }; + +static void handle_init_devid(struct ipmi_serial_codec_data *data, + const unsigned char *msg, + unsigned int len, + unsigned int seq) +{ + int rv; + unsigned long flags; + + if ((len == sizeof(devid_msg)) + && (memcmp(msg, devid_msg, len) == 0)) { + data->echo_on = 1; + return; + } + + spin_lock_irqsave(&data->lock, flags); + if (data->handshake_done) { + spin_unlock_irqrestore(&data->lock, flags); + return; + } + + rv = ipmi_demangle_device_id(msg, len, &data->id); + if (rv) { + data->handshake_done = 1; + spin_unlock_irqrestore(&data->lock, flags); + printk(KERN_WARNING PFX "invalid device id: %d\n", rv); + finish_init(data, rv); + return; + } + + check_devid_options(data); + + if (data->options.pigeonpoint && data->echo_on) { + pp_start_disable_echo(data); + spin_unlock_irqrestore(&data->lock, flags); + } else { + data->handshake_done = 1; + data->recv_msg_handler = normal_recv; + spin_unlock_irqrestore(&data->lock, flags); + finish_init(data, 0); + } +} + +static int tm_init(struct ipmi_serial_codec_data *data, + struct ipmi_serial_info *info, + const char *options) +{ + memset(data, 0, sizeof(*data)); + spin_lock_init(&data->lock); + data->info = info; + return check_options(data, options); +} + +static int tm_start(struct ipmi_serial_codec_data *data) +{ + unsigned long flags; + + /* + * Send a "get device id" to test basic function and get + * useful info. + */ + spin_lock_irqsave(&data->lock, flags); + data->recv_msg_handler = handle_init_devid; + data->handshake_time = TM_HANDSHAKE_TIME; + data->handshake_retries_left = TM_HANDSHAKE_RETRIES; + format_msg(data, devid_msg, sizeof(devid_msg), 0); + data->xmit_msg_len = 0; + try_to_send_data(data); + spin_unlock_irqrestore(&data->lock, flags); + return 0; +} + +static void tm_cleanup(struct ipmi_serial_codec_data *data) +{ + /* Nothing to do. */ +} + +static int tm_size(void) +{ + return sizeof(struct ipmi_serial_codec_data); +} + +static void tm_handle_char(struct ipmi_serial_codec_data *data, + unsigned char ch) +{ + unsigned int len = data->recv_chars_len; + unsigned char *r; + int rv; + + if (data->options.attn && (ch == data->options.attn_char)) { + ipmi_serial_ll_attn(data->info); + return; + } + + if (ch == '[') { + /* + * Start of a command. Note that if a command is + * already in progress (len != 0) we abort it. + */ + if (len != 0) + ipmi_serial_ll_protocol_violation(data->info); + + /* Convert the leading '[' to a space, that's innocuous. */ + data->recv_chars[0] = ' '; + data->recv_chars_len = 1; + data->recv_chars_too_many = 0; + return; + } + + if (len == 0) + /* Ignore everything outside [ ]. */ + return; + + if (ch == ']') { + /* End of command, handle it. */ + if (data->recv_chars_too_many) { + /* Input data overrun. */ + ipmi_serial_ll_protocol_violation(data->info); + data->recv_chars_too_many = 0; + data->recv_chars_len = 0; + return; + } + rv = unformat_msg(data); + data->recv_chars_len = 0; + if (rv) { + /* Bad input data. */ + ipmi_serial_ll_protocol_violation(data->info); + return; + } + handle_recv_msg(data); + return; + } + + if (data->recv_chars_too_many) + return; + + r = data->recv_chars; + + if (len >= sizeof(data->recv_chars)) { + data->recv_chars_too_many = 1; + } else if (isspace(r[len-1]) && isspace(ch)) { + /* Ignore multiple spaces together. */ + } else { + r[len] = ch; + data->recv_chars_len++; + } +} + +static int tm_send_msg(struct ipmi_serial_codec_data *data, + const unsigned char *msg, unsigned int msg_len, + unsigned int seq) +{ + unsigned long flags; + int rv = 0; + + if (msg_len > IPMI_MAX_MSG_LENGTH) + return -EFBIG; + if (msg_len < 2) + return -EINVAL; + + spin_lock_irqsave(&data->lock, flags); + if (data->xmit_msg_len) { + /* Something is still waiting to be sent. */ + rv = -EBUSY; + goto out_unlock; + } + + if (data->xmit_chars_len == 0) { + /* Transmit queue is empty, just format it now to go. */ + format_msg(data, msg, msg_len, seq); + try_to_send_data(data); + } else { + /* + * Get it ready to be sent later when the transmit + * queue empties. + */ + memcpy(data->xmit_msg, msg, msg_len); + data->xmit_msg_len = msg_len; + data->xmit_msg_seq = seq; + } + + out_unlock: + spin_unlock_irqrestore(&data->lock, flags); + return rv; +} + +static void tm_tx_ready(struct ipmi_serial_codec_data *data) +{ + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + try_to_send_data(data); + spin_unlock_irqrestore(&data->lock, flags); +} + +static unsigned int tm_capabilities(struct ipmi_serial_codec_data *data) +{ + unsigned int c = (IPMI_SERIAL_SUPPORTS_GET_FLAGS + | IPMI_SERIAL_SUPPORTS_EVENT_BUFFER); + if (data->options.attn) + c |= IPMI_SERIAL_HAS_ATTN; + else + c |= IPMI_SERIAL_NEEDS_GET_FLAGS_POLLING; + return c; +} + +static void tm_timer_tick(struct ipmi_serial_codec_data *data, + unsigned int time_since_last) +{ + unsigned long flags; + + /* Check quickly first to avoid grabbing the lock normally. */ + if (likely(data->handshake_done)) + return; + + spin_lock_irqsave(&data->lock, flags); + /* Recheck to avoid races */ + if (data->handshake_done) + goto out_unlock; + + data->handshake_time -= time_since_last; + if (time_since_last <= 0) { + data->handshake_retries_left--; + if (data->handshake_retries_left <= 0) { + data->handshake_done = 1; + spin_unlock_irqrestore(&data->lock, flags); + finish_init(data, -ETIMEDOUT); + goto out; + } + + /* Resend the appropriate message */ + data->handshake_time = TM_HANDSHAKE_TIME; + if (data->recv_msg_handler == handle_init_devid) { + format_msg(data, devid_msg, sizeof(devid_msg), 0); + data->xmit_msg_len = 0; + try_to_send_data(data); + } else if (data->recv_msg_handler == handle_pp_get_ser) { + format_msg(data, data->xmit_msg, + sizeof(pp_get_ser_intf_cmd), 0); + data->xmit_msg_len = 0; + try_to_send_data(data); + } else if (data->recv_msg_handler == handle_pp_set_ser) { + data->xmit_msg_len = sizeof(pp_set_ser_intf_cmd); + format_msg(data, data->xmit_msg, + data->xmit_msg_len, 0); + data->xmit_msg_len = 0; + try_to_send_data(data); + } + } + out_unlock: + spin_unlock_irqrestore(&data->lock, flags); + out: + return; +} + +static struct ipmi_serial_codec tm_codec = { + .owner = THIS_MODULE, + .name = "TerminalMode", + + .capabilities = tm_capabilities, + .setup_termios = tm_setup_termios, + .init = tm_init, + .start = tm_start, + .cleanup = tm_cleanup, + .size = tm_size, + .send_msg = tm_send_msg, + .handle_char = tm_handle_char, + .tx_ready = tm_tx_ready, + .add_options = tm_add_options, + .timer_tick = tm_timer_tick +}; + +static __init int +init_ipmi_serial_tm_codec(void) +{ + return ipmi_serial_codec_register(&tm_codec); +} + +module_init(init_ipmi_serial_tm_codec); + +static __exit void +exit_ipmi_serial_tm_codec(void) +{ + ipmi_serial_codec_unregister(&tm_codec); +} +module_exit(exit_ipmi_serial_tm_codec); +MODULE_LICENSE("GPL"); Index: linux-2.6.30/drivers/char/ipmi/ipmi_serial_direct.c =================================================================== --- /dev/null +++ linux-2.6.30/drivers/char/ipmi/ipmi_serial_direct.c @@ -0,0 +1,643 @@ +/* + * ipmi_serial_direct.c + * Serial interface encoder and decoder routines for IPMI direct + * serial interfaces. + * + * Author: MontaVista Software, Inc. + * source@mvista.com + * Corey Minyard + * + * Copyright 2006 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 + +#define PFX "ipmi_serial_direct: " + +struct tm_options { +#define ATTN_OPTION "attn" + int attn; + int attn_forced; /* Set by command line. */ + +#define EATTN_OPTION "eattn" + int eattn; + int eattn_forced; /* Set by command line. */ + unsigned char eattn_char; + +#define RQADDR_OPTION "rqa" + unsigned char rqaddr; +#define RSADDR_OPTION "rsa" + unsigned char rsaddr; +}; + +#define DEFAULT_RSADDR 0x20 +#define DEFAULT_RQADDR 0x61 + +/* + * Give the remote end this much time (100ms) to send a handshake + * character before we give up. + */ +#define DM_HANDSHAKE_TIME 100000 + +/* + * We have 4 bytes more for the header and then the ending checksum + * above the KCS header size we get/send to the upper layer. + */ +#define DM_HEADER_OVERHEAD_BYTES 5 + +/* + * We have start, stop, 4 bytes more for the header beyond the header + * we get, and the checksum. The start and stop do not need escaping, + * thus do not need doubling, but the rest do. So we have: + * 2 + (5 * 2) = 12 + */ +#define DM_OVERHEAD_BYTES (2 + (DM_HEADER_OVERHEAD_BYTES * 2)) + +struct ipmi_serial_codec_data { + struct ipmi_serial_info *info; + + unsigned char xmit_chars[IPMI_MAX_MSG_LENGTH * 2 + DM_OVERHEAD_BYTES]; + unsigned int xmit_chars_len; + unsigned int xmit_chars_pos; + + unsigned char seqno; + unsigned int seqnum_table[0x40]; + + unsigned char xmit_msg[IPMI_MAX_MSG_LENGTH + DM_HEADER_OVERHEAD_BYTES]; + unsigned int xmit_msg_len; + + unsigned char recv_msg[IPMI_MAX_MSG_LENGTH + DM_HEADER_OVERHEAD_BYTES]; + unsigned int recv_msg_len; + int in_recv_msg; + int in_escape; + int recv_msg_too_many; + + spinlock_t lock; + + /* + * Used to time receiving a handshake. We give the remote end + * so much time to send the handshake before we give up and + * just send. + */ + int handshake_time; + + struct tm_options options; +}; + +#define SUN_MFG_ID 0x00002a +#define SUN_CP3020_PROD_ID 0x0bcc + +/* + * BMCs that support using the ASCII escape char (0x1b) as attention. + */ +static struct { unsigned int mfg_id, prod_id; } attn_bmcs[] = +{ + { 0, 0 } +}; + +/* + * BMCs that use a special escape sequence (0xaa ) as attention. + */ +static struct { unsigned int mfg_id, prod_id, val; } eattn_bmcs[] = +{ + { SUN_MFG_ID, SUN_CP3020_PROD_ID, 0x47 }, + { 0, 0 } +}; + +static void check_devid_options(struct ipmi_serial_codec_data *data, + struct ipmi_device_id *id) +{ + int i; + + if (!data->options.attn_forced) { + for (i = 0; attn_bmcs[i].mfg_id != 0; i++) { + if ((id->manufacturer_id == attn_bmcs[i].mfg_id) + && (id->product_id == attn_bmcs[i].prod_id)) { + data->options.attn = 1; + break; + } + } + } + if (!data->options.eattn_forced) { + for (i = 0; eattn_bmcs[i].mfg_id != 0; i++) { + if ((id->manufacturer_id == eattn_bmcs[i].mfg_id) + && (id->product_id == eattn_bmcs[i].prod_id)) { + data->options.eattn = 1; + data->options.eattn_char = eattn_bmcs[i].val; + break; + } + } + } +} + +static int check_options(struct ipmi_serial_codec_data *data, + const char *options) +{ + const char *s; + char *optval; + int optval_len; + char *next; + int len; + char *end; + + for (s = options; s; s = next) { + next = strchr(s, '+'); + if (next) { + len = next - s; + next++; + } else + len = strlen(s); + + optval = strchr(s, '='); + if (next && optval >= next) + optval = NULL; + if (optval) { + len = optval - s; + optval++; + if (next) { + optval_len = next - optval; + next++; + } else + optval_len = strlen(optval); + } else + optval_len = 0; + end = NULL; + if (strncmp(ATTN_OPTION, s, len) == 0) { + data->options.attn = 1; + data->options.attn_forced = 1; + } else if (strncmp("no" ATTN_OPTION, s, len) == 0) { + data->options.attn = 0; + data->options.attn_forced = 1; + } else if (strncmp(EATTN_OPTION, s, len) == 0) { + data->options.eattn = 1; + data->options.eattn_forced = 1; + if (optval) + data->options.eattn_char + = simple_strtoul(optval, &end, 0); + else + data->options.eattn_char = 0x47; + } else if (strncmp("no" EATTN_OPTION, s, len) == 0) { + data->options.attn = 0; + data->options.attn_forced = 1; + } else if (strncmp(RQADDR_OPTION, s, len) == 0) { + if (optval) + data->options.rqaddr + = simple_strtoul(optval, &end, 0); + else { + printk(KERN_WARNING PFX + "rqa option given without value\n"); + return -EINVAL; + } + } else if (strncmp(RSADDR_OPTION, s, len) == 0) { + if (optval) + data->options.rsaddr + = simple_strtoul(optval, &end, 0); + else { + printk(KERN_WARNING PFX + "rsa option given without value\n"); + return -EINVAL; + } + } else { + printk(KERN_WARNING PFX "Unknown options: %s\n", + options); + return -EINVAL; + } + + if (optval && !end) { + printk(KERN_WARNING PFX "No value needed at: %s\n", s); + return -EINVAL; + } + if (optval && end != (optval + optval_len)) { + printk(KERN_WARNING PFX "Invalid value for: %s\n", s); + return -EINVAL; + } + } + + return 0; +} + +static int sd_add_options(struct ipmi_serial_codec_data *data, char *page) +{ + char pfx = ','; + char *out = page; + + if (data->options.attn) { + out += sprintf(out, "%cattn", pfx); + pfx = '+'; + } + if (data->options.eattn) { + out += sprintf(out, "%ceattn", pfx); + if (data->options.eattn_char != 0x47) + out += sprintf(out, "=0x%x", data->options.eattn_char); + pfx = '+'; + } + if (data->options.rqaddr != DEFAULT_RQADDR) { + out += sprintf(out, "%crqa=0x%x", pfx, data->options.rqaddr); + pfx = '+'; + } + if (data->options.rsaddr != DEFAULT_RSADDR) + out += sprintf(out, "%crsa=0x%x", pfx, data->options.rsaddr); + + return out - page; +} + +static unsigned char ipmb_checksum(unsigned char *data, int size) +{ + unsigned char csum = 0; + + for (; size > 0; size--, data++) + csum += *data; + + return -csum; +} + +#define DM_START_CHAR 0xA0 +#define DM_STOP_CHAR 0xA5 +#define DM_PACKET_HANDSHAKE 0xA6 +#define DM_DATA_ESCAPE_CHAR 0xAA + +static void format_msg(struct ipmi_serial_codec_data *data, + const unsigned char *msg, unsigned int msg_len) +{ + unsigned int i; + unsigned int len = 0; + unsigned char *c = data->xmit_chars; + + c[len++] = 0xA0; + for (i = 0; i < msg_len; i++) { + switch (msg[i]) { + case 0xA0: + c[len++] = 0xAA; + c[len++] = 0xB0; + break; + + case 0xA5: + c[len++] = 0xAA; + c[len++] = 0xB5; + break; + + case 0xA6: + c[len++] = 0xAA; + c[len++] = 0xB6; + break; + + case 0xAA: + c[len++] = 0xAA; + c[len++] = 0xBA; + break; + + case 0x1B: + c[len++] = 0xAA; + c[len++] = 0x3B; + break; + + default: + c[len++] = msg[i]; + } + + } + c[len++] = 0xA5; + data->xmit_chars_len = len; + data->xmit_chars_pos = 0; +} + +static void handle_recv_msg(struct ipmi_serial_codec_data *data) +{ + unsigned int seq; + unsigned char *m = data->recv_msg; + unsigned int len = data->recv_msg_len; + int i; + + if (len < 8) { + /* Messages must be at least 8 bytes to be valid. */ + ipmi_serial_ll_protocol_violation(data->info); + return; + } + + /* Note that this validates both checksums in one shot. */ + if (ipmb_checksum(m, len) != 0) { + ipmi_serial_ll_checksum_error(data->info); + return; + } + len--; /* Remove the checksum */ + + seq = m[4] >> 2; + + /* Pull the rsLun and the NetFN together. */ + m[0] = (m[1] & 0xfc) | (m[4] & 0x3); + /* Now the rest of the data */ + for (i = 1; i < (len - 4); i++) + m[i] = m[i + 4]; + len -= 4; + + ipmi_serial_ll_recv(data->info, m, len, data->seqnum_table[seq]); +} + +static void try_to_send_data(struct ipmi_serial_codec_data *data) +{ + unsigned char *c; + unsigned int left; + unsigned int sent; + + if (!data->xmit_chars_len) + return; + + if (data->handshake_time > 0) + return; + + c = data->xmit_chars + data->xmit_chars_pos; + left = data->xmit_chars_len - data->xmit_chars_pos; + sent = ipmi_serial_ll_xmit(data->info, c, left); + if (sent == left) { + /* We are done with this message. */ + if (data->xmit_msg_len) { + /* Send the next message we have waiting. */ + format_msg(data, data->xmit_msg, data->xmit_msg_len); + data->xmit_msg_len = 0; + } else { + /* Nothing to do. */ + data->xmit_chars_len = 0; + } + /* Wait for the handshake. */ + data->handshake_time = DM_HANDSHAKE_TIME; + } else { + data->xmit_chars_pos += sent; + } +} + +static int sd_setup_termios(struct ktermios *t) +{ + /* Nothing to do, the default is fine. */ + return 0; +} + +static int sd_init(struct ipmi_serial_codec_data *data, + struct ipmi_serial_info *info, + const char *options) +{ + memset(data, 0, sizeof(*data)); + spin_lock_init(&data->lock); + data->info = info; + + /* Pick some good defaults for header addresses. */ + data->options.rsaddr = DEFAULT_RSADDR; + data->options.rqaddr = DEFAULT_RQADDR; + + return check_options(data, options); +} + +static void sd_cleanup(struct ipmi_serial_codec_data *data) +{ + /* Nothing to do. */ +} + +static int sd_size(void) +{ + return sizeof(struct ipmi_serial_codec_data); +} + +static void sd_handle_char(struct ipmi_serial_codec_data *data, + unsigned char ch) +{ + unsigned int len = data->recv_msg_len; + unsigned long flags; + + switch (ch) { + case DM_START_CHAR: + if (data->in_recv_msg) + ipmi_serial_ll_protocol_violation(data->info); + data->in_recv_msg = 1; + data->recv_msg_len = 0; + data->recv_msg_too_many = 0; + data->in_escape = 0; + break; + + case DM_STOP_CHAR: + if (!data->in_recv_msg) + ipmi_serial_ll_protocol_violation(data->info); + else if (data->in_escape) { + data->in_recv_msg = 0; + ipmi_serial_ll_protocol_violation(data->info); + } else if (data->recv_msg_too_many) { + data->in_recv_msg = 0; + ipmi_serial_ll_protocol_violation(data->info); + } else { + data->in_recv_msg = 0; + handle_recv_msg(data); + } + data->in_escape = 0; + break; + + case DM_PACKET_HANDSHAKE: + /* Got a handshake, we can send now. */ + spin_lock_irqsave(&data->lock, flags); + data->handshake_time = 0; + try_to_send_data(data); + spin_unlock_irqrestore(&data->lock, flags); + data->in_escape = 0; + break; + + case DM_DATA_ESCAPE_CHAR: + data->in_escape = 1; + break; + + case 0x1b: + if (data->options.attn) + ipmi_serial_ll_attn(data->info); + break; + + default: + if (!data->in_recv_msg) + /* Ignore characters outside of messages. */ + break; + + if (data->in_escape) { + data->in_escape = 0; + if (data->options.eattn + && (ch == data->options.eattn_char)) { + ipmi_serial_ll_attn(data->info); + goto out; + } + + switch (ch) { + case 0xB0: + ch = DM_START_CHAR; + break; + case 0xB5: + ch = DM_STOP_CHAR; + break; + case 0xB6: + ch = DM_PACKET_HANDSHAKE; + break; + case 0xBA: + ch = DM_DATA_ESCAPE_CHAR; + break; + case 0x3B: + ch = 0x1b; + break; + default: + ipmi_serial_ll_protocol_violation(data->info); + data->recv_msg_too_many = 1; + return; + } + } + + if (!data->recv_msg_too_many) { + if (len >= sizeof(data->recv_msg)) { + data->recv_msg_too_many = 1; + break; + } + + data->recv_msg[len] = ch; + data->recv_msg_len++; + } + break; + } + out: + return; +} + +static int sd_send_msg(struct ipmi_serial_codec_data *data, + const unsigned char *msg, unsigned int msg_len, + unsigned int seq) +{ + unsigned long flags; + int rv = 0; + unsigned char *m; + unsigned int i, j; + unsigned char seqno; + + if (msg_len > IPMI_MAX_MSG_LENGTH) + return -EFBIG; + if (msg_len < 2) + return -EINVAL; + + spin_lock_irqsave(&data->lock, flags); + if (data->xmit_msg_len) { + /* Something is still waiting to be sent. */ + rv = -EBUSY; + goto out_unlock; + } + + i = 0; + m = data->xmit_msg; + m[i++] = data->options.rsaddr; /* rsAddr */ + m[i++] = msg[0]; /* NetFN/rsLUN */ + m[i++] = ipmb_checksum(m, 2); /* checksum1 */ + m[i++] = data->options.rqaddr; /* rqAddr */ + + seqno = data->seqno; + data->seqno = (data->seqno + 1) & 0x3f; + data->seqnum_table[seqno] = seq; + m[i++] = seqno << 2; /* seqno/rqLUN */ + m[i++] = msg[1]; /* cmd */ + for (j = 2; j < msg_len; j++) + m[i++] = msg[j]; /* data */ + m[i] = ipmb_checksum(m + 3, i - 3); /* checksum2 */ + i++; + + if (data->xmit_chars_len == 0) { + /* Transmit queue is empty, just format it now to go. */ + format_msg(data, m, i); + try_to_send_data(data); + } else { + /* Save it for after the transmit queue emptying. */ + data->xmit_msg_len = i; + } + + out_unlock: + spin_unlock_irqrestore(&data->lock, flags); + return rv; +} + +static void sd_tx_ready(struct ipmi_serial_codec_data *data) +{ + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + try_to_send_data(data); + spin_unlock_irqrestore(&data->lock, flags); +} + +static void sd_timer_tick(struct ipmi_serial_codec_data *data, + unsigned int time_since_last) +{ + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + if (data->handshake_time > 0) { + data->handshake_time -= time_since_last; + if (data->handshake_time <= 0) + try_to_send_data(data); + } + spin_unlock_irqrestore(&data->lock, flags); +} + +static unsigned int sd_capabilities(struct ipmi_serial_codec_data *data) +{ + unsigned int c = (IPMI_SERIAL_SUPPORTS_GET_FLAGS + | IPMI_SERIAL_SUPPORTS_EVENT_BUFFER); + if ((data->options.attn) || (data->options.eattn)) + c |= IPMI_SERIAL_HAS_ATTN; + else + c |= IPMI_SERIAL_NEEDS_GET_FLAGS_POLLING; + return c; +} + +static struct ipmi_serial_codec sd_codec = { + .owner = THIS_MODULE, + .name = "Direct", + + .capabilities = sd_capabilities, + .setup_termios = sd_setup_termios, + .init = sd_init, + .cleanup = sd_cleanup, + .size = sd_size, + .send_msg = sd_send_msg, + .handle_char = sd_handle_char, + .check_dev_id = check_devid_options, + .timer_tick = sd_timer_tick, + .tx_ready = sd_tx_ready, + .add_options = sd_add_options +}; + +static __init int +init_ipmi_serial_direct_codec(void) +{ + return ipmi_serial_codec_register(&sd_codec); +} + +module_init(init_ipmi_serial_direct_codec); + +static __exit void +exit_ipmi_serial_direct_codec(void) +{ + ipmi_serial_codec_unregister(&sd_codec); +} +module_exit(exit_ipmi_serial_direct_codec); +MODULE_LICENSE("GPL"); Index: linux-2.6.30/drivers/char/ipmi/ipmi_serial_radisys_ascii.c =================================================================== --- /dev/null +++ linux-2.6.30/drivers/char/ipmi/ipmi_serial_radisys_ascii.c @@ -0,0 +1,611 @@ +/* + * ipmi_serial_radisys_ascii.c + * Serial interface encoder and decoder routines for Radisys ASCII mode. + * + * Author: MontaVista Software, Inc. + * source@mvista.com + * Corey Minyard + * + * Copyright 2006 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 + +#define PFX "ipmi_serial_radisys_ascii: " + +/* + * Two bytes for every character plus the trailing newline. There are + * five overhead bytes that are for the header and checksum beyond the + * KCS header we get/send to the upper layer.. + */ +#define RA_OVERHEAD 5 +#define RA_MAX_CHARS_SIZE (((IPMI_MAX_MSG_LENGTH + RA_OVERHEAD) * 2) + 4) + +/* + * Give the remote end this much time (100ms) to send a response + * before we give up and retry the given number of times. + */ +#define RA_HANDSHAKE_TIME 5000000 +#define RA_HANDSHAKE_RETRIES 5 + +struct ipmi_serial_codec_data { + struct ipmi_serial_info *info; + + unsigned char xmit_chars[RA_MAX_CHARS_SIZE]; + unsigned int xmit_chars_len; + unsigned int xmit_chars_pos; + unsigned int xmit_chars_seq; + unsigned char xmit_chars_netfn; + unsigned char xmit_chars_cmd; + + unsigned char recv_chars[RA_MAX_CHARS_SIZE]; + unsigned int recv_chars_len; + int recv_chars_too_many; + + unsigned char seqno; + unsigned int seqnum_table[0x40]; + + unsigned char xmit_msg[IPMI_MAX_MSG_LENGTH + RA_OVERHEAD]; + unsigned int xmit_msg_len; + unsigned int xmit_msg_seq; + unsigned char xmit_msg_netfn; + unsigned char xmit_msg_cmd; + unsigned char recv_msg[IPMI_MAX_MSG_LENGTH + RA_OVERHEAD]; + unsigned int recv_msg_len; + + unsigned char bmc_i2c_addr; + unsigned char smi_i2c_addr; + + /* + * Used to time receiving the IPMB address. + */ + int handshake_time; + int handshake_retries_left; + int handshake_done; + + spinlock_t lock; + + void (*recv_msg_handler)(struct ipmi_serial_codec_data *data, + const unsigned char *msg, + unsigned int len); +}; + +static int ra_add_options(struct ipmi_serial_codec_data *data, char *page) +{ + return 0; +} + +static unsigned char +ipmb_checksum(const unsigned char *data, int size) +{ + unsigned char csum = 0; + + for (; size > 0; size--, data++) + csum += *data; + + return -csum; +} + +static unsigned char hex2char[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' +}; + +static void format_msg(struct ipmi_serial_codec_data *data, + const unsigned char *msg, unsigned int msg_len) +{ + int i; + int len; + unsigned char *c = data->xmit_chars; + + /* + * No need for checks on the output length, the output buffer is + * guaranteed to be big enough. + */ + len = 0; + for (i = 0; i < msg_len; i++) { + c[len++] = hex2char[msg[i] >> 4]; + c[len++] = hex2char[msg[i] & 0xf]; + } + c[len++] = 0x0d; + + data->xmit_chars_pos = 0; + data->xmit_chars_len = len; + data->xmit_chars_seq = data->xmit_msg_seq; + data->xmit_chars_netfn = data->xmit_msg_netfn; + data->xmit_chars_cmd = data->xmit_msg_cmd; +} + +static int fromhex(unsigned char c) +{ + if (isdigit(c)) + return c - '0'; + else if (isxdigit(c)) + return tolower(c) - 'a' + 10; + else + return -EINVAL; +} + +/* + * Called when the 0x0d is seen. + */ +static int unformat_msg(struct ipmi_serial_codec_data *data) +{ + unsigned char *r = data->recv_chars; + unsigned char *o = data->recv_msg; + unsigned int len = data->recv_chars_len; + unsigned int p = 0; + unsigned int i = 0; + int rv; + + while (p < len) { + if (i > sizeof(data->recv_msg)) + return -EFBIG; + rv = fromhex(r[p]); + if (rv < 0) + return rv; + o[i] = rv << 4; + p++; + if (p >= len) + return -EINVAL; + rv = fromhex(r[p]); + if (rv < 0) + return rv; + o[i] |= rv; + p++; + i++; + } + data->recv_msg_len = i; + return 0; +} + +static void handle_recv_msg(struct ipmi_serial_codec_data *data) +{ + unsigned int seq; + unsigned char *m = data->recv_msg; + unsigned int len = data->recv_msg_len; + int i; + + if (data->recv_msg_handler) { + data->recv_msg_handler(data, m, len); + return; + } + + if (len < 8) { + /* Messages must be at least 8 bytes */ + ipmi_serial_ll_protocol_violation(data->info); + return; + } + + if (m[3] == data->bmc_i2c_addr) { + if (ipmb_checksum(m, len) != 0) { + ipmi_serial_ll_checksum_error(data->info); + return; + } + /* Remove the final checksum. */ + len--; + + /* + * It's a message straight from the BMC (a response). + * Take the data from the header that we need and + * write over the header. + */ + m[0] = (m[1] & 0xfc) | (m[4] & 0x03); + m[1] = m[5]; + seq = m[4] >> 2; + for (i = 2; i < (len - 4); i++) + m[i] = m[i+4]; + ipmi_serial_ll_recv(data->info, m, len - 4, + data->seqnum_table[seq]); + } else { + /* Not from the BMC, must be from the IPMB. */ + + if ((len + 3) > IPMI_MAX_MSG_LENGTH) { + ipmi_serial_ll_protocol_violation(data->info); + return; + } + + /* Make room for a get message command header. */ + for (i = len; i > 0; i--) + /* + * Note: This is i + 3, not i + 4, because the + * GET_MSG response does not have the + * destination address in the message. So we + * write over that field. + */ + m[i + 3] = m[i]; + /* Get message command header. */ + m[0] = IPMI_NETFN_APP_RESPONSE << 2; + m[1] = IPMI_GET_MSG_CMD; + m[2] = 0; /* completion code */ + m[3] = 0; /* channel */ + ipmi_serial_ll_async(data->info, m, len + 3); + } +} + +static void try_to_send_data(struct ipmi_serial_codec_data *data, + unsigned long *flags) +{ + unsigned char *c; + unsigned int left; + unsigned int sent; + + if (!data->xmit_chars_len) + return; + + restart: + c = data->xmit_chars + data->xmit_chars_pos; + left = data->xmit_chars_len - data->xmit_chars_pos; + sent = ipmi_serial_ll_xmit(data->info, c, left); + if (sent == left) { + /* We are done with this message. */ + + if ((data->xmit_chars_netfn == IPMI_NETFN_APP_REQUEST) + && (data->xmit_chars_cmd == IPMI_SEND_MSG_CMD)) { + /* + * The transmitted characters will not get a + * response from a send message, it goes + * straight out onto the IPMB. So simulate + * the response. + */ + char msg[3]; + spin_unlock_irqrestore(&data->lock, *flags); + /* Make into response */ + msg[0] = (data->xmit_chars_netfn | 1) << 2; + msg[1] = data->xmit_chars_cmd; + msg[2] = 0; + ipmi_serial_ll_recv(data->info, msg, 3, + data->xmit_chars_seq); + spin_lock_irqsave(&data->lock, *flags); + } + + if (data->xmit_msg_len) { + /* Send the next message we have waiting. */ + format_msg(data, data->xmit_msg, data->xmit_msg_len); + data->xmit_msg_len = 0; + goto restart; + } else { + /* Nothing to do. */ + data->xmit_chars_len = 0; + } + } else { + data->xmit_chars_pos += sent; + } +} + +static int ra_setup_termios(struct ktermios *t) +{ + /* Nothing to do, the default is fine. */ + return 0; +} + +#define RA_OEM1_NETFN 0x30 +#define RA_CONTROLLER_OEM_NETFN 0x3e +#define RA_GET_IPMB_ADDR_CMD 0x12 +static unsigned char get_ipmbaddr_msg1[] = { 0x01, /* dest addr */ + RA_OEM1_NETFN << 2, + 0x3f, /* checksum1 */ + 0x01, /* source addr */ + 0x00, /* seq num/rsLUN */ + RA_GET_IPMB_ADDR_CMD, + 0xed /* checksum2 */ +}; + +static unsigned char get_ipmbaddr_msg2[] = { 0x01, /* dest addr */ + RA_CONTROLLER_OEM_NETFN << 2, + 0x07, /* checksum1 */ + 0x01, /* source addr */ + 0x00, /* seq num/rsLUN */ + RA_GET_IPMB_ADDR_CMD, + 0xed /* checksum2 */ +}; + +static void handle_init_getipmbaddr(struct ipmi_serial_codec_data *data, + const unsigned char *msg, + unsigned int len) +{ + unsigned long flags; + + if (len < 8) + return; + if (ipmb_checksum(msg, len) != 0) + return; + len--; + + if (((msg[1] >> 2) != (RA_OEM1_NETFN | 1) + && (msg[1] >> 2) != (RA_CONTROLLER_OEM_NETFN | 1)) + || (msg[5] != RA_GET_IPMB_ADDR_CMD)) + return; + + spin_lock_irqsave(&data->lock, flags); + if (data->handshake_done) { + spin_unlock_irqrestore(&data->lock, flags); + return; + } + + if (msg[6] != 0) { + if ((msg[1] >> 2) == (RA_OEM1_NETFN | 1)) { + unsigned long flags; + + format_msg(data, get_ipmbaddr_msg2, + sizeof(get_ipmbaddr_msg2)); + try_to_send_data(data, &flags); + spin_unlock_irqrestore(&data->lock, flags); + } else { + printk(KERN_ERR "IPMI: RadisysAscii: Got error" + " fetching the IPMB address, this may not" + " be a Radisys system.\n"); + data->handshake_done = 1; + spin_unlock_irqrestore(&data->lock, flags); + ipmi_serial_ll_init_complete(data->info, 0, -EINVAL); + } + return; + } + + data->bmc_i2c_addr = msg[7]; + data->recv_msg_handler = NULL; + data->handshake_done = 1; + spin_unlock_irqrestore(&data->lock, flags); + ipmi_serial_ll_init_complete(data->info, data->bmc_i2c_addr, 0); +} + +static int ra_init(struct ipmi_serial_codec_data *data, + struct ipmi_serial_info *info, + const char *options) +{ + memset(data, 0, sizeof(*data)); + spin_lock_init(&data->lock); + data->info = info; + data->bmc_i2c_addr = 1; /* Initial setting should work ok. */ + data->smi_i2c_addr = 1; /* Initial setting should work ok. */ + + data->handshake_time = RA_HANDSHAKE_TIME; + data->handshake_retries_left = RA_HANDSHAKE_RETRIES; + + return 0; +} + +static int ra_start(struct ipmi_serial_codec_data *data) +{ + unsigned long flags; + + data->recv_msg_handler = handle_init_getipmbaddr; + spin_lock_irqsave(&data->lock, flags); + format_msg(data, get_ipmbaddr_msg1, sizeof(get_ipmbaddr_msg1)); + try_to_send_data(data, &flags); + spin_unlock_irqrestore(&data->lock, flags); + return 0; +} + +static void ra_cleanup(struct ipmi_serial_codec_data *data) +{ + /* Nothing to do. */ +} + +static int ra_size(void) +{ + return sizeof(struct ipmi_serial_codec_data); +} + +static void ra_handle_char(struct ipmi_serial_codec_data *data, + unsigned char ch) +{ + unsigned int len = data->recv_chars_len; + unsigned char *r; + int rv; + + if (ch == 0x0d) { + /* End of command, handle it. */ + if (data->recv_chars_too_many) { + /* Input data overrun. */ + ipmi_serial_ll_protocol_violation(data->info); + data->recv_chars_too_many = 0; + data->recv_chars_len = 0; + return; + } + rv = unformat_msg(data); + if (rv) { + /* Bad input data. */ + ipmi_serial_ll_protocol_violation(data->info); + return; + } + handle_recv_msg(data); + data->recv_chars_len = 0; + return; + } + + if (data->recv_chars_too_many) + return; + + r = data->recv_chars; + + if (len >= sizeof(data->recv_chars)) { + data->recv_chars_too_many = 1; + } else if (isspace(r[len-1]) && isspace(ch)) { + /* Ignore multiple spaces together. */ + } else { + r[len] = ch; + data->recv_chars_len++; + } +} + +static int ra_send_msg(struct ipmi_serial_codec_data *data, + const unsigned char *msg, unsigned int msg_len, + unsigned int seq) +{ + unsigned long flags; + int rv = 0; + int i, j; + unsigned char seqno; + + if (msg_len > IPMI_MAX_MSG_LENGTH) + return -EFBIG; + if (msg_len < 2) + return -EINVAL; + + spin_lock_irqsave(&data->lock, flags); + if (data->xmit_msg_len) { + /* Something is still waiting to be sent. */ + goto out_unlock; + rv = -EBUSY; + } + + data->xmit_msg_netfn = msg[0] >> 2; + data->xmit_msg_cmd = msg[1]; + data->xmit_msg_seq = seq; + i = 0; + if ((data->xmit_msg_netfn == IPMI_NETFN_APP_REQUEST) + && (data->xmit_msg_cmd == IPMI_SEND_MSG_CMD)) { + if ((msg[2] & 0xf) != 0) { + /* Only channels 0 is supported. */ + rv = -EINVAL; + goto out_unlock; + } + /* + * Skip over the message header and send the raw IPMB + * message. + */ + for (j = 3; j < msg_len; j++) + data->xmit_msg[i++] = msg[j]; + } else { + data->xmit_msg[i++] = data->bmc_i2c_addr; + data->xmit_msg[i++] = msg[0]; /* NetFN/LUN */ + data->xmit_msg[i++] = ipmb_checksum(data->xmit_msg, 2); + data->xmit_msg[i++] = data->smi_i2c_addr; + seqno = data->seqno; + data->seqno = (data->seqno + 1) & 0x3f; + data->seqnum_table[seqno] = seq; + data->xmit_msg[i++] = seqno << 2; /* seqno/rqLUN */ + data->xmit_msg[i++] = msg[1]; /* cmd */ + for (j = 2; j < msg_len; j++) + data->xmit_msg[i++] = msg[j]; + data->xmit_msg[i] = ipmb_checksum(data->xmit_msg + 3, i - 3); + i++; + } + + + if (data->xmit_chars_len == 0) { + /* Transmit queue is empty, just format it now to go. */ + format_msg(data, data->xmit_msg, i); + try_to_send_data(data, &flags); + } else { + /* + * Get it ready to be sent later when the transmit + * queue empties. + */ + data->xmit_msg_len = i; + } + out_unlock: + spin_unlock_irqrestore(&data->lock, flags); + return rv; +} + +static void ra_tx_ready(struct ipmi_serial_codec_data *data) +{ + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + try_to_send_data(data, &flags); + spin_unlock_irqrestore(&data->lock, flags); +} + +static unsigned int ra_capabilities(struct ipmi_serial_codec_data *data) +{ + return 0; +} + + +static void ra_timer_tick(struct ipmi_serial_codec_data *data, + unsigned int time_since_last) +{ + unsigned long flags; + + /* Check quickly first to avoid grabbing the lock normally. */ + if (likely(data->handshake_done)) + return; + + spin_lock_irqsave(&data->lock, flags); + /* Recheck to avoid races */ + if (data->handshake_done) + goto out_unlock; + + data->handshake_time -= time_since_last; + if (data->handshake_time <= 0) { + data->handshake_retries_left--; + if (data->handshake_retries_left <= 0) { + data->handshake_done = 1; + spin_unlock_irqrestore(&data->lock, flags); + ipmi_serial_ll_init_complete(data->info, 0, -ETIMEDOUT); + goto out; + } + + /* Resend the IPMB fetch */ + data->handshake_time = RA_HANDSHAKE_TIME; + format_msg(data, get_ipmbaddr_msg1, + sizeof(get_ipmbaddr_msg1)); + try_to_send_data(data, &flags); + } + out_unlock: + spin_unlock_irqrestore(&data->lock, flags); + out: + return; +} + +static struct ipmi_serial_codec ra_codec = { + .owner = THIS_MODULE, + .name = "RadisysAscii", + + .capabilities = ra_capabilities, + .setup_termios = ra_setup_termios, + .init = ra_init, + .start = ra_start, + .cleanup = ra_cleanup, + .size = ra_size, + .send_msg = ra_send_msg, + .handle_char = ra_handle_char, + .tx_ready = ra_tx_ready, + .add_options = ra_add_options, + .timer_tick = ra_timer_tick +}; + +static __init int +init_ipmi_serial_ra_codec(void) +{ + return ipmi_serial_codec_register(&ra_codec); +} + +module_init(init_ipmi_serial_ra_codec); + +static __exit void +exit_ipmi_serial_ra_codec(void) +{ + ipmi_serial_codec_unregister(&ra_codec); +} +module_exit(exit_ipmi_serial_ra_codec); +MODULE_LICENSE("GPL"); Index: linux-2.6.30/Documentation/IPMI.txt =================================================================== --- linux-2.6.30.orig/Documentation/IPMI.txt +++ linux-2.6.30/Documentation/IPMI.txt @@ -50,6 +50,10 @@ figuring to see if it will work on your information is wrong or not present. It is fairly safe to have both these enabled and let the drivers auto-detect what is present. +The driver also support an interface over a serial port. This is +rather complex to set up and use correctly. See the section on the +IPMI Serial Driver for details. + You should generally enable ACPI on your system, as systems with IPMI can have ACPI tables describing them. @@ -111,6 +115,19 @@ ipmi_smb - A driver for accessing BMCs o I2C kernel driver's SMBus interfaces to send and receive IPMI messages over the SMBus. +ipmi_serial - A driver for accessing BMCs over a serial port. This +requires patches to the serial port code. It also requires that a +specific codec be loaded selected. + +ipmi_serial_terminal_mode - An IPMI serial codec for terminal mode +(per the IPMI spec) + +ipmi_serial_direct - An IPMI serial codec for direct mode (per the +IPMI spec). + +ipmi_serial_radisys_ascii - An IPMI serial codec for certain Radisys +boards. + ipmi_watchdog - IPMI requires systems to have a very capable watchdog timer. This driver implements the standard Linux watchdog timer interface on top of the IPMI message handler. @@ -526,6 +543,135 @@ is enabled along with this, so the I2C d completion during sending a panic event. +The IPMI Serial Driver +---------------------- + +First let met get this off my chest. This type if interface is a bad +idea, because: + * It is not standard. Decent standards are available that are + very cheap to implement (like SMBus). You think that existing + standards would be enough to keep manufacturers away from wierd + things like this. + * The interface cannot be autodetected and *NO* I am not going to + send random bytes down a serial port to see if something is there. + This is a huge problem. The standard interfaces will just work + out of the box. These interfaces won't. + * Layering drivers is a bad idea. + * The error checking is not strong enough be be reliable enough, + at least for the currently defined codecs. + * In particular, terminal mode is a bad idea because it doesn't have + any checksums. +If you have one of these, please tell your manufacturer to not use it any +more. With that off my chest, these things exist in the field, and need +to be supported, so here we go... + +Note that to use this, the serial core must have the layered interface +patches. If your kernel does not have them, you can get them at the +OpenIPMI web site. + +Configuration for this driver is quite complex. Config options are: + 'IPMI Serial Interface' - Turn on the main interface. + 'Default serial IPMI interface setup' - You may specify a setup + string in this option. + 'Terminal mode interface for IPMI' + 'Direct mode interface for IPMI' + 'Radisys ASCII interface for IPMI' + +Those last options are codecs. You can safely choose and install them +all; they will not be used unless configured. + +It is recommended that you load these as modules and specify the +port_info module parameter to configure the driver and leave the +default alone. If you compile the driver into the kernel, you can +specify ipmi_serial.port_info=... on the kernel command line to +configure the driver. Setting the default setup is only recommended +if the kernel will only be used for one type of system. + +The port_info parameter allows configuration for one or more ports to +be specified as follows: + port_info=[:[:...]] +A portconfig has the following format: + ,,, + + - The name of the serial port, like ttyS0, ttyS1, etc. + - Standard tty configuration string, like 38400N81r. + The first part is the speed. After that is parity (n, o, e) + defaulting to 'n'. Then the number of bits (5 - 8) defaulting + to 8. Then number of stop bits (1 or 2), defaulting to 1. + After that is configuration options, which there may be zero + or more of. The "r" option turns on CTS/RTS handling. Normally the + driver enables DTR and RTS lines sent to the device, as they + may be ignored, may be required, but generally don't hurt + anything. You may add "R" to turn off RTS sending and "D" to turn + off DTR sending. Like 9600n81RD, for instance. + - Which codec to use for this interface. + - Options for this codec, separated by '+' + +Codecs: + TerminalMode - An interface using the "terminal mode" handling as + defined in the IPMI spec. I know, I said it wasn't a standard. + The problem is this is defined for modem dial-ins for interactive + users. Not for local BMC access. + + Terminal mode options are "pp" to enable special PigeonPoint + handling for echo suppression, or "nopp" to disable this. + "ppInvIANA" is only valid if "pp" is enabled, it tells the codec + that the IANA in the PigeonPoint-specific messages is backwards. + The codec will attempt to auto-detect this, but you can force + it if necessary by add this, or "noppInvIANA. If you force it, + that will turn off auto-detection. + + Specifying "attn" will enable detecting an attention character + the BMC may send at any time to tell the driver to fetch the + flags. This defaults to the BELL character (0x07) but may be + overridden by doing "attn=0xNN". Specifying "noattn" turns this + feature off. Note that you need to get the attn character + setting right, or the data stream will be corrupted when the + attn character comes in. Multiple options are separated by a + "+: character, like: "attn=0x09+nopp". These options might be + properly autodetected on some boards. + + Direct - Use direct mode as specified in the IPMI spec. Again, this + is designed for automated remote modem access, not local BMC access. + + The options available are: + + attn - Enable using the ASCII escape character (0x1b) as an + attention character. + noattn - Force attn off. + eattn[=char] - Enable using a specific escape sequence as an + attention notification. The escape sequence is 0xAA + where the default char is 0x47 but may be overriden by being + specified. + noeattn - Force eattn off. + rqa - Set the requester address. The default is 0x61. + rsa - Set the responder address. The default is 0x20. + + Note that you must set the attn and eattn values correct or the + protocol may not work correctly. The options may be automatically + set correctly for boards the driver knows about. Multiple options + must be separated by a '+'. + + RadisysAscii - A codec for certain Radisys boads. + + No options are supported. + +The driver supports a hot add and remove of interfaces. This way, +interfaces can be added or removed after the kernel is up and running. +This is done using /sys/modules/ipmi_serial/parameters/hotmod, which is a +write-only parameter. You write a string to this interface. The +string has the format: + [:[:...]] +The "op"s are: + add|remove, +where the portconfig parameter is described above. Note that for +removal only the port is use for comparison, the rest of the +parameters are ignored. + +Note that if you are doing driver development on this, the OpenIPMI +library has a serial BMC emulator + + Other Pieces ------------