summaryrefslogtreecommitdiff
path: root/drivers/char/ipmi/ipmi_msghandler.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/ipmi/ipmi_msghandler.c')
-rw-r--r--drivers/char/ipmi/ipmi_msghandler.c205
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;