📄 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 25struct 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;};#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 { unsigned long timeout; int inuse; struct ipmi_recv_msg *recv_msg; } 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){ int 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){ int 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. */static int intf_next_seq(ipmi_smi_t intf, struct ipmi_recv_msg *recv_msg, unsigned long timeout, unsigned char *seq){ int rv = 0; unsigned long flags; unsigned int i; spin_lock_irqsave(&(intf->seq_lock), flags); for (i=intf->curr_seq; i!=(intf->curr_seq-1); 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; intf->seq_table[i].timeout = timeout; intf->seq_table[i].inuse = 1; *seq = i; intf->curr_seq = (i+1)%IPMI_IPMB_NUM_SEQ; } else { rv = -EAGAIN; } spin_unlock_irqrestore(&(intf->seq_lock), flags); 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;}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; } up_read(&interfaces_sem); return rv;}static int ipmi_destroy_user_nolock(ipmi_user_t user){ int rv = -ENODEV; ipmi_user_t t_user; struct list_head *entry, *entry2; int i; unsigned long flags; /* Find the user and delete them from the list. */ list_for_each(entry, &(user->intf->users)) { t_user = list_entry(entry, struct ipmi_user, link); if (t_user == user) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -