diff options
Diffstat (limited to 'drivers/char/ipmi')
-rw-r--r-- | drivers/char/ipmi/ipmi_msghandler.c | 205 |
1 files changed, 190 insertions, 15 deletions
diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index f42459a27b19..9157a9e17c36 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -159,6 +159,9 @@ static struct proc_dir_entry *proc_ipmi_root; */ #define IPMI_REQUEST_EV_TIME (1000 / (IPMI_TIMEOUT_TIME)) +/* How long should we cache dynamic device IDs? */ +#define IPMI_DYN_DEV_ID_EXPIRY (10 * HZ) + /* * The main "user" data structure. */ @@ -264,8 +267,12 @@ struct ipmi_proc_entry { struct bmc_device { struct platform_device pdev; - struct ipmi_device_id id; struct list_head intfs; + struct ipmi_device_id id; + struct ipmi_device_id fetch_id; + int dyn_id_set; + unsigned long dyn_id_expiry; + struct mutex dyn_mutex; /* protects id & dyn* fields */ unsigned char guid[16]; int guid_set; struct kref usecount; @@ -402,6 +409,13 @@ struct ipmi_smi { /* Used for wake ups at startup. */ wait_queue_head_t waitq; + /* + * Prevents the interface from being unregistered when the + * interface is used by being looked up through the BMC + * structure. + */ + struct mutex bmc_reg_mutex; + struct bmc_device *bmc; bool bmc_registered; struct list_head bmc_link; @@ -491,6 +505,11 @@ struct ipmi_smi { * interface comes in with a NULL user, call this routine with * it. Note that the message will still be freed by the * caller. This only works on the system interface. + * + * The only user outside of initialization an panic handling is + * the dynamic device id fetching, so no mutex is currently + * required on this. If more users come along, some sort of + * mutex will be required. */ void (*null_user_handler)(ipmi_smi_t intf, struct ipmi_recv_msg *msg); @@ -2081,14 +2100,158 @@ int ipmi_request_supply_msgs(ipmi_user_t user, } EXPORT_SYMBOL(ipmi_request_supply_msgs); +static void bmc_device_id_handler(ipmi_smi_t intf, struct ipmi_recv_msg *msg) +{ + int rv; + + if ((msg->addr.addr_type != IPMI_SYSTEM_INTERFACE_ADDR_TYPE) + || (msg->msg.netfn != IPMI_NETFN_APP_RESPONSE) + || (msg->msg.cmd != IPMI_GET_DEVICE_ID_CMD)) { + pr_warn(PFX "invalid device_id msg: addr_type=%d netfn=%x cmd=%x\n", + msg->addr.addr_type, msg->msg.netfn, msg->msg.cmd); + return; + } + + rv = ipmi_demangle_device_id(msg->msg.netfn, msg->msg.cmd, + msg->msg.data, msg->msg.data_len, &intf->bmc->fetch_id); + if (rv) { + pr_warn(PFX "device id demangle failed: %d\n", rv); + intf->bmc->dyn_id_set = 0; + } else { + /* + * Make sure the id data is available before setting + * dyn_id_set. + */ + smp_wmb(); + intf->bmc->dyn_id_set = 1; + } + + wake_up(&intf->waitq); +} + +static int +send_get_device_id_cmd(ipmi_smi_t intf) +{ + struct ipmi_system_interface_addr si; + struct kernel_ipmi_msg msg; + + si.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + si.channel = IPMI_BMC_CHANNEL; + si.lun = 0; + + msg.netfn = IPMI_NETFN_APP_REQUEST; + msg.cmd = IPMI_GET_DEVICE_ID_CMD; + msg.data = NULL; + msg.data_len = 0; + + return i_ipmi_request(NULL, + intf, + (struct ipmi_addr *) &si, + 0, + &msg, + intf, + NULL, + NULL, + 0, + intf->channels[0].address, + intf->channels[0].lun, + -1, 0); +} + +static int __get_device_id(ipmi_smi_t intf, struct bmc_device *bmc) +{ + int rv; + + bmc->dyn_id_set = 2; + + intf->null_user_handler = bmc_device_id_handler; + + rv = send_get_device_id_cmd(intf); + if (rv) + return rv; + + wait_event(intf->waitq, bmc->dyn_id_set != 2); + + if (!bmc->dyn_id_set) + rv = -EIO; /* Something went wrong in the fetch. */ + + /* dyn_id_set makes the id data available. */ + smp_rmb(); + + intf->null_user_handler = NULL; + + return rv; +} + +/* + * Fetch the device id for the bmc/interface. You must pass in either + * bmc or intf, this code will get the other one. If the data has + * been recently fetched, this will just use the cached data. Otherwise + * it will run a new fetch. + * + * Except for the first time this is called (in ipmi_register_smi()), + * this will always return good data; + */ static int bmc_get_device_id(ipmi_smi_t intf, struct bmc_device *bmc, struct ipmi_device_id *id) { - if (!bmc) + int rv = 0; + int prev_dyn_id_set; + + if (!intf) { + mutex_lock(&bmc->dyn_mutex); +retry_bmc_lock: + if (list_empty(&bmc->intfs)) { + mutex_unlock(&bmc->dyn_mutex); + return -ENOENT; + } + intf = list_first_entry(&bmc->intfs, struct ipmi_smi, + bmc_link); + kref_get(&intf->refcount); + mutex_unlock(&bmc->dyn_mutex); + mutex_lock(&intf->bmc_reg_mutex); + mutex_lock(&bmc->dyn_mutex); + if (intf != list_first_entry(&bmc->intfs, struct ipmi_smi, + bmc_link)) { + mutex_unlock(&intf->bmc_reg_mutex); + kref_put(&intf->refcount, intf_free); + goto retry_bmc_lock; + } + } else { + mutex_lock(&intf->bmc_reg_mutex); bmc = intf->bmc; + mutex_lock(&bmc->dyn_mutex); + kref_get(&intf->refcount); + } - *id = bmc->id; - return 0; + /* If we have a valid and current ID, just return that. */ + if (bmc->dyn_id_set && time_is_after_jiffies(bmc->dyn_id_expiry)) + goto out; + + prev_dyn_id_set = bmc->dyn_id_set; + + rv = __get_device_id(intf, bmc); + if (rv) + goto out; + + memcpy(&bmc->id, &bmc->fetch_id, sizeof(bmc->id)); + + bmc->dyn_id_expiry = jiffies + IPMI_DYN_DEV_ID_EXPIRY; + +out: + if (rv && prev_dyn_id_set) { + rv = 0; /* Ignore failures if we have previous data. */ + bmc->dyn_id_set = prev_dyn_id_set; + } + + if (id) + *id = bmc->id; + + mutex_unlock(&bmc->dyn_mutex); + mutex_unlock(&intf->bmc_reg_mutex); + + kref_put(&intf->refcount, intf_free); + return rv; } #ifdef CONFIG_PROC_FS @@ -2615,17 +2778,23 @@ static void ipmi_bmc_unregister(ipmi_smi_t intf) if (!intf->bmc_registered) return; + mutex_lock(&intf->bmc_reg_mutex); + sysfs_remove_link(&intf->si_dev->kobj, "bmc"); sysfs_remove_link(&bmc->pdev.dev.kobj, intf->my_dev_name); kfree(intf->my_dev_name); intf->my_dev_name = NULL; - mutex_lock(&ipmidriver_mutex); + mutex_lock(&bmc->dyn_mutex); list_del(&intf->bmc_link); + mutex_unlock(&bmc->dyn_mutex); intf->bmc = NULL; + mutex_lock(&ipmidriver_mutex); kref_put(&bmc->usecount, cleanup_bmc_device); mutex_unlock(&ipmidriver_mutex); intf->bmc_registered = false; + + mutex_unlock(&intf->bmc_reg_mutex); } static int ipmi_bmc_register(ipmi_smi_t intf, int ifnum) @@ -2653,11 +2822,11 @@ static int ipmi_bmc_register(ipmi_smi_t intf, int ifnum) */ if (old_bmc) { kfree(bmc); - mutex_lock(&ipmidriver_mutex); + bmc = old_bmc; intf->bmc = old_bmc; + mutex_lock(&bmc->dyn_mutex); list_add_tail(&intf->bmc_link, &bmc->intfs); - mutex_unlock(&ipmidriver_mutex); - bmc = old_bmc; + mutex_unlock(&bmc->dyn_mutex); printk(KERN_INFO "ipmi: interfacing existing BMC (man_id: 0x%6.6x," @@ -2677,10 +2846,12 @@ static int ipmi_bmc_register(ipmi_smi_t intf, int ifnum) bmc->pdev.dev.type = &bmc_device_type; kref_init(&bmc->usecount); - rv = platform_device_register(&bmc->pdev); - mutex_lock(&ipmidriver_mutex); + intf->bmc = bmc; + mutex_lock(&bmc->dyn_mutex); list_add_tail(&intf->bmc_link, &bmc->intfs); - mutex_unlock(&ipmidriver_mutex); + mutex_unlock(&bmc->dyn_mutex); + + rv = platform_device_register(&bmc->pdev); if (rv) { printk(KERN_ERR "ipmi_msghandler:" @@ -2743,18 +2914,20 @@ out_unlink1: sysfs_remove_link(&intf->si_dev->kobj, "bmc"); out_put_bmc: - mutex_lock(&ipmidriver_mutex); + mutex_lock(&bmc->dyn_mutex); list_del(&intf->bmc_link); + mutex_unlock(&bmc->dyn_mutex); intf->bmc = NULL; + mutex_lock(&ipmidriver_mutex); kref_put(&bmc->usecount, cleanup_bmc_device); mutex_unlock(&ipmidriver_mutex); goto out; out_list_del: - mutex_lock(&ipmidriver_mutex); + mutex_lock(&bmc->dyn_mutex); list_del(&intf->bmc_link); + mutex_unlock(&bmc->dyn_mutex); intf->bmc = NULL; - mutex_unlock(&ipmidriver_mutex); put_device(&bmc->pdev.dev); goto out; } @@ -2976,9 +3149,11 @@ int ipmi_register_smi(const struct ipmi_smi_handlers *handlers, return -ENOMEM; } INIT_LIST_HEAD(&intf->bmc->intfs); + mutex_init(&intf->bmc->dyn_mutex); + INIT_LIST_HEAD(&intf->bmc_link); + mutex_init(&intf->bmc_reg_mutex); intf->intf_num = -1; /* Mark it invalid for now. */ kref_init(&intf->refcount); - intf->bmc->id = *device_id; intf->si_dev = si_dev; for (j = 0; j < IPMI_MAX_CHANNELS; j++) { intf->channels[j].address = IPMI_BMC_SLAVE_ADDR; |