📄 ipmi_msghandler.c
字号:
/* * ipmi_msghandler.c * * Incoming and outgoing message routing for an IPMI interface. * * Author: MontaVista Software, Inc. * Corey Minyard <minyard@mvista.com> * source@mvista.com * * Copyright 2002 MontaVista Software Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 675 Mass Ave, Cambridge, MA 02139, USA. */#include <linux/config.h>#include <linux/module.h>#include <linux/errno.h>#include <asm/system.h>#include <linux/sched.h>#include <linux/poll.h>#include <linux/spinlock.h>#include <linux/rwsem.h>#include <linux/slab.h>#include <linux/ipmi.h>#include <linux/ipmi_smi.h>#include <linux/notifier.h>#include <linux/init.h>struct ipmi_recv_msg *ipmi_alloc_recv_msg(void);static int ipmi_init_msghandler(void);static int initialized = 0;#define MAX_EVENTS_IN_QUEUE 25/* Don't let a message sit in a queue forever, always time it with at lest the max message timer. */#define MAX_MSG_TIMEOUT 60000struct ipmi_user{ struct list_head link; /* The upper layer that handles receive messages. */ struct ipmi_user_hndl *handler; void *handler_data; /* The interface this user is bound to. */ ipmi_smi_t intf; /* Does this interface receive IPMI events? */ int gets_events;};struct cmd_rcvr{ struct list_head link; ipmi_user_t user; unsigned char netfn; unsigned char cmd;};struct seq_table{ int inuse : 1; unsigned long timeout; unsigned long orig_timeout; unsigned int retries_left; /* To verify on an incoming send message response that this is the message that the response is for, we keep a sequence id and increment it every time we send a message. */ long seqid; /* This is held so we can properly respond to the message on a timeout, and it is used to hold the temporary data for retransmission, too. */ struct ipmi_recv_msg *recv_msg;};/* Store the information in a msgid (long) to allow us to find a sequence table entry from the msgid. */#define STORE_SEQ_IN_MSGID(seq, seqid) (((seq&0xff)<<26) | (seqid&0x3ffffff))#define GET_SEQ_FROM_MSGID(msgid, seq, seqid) \ do { \ seq = ((msgid >> 26) & 0x3f); \ seqid = (msgid & 0x3fffff); \ } while(0)#define NEXT_SEQID(seqid) (((seqid) + 1) & 0x3fffff)#define IPMI_IPMB_NUM_SEQ 64struct ipmi_smi{ /* The list of upper layers that are using me. We read-lock this when delivering messages to the upper layer to keep the user from going away while we are processing the message. This means that you cannot add or delete a user from the receive callback. */ rwlock_t users_lock; struct list_head users; /* The IPMI version of the BMC on the other end. */ unsigned char version_major; unsigned char version_minor; /* This is the lower-layer's sender routine. */ struct ipmi_smi_handlers *handlers; void *send_info; /* A table of sequence numbers for this interface. We use the sequence numbers for IPMB messages that go out of the interface to match them up with their responses. A routine is called periodically to time the items in this list. */ spinlock_t seq_lock; struct seq_table seq_table[IPMI_IPMB_NUM_SEQ]; int curr_seq; /* Messages that were delayed for some reason (out of memory, for instance), will go in here to be processed later in a periodic timer interrupt. */ spinlock_t waiting_msgs_lock; struct list_head waiting_msgs; /* The list of command receivers that are registered for commands on this interface. */ rwlock_t cmd_rcvr_lock; struct list_head cmd_rcvrs; /* Events that were queues because no one was there to receive them. */ spinlock_t events_lock; /* For dealing with event stuff. */ struct list_head waiting_events; unsigned int waiting_events_count; /* How many events in queue? */ /* This will be non-null if someone registers to receive all IPMI commands (this is for interface emulation). There may not be any things in the cmd_rcvrs list above when this is registered. */ ipmi_user_t all_cmd_rcvr; /* My slave address. This is initialized to IPMI_BMC_SLAVE_ADDR, but may be changed by the user. */ unsigned char my_address; /* My LUN. This should generally stay the SMS LUN, but just in case... */ unsigned char my_lun;};intipmi_register_all_cmd_rcvr(ipmi_user_t user){ unsigned long flags; int rv = -EBUSY; write_lock_irqsave(&(user->intf->users_lock), flags); write_lock(&(user->intf->cmd_rcvr_lock)); if ((user->intf->all_cmd_rcvr == NULL) && (list_empty(&(user->intf->cmd_rcvrs)))) { user->intf->all_cmd_rcvr = user; rv = 0; } write_unlock(&(user->intf->cmd_rcvr_lock)); write_unlock_irqrestore(&(user->intf->users_lock), flags); return rv;}intipmi_unregister_all_cmd_rcvr(ipmi_user_t user){ unsigned long flags; int rv = -EINVAL; write_lock_irqsave(&(user->intf->users_lock), flags); write_lock(&(user->intf->cmd_rcvr_lock)); if (user->intf->all_cmd_rcvr == user) { user->intf->all_cmd_rcvr = NULL; rv = 0; } write_unlock(&(user->intf->cmd_rcvr_lock)); write_unlock_irqrestore(&(user->intf->users_lock), flags); return rv;}#define MAX_IPMI_INTERFACES 4static ipmi_smi_t ipmi_interfaces[MAX_IPMI_INTERFACES];/* Used to keep interfaces from going away while operations are operating on interfaces. Grab read if you are not modifying the interfaces, write if you are. */static DECLARE_RWSEM(interfaces_sem);/* Directly protects the ipmi_interfaces data structure. This is claimed in the timer interrupt. */static spinlock_t interfaces_lock = SPIN_LOCK_UNLOCKED;/* List of watchers that want to know when smi's are added and deleted. */static struct list_head smi_watchers = LIST_HEAD_INIT(smi_watchers);static DECLARE_RWSEM(smi_watchers_sem);int ipmi_smi_watcher_register(struct ipmi_smi_watcher *watcher){ int i; down_read(&interfaces_sem); down_write(&smi_watchers_sem); list_add(&(watcher->link), &smi_watchers); for (i=0; i<MAX_IPMI_INTERFACES; i++) { if (ipmi_interfaces[i] != NULL) { watcher->new_smi(i); } } up_write(&smi_watchers_sem); up_read(&interfaces_sem); return 0;}int ipmi_smi_watcher_unregister(struct ipmi_smi_watcher *watcher){ down_write(&smi_watchers_sem); list_del(&(watcher->link)); up_write(&smi_watchers_sem); return 0;}intipmi_addr_equal(struct ipmi_addr *addr1, struct ipmi_addr *addr2){ if (addr1->addr_type != addr2->addr_type) return 0; if (addr1->channel != addr2->channel) return 0; if (addr1->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) { struct ipmi_system_interface_addr *smi_addr1 = (struct ipmi_system_interface_addr *) addr1; struct ipmi_system_interface_addr *smi_addr2 = (struct ipmi_system_interface_addr *) addr2; return (smi_addr1->lun == smi_addr2->lun); } if ((addr1->addr_type == IPMI_IPMB_ADDR_TYPE) || (addr1->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)) { struct ipmi_ipmb_addr *ipmb_addr1 = (struct ipmi_ipmb_addr *) addr1; struct ipmi_ipmb_addr *ipmb_addr2 = (struct ipmi_ipmb_addr *) addr2; return ((ipmb_addr1->slave_addr == ipmb_addr2->slave_addr) && (ipmb_addr1->lun == ipmb_addr2->lun)); } return 1;}int ipmi_validate_addr(struct ipmi_addr *addr, int len){ if (len < sizeof(struct ipmi_system_interface_addr)) { return -EINVAL; } if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) { if (addr->channel != IPMI_BMC_CHANNEL) return -EINVAL; return 0; } if ((addr->channel == IPMI_BMC_CHANNEL) || (addr->channel >= IPMI_NUM_CHANNELS) || (addr->channel < 0)) return -EINVAL; if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE) || (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)) { if (len < sizeof(struct ipmi_ipmb_addr)) { return -EINVAL; } return 0; } return -EINVAL;}unsigned int ipmi_addr_length(int addr_type){ if (addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) return sizeof(struct ipmi_system_interface_addr); if ((addr_type == IPMI_IPMB_ADDR_TYPE) || (addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)) { return sizeof(struct ipmi_ipmb_addr); } return 0;}static void deliver_response(struct ipmi_recv_msg *msg){ msg->user->handler->ipmi_recv_hndl(msg, msg->user->handler_data);}/* Find the next sequence number not being used and add the given message with the given timeout to the sequence table. This must be called with the interface's seq_lock held. */static int intf_next_seq(ipmi_smi_t intf, struct ipmi_recv_msg *recv_msg, unsigned long timeout, int retries, unsigned char *seq, long *seqid){ int rv = 0; unsigned int i; for (i=intf->curr_seq; (i+1)%IPMI_IPMB_NUM_SEQ != intf->curr_seq; i=(i+1)%IPMI_IPMB_NUM_SEQ) { if (! intf->seq_table[i].inuse) break; } if (! intf->seq_table[i].inuse) { intf->seq_table[i].recv_msg = recv_msg; /* Start with the maximum timeout, when the send response comes in we will start the real timer. */ intf->seq_table[i].timeout = MAX_MSG_TIMEOUT; intf->seq_table[i].orig_timeout = timeout; intf->seq_table[i].retries_left = retries; intf->seq_table[i].inuse = 1; intf->seq_table[i].seqid = NEXT_SEQID(intf->seq_table[i].seqid); *seq = i; *seqid = intf->seq_table[i].seqid; intf->curr_seq = (i+1)%IPMI_IPMB_NUM_SEQ; } else { rv = -EAGAIN; } return rv;}/* Return the receive message for the given sequence number and release the sequence number so it can be reused. Some other data is passed in to be sure the message matches up correctly (to help guard against message coming in after their timeout and the sequence number being reused). */static int intf_find_seq(ipmi_smi_t intf, unsigned char seq, short channel, unsigned char cmd, unsigned char netfn, struct ipmi_addr *addr, struct ipmi_recv_msg **recv_msg){ int rv = -ENODEV; unsigned long flags; if (seq >= IPMI_IPMB_NUM_SEQ) return -EINVAL; spin_lock_irqsave(&(intf->seq_lock), flags); if (intf->seq_table[seq].inuse) { struct ipmi_recv_msg *msg = intf->seq_table[seq].recv_msg; if ((msg->addr.channel == channel) && (msg->msg.cmd == cmd) && (msg->msg.netfn == netfn) && (ipmi_addr_equal(addr, &(msg->addr)))) { *recv_msg = msg; intf->seq_table[seq].inuse = 0; rv = 0; } } spin_unlock_irqrestore(&(intf->seq_lock), flags); return rv;}/* Start the timer for a specific sequence table entry. */static int intf_start_seq_timer(ipmi_smi_t intf, long msgid){ int rv = -ENODEV; unsigned long flags; unsigned char seq; unsigned long seqid; GET_SEQ_FROM_MSGID(msgid, seq, seqid); spin_lock_irqsave(&(intf->seq_lock), flags); /* We do this verification because the user can be deleted while a message is outstanding. */ if ((intf->seq_table[seq].inuse) && (intf->seq_table[seq].seqid == seqid)) { struct seq_table *ent = &(intf->seq_table[seq]); ent->timeout = ent->orig_timeout; } spin_unlock_irqrestore(&(intf->seq_lock), flags); return rv;}int ipmi_create_user(unsigned int if_num, struct ipmi_user_hndl *handler, void *handler_data, ipmi_user_t *user){ unsigned long flags; ipmi_user_t new_user; int rv = 0; /* There is no module usecount here, because it's not required. Since this can only be used by and called from other modules, they will implicitly use this module, and thus this can't be removed unless the other modules are removed. */ if (handler == NULL) return -EINVAL; /* Make sure the driver is actually initialized, this handles problems with initialization order. */ if (!initialized) { rv = ipmi_init_msghandler(); if (rv) return rv; /* The init code doesn't return an error if it was turned off, but it won't initialize. Check that. */ if (!initialized) return -ENODEV; } new_user = kmalloc(sizeof(*new_user), GFP_KERNEL); if (! new_user) return -ENOMEM; down_read(&interfaces_sem); if ((if_num > MAX_IPMI_INTERFACES) || ipmi_interfaces[if_num] == NULL) { rv = -EINVAL; goto out_unlock; } new_user->handler = handler; new_user->handler_data = handler_data; new_user->intf = ipmi_interfaces[if_num]; new_user->gets_events = 0; rv = new_user->intf->handlers->new_user(new_user->intf->send_info); if (rv) goto out_unlock; write_lock_irqsave(&(new_user->intf->users_lock), flags); list_add_tail(&(new_user->link), &(new_user->intf->users)); write_unlock_irqrestore(&(new_user->intf->users_lock), flags); out_unlock: if (rv) { kfree(new_user); } else { *user = new_user; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -