diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/char/ipmi/Kconfig | 14 | ||||
-rw-r--r-- | drivers/char/ipmi/Makefile | 2 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_msghandler.c | 560 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_powernv.c | 310 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_si_intf.c | 497 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_ssif.c | 1870 |
6 files changed, 2748 insertions, 505 deletions
diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index db1c9b7..6ed9e9f 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig @@ -62,6 +62,20 @@ config IPMI_SI_PROBE_DEFAULTS only be available on older systems if the "ipmi_si_intf.trydefaults=1" boot argument is passed. +config IPMI_SSIF + tristate 'IPMI SMBus handler (SSIF)' + select I2C + help + Provides a driver for a SMBus interface to a BMC, meaning that you + have a driver that must be accessed over an I2C bus instead of a + standard interface. This module requires I2C support. + +config IPMI_POWERNV + depends on PPC_POWERNV + tristate 'POWERNV (OPAL firmware) IPMI interface' + help + Provides a driver for OPAL firmware-based IPMI interfaces. + config IPMI_WATCHDOG tristate 'IPMI Watchdog Timer' help diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile index 16a9364..f3ffde1 100644 --- a/drivers/char/ipmi/Makefile +++ b/drivers/char/ipmi/Makefile @@ -7,5 +7,7 @@ ipmi_si-y := ipmi_si_intf.o ipmi_kcs_sm.o ipmi_smic_sm.o ipmi_bt_sm.o obj-$(CONFIG_IPMI_HANDLER) += ipmi_msghandler.o obj-$(CONFIG_IPMI_DEVICE_INTERFACE) += ipmi_devintf.o obj-$(CONFIG_IPMI_SI) += ipmi_si.o +obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o +obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index f816211..5fa83f7 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -56,6 +56,8 @@ static int ipmi_init_msghandler(void); static void smi_recv_tasklet(unsigned long); static void handle_new_recv_msgs(ipmi_smi_t intf); static void need_waiter(ipmi_smi_t intf); +static int handle_one_recv_msg(ipmi_smi_t intf, + struct ipmi_smi_msg *msg); static int initialized; @@ -191,12 +193,12 @@ struct ipmi_proc_entry { #endif struct bmc_device { - struct platform_device *dev; + struct platform_device pdev; struct ipmi_device_id id; unsigned char guid[16]; int guid_set; - - struct kref refcount; + char name[16]; + struct kref usecount; /* bmc device attributes */ struct device_attribute device_id_attr; @@ -210,6 +212,7 @@ struct bmc_device { struct device_attribute guid_attr; struct device_attribute aux_firmware_rev_attr; }; +#define to_bmc_device(x) container_of((x), struct bmc_device, pdev.dev) /* * Various statistics for IPMI, these index stats[] in the ipmi_smi @@ -323,6 +326,9 @@ struct ipmi_smi { struct kref refcount; + /* Set when the interface is being unregistered. */ + bool in_shutdown; + /* Used for a list of interfaces. */ struct list_head link; @@ -341,7 +347,6 @@ struct ipmi_smi { struct bmc_device *bmc; char *my_dev_name; - char *sysfs_name; /* * This is the lower-layer's sender routine. Note that you @@ -377,11 +382,16 @@ struct ipmi_smi { * periodic timer interrupt. The tasklet is for handling received * messages directly from the handler. */ - spinlock_t waiting_msgs_lock; - struct list_head waiting_msgs; + spinlock_t waiting_rcv_msgs_lock; + struct list_head waiting_rcv_msgs; atomic_t watchdog_pretimeouts_to_deliver; struct tasklet_struct recv_tasklet; + spinlock_t xmit_msgs_lock; + struct list_head xmit_msgs; + struct ipmi_smi_msg *curr_msg; + struct list_head hp_xmit_msgs; + /* * The list of command receivers that are registered for commands * on this interface. @@ -474,6 +484,18 @@ static DEFINE_MUTEX(smi_watchers_mutex); #define ipmi_get_stat(intf, stat) \ ((unsigned int) atomic_read(&(intf)->stats[IPMI_STAT_ ## stat])) +static char *addr_src_to_str[] = { "invalid", "hotmod", "hardcoded", "SPMI", + "ACPI", "SMBIOS", "PCI", + "device-tree", "default" }; + +const char *ipmi_addr_src_to_str(enum ipmi_addr_src src) +{ + if (src > SI_DEFAULT) + src = 0; /* Invalid */ + return addr_src_to_str[src]; +} +EXPORT_SYMBOL(ipmi_addr_src_to_str); + static int is_lan_addr(struct ipmi_addr *addr) { return addr->addr_type == IPMI_LAN_ADDR_TYPE; @@ -517,7 +539,7 @@ static void clean_up_interface_data(ipmi_smi_t intf) tasklet_kill(&intf->recv_tasklet); - free_smi_msg_list(&intf->waiting_msgs); + free_smi_msg_list(&intf->waiting_rcv_msgs); free_recv_msg_list(&intf->waiting_events); /* @@ -1473,6 +1495,30 @@ static inline void format_lan_msg(struct ipmi_smi_msg *smi_msg, smi_msg->msgid = msgid; } +static void smi_send(ipmi_smi_t intf, struct ipmi_smi_handlers *handlers, + struct ipmi_smi_msg *smi_msg, int priority) +{ + int run_to_completion = intf->run_to_completion; + unsigned long flags; + + if (!run_to_completion) + spin_lock_irqsave(&intf->xmit_msgs_lock, flags); + if (intf->curr_msg) { + if (priority > 0) + list_add_tail(&smi_msg->link, &intf->hp_xmit_msgs); + else + list_add_tail(&smi_msg->link, &intf->xmit_msgs); + smi_msg = NULL; + } else { + intf->curr_msg = smi_msg; + } + if (!run_to_completion) + spin_unlock_irqrestore(&intf->xmit_msgs_lock, flags); + + if (smi_msg) + handlers->sender(intf->send_info, smi_msg); +} + /* * Separate from ipmi_request so that the user does not have to be * supplied in certain circumstances (mainly at panic time). If @@ -1497,7 +1543,6 @@ static int i_ipmi_request(ipmi_user_t user, struct ipmi_smi_msg *smi_msg; struct ipmi_recv_msg *recv_msg; unsigned long flags; - struct ipmi_smi_handlers *handlers; if (supplied_recv) @@ -1520,8 +1565,7 @@ static int i_ipmi_request(ipmi_user_t user, } rcu_read_lock(); - handlers = intf->handlers; - if (!handlers) { + if (intf->in_shutdown) { rv = -ENODEV; goto out_err; } @@ -1856,7 +1900,7 @@ static int i_ipmi_request(ipmi_user_t user, } #endif - handlers->sender(intf->send_info, smi_msg, priority); + smi_send(intf, intf->handlers, smi_msg, priority); rcu_read_unlock(); return 0; @@ -2153,7 +2197,7 @@ static void remove_proc_entries(ipmi_smi_t smi) static int __find_bmc_guid(struct device *dev, void *data) { unsigned char *id = data; - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return memcmp(bmc->guid, id, 16) == 0; } @@ -2164,7 +2208,7 @@ static struct bmc_device *ipmi_find_bmc_guid(struct device_driver *drv, dev = driver_find_device(drv, NULL, guid, __find_bmc_guid); if (dev) - return dev_get_drvdata(dev); + return to_bmc_device(dev); else return NULL; } @@ -2177,7 +2221,7 @@ struct prod_dev_id { static int __find_bmc_prod_dev_id(struct device *dev, void *data) { struct prod_dev_id *id = data; - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return (bmc->id.product_id == id->product_id && bmc->id.device_id == id->device_id); @@ -2195,7 +2239,7 @@ static struct bmc_device *ipmi_find_bmc_prod_dev_id( dev = driver_find_device(drv, NULL, &id, __find_bmc_prod_dev_id); if (dev) - return dev_get_drvdata(dev); + return to_bmc_device(dev); else return NULL; } @@ -2204,84 +2248,92 @@ static ssize_t device_id_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return snprintf(buf, 10, "%u\n", bmc->id.device_id); } +DEVICE_ATTR(device_id, S_IRUGO, device_id_show, NULL); -static ssize_t provides_dev_sdrs_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t provides_device_sdrs_show(struct device *dev, + struct device_attribute *attr, + char *buf) { - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return snprintf(buf, 10, "%u\n", (bmc->id.device_revision & 0x80) >> 7); } +DEVICE_ATTR(provides_device_sdrs, S_IRUGO, provides_device_sdrs_show, NULL); static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return snprintf(buf, 20, "%u\n", bmc->id.device_revision & 0x0F); } +DEVICE_ATTR(revision, S_IRUGO, revision_show, NULL); -static ssize_t firmware_rev_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t firmware_revision_show(struct device *dev, + struct device_attribute *attr, + char *buf) { - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return snprintf(buf, 20, "%u.%x\n", bmc->id.firmware_revision_1, bmc->id.firmware_revision_2); } +DEVICE_ATTR(firmware_revision, S_IRUGO, firmware_revision_show, NULL); static ssize_t ipmi_version_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return snprintf(buf, 20, "%u.%u\n", ipmi_version_major(&bmc->id), ipmi_version_minor(&bmc->id)); } +DEVICE_ATTR(ipmi_version, S_IRUGO, ipmi_version_show, NULL); static ssize_t add_dev_support_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return snprintf(buf, 10, "0x%02x\n", bmc->id.additional_device_support); } +DEVICE_ATTR(additional_device_support, S_IRUGO, add_dev_support_show, NULL); static ssize_t manufacturer_id_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return snprintf(buf, 20, "0x%6.6x\n", bmc->id.manufacturer_id); } +DEVICE_ATTR(manufacturer_id, S_IRUGO, manufacturer_id_show, NULL); static ssize_t product_id_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return snprintf(buf, 10, "0x%4.4x\n", bmc->id.product_id); } +DEVICE_ATTR(product_id, S_IRUGO, product_id_show, NULL); static ssize_t aux_firmware_rev_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return snprintf(buf, 21, "0x%02x 0x%02x 0x%02x 0x%02x\n", bmc->id.aux_firmware_revision[3], @@ -2289,174 +2341,96 @@ static ssize_t aux_firmware_rev_show(struct device *dev, bmc->id.aux_firmware_revision[1], bmc->id.aux_firmware_revision[0]); } +DEVICE_ATTR(aux_firmware_revision, S_IRUGO, aux_firmware_rev_show, NULL); static ssize_t guid_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct bmc_device *bmc = dev_get_drvdata(dev); + struct bmc_device *bmc = to_bmc_device(dev); return snprintf(buf, 100, "%Lx%Lx\n", (long long) bmc->guid[0], (long long) bmc->guid[8]); } +DEVICE_ATTR(guid, S_IRUGO, guid_show, NULL); + +static struct attribute *bmc_dev_attrs[] = { + &dev_attr_device_id.attr, + &dev_attr_provides_device_sdrs.attr, + &dev_attr_revision.attr, + &dev_attr_firmware_revision.attr, + &dev_attr_ipmi_version.attr, + &dev_attr_additional_device_support.attr, + &dev_attr_manufacturer_id.attr, + &dev_attr_product_id.attr, + NULL +}; -static void remove_files(struct bmc_device *bmc) -{ - if (!bmc->dev) - return; +static struct attribute_group bmc_dev_attr_group = { + .attrs = bmc_dev_attrs, +}; - device_remove_file(&bmc->dev->dev, - &bmc->device_id_attr); - device_remove_file(&bmc->dev->dev, - &bmc->provides_dev_sdrs_attr); - device_remove_file(&bmc->dev->dev, - &bmc->revision_attr); - device_remove_file(&bmc->dev->dev, - &bmc->firmware_rev_attr); - device_remove_file(&bmc->dev->dev, - &bmc->version_attr); - device_remove_file(&bmc->dev->dev, - &bmc->add_dev_support_attr); - device_remove_file(&bmc->dev->dev, - &bmc->manufacturer_id_attr); - device_remove_file(&bmc->dev->dev, - &bmc->product_id_attr); +static const struct attribute_group *bmc_dev_attr_groups[] = { + &bmc_dev_attr_group, + NULL +}; - if (bmc->id.aux_firmware_revision_set) - device_remove_file(&bmc->dev->dev, - &bmc->aux_firmware_rev_attr); - if (bmc->guid_set) - device_remove_file(&bmc->dev->dev, - &bmc->guid_attr); +static struct device_type bmc_device_type = { + .groups = bmc_dev_attr_groups, +}; + +static void +release_bmc_device(struct device *dev) +{ + kfree(to_bmc_device(dev)); } static void cleanup_bmc_device(struct kref *ref) { - struct bmc_device *bmc; + struct bmc_device *bmc = container_of(ref, struct bmc_device, usecount); - bmc = container_of(ref, struct bmc_device, refcount); + if (bmc->id.aux_firmware_revision_set) + device_remove_file(&bmc->pdev.dev, + &bmc->aux_firmware_rev_attr); + if (bmc->guid_set) + device_remove_file(&bmc->pdev.dev, + &bmc->guid_attr); - remove_files(bmc); - platform_device_unregister(bmc->dev); - kfree(bmc); + platform_device_unregister(&bmc->pdev); } static void ipmi_bmc_unregister(ipmi_smi_t intf) { struct bmc_device *bmc = intf->bmc; - if (intf->sysfs_name) { - sysfs_remove_link(&intf->si_dev->kobj, intf->sysfs_name); - kfree(intf->sysfs_name); - intf->sysfs_name = NULL; - } + sysfs_remove_link(&intf->si_dev->kobj, "bmc"); if (intf->my_dev_name) { - sysfs_remove_link(&bmc->dev->dev.kobj, intf->my_dev_name); + 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); - kref_put(&bmc->refcount, cleanup_bmc_device); + kref_put(&bmc->usecount, cleanup_bmc_device); intf->bmc = NULL; mutex_unlock(&ipmidriver_mutex); } -static int create_files(struct bmc_device *bmc) +static int create_bmc_files(struct bmc_device *bmc) { int err; - bmc->device_id_attr.attr.name = "device_id"; - bmc->device_id_attr.attr.mode = S_IRUGO; - bmc->device_id_attr.show = device_id_show; - sysfs_attr_init(&bmc->device_id_attr.attr); - - bmc->provides_dev_sdrs_attr.attr.name = "provides_device_sdrs"; - bmc->provides_dev_sdrs_attr.attr.mode = S_IRUGO; - bmc->provides_dev_sdrs_attr.show = provides_dev_sdrs_show; - sysfs_attr_init(&bmc->provides_dev_sdrs_attr.attr); - - bmc->revision_attr.attr.name = "revision"; - bmc->revision_attr.attr.mode = S_IRUGO; - bmc->revision_attr.show = revision_show; - sysfs_attr_init(&bmc->revision_attr.attr); - - bmc->firmware_rev_attr.attr.name = "firmware_revision"; - bmc->firmware_rev_attr.attr.mode = S_IRUGO; - bmc->firmware_rev_attr.show = firmware_rev_show; - sysfs_attr_init(&bmc->firmware_rev_attr.attr); - - bmc->version_attr.attr.name = "ipmi_version"; - bmc->version_attr.attr.mode = S_IRUGO; - bmc->version_attr.show = ipmi_version_show; - sysfs_attr_init(&bmc->version_attr.attr); - - bmc->add_dev_support_attr.attr.name = "additional_device_support"; - bmc->add_dev_support_attr.attr.mode = S_IRUGO; - bmc->add_dev_support_attr.show = add_dev_support_show; - sysfs_attr_init(&bmc->add_dev_support_attr.attr); - - bmc->manufacturer_id_attr.attr.name = "manufacturer_id"; - bmc->manufacturer_id_attr.attr.mode = S_IRUGO; - bmc->manufacturer_id_attr.show = manufacturer_id_show; - sysfs_attr_init(&bmc->manufacturer_id_attr.attr); - - bmc->product_id_attr.attr.name = "product_id"; - bmc->product_id_attr.attr.mode = S_IRUGO; - bmc->product_id_attr.show = product_id_show; - sysfs_attr_init(&bmc->product_id_attr.attr); - - bmc->guid_attr.attr.name = "guid"; - bmc->guid_attr.attr.mode = S_IRUGO; - bmc->guid_attr.show = guid_show; - sysfs_attr_init(&bmc->guid_attr.attr); - - bmc->aux_firmware_rev_attr.attr.name = "aux_firmware_revision"; - bmc->aux_firmware_rev_attr.attr.mode = S_IRUGO; - bmc->aux_firmware_rev_attr.show = aux_firmware_rev_show; - sysfs_attr_init(&bmc->aux_firmware_rev_attr.attr); - - err = device_create_file(&bmc->dev->dev, - &bmc->device_id_attr); - if (err) - goto out; - err = device_create_file(&bmc->dev->dev, - &bmc->provides_dev_sdrs_attr); - if (err) - goto out_devid; - err = device_create_file(&bmc->dev->dev, - &bmc->revision_attr); - if (err) - goto out_sdrs; - err = device_create_file(&bmc->dev->dev, - &bmc->firmware_rev_attr); - if (err) - goto out_rev; - err = device_create_file(&bmc->dev->dev, - &bmc->version_attr); - if (err) - goto out_firm; - err = device_create_file(&bmc->dev->dev, - &bmc->add_dev_support_attr); - if (err) - goto out_version; - err = device_create_file(&bmc->dev->dev, - &bmc->manufacturer_id_attr); - if (err) - goto out_add_dev; - err = device_create_file(&bmc->dev->dev, - &bmc->product_id_attr); - if (err) - goto out_manu; if (bmc->id.aux_firmware_revision_set) { - err = device_create_file(&bmc->dev->dev, + bmc->aux_firmware_rev_attr.attr.name = "aux_firmware_revision"; + err = device_create_file(&bmc->pdev.dev, &bmc->aux_firmware_rev_attr); if (err) - goto out_prod_id; + goto out; } if (bmc->guid_set) { - err = device_create_file(&bmc->dev->dev, + bmc->guid_attr.attr.name = "guid"; + err = device_create_file(&bmc->pdev.dev, &bmc->guid_attr); if (err) goto out_aux_firm; @@ -2466,44 +2440,17 @@ static int create_files(struct bmc_device *bmc) out_aux_firm: if (bmc->id.aux_firmware_revision_set) - device_remove_file(&bmc->dev->dev, + device_remove_file(&bmc->pdev.dev, &bmc->aux_firmware_rev_attr); -out_prod_id: - device_remove_file(&bmc->dev->dev, - &bmc->product_id_attr); -out_manu: - device_remove_file(&bmc->dev->dev, - &bmc->manufacturer_id_attr); -out_add_dev: - device_remove_file(&bmc->dev->dev, - &bmc->add_dev_support_attr); -out_version: - device_remove_file(&bmc->dev->dev, - &bmc->version_attr); -out_firm: - device_remove_file(&bmc->dev->dev, - &bmc->firmware_rev_attr); -out_rev: - device_remove_file(&bmc->dev->dev, - &bmc->revision_attr); -out_sdrs: - device_remove_file(&bmc->dev->dev, - &bmc->provides_dev_sdrs_attr); -out_devid: - device_remove_file(&bmc->dev->dev, - &bmc->device_id_attr); out: return err; } -static int ipmi_bmc_register(ipmi_smi_t intf, int ifnum, - const char *sysfs_name) +static int ipmi_bmc_register(ipmi_smi_t intf, int ifnum) { int rv; struct bmc_device *bmc = intf->bmc; struct bmc_device *old_bmc; - int size; - char dummy[1]; mutex_lock(&ipmidriver_mutex); @@ -2527,7 +2474,7 @@ static int ipmi_bmc_register(ipmi_smi_t intf, int ifnum, intf->bmc = old_bmc; bmc = old_bmc; - kref_get(&bmc->refcount); + kref_get(&bmc->usecount); mutex_unlock(&ipmidriver_mutex); printk(KERN_INFO @@ -2537,12 +2484,12 @@ static int ipmi_bmc_register(ipmi_smi_t intf, int ifnum, bmc->id.product_id, bmc->id.device_id); } else { - char name[14]; unsigned char orig_dev_id = bmc->id.device_id; int warn_printed = 0; - snprintf(name, sizeof(name), + snprintf(bmc->name, sizeof(bmc->name), "ipmi_bmc.%4.4x", bmc->id.product_id); + bmc->pdev.name = bmc->name; while (ipmi_find_bmc_prod_dev_id(&ipmidriver.driver, bmc->id.product_id, @@ -2566,23 +2513,16 @@ static int ipmi_bmc_register(ipmi_smi_t intf, int ifnum, } } - bmc->dev = platform_device_alloc(name, bmc->id.device_id); - if (!bmc->dev) { - mutex_unlock(&ipmidriver_mutex); - printk(KERN_ERR - "ipmi_msghandler:" - " Unable to allocate platform device\n"); - return -ENOMEM; - } - bmc->dev->dev.driver = &ipmidriver.driver; - dev_set_drvdata(&bmc->dev->dev, bmc); - kref_init(&bmc->refcount); + bmc->pdev.dev.driver = &ipmidriver.driver; + bmc->pdev.id = bmc->id.device_id; + bmc->pdev.dev.release = release_bmc_device; + bmc->pdev.dev.type = &bmc_device_type; + kref_init(&bmc->usecount); - rv = platform_device_add(bmc->dev); + rv = platform_device_register(&bmc->pdev); mutex_unlock(&ipmidriver_mutex); if (rv) { - platform_device_put(bmc->dev); - bmc->dev = NULL; + put_device(&bmc->pdev.dev); printk(KERN_ERR "ipmi_msghandler:" " Unable to register bmc device: %d\n", @@ -2594,10 +2534,10 @@ static int ipmi_bmc_register(ipmi_smi_t intf, int ifnum, return rv; } - rv = create_files(bmc); + rv = create_bmc_files(bmc); if (rv) { mutex_lock(&ipmidriver_mutex); - platform_device_unregister(bmc->dev); + platform_device_unregister(&bmc->pdev); mutex_unlock(&ipmidriver_mutex); return rv; @@ -2614,44 +2554,26 @@ static int ipmi_bmc_register(ipmi_smi_t intf, int ifnum, * create symlink from system interface device to bmc device * and back. */ - intf->sysfs_name = kstrdup(sysfs_name, GFP_KERNEL); - if (!intf->sysfs_name) { - rv = -ENOMEM; - printk(KERN_ERR - "ipmi_msghandler: allocate link to BMC: %d\n", - rv); - goto out_err; - } - - rv = sysfs_create_link(&intf->si_dev->kobj, - &bmc->dev->dev.kobj, intf->sysfs_name); + rv = sysfs_create_link(&intf->si_dev->kobj, &bmc->pdev.dev.kobj, "bmc"); if (rv) { - kfree(intf->sysfs_name); - intf->sysfs_name = NULL; printk(KERN_ERR "ipmi_msghandler: Unable to create bmc symlink: %d\n", rv); goto out_err; } - size = snprintf(dummy, 0, "ipmi%d", ifnum); - intf->my_dev_name = kmalloc(size+1, GFP_KERNEL); + intf->my_dev_name = kasprintf(GFP_KERNEL, "ipmi%d", ifnum); if (!intf->my_dev_name) { - kfree(intf->sysfs_name); - intf->sysfs_name = NULL; rv = -ENOMEM; printk(KERN_ERR "ipmi_msghandler: allocate link from BMC: %d\n", rv); goto out_err; } - snprintf(intf->my_dev_name, size+1, "ipmi%d", ifnum); - rv = sysfs_create_link(&bmc->dev->dev.kobj, &intf->si_dev->kobj, + rv = sysfs_create_link(&bmc->pdev.dev.kobj, &intf->si_dev->kobj, intf->my_dev_name); if (rv) { - kfree(intf->sysfs_name); - intf->sysfs_name = NULL; kfree(intf->my_dev_name); intf->my_dev_name = NULL; printk(KERN_ERR @@ -2850,7 +2772,6 @@ int ipmi_register_smi(struct ipmi_smi_handlers *handlers, void *send_info, struct ipmi_device_id *device_id, struct device *si_dev, - const char *sysfs_name, unsigned char slave_addr) { int i, j; @@ -2909,12 +2830,15 @@ int ipmi_register_smi(struct ipmi_smi_handlers *handlers, #ifdef CONFIG_PROC_FS mutex_init(&intf->proc_entry_lock); #endif - spin_lock_init(&intf->waiting_msgs_lock); - INIT_LIST_HEAD(&intf->waiting_msgs); + spin_lock_init(&intf->waiting_rcv_msgs_lock); + INIT_LIST_HEAD(&intf->waiting_rcv_msgs); tasklet_init(&intf->recv_tasklet, smi_recv_tasklet, (unsigned long) intf); atomic_set(&intf->watchdog_pretimeouts_to_deliver, 0); + spin_lock_init(&intf->xmit_msgs_lock); + INIT_LIST_HEAD(&intf->xmit_msgs); + INIT_LIST_HEAD(&intf->hp_xmit_msgs); spin_lock_init(&intf->events_lock); atomic_set(&intf->event_waiters, 0); intf->ticks_to_req_ev = IPMI_REQUEST_EV_TIME; @@ -2984,7 +2908,7 @@ int ipmi_register_smi(struct ipmi_smi_handlers *handlers, if (rv == 0) rv = add_proc_entries(intf, i); - rv = ipmi_bmc_register(intf, i, sysfs_name); + rv = ipmi_bmc_register(intf, i); out: if (rv) { @@ -3014,12 +2938,50 @@ int ipmi_register_smi(struct ipmi_smi_handlers *handlers, } EXPORT_SYMBOL(ipmi_register_smi); +static void deliver_smi_err_response(ipmi_smi_t intf, + struct ipmi_smi_msg *msg, + unsigned char err) +{ + msg->rsp[0] = msg->data[0] | 4; + msg->rsp[1] = msg->data[1]; + msg->rsp[2] = err; + msg->rsp_size = 3; + /* It's an error, so it will never requeue, no need to check return. */ + handle_one_recv_msg(intf, msg); +} + static void cleanup_smi_msgs(ipmi_smi_t intf) { int i; struct seq_table *ent; + struct ipmi_smi_msg *msg; + struct list_head *entry; + struct list_head tmplist; + + /* Clear out our transmit queues and hold the messages. */ + INIT_LIST_HEAD(&tmplist); + list_splice_tail(&intf->hp_xmit_msgs, &tmplist); + list_splice_tail(&intf->xmit_msgs, &tmplist); + + /* Current message first, to preserve order */ + while (intf->curr_msg && !list_empty(&intf->waiting_rcv_msgs)) { + /* Wait for the message to clear out. */ + schedule_timeout(1); + } /* No need for locks, the interface is down. */ + + /* + * Return errors for all pending messages in queue and in the + * tables waiting for remote responses. + */ + while (!list_empty(&tmplist)) { + entry = tmplist.next; + list_del(entry); + msg = list_entry(entry, struct ipmi_smi_msg, link); + deliver_smi_err_response(intf, msg, IPMI_ERR_UNSPECIFIED); + } + for (i = 0; i < IPMI_IPMB_NUM_SEQ; i++) { ent = &(intf->seq_table[i]); if (!ent->inuse) @@ -3031,20 +2993,33 @@ static void cleanup_smi_msgs(ipmi_smi_t intf) int ipmi_unregister_smi(ipmi_smi_t intf) { struct ipmi_smi_watcher *w; - int intf_num = intf->intf_num; + int intf_num = intf->intf_num; + ipmi_user_t user; ipmi_bmc_unregister(intf); mutex_lock(&smi_watchers_mutex); mutex_lock(&ipmi_interfaces_mutex); intf->intf_num = -1; - intf->handlers = NULL; + intf->in_shutdown = true; list_del_rcu(&intf->link); mutex_unlock(&ipmi_interfaces_mutex); synchronize_rcu(); cleanup_smi_msgs(intf); + /* Clean up the effects of users on the lower-level software. */ + mutex_lock(&ipmi_interfaces_mutex); + rcu_read_lock(); + list_for_each_entry_rcu(user, &intf->users, link) { + module_put(intf->handlers->owner); + if (intf->handlers->dec_usecount) + intf->handlers->dec_usecount(intf->send_info); + } + rcu_read_unlock(); + intf->handlers = NULL; + mutex_unlock(&ipmi_interfaces_mutex); + remove_proc_entries(intf); /* @@ -3134,7 +3109,6 @@ static int handle_ipmb_get_msg_cmd(ipmi_smi_t intf, ipmi_user_t user = NULL; struct ipmi_ipmb_addr *ipmb_addr; struct ipmi_recv_msg *recv_msg; - struct ipmi_smi_handlers *handlers; if (msg->rsp_size < 10) { /* Message not big enough, just ignore it. */ @@ -3188,9 +3162,8 @@ static int handle_ipmb_get_msg_cmd(ipmi_smi_t intf, } #endif rcu_read_lock(); - handlers = intf->handlers; - if (handlers) { - handlers->sender(intf->send_info, msg, 0); + if (!intf->in_shutdown) { + smi_send(intf, intf->handlers, msg, 0); /* * We used the message, so return the value * that causes it to not be freed or @@ -3857,32 +3830,32 @@ static void handle_new_recv_msgs(ipmi_smi_t intf) /* See if any waiting messages need to be processed. */ if (!run_to_completion) - spin_lock_irqsave(&intf->waiting_msgs_lock, flags); - while (!list_empty(&intf->waiting_msgs)) { - smi_msg = list_entry(intf->waiting_msgs.next, + spin_lock_irqsave(&intf->waiting_rcv_msgs_lock, flags); + while (!list_empty(&intf->waiting_rcv_msgs)) { + smi_msg = list_entry(intf->waiting_rcv_msgs.next, struct ipmi_smi_msg, link); - list_del(&smi_msg->link); if (!run_to_completion) - spin_unlock_irqrestore(&intf->waiting_msgs_lock, flags); + spin_unlock_irqrestore(&intf->waiting_rcv_msgs_lock, + flags); rv = handle_one_recv_msg(intf, smi_msg); if (!run_to_completion) - spin_lock_irqsave(&intf->waiting_msgs_lock, flags); - if (rv == 0) { - /* Message handled */ - ipmi_free_smi_msg(smi_msg); - } else if (rv < 0) { - /* Fatal error on the message, del but don't free. */ - } else { + spin_lock_irqsave(&intf->waiting_rcv_msgs_lock, flags); + if (rv > 0) { /* * To preserve message order, quit if we * can't handle a message. */ - list_add(&smi_msg->link, &intf->waiting_msgs); break; + } else { + list_del(&smi_msg->link); + if (rv == 0) + /* Message handled */ + ipmi_free_smi_msg(smi_msg); + /* If rv < 0, fatal error, del but don't free. */ } } if (!run_to_completion) - spin_unlock_irqrestore(&intf->waiting_msgs_lock, flags); + spin_unlock_irqrestore(&intf->waiting_rcv_msgs_lock, flags); /* * If the pretimout count is non-zero, decrement one from it and @@ -3903,7 +3876,41 @@ static void handle_new_recv_msgs(ipmi_smi_t intf) static void smi_recv_tasklet(unsigned long val) { - handle_new_recv_msgs((ipmi_smi_t) val); + unsigned long flags = 0; /* keep us warning-free. */ + ipmi_smi_t intf = (ipmi_smi_t) val; + int run_to_completion = intf->run_to_completion; + struct ipmi_smi_msg *newmsg = NULL; + + /* + * Start the next message if available. + * + * Do this here, not in the actual receiver, because we may deadlock + * because the lower layer is allowed to hold locks while calling + * message delivery. + */ + if (!run_to_completion) + spin_lock_irqsave(&intf->xmit_msgs_lock, flags); + if (intf->curr_msg == NULL && !intf->in_shutdown) { + struct list_head *entry = NULL; + + /* Pick the high priority queue first. */ + if (!list_empty(&intf->hp_xmit_msgs)) + entry = intf->hp_xmit_msgs.next; + else if (!list_empty(&intf->xmit_msgs)) + entry = intf->xmit_msgs.next; + + if (entry) { + list_del(entry); + newmsg = list_entry(entry, struct ipmi_smi_msg, link); + intf->curr_msg = newmsg; + } + } + if (!run_to_completion) + spin_unlock_irqrestore(&intf->xmit_msgs_lock, flags); + if (newmsg) + intf->handlers->sender(intf->send_info, newmsg); + + handle_new_recv_msgs(intf); } /* Handle a new message from the lower layer. */ @@ -3911,13 +3918,16 @@ void ipmi_smi_msg_received(ipmi_smi_t intf, struct ipmi_smi_msg *msg) { unsigned long flags = 0; /* keep us warning-free. */ - int run_to_completion; - + int run_to_completion = intf->run_to_completion; if ((msg->data_size >= 2) && (msg->data[0] == (IPMI_NETFN_APP_REQUEST << 2)) && (msg->data[1] == IPMI_SEND_MSG_CMD) && (msg->user_data == NULL)) { + + if (intf->in_shutdown) + goto free_msg; + /* * This is the local response to a command send, start * the timer for these. The user_data will not be @@ -3953,29 +3963,40 @@ void ipmi_smi_msg_received(ipmi_smi_t intf, /* The message was sent, start the timer. */ intf_start_seq_timer(intf, msg->msgid); +free_msg: ipmi_free_smi_msg(msg); - goto out; + } else { + /* + * To preserve message order, we keep a queue and deliver from + * a tasklet. + */ + if (!run_to_completion) + spin_lock_irqsave(&intf->waiting_rcv_msgs_lock, flags); + list_add_tail(&msg->link, &intf->waiting_rcv_msgs); + if (!run_to_completion) + spin_unlock_irqrestore(&intf->waiting_rcv_msgs_lock, + flags); } - /* - * To preserve message order, if the list is not empty, we - * tack this message onto the end of the list. - */ - run_to_completion = intf->run_to_completion; if (!run_to_completion) - spin_lock_irqsave(&intf->waiting_msgs_lock, flags); - list_add_tail(&msg->link, &intf->waiting_msgs); + spin_lock_irqsave(&intf->xmit_msgs_lock, flags); + if (msg == intf->curr_msg) + intf->curr_msg = NULL; if (!run_to_completion) - spin_unlock_irqrestore(&intf->waiting_msgs_lock, flags); + spin_unlock_irqrestore(&intf->xmit_msgs_lock, flags); - tasklet_schedule(&intf->recv_tasklet); - out: - return; + if (run_to_completion) + smi_recv_tasklet((unsigned long) intf); + else + tasklet_schedule(&intf->recv_tasklet); } EXPORT_SYMBOL(ipmi_smi_msg_received); void ipmi_smi_watchdog_pretimeout(ipmi_smi_t intf) { + if (intf->in_shutdown) + return; + atomic_set(&intf->watchdog_pretimeouts_to_deliver, 1); tasklet_schedule(&intf->recv_tasklet); } @@ -4017,7 +4038,7 @@ static void check_msg_timeout(ipmi_smi_t intf, struct seq_table *ent, struct ipmi_recv_msg *msg; struct ipmi_smi_handlers *handlers; - if (intf->intf_num == -1) + if (intf->in_shutdown) return; if (!ent->inuse) @@ -4082,8 +4103,7 @@ static void check_msg_timeout(ipmi_smi_t intf, struct seq_table *ent, ipmi_inc_stat(intf, retransmitted_ipmb_commands); - intf->handlers->sender(intf->send_info, - smi_msg, 0); + smi_send(intf, intf->handlers, smi_msg, 0); } else ipmi_free_smi_msg(smi_msg); @@ -4145,15 +4165,12 @@ static unsigned int ipmi_timeout_handler(ipmi_smi_t intf, long timeout_period) static void ipmi_request_event(ipmi_smi_t intf) { - struct ipmi_smi_handlers *handlers; - /* No event requests when in maintenance mode. */ if (intf->maintenance_mode_enable) return; - handlers = intf->handlers; - if (handlers) - handlers->request_events(intf->send_info); + if (!intf->in_shutdown) + intf->handlers->request_events(intf->send_info); } static struct timer_list ipmi_timer; @@ -4548,6 +4565,7 @@ static int ipmi_init_msghandler(void) proc_ipmi_root = proc_mkdir("ipmi", NULL); if (!proc_ipmi_root) { printk(KERN_ERR PFX "Unable to create IPMI proc dir"); + driver_unregister(&ipmidriver.driver); return -ENOMEM; } diff --git a/drivers/char/ipmi/ipmi_powernv.c b/drivers/char/ipmi/ipmi_powernv.c new file mode 100644 index 0000000..79524ed --- /dev/null +++ b/drivers/char/ipmi/ipmi_powernv.c @@ -0,0 +1,310 @@ +/* + * PowerNV OPAL IPMI driver + * + * Copyright 2014 IBM Corp. + * + * 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. + */ + +#define pr_fmt(fmt) "ipmi-powernv: " fmt + +#include <linux/ipmi_smi.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <asm/opal.h> + + +struct ipmi_smi_powernv { + u64 interface_id; + struct ipmi_device_id ipmi_id; + ipmi_smi_t intf; + u64 event; + struct notifier_block event_nb; + + /** + * We assume that there can only be one outstanding request, so + * keep the pending message in cur_msg. We protect this from concurrent + * updates through send & recv calls, (and consequently opal_msg, which + * is in-use when cur_msg is set) with msg_lock + */ + spinlock_t msg_lock; + struct ipmi_smi_msg *cur_msg; + struct opal_ipmi_msg *opal_msg; +}; + +static int ipmi_powernv_start_processing(void *send_info, ipmi_smi_t intf) +{ + struct ipmi_smi_powernv *smi = send_info; + + smi->intf = intf; + return 0; +} + +static void send_error_reply(struct ipmi_smi_powernv *smi, + struct ipmi_smi_msg *msg, u8 completion_code) +{ + msg->rsp[0] = msg->data[0] | 0x4; + msg->rsp[1] = msg->data[1]; + msg->rsp[2] = completion_code; + msg->rsp_size = 3; + ipmi_smi_msg_received(smi->intf, msg); +} + +static void ipmi_powernv_send(void *send_info, struct ipmi_smi_msg *msg) +{ + struct ipmi_smi_powernv *smi = send_info; + struct opal_ipmi_msg *opal_msg; + unsigned long flags; + int comp, rc; + size_t size; + + /* ensure data_len will fit in the opal_ipmi_msg buffer... */ + if (msg->data_size > IPMI_MAX_MSG_LENGTH) { + comp = IPMI_REQ_LEN_EXCEEDED_ERR; + goto err; + } + + /* ... and that we at least have netfn and cmd bytes */ + if (msg->data_size < 2) { + comp = IPMI_REQ_LEN_INVALID_ERR; + goto err; + } + + spin_lock_irqsave(&smi->msg_lock, flags); + + if (smi->cur_msg) { + comp = IPMI_NODE_BUSY_ERR; + goto err_unlock; + } + + /* format our data for the OPAL API */ + opal_msg = smi->opal_msg; + opal_msg->version = OPAL_IPMI_MSG_FORMAT_VERSION_1; + opal_msg->netfn = msg->data[0]; + opal_msg->cmd = msg->data[1]; + if (msg->data_size > 2) + memcpy(opal_msg->data, msg->data + 2, msg->data_size - 2); + + /* data_size already includes the netfn and cmd bytes */ + size = sizeof(*opal_msg) + msg->data_size - 2; + + pr_devel("%s: opal_ipmi_send(0x%llx, %p, %ld)\n", __func__, + smi->interface_id, opal_msg, size); + rc = opal_ipmi_send(smi->interface_id, opal_msg, size); + pr_devel("%s: -> %d\n", __func__, rc); + + if (!rc) { + smi->cur_msg = msg; + spin_unlock_irqrestore(&smi->msg_lock, flags); + return; + } + + comp = IPMI_ERR_UNSPECIFIED; +err_unlock: + spin_unlock_irqrestore(&smi->msg_lock, flags); +err: + send_error_reply(smi, msg, comp); +} + +static int ipmi_powernv_recv(struct ipmi_smi_powernv *smi) +{ + struct opal_ipmi_msg *opal_msg; + struct ipmi_smi_msg *msg; + unsigned long flags; + uint64_t size; + int rc; + + pr_devel("%s: opal_ipmi_recv(%llx, msg, sz)\n", __func__, + smi->interface_id); + + spin_lock_irqsave(&smi->msg_lock, flags); + + if (!smi->cur_msg) { + pr_warn("no current message?\n"); + return 0; + } + + msg = smi->cur_msg; + opal_msg = smi->opal_msg; + + size = cpu_to_be64(sizeof(*opal_msg) + IPMI_MAX_MSG_LENGTH); + + rc = opal_ipmi_recv(smi->interface_id, + opal_msg, + &size); + size = be64_to_cpu(size); + pr_devel("%s: -> %d (size %lld)\n", __func__, + rc, rc == 0 ? size : 0); + if (rc) { + spin_unlock_irqrestore(&smi->msg_lock, flags); + ipmi_free_smi_msg(msg); + return 0; + } + + if (size < sizeof(*opal_msg)) { + spin_unlock_irqrestore(&smi->msg_lock, flags); + pr_warn("unexpected IPMI message size %lld\n", size); + return 0; + } + + if (opal_msg->version != OPAL_IPMI_MSG_FORMAT_VERSION_1) { + spin_unlock_irqrestore(&smi->msg_lock, flags); + pr_warn("unexpected IPMI message format (version %d)\n", + opal_msg->version); + return 0; + } + + msg->rsp[0] = opal_msg->netfn; + msg->rsp[1] = opal_msg->cmd; + if (size > sizeof(*opal_msg)) + memcpy(&msg->rsp[2], opal_msg->data, size - sizeof(*opal_msg)); + msg->rsp_size = 2 + size - sizeof(*opal_msg); + + smi->cur_msg = NULL; + spin_unlock_irqrestore(&smi->msg_lock, flags); + ipmi_smi_msg_received(smi->intf, msg); + return 0; +} + +static void ipmi_powernv_request_events(void *send_info) +{ +} + +static void ipmi_powernv_set_run_to_completion(void *send_info, + bool run_to_completion) +{ +} + +static void ipmi_powernv_poll(void *send_info) +{ + struct ipmi_smi_powernv *smi = send_info; + + ipmi_powernv_recv(smi); +} + +static struct ipmi_smi_handlers ipmi_powernv_smi_handlers = { + .owner = THIS_MODULE, + .start_processing = ipmi_powernv_start_processing, + .sender = ipmi_powernv_send, + .request_events = ipmi_powernv_request_events, + .set_run_to_completion = ipmi_powernv_set_run_to_completion, + .poll = ipmi_powernv_poll, +}; + +static int ipmi_opal_event(struct notifier_block *nb, + unsigned long events, void *change) +{ + struct ipmi_smi_powernv *smi = container_of(nb, + struct ipmi_smi_powernv, event_nb); + + if (events & smi->event) + ipmi_powernv_recv(smi); + return 0; +} + +static int ipmi_powernv_probe(struct platform_device *pdev) +{ + struct ipmi_smi_powernv *ipmi; + struct device *dev; + u32 prop; + int rc; + + if (!pdev || !pdev->dev.of_node) + return -ENODEV; + + dev = &pdev->dev; + + ipmi = devm_kzalloc(dev, sizeof(*ipmi), GFP_KERNEL); + if (!ipmi) + return -ENOMEM; + + spin_lock_init(&ipmi->msg_lock); + + rc = of_property_read_u32(dev->of_node, "ibm,ipmi-interface-id", + &prop); + if (rc) { + dev_warn(dev, "No interface ID property\n"); + goto err_free; + } + ipmi->interface_id = prop; + + rc = of_property_read_u32(dev->of_node, "interrupts", &prop); + if (rc) { + dev_warn(dev, "No interrupts property\n"); + goto err_free; + } + + ipmi->event = 1ull << prop; + ipmi->event_nb.notifier_call = ipmi_opal_event; + + rc = opal_notifier_register(&ipmi->event_nb); + if (rc) { + dev_warn(dev, "OPAL notifier registration failed (%d)\n", rc); + goto err_free; + } + + ipmi->opal_msg = devm_kmalloc(dev, + sizeof(*ipmi->opal_msg) + IPMI_MAX_MSG_LENGTH, + GFP_KERNEL); + if (!ipmi->opal_msg) { + rc = -ENOMEM; + goto err_unregister; + } + + /* todo: query actual ipmi_device_id */ + rc = ipmi_register_smi(&ipmi_powernv_smi_handlers, ipmi, + &ipmi->ipmi_id, dev, 0); + if (rc) { + dev_warn(dev, "IPMI SMI registration failed (%d)\n", rc); + goto err_free_msg; + } + + dev_set_drvdata(dev, ipmi); + return 0; + +err_free_msg: + devm_kfree(dev, ipmi->opal_msg); +err_unregister: + opal_notifier_unregister(&ipmi->event_nb); +err_free: + devm_kfree(dev, ipmi); + return rc; +} + +static int ipmi_powernv_remove(struct platform_device *pdev) +{ + struct ipmi_smi_powernv *smi = dev_get_drvdata(&pdev->dev); + + ipmi_unregister_smi(smi->intf); + opal_notifier_unregister(&smi->event_nb); + return 0; +} + +static const struct of_device_id ipmi_powernv_match[] = { + { .compatible = "ibm,opal-ipmi" }, + { }, +}; + + +static struct platform_driver powernv_ipmi_driver = { + .driver = { + .name = "ipmi-powernv", + .owner = THIS_MODULE, + .of_match_table = ipmi_powernv_match, + }, + .probe = ipmi_powernv_probe, + .remove = ipmi_powernv_remove, +}; + + +module_platform_driver(powernv_ipmi_driver); + +MODULE_DEVICE_TABLE(of, ipmi_powernv_match); +MODULE_DESCRIPTION("powernv IPMI driver"); +MODULE_AUTHOR("Jeremy Kerr <jk@ozlabs.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c index 5c4e1f6..90c7fdf 100644 --- a/drivers/char/ipmi/ipmi_si_intf.c +++ b/drivers/char/ipmi/ipmi_si_intf.c @@ -92,12 +92,9 @@ enum si_intf_state { SI_GETTING_FLAGS, SI_GETTING_EVENTS, SI_CLEARING_FLAGS, - SI_CLEARING_FLAGS_THEN_SET_IRQ, SI_GETTING_MESSAGES, - SI_ENABLE_INTERRUPTS1, - SI_ENABLE_INTERRUPTS2, - SI_DISABLE_INTERRUPTS1, - SI_DISABLE_INTERRUPTS2 + SI_CHECKING_ENABLES, + SI_SETTING_ENABLES /* FIXME - add watchdog stuff. */ }; @@ -111,10 +108,6 @@ enum si_type { }; static char *si_to_str[] = { "kcs", "smic", "bt" }; -static char *ipmi_addr_src_to_str[] = { NULL, "hotmod", "hardcoded", "SPMI", - "ACPI", "SMBIOS", "PCI", - "device-tree", "default" }; - #define DEVICE_NAME "ipmi_si" static struct platform_driver ipmi_driver; @@ -174,8 +167,7 @@ struct smi_info { struct si_sm_handlers *handlers; enum si_type si_type; spinlock_t si_lock; - struct list_head xmit_msgs; - struct list_head hp_xmit_msgs; + struct ipmi_smi_msg *waiting_msg; struct ipmi_smi_msg *curr_msg; enum si_intf_state si_state; @@ -254,9 +246,6 @@ struct smi_info { /* The time (in jiffies) the last timeout occurred at. */ unsigned long last_timeout_jiffies; - /* Used to gracefully stop the timer without race conditions. */ - atomic_t stop_operation; - /* Are we waiting for the events, pretimeouts, received msgs? */ atomic_t need_watch; @@ -268,6 +257,16 @@ struct smi_info { */ bool interrupt_disabled; + /* + * Does the BMC support events? + */ + bool supports_event_msg_buff; + + /* + * Did we get an attention that we did not handle? + */ + bool got_attn; + /* From the get device id response... */ struct ipmi_device_id device_id; @@ -332,7 +331,10 @@ static void deliver_recv_msg(struct smi_info *smi_info, struct ipmi_smi_msg *msg) { /* Deliver the message to the upper layer. */ - ipmi_smi_msg_received(smi_info->intf, msg); + if (smi_info->intf) + ipmi_smi_msg_received(smi_info->intf, msg); + else + ipmi_free_smi_msg(msg); } static void return_hosed_msg(struct smi_info *smi_info, int cCode) @@ -356,28 +358,18 @@ static void return_hosed_msg(struct smi_info *smi_info, int cCode) static enum si_sm_result start_next_msg(struct smi_info *smi_info) { int rv; - struct list_head *entry = NULL; #ifdef DEBUG_TIMING struct timeval t; #endif - /* Pick the high priority queue first. */ - if (!list_empty(&(smi_info->hp_xmit_msgs))) { - entry = smi_info->hp_xmit_msgs.next; - } else if (!list_empty(&(smi_info->xmit_msgs))) { - entry = smi_info->xmit_msgs.next; - } - - if (!entry) { + if (!smi_info->waiting_msg) { smi_info->curr_msg = NULL; rv = SI_SM_IDLE; } else { int err; - list_del(entry); - smi_info->curr_msg = list_entry(entry, - struct ipmi_smi_msg, - link); + smi_info->curr_msg = smi_info->waiting_msg; + smi_info->waiting_msg = NULL; #ifdef DEBUG_TIMING do_gettimeofday(&t); printk(KERN_DEBUG "**Start2: %d.%9.9d\n", t.tv_sec, t.tv_usec); @@ -401,30 +393,15 @@ static enum si_sm_result start_next_msg(struct smi_info *smi_info) return rv; } -static void start_enable_irq(struct smi_info *smi_info) +static void start_check_enables(struct smi_info *smi_info) { unsigned char msg[2]; - /* - * If we are enabling interrupts, we have to tell the - * BMC to use them. - */ msg[0] = (IPMI_NETFN_APP_REQUEST << 2); msg[1] = IPMI_GET_BMC_GLOBAL_ENABLES_CMD; smi_info->handlers->start_transaction(smi_info->si_sm, msg, 2); - smi_info->si_state = SI_ENABLE_INTERRUPTS1; -} - -static void start_disable_irq(struct smi_info *smi_info) -{ - unsigned char msg[2]; - - msg[0] = (IPMI_NETFN_APP_REQUEST << 2); - msg[1] = IPMI_GET_BMC_GLOBAL_ENABLES_CMD; - - smi_info->handlers->start_transaction(smi_info->si_sm, msg, 2); - smi_info->si_state = SI_DISABLE_INTERRUPTS1; + smi_info->si_state = SI_CHECKING_ENABLES; } static void start_clear_flags(struct smi_info *smi_info) @@ -440,6 +417,32 @@ static void start_clear_flags(struct smi_info *smi_info) smi_info->si_state = SI_CLEARING_FLAGS; } +static void start_getting_msg_queue(struct smi_info *smi_info) +{ + smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); + smi_info->curr_msg->data[1] = IPMI_GET_MSG_CMD; + smi_info->curr_msg->data_size = 2; + + smi_info->handlers->start_transaction( + smi_info->si_sm, + smi_info->curr_msg->data, + smi_info->curr_msg->data_size); + smi_info->si_state = SI_GETTING_MESSAGES; +} + +static void start_getting_events(struct smi_info *smi_info) +{ + smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); + smi_info->curr_msg->data[1] = IPMI_READ_EVENT_MSG_BUFFER_CMD; + smi_info->curr_msg->data_size = 2; + + smi_info->handlers->start_transaction( + smi_info->si_sm, + smi_info->curr_msg->data, + smi_info->curr_msg->data_size); + smi_info->si_state = SI_GETTING_EVENTS; +} + static void smi_mod_timer(struct smi_info *smi_info, unsigned long new_val) { smi_info->last_timeout_jiffies = jiffies; @@ -453,22 +456,45 @@ static void smi_mod_timer(struct smi_info *smi_info, unsigned long new_val) * polled until we can allocate some memory. Once we have some * memory, we will re-enable the interrupt. */ -static inline void disable_si_irq(struct smi_info *smi_info) +static inline bool disable_si_irq(struct smi_info *smi_info) { if ((smi_info->irq) && (!smi_info->interrupt_disabled)) { - start_disable_irq(smi_info); smi_info->interrupt_disabled = true; - if (!atomic_read(&smi_info->stop_operation)) - smi_mod_timer(smi_info, jiffies + SI_TIMEOUT_JIFFIES); + start_check_enables(smi_info); + return true; } + return false; } -static inline void enable_si_irq(struct smi_info *smi_info) +static inline bool enable_si_irq(struct smi_info *smi_info) { if ((smi_info->irq) && (smi_info->interrupt_disabled)) { - start_enable_irq(smi_info); smi_info->interrupt_disabled = false; + start_check_enables(smi_info); + return true; } + return false; +} + +/* + * Allocate a message. If unable to allocate, start the interrupt + * disable process and return NULL. If able to allocate but + * interrupts are disabled, free the message and return NULL after + * starting the interrupt enable process. + */ +static struct ipmi_smi_msg *alloc_msg_handle_irq(struct smi_info *smi_info) +{ + struct ipmi_smi_msg *msg; + + msg = ipmi_alloc_smi_msg(); + if (!msg) { + if (!disable_si_irq(smi_info)) + smi_info->si_state = SI_NORMAL; + } else if (enable_si_irq(smi_info)) { + ipmi_free_smi_msg(msg); + msg = NULL; + } + return msg; } static void handle_flags(struct smi_info *smi_info) @@ -480,45 +506,22 @@ static void handle_flags(struct smi_info *smi_info) start_clear_flags(smi_info); smi_info->msg_flags &= ~WDT_PRE_TIMEOUT_INT; - ipmi_smi_watchdog_pretimeout(smi_info->intf); + if (smi_info->intf) + ipmi_smi_watchdog_pretimeout(smi_info->intf); } else if (smi_info->msg_flags & RECEIVE_MSG_AVAIL) { /* Messages available. */ - smi_info->curr_msg = ipmi_alloc_smi_msg(); - if (!smi_info->curr_msg) { - disable_si_irq(smi_info); - smi_info->si_state = SI_NORMAL; + smi_info->curr_msg = alloc_msg_handle_irq(smi_info); + if (!smi_info->curr_msg) return; - } - enable_si_irq(smi_info); - - smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); - smi_info->curr_msg->data[1] = IPMI_GET_MSG_CMD; - smi_info->curr_msg->data_size = 2; - smi_info->handlers->start_transaction( - smi_info->si_sm, - smi_info->curr_msg->data, - smi_info->curr_msg->data_size); - smi_info->si_state = SI_GETTING_MESSAGES; + start_getting_msg_queue(smi_info); } else if (smi_info->msg_flags & EVENT_MSG_BUFFER_FULL) { /* Events available. */ - smi_info->curr_msg = ipmi_alloc_smi_msg(); - if (!smi_info->curr_msg) { - disable_si_irq(smi_info); - smi_info->si_state = SI_NORMAL; + smi_info->curr_msg = alloc_msg_handle_irq(smi_info); + if (!smi_info->curr_msg) return; - } - enable_si_irq(smi_info); - - smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); - smi_info->curr_msg->data[1] = IPMI_READ_EVENT_MSG_BUFFER_CMD; - smi_info->curr_msg->data_size = 2; - smi_info->handlers->start_transaction( - smi_info->si_sm, - smi_info->curr_msg->data, - smi_info->curr_msg->data_size); - smi_info->si_state = SI_GETTING_EVENTS; + start_getting_events(smi_info); } else if (smi_info->msg_flags & OEM_DATA_AVAIL && smi_info->oem_data_avail_handler) { if (smi_info->oem_data_avail_handler(smi_info)) @@ -527,6 +530,55 @@ static void handle_flags(struct smi_info *smi_info) smi_info->si_state = SI_NORMAL; } +/* + * Global enables we care about. + */ +#define GLOBAL_ENABLES_MASK (IPMI_BMC_EVT_MSG_BUFF | IPMI_BMC_RCV_MSG_INTR | \ + IPMI_BMC_EVT_MSG_INTR) + +static u8 current_global_enables(struct smi_info *smi_info, u8 base, + bool *irq_on) +{ + u8 enables = 0; + + if (smi_info->supports_event_msg_buff) + enables |= IPMI_BMC_EVT_MSG_BUFF; + else + enables &= ~IPMI_BMC_EVT_MSG_BUFF; + + if (smi_info->irq && !smi_info->interrupt_disabled) + enables |= IPMI_BMC_RCV_MSG_INTR; + else + enables &= ~IPMI_BMC_RCV_MSG_INTR; + + if (smi_info->supports_event_msg_buff && + smi_info->irq && !smi_info->interrupt_disabled) + + enables |= IPMI_BMC_EVT_MSG_INTR; + else + enables &= ~IPMI_BMC_EVT_MSG_INTR; + + *irq_on = enables & (IPMI_BMC_EVT_MSG_INTR | IPMI_BMC_RCV_MSG_INTR); + + return enables; +} + +static void check_bt_irq(struct smi_info *smi_info, bool irq_on) +{ + u8 irqstate = smi_info->io.inputb(&smi_info->io, IPMI_BT_INTMASK_REG); + + irqstate &= IPMI_BT_INTMASK_ENABLE_IRQ_BIT; + + if ((bool)irqstate == irq_on) + return; + + if (irq_on) + smi_info->io.outputb(&smi_info->io, IPMI_BT_INTMASK_REG, + IPMI_BT_INTMASK_ENABLE_IRQ_BIT); + else + smi_info->io.outputb(&smi_info->io, IPMI_BT_INTMASK_REG, 0); +} + static void handle_transaction_done(struct smi_info *smi_info) { struct ipmi_smi_msg *msg; @@ -581,7 +633,6 @@ static void handle_transaction_done(struct smi_info *smi_info) } case SI_CLEARING_FLAGS: - case SI_CLEARING_FLAGS_THEN_SET_IRQ: { unsigned char msg[3]; @@ -592,10 +643,7 @@ static void handle_transaction_done(struct smi_info *smi_info) dev_warn(smi_info->dev, "Error clearing flags: %2.2x\n", msg[2]); } - if (smi_info->si_state == SI_CLEARING_FLAGS_THEN_SET_IRQ) - start_enable_irq(smi_info); - else - smi_info->si_state = SI_NORMAL; + smi_info->si_state = SI_NORMAL; break; } @@ -675,9 +723,11 @@ static void handle_transaction_done(struct smi_info *smi_info) break; } - case SI_ENABLE_INTERRUPTS1: + case SI_CHECKING_ENABLES: { unsigned char msg[4]; + u8 enables; + bool irq_on; /* We got the flags from the SMI, now handle them. */ smi_info->handlers->get_result(smi_info->si_sm, msg, 4); @@ -687,70 +737,53 @@ static void handle_transaction_done(struct smi_info *smi_info) dev_warn(smi_info->dev, "Maybe ok, but ipmi might run very slowly.\n"); smi_info->si_state = SI_NORMAL; - } else { + break; + } + enables = current_global_enables(smi_info, 0, &irq_on); + if (smi_info->si_type == SI_BT) + /* BT has its own interrupt enable bit. */ + check_bt_irq(smi_info, irq_on); + if (enables != (msg[3] & GLOBAL_ENABLES_MASK)) { + /* Enables are not correct, fix them. */ msg[0] = (IPMI_NETFN_APP_REQUEST << 2); msg[1] = IPMI_SET_BMC_GLOBAL_ENABLES_CMD; - msg[2] = (msg[3] | - IPMI_BMC_RCV_MSG_INTR | - IPMI_BMC_EVT_MSG_INTR); + msg[2] = enables | (msg[3] & ~GLOBAL_ENABLES_MASK); smi_info->handlers->start_transaction( smi_info->si_sm, msg, 3); - smi_info->si_state = SI_ENABLE_INTERRUPTS2; + smi_info->si_state = SI_SETTING_ENABLES; + } else if (smi_info->supports_event_msg_buff) { + smi_info->curr_msg = ipmi_alloc_smi_msg(); + if (!smi_info->curr_msg) { + smi_info->si_state = SI_NORMAL; + break; + } + start_getting_msg_queue(smi_info); + } else { + smi_info->si_state = SI_NORMAL; } break; } - case SI_ENABLE_INTERRUPTS2: + case SI_SETTING_ENABLES: { unsigned char msg[4]; - /* We got the flags from the SMI, now handle them. */ smi_info->handlers->get_result(smi_info->si_sm, msg, 4); - if (msg[2] != 0) { + if (msg[2] != 0) dev_warn(smi_info->dev, - "Couldn't set irq info: %x.\n", msg[2]); - dev_warn(smi_info->dev, - "Maybe ok, but ipmi might run very slowly.\n"); - } else - smi_info->interrupt_disabled = false; - smi_info->si_state = SI_NORMAL; - break; - } - - case SI_DISABLE_INTERRUPTS1: - { - unsigned char msg[4]; + "Could not set the global enables: 0x%x.\n", + msg[2]); - /* We got the flags from the SMI, now handle them. */ - smi_info->handlers->get_result(smi_info->si_sm, msg, 4); - if (msg[2] != 0) { - dev_warn(smi_info->dev, "Could not disable interrupts" - ", failed get.\n"); - smi_info->si_state = SI_NORMAL; + if (smi_info->supports_event_msg_buff) { + smi_info->curr_msg = ipmi_alloc_smi_msg(); + if (!smi_info->curr_msg) { + smi_info->si_state = SI_NORMAL; + break; + } + start_getting_msg_queue(smi_info); } else { - msg[0] = (IPMI_NETFN_APP_REQUEST << 2); - msg[1] = IPMI_SET_BMC_GLOBAL_ENABLES_CMD; - msg[2] = (msg[3] & - ~(IPMI_BMC_RCV_MSG_INTR | - IPMI_BMC_EVT_MSG_INTR)); - smi_info->handlers->start_transaction( - smi_info->si_sm, msg, 3); - smi_info->si_state = SI_DISABLE_INTERRUPTS2; - } - break; - } - - case SI_DISABLE_INTERRUPTS2: - { - unsigned char msg[4]; - - /* We got the flags from the SMI, now handle them. */ - smi_info->handlers->get_result(smi_info->si_sm, msg, 4); - if (msg[2] != 0) { - dev_warn(smi_info->dev, "Could not disable interrupts" - ", failed set.\n"); + smi_info->si_state = SI_NORMAL; } - smi_info->si_state = SI_NORMAL; break; } } @@ -808,25 +841,35 @@ static enum si_sm_result smi_event_handler(struct smi_info *smi_info, * We prefer handling attn over new messages. But don't do * this if there is not yet an upper layer to handle anything. */ - if (likely(smi_info->intf) && si_sm_result == SI_SM_ATTN) { + if (likely(smi_info->intf) && + (si_sm_result == SI_SM_ATTN || smi_info->got_attn)) { unsigned char msg[2]; - smi_inc_stat(smi_info, attentions); + if (smi_info->si_state != SI_NORMAL) { + /* + * We got an ATTN, but we are doing something else. + * Handle the ATTN later. + */ + smi_info->got_attn = true; + } else { + smi_info->got_attn = false; + smi_inc_stat(smi_info, attentions); - /* - * Got a attn, send down a get message flags to see - * what's causing it. It would be better to handle - * this in the upper layer, but due to the way - * interrupts work with the SMI, that's not really - * possible. - */ - msg[0] = (IPMI_NETFN_APP_REQUEST << 2); - msg[1] = IPMI_GET_MSG_FLAGS_CMD; + /* + * Got a attn, send down a get message flags to see + * what's causing it. It would be better to handle + * this in the upper layer, but due to the way + * interrupts work with the SMI, that's not really + * possible. + */ + msg[0] = (IPMI_NETFN_APP_REQUEST << 2); + msg[1] = IPMI_GET_MSG_FLAGS_CMD; - smi_info->handlers->start_transaction( - smi_info->si_sm, msg, 2); - smi_info->si_state = SI_GETTING_FLAGS; - goto restart; + smi_info->handlers->start_transaction( + smi_info->si_sm, msg, 2); + smi_info->si_state = SI_GETTING_FLAGS; + goto restart; + } } /* If we are currently idle, try to start the next message. */ @@ -846,19 +889,21 @@ static enum si_sm_result smi_event_handler(struct smi_info *smi_info, */ atomic_set(&smi_info->req_events, 0); - smi_info->curr_msg = ipmi_alloc_smi_msg(); - if (!smi_info->curr_msg) - goto out; - - smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); - smi_info->curr_msg->data[1] = IPMI_READ_EVENT_MSG_BUFFER_CMD; - smi_info->curr_msg->data_size = 2; + /* + * Take this opportunity to check the interrupt and + * message enable state for the BMC. The BMC can be + * asynchronously reset, and may thus get interrupts + * disable and messages disabled. + */ + if (smi_info->supports_event_msg_buff || smi_info->irq) { + start_check_enables(smi_info); + } else { + smi_info->curr_msg = alloc_msg_handle_irq(smi_info); + if (!smi_info->curr_msg) + goto out; - smi_info->handlers->start_transaction( - smi_info->si_sm, - smi_info->curr_msg->data, - smi_info->curr_msg->data_size); - smi_info->si_state = SI_GETTING_EVENTS; + start_getting_events(smi_info); + } goto restart; } out: @@ -879,8 +924,7 @@ static void check_start_timer_thread(struct smi_info *smi_info) } static void sender(void *send_info, - struct ipmi_smi_msg *msg, - int priority) + struct ipmi_smi_msg *msg) { struct smi_info *smi_info = send_info; enum si_sm_result result; @@ -889,14 +933,8 @@ static void sender(void *send_info, struct timeval t; #endif - if (atomic_read(&smi_info->stop_operation)) { - msg->rsp[0] = msg->data[0] | 4; - msg->rsp[1] = msg->data[1]; - msg->rsp[2] = IPMI_ERR_UNSPECIFIED; - msg->rsp_size = 3; - deliver_recv_msg(smi_info, msg); - return; - } + BUG_ON(smi_info->waiting_msg); + smi_info->waiting_msg = msg; #ifdef DEBUG_TIMING do_gettimeofday(&t); @@ -905,16 +943,16 @@ static void sender(void *send_info, if (smi_info->run_to_completion) { /* - * If we are running to completion, then throw it in - * the list and run transactions until everything is - * clear. Priority doesn't matter here. + * If we are running to completion, start it and run + * transactions until everything is clear. */ + smi_info->curr_msg = smi_info->waiting_msg; + smi_info->waiting_msg = NULL; /* * Run to completion means we are single-threaded, no * need for locks. */ - list_add_tail(&(msg->link), &(smi_info->xmit_msgs)); result = smi_event_handler(smi_info, 0); while (result != SI_SM_IDLE) { @@ -926,11 +964,6 @@ static void sender(void *send_info, } spin_lock_irqsave(&smi_info->si_lock, flags); - if (priority > 0) - list_add_tail(&msg->link, &smi_info->hp_xmit_msgs); - else - list_add_tail(&msg->link, &smi_info->xmit_msgs); - check_start_timer_thread(smi_info); spin_unlock_irqrestore(&smi_info->si_lock, flags); } @@ -1068,8 +1101,7 @@ static void request_events(void *send_info) { struct smi_info *smi_info = send_info; - if (atomic_read(&smi_info->stop_operation) || - !smi_info->has_event_buffer) + if (!smi_info->has_event_buffer) return; atomic_set(&smi_info->req_events, 1); @@ -1697,7 +1729,7 @@ static int parse_str(struct hotmod_vals *v, int *val, char *name, char **curr) } *s = '\0'; s++; - for (i = 0; hotmod_ops[i].name; i++) { + for (i = 0; v[i].name; i++) { if (strcmp(*curr, v[i].name) == 0) { *val = v[i].val; *curr = s; @@ -2133,6 +2165,9 @@ static int try_init_spmi(struct SPMITable *spmi) case 3: /* BT */ info->si_type = SI_BT; break; + case 4: /* SSIF, just ignore */ + kfree(info); + return -EIO; default: printk(KERN_INFO PFX "Unknown ACPI/SPMI SI type %d\n", spmi->InterfaceType); @@ -2250,6 +2285,8 @@ static int ipmi_pnp_probe(struct pnp_dev *dev, case 3: info->si_type = SI_BT; break; + case 4: /* SSIF, just ignore */ + goto err_free; default: dev_info(&dev->dev, "unknown IPMI type %lld\n", tmp); goto err_free; @@ -2913,9 +2950,11 @@ static int try_enable_event_buffer(struct smi_info *smi_info) goto out; } - if (resp[3] & IPMI_BMC_EVT_MSG_BUFF) + if (resp[3] & IPMI_BMC_EVT_MSG_BUFF) { /* buffer is already enabled, nothing to do. */ + smi_info->supports_event_msg_buff = true; goto out; + } msg[0] = IPMI_NETFN_APP_REQUEST << 2; msg[1] = IPMI_SET_BMC_GLOBAL_ENABLES_CMD; @@ -2948,6 +2987,9 @@ static int try_enable_event_buffer(struct smi_info *smi_info) * that the event buffer is not supported. */ rv = -ENOENT; + else + smi_info->supports_event_msg_buff = true; + out: kfree(resp); return rv; @@ -3188,15 +3230,10 @@ static void setup_xaction_handlers(struct smi_info *smi_info) static inline void wait_for_timer_and_thread(struct smi_info *smi_info) { - if (smi_info->intf) { - /* - * The timer and thread are only running if the - * interface has been started up and registered. - */ - if (smi_info->thread != NULL) - kthread_stop(smi_info->thread); + if (smi_info->thread != NULL) + kthread_stop(smi_info->thread); + if (smi_info->timer_running) del_timer_sync(&smi_info->si_timer); - } } static struct ipmi_default_vals @@ -3274,8 +3311,8 @@ static int add_smi(struct smi_info *new_smi) int rv = 0; printk(KERN_INFO PFX "Adding %s-specified %s state machine", - ipmi_addr_src_to_str[new_smi->addr_source], - si_to_str[new_smi->si_type]); + ipmi_addr_src_to_str(new_smi->addr_source), + si_to_str[new_smi->si_type]); mutex_lock(&smi_infos_lock); if (!is_new_interface(new_smi)) { printk(KERN_CONT " duplicate interface\n"); @@ -3305,7 +3342,7 @@ static int try_smi_init(struct smi_info *new_smi) printk(KERN_INFO PFX "Trying %s-specified %s state" " machine at %s address 0x%lx, slave address 0x%x," " irq %d\n", - ipmi_addr_src_to_str[new_smi->addr_source], + ipmi_addr_src_to_str(new_smi->addr_source), si_to_str[new_smi->si_type], addr_space_to_str[new_smi->io.addr_type], new_smi->io.addr_data, @@ -3371,8 +3408,7 @@ static int try_smi_init(struct smi_info *new_smi) setup_oem_data_handler(new_smi); setup_xaction_handlers(new_smi); - INIT_LIST_HEAD(&(new_smi->xmit_msgs)); - INIT_LIST_HEAD(&(new_smi->hp_xmit_msgs)); + new_smi->waiting_msg = NULL; new_smi->curr_msg = NULL; atomic_set(&new_smi->req_events, 0); new_smi->run_to_completion = false; @@ -3380,7 +3416,6 @@ static int try_smi_init(struct smi_info *new_smi) atomic_set(&new_smi->stats[i], 0); new_smi->interrupt_disabled = true; - atomic_set(&new_smi->stop_operation, 0); atomic_set(&new_smi->need_watch, 0); new_smi->intf_num = smi_num; smi_num++; @@ -3394,9 +3429,15 @@ static int try_smi_init(struct smi_info *new_smi) * timer to avoid racing with the timer. */ start_clear_flags(new_smi); - /* IRQ is defined to be set when non-zero. */ - if (new_smi->irq) - new_smi->si_state = SI_CLEARING_FLAGS_THEN_SET_IRQ; + + /* + * IRQ is defined to be set when non-zero. req_events will + * cause a global flags check that will enable interrupts. + */ + if (new_smi->irq) { + new_smi->interrupt_disabled = false; + atomic_set(&new_smi->req_events, 1); + } if (!new_smi->dev) { /* @@ -3428,7 +3469,6 @@ static int try_smi_init(struct smi_info *new_smi) new_smi, &new_smi->device_id, new_smi->dev, - "bmc", new_smi->slave_addr); if (rv) { dev_err(new_smi->dev, "Unable to register device: error %d\n", @@ -3466,15 +3506,15 @@ static int try_smi_init(struct smi_info *new_smi) return 0; out_err_stop_timer: - atomic_inc(&new_smi->stop_operation); wait_for_timer_and_thread(new_smi); out_err: new_smi->interrupt_disabled = true; if (new_smi->intf) { - ipmi_unregister_smi(new_smi->intf); + ipmi_smi_t intf = new_smi->intf; new_smi->intf = NULL; + ipmi_unregister_smi(intf); } if (new_smi->irq_cleanup) { @@ -3653,60 +3693,49 @@ module_init(init_ipmi_si); static void cleanup_one_si(struct smi_info *to_clean) { int rv = 0; - unsigned long flags; if (!to_clean) return; + if (to_clean->intf) { + ipmi_smi_t intf = to_clean->intf; + + to_clean->intf = NULL; + rv = ipmi_unregister_smi(intf); + if (rv) { + pr_err(PFX "Unable to unregister device: errno=%d\n", + rv); + } + } + if (to_clean->dev) dev_set_drvdata(to_clean->dev, NULL); list_del(&to_clean->link); - /* Tell the driver that we are shutting down. */ - atomic_inc(&to_clean->stop_operation); - /* - * Make sure the timer and thread are stopped and will not run - * again. + * Make sure that interrupts, the timer and the thread are + * stopped and will not run again. */ + if (to_clean->irq_cleanup) + to_clean->irq_cleanup(to_clean); wait_for_timer_and_thread(to_clean); /* * Timeouts are stopped, now make sure the interrupts are off - * for the device. A little tricky with locks to make sure - * there are no races. + * in the BMC. Note that timers and CPU interrupts are off, + * so no need for locks. */ - spin_lock_irqsave(&to_clean->si_lock, flags); while (to_clean->curr_msg || (to_clean->si_state != SI_NORMAL)) { - spin_unlock_irqrestore(&to_clean->si_lock, flags); poll(to_clean); schedule_timeout_uninterruptible(1); - spin_lock_irqsave(&to_clean->si_lock, flags); } disable_si_irq(to_clean); - spin_unlock_irqrestore(&to_clean->si_lock, flags); while (to_clean->curr_msg || (to_clean->si_state != SI_NORMAL)) { poll(to_clean); schedule_timeout_uninterruptible(1); } - /* Clean up interrupts and make sure that everything is done. */ - if (to_clean->irq_cleanup) - to_clean->irq_cleanup(to_clean); - while (to_clean->curr_msg || (to_clean->si_state != SI_NORMAL)) { - poll(to_clean); - schedule_timeout_uninterruptible(1); - } - - if (to_clean->intf) - rv = ipmi_unregister_smi(to_clean->intf); - - if (rv) { - printk(KERN_ERR PFX "Unable to unregister device: errno=%d\n", - rv); - } - if (to_clean->handlers) to_clean->handlers->cleanup(to_clean->si_sm); diff --git a/drivers/char/ipmi/ipmi_ssif.c b/drivers/char/ipmi/ipmi_ssif.c new file mode 100644 index 0000000..e178ac2 --- /dev/null +++ b/drivers/char/ipmi/ipmi_ssif.c @@ -0,0 +1,1870 @@ +/* + * ipmi_ssif.c + * + * The interface to the IPMI driver for SMBus access to a SMBus + * compliant device. Called SSIF by the IPMI spec. + * + * Author: Intel Corporation + * Todd Davis <todd.c.davis@intel.com> + * + * Rewritten by Corey Minyard <minyard@acm.org> to support the + * non-blocking I2C interface, add support for multi-part + * transactions, add PEC support, and general clenaup. + * + * Copyright 2003 Intel Corporation + * Copyright 2005 MontaVista Software + * + * 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 file holds the "policy" for the interface to the SSIF state + * machine. It does the configuration, handles timers and interrupts, + * and drives the real SSIF state machine. + */ + +/* + * TODO: Figure out how to use SMB alerts. This will require a new + * interface into the I2C driver, I believe. + */ + +#include <linux/version.h> +#if defined(MODVERSIONS) +#include <linux/modversions.h> +#endif + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/sched.h> +#include <linux/seq_file.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/i2c.h> +#include <linux/ipmi_smi.h> +#include <linux/init.h> +#include <linux/dmi.h> +#include <linux/kthread.h> +#include <linux/acpi.h> + +#define PFX "ipmi_ssif: " +#define DEVICE_NAME "ipmi_ssif" + +#define IPMI_GET_SYSTEM_INTERFACE_CAPABILITIES_CMD 0x57 + +#define SSIF_IPMI_REQUEST 2 +#define SSIF_IPMI_MULTI_PART_REQUEST_START 6 +#define SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE 7 +#define SSIF_IPMI_RESPONSE 3 +#define SSIF_IPMI_MULTI_PART_RESPONSE_MIDDLE 9 + +/* ssif_debug is a bit-field + * SSIF_DEBUG_MSG - commands and their responses + * SSIF_DEBUG_STATES - message states + * SSIF_DEBUG_TIMING - Measure times between events in the driver + */ +#define SSIF_DEBUG_TIMING 4 +#define SSIF_DEBUG_STATE 2 +#define SSIF_DEBUG_MSG 1 +#define SSIF_NODEBUG 0 +#define SSIF_DEFAULT_DEBUG (SSIF_NODEBUG) + +/* + * Timer values + */ +#define SSIF_MSG_USEC 20000 /* 20ms between message tries. */ +#define SSIF_MSG_PART_USEC 5000 /* 5ms for a message part */ + +/* How many times to we retry sending/receiving the message. */ +#define SSIF_SEND_RETRIES 5 +#define SSIF_RECV_RETRIES 250 + +#define SSIF_MSG_MSEC (SSIF_MSG_USEC / 1000) +#define SSIF_MSG_JIFFIES ((SSIF_MSG_USEC * 1000) / TICK_NSEC) +#define SSIF_MSG_PART_JIFFIES ((SSIF_MSG_PART_USEC * 1000) / TICK_NSEC) + +enum ssif_intf_state { + SSIF_NORMAL, + SSIF_GETTING_FLAGS, + SSIF_GETTING_EVENTS, + SSIF_CLEARING_FLAGS, + SSIF_GETTING_MESSAGES, + /* FIXME - add watchdog stuff. */ +}; + +#define SSIF_IDLE(ssif) ((ssif)->ssif_state == SSIF_NORMAL \ + && (ssif)->curr_msg == NULL) + +/* + * Indexes into stats[] in ssif_info below. + */ +enum ssif_stat_indexes { + /* Number of total messages sent. */ + SSIF_STAT_sent_messages = 0, + + /* + * Number of message parts sent. Messages may be broken into + * parts if they are long. + */ + SSIF_STAT_sent_messages_parts, + + /* + * Number of time a message was retried. + */ + SSIF_STAT_send_retries, + + /* + * Number of times the send of a message failed. + */ + SSIF_STAT_send_errors, + + /* + * Number of message responses received. + */ + SSIF_STAT_received_messages, + + /* + * Number of message fragments received. + */ + SSIF_STAT_received_message_parts, + + /* + * Number of times the receive of a message was retried. + */ + SSIF_STAT_receive_retries, + + /* + * Number of errors receiving messages. + */ + SSIF_STAT_receive_errors, + + /* + * Number of times a flag fetch was requested. + */ + SSIF_STAT_flag_fetches, + + /* + * Number of times the hardware didn't follow the state machine. + */ + SSIF_STAT_hosed, + + /* + * Number of received events. + */ + SSIF_STAT_events, + + /* Number of asyncronous messages received. */ + SSIF_STAT_incoming_messages, + + /* Number of watchdog pretimeouts. */ + SSIF_STAT_watchdog_pretimeouts, + + /* Always add statistics before this value, it must be last. */ + SSIF_NUM_STATS +}; + +struct ssif_addr_info { + unsigned short addr; + struct i2c_board_info binfo; + char *adapter_name; + int debug; + int slave_addr; + enum ipmi_addr_src addr_src; + union ipmi_smi_info_union addr_info; + + struct mutex clients_mutex; + struct list_head clients; + + struct list_head link; +}; + +struct ssif_info; + +typedef void (*ssif_i2c_done)(struct ssif_info *ssif_info, int result, + unsigned char *data, unsigned int len); + +struct ssif_info { + ipmi_smi_t intf; + int intf_num; + spinlock_t lock; + struct ipmi_smi_msg *waiting_msg; + struct ipmi_smi_msg *curr_msg; + enum ssif_intf_state ssif_state; + unsigned long ssif_debug; + + struct ipmi_smi_handlers handlers; + + enum ipmi_addr_src addr_source; /* ACPI, PCI, SMBIOS, hardcode, etc. */ + union ipmi_smi_info_union addr_info; + + /* + * 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 + unsigned char msg_flags; + + bool has_event_buffer; + + /* + * If set to true, this will request events the next time the + * state machine is idle. + */ + bool req_events; + + /* + * If set to true, this will request flags the next time the + * state machine is idle. + */ + bool req_flags; + + /* + * Used to perform timer operations when run-to-completion + * mode is on. This is a countdown timer. + */ + int rtc_us_timer; + + /* Used for sending/receiving data. +1 for the length. */ + unsigned char data[IPMI_MAX_MSG_LENGTH + 1]; + unsigned int data_len; + + /* Temp receive buffer, gets copied into data. */ + unsigned char recv[I2C_SMBUS_BLOCK_MAX]; + + struct i2c_client *client; + ssif_i2c_done done_handler; + + /* Thread interface handling */ + struct task_struct *thread; + struct completion wake_thread; + bool stopping; + int i2c_read_write; + int i2c_command; + unsigned char *i2c_data; + unsigned int i2c_size; + + /* From the device id response. */ + struct ipmi_device_id device_id; + + struct timer_list retry_timer; + int retries_left; + + /* Info from SSIF cmd */ + unsigned char max_xmit_msg_size; + unsigned char max_recv_msg_size; + unsigned int multi_support; + int supports_pec; + +#define SSIF_NO_MULTI 0 +#define SSIF_MULTI_2_PART 1 +#define SSIF_MULTI_n_PART 2 + unsigned char *multi_data; + unsigned int multi_len; + unsigned int multi_pos; + + atomic_t stats[SSIF_NUM_STATS]; +}; + +#define ssif_inc_stat(ssif, stat) \ + atomic_inc(&(ssif)->stats[SSIF_STAT_ ## stat]) +#define ssif_get_stat(ssif, stat) \ + ((unsigned int) atomic_read(&(ssif)->stats[SSIF_STAT_ ## stat])) + +static bool initialized; + +static atomic_t next_intf = ATOMIC_INIT(0); + +static void return_hosed_msg(struct ssif_info *ssif_info, + struct ipmi_smi_msg *msg); +static void start_next_msg(struct ssif_info *ssif_info, unsigned long *flags); +static int start_send(struct ssif_info *ssif_info, + unsigned char *data, + unsigned int len); + +static unsigned long *ipmi_ssif_lock_cond(struct ssif_info *ssif_info, + unsigned long *flags) +{ + spin_lock_irqsave(&ssif_info->lock, *flags); + return flags; +} + +static void ipmi_ssif_unlock_cond(struct ssif_info *ssif_info, + unsigned long *flags) +{ + spin_unlock_irqrestore(&ssif_info->lock, *flags); +} + +static void deliver_recv_msg(struct ssif_info *ssif_info, + struct ipmi_smi_msg *msg) +{ + ipmi_smi_t intf = ssif_info->intf; + + if (!intf) { + ipmi_free_smi_msg(msg); + } else if (msg->rsp_size < 0) { + return_hosed_msg(ssif_info, msg); + pr_err(PFX + "Malformed message in deliver_recv_msg: rsp_size = %d\n", + msg->rsp_size); + } else { + ipmi_smi_msg_received(intf, msg); + } +} + +static void return_hosed_msg(struct ssif_info *ssif_info, + struct ipmi_smi_msg *msg) +{ + ssif_inc_stat(ssif_info, hosed); + + /* Make it a response */ + msg->rsp[0] = msg->data[0] | 4; + msg->rsp[1] = msg->data[1]; + msg->rsp[2] = 0xFF; /* Unknown error. */ + msg->rsp_size = 3; + + deliver_recv_msg(ssif_info, msg); +} + +/* + * Must be called with the message lock held. This will release the + * message lock. Note that the caller will check SSIF_IDLE and start a + * new operation, so there is no need to check for new messages to + * start in here. + */ +static void start_clear_flags(struct ssif_info *ssif_info, unsigned long *flags) +{ + unsigned char msg[3]; + + ssif_info->msg_flags &= ~WDT_PRE_TIMEOUT_INT; + ssif_info->ssif_state = SSIF_CLEARING_FLAGS; + ipmi_ssif_unlock_cond(ssif_info, flags); + + /* Make sure the watchdog pre-timeout flag is not set at startup. */ + msg[0] = (IPMI_NETFN_APP_REQUEST << 2); + msg[1] = IPMI_CLEAR_MSG_FLAGS_CMD; + msg[2] = WDT_PRE_TIMEOUT_INT; + + if (start_send(ssif_info, msg, 3) != 0) { + /* Error, just go to normal state. */ + ssif_info->ssif_state = SSIF_NORMAL; + } +} + +static void start_flag_fetch(struct ssif_info *ssif_info, unsigned long *flags) +{ + unsigned char mb[2]; + + ssif_info->req_flags = false; + ssif_info->ssif_state = SSIF_GETTING_FLAGS; + ipmi_ssif_unlock_cond(ssif_info, flags); + + mb[0] = (IPMI_NETFN_APP_REQUEST << 2); + mb[1] = IPMI_GET_MSG_FLAGS_CMD; + if (start_send(ssif_info, mb, 2) != 0) + ssif_info->ssif_state = SSIF_NORMAL; +} + +static void check_start_send(struct ssif_info *ssif_info, unsigned long *flags, + struct ipmi_smi_msg *msg) +{ + if (start_send(ssif_info, msg->data, msg->data_size) != 0) { + unsigned long oflags; + + flags = ipmi_ssif_lock_cond(ssif_info, &oflags); + ssif_info->curr_msg = NULL; + ssif_info->ssif_state = SSIF_NORMAL; + ipmi_ssif_unlock_cond(ssif_info, flags); + ipmi_free_smi_msg(msg); + } +} + +static void start_event_fetch(struct ssif_info *ssif_info, unsigned long *flags) +{ + struct ipmi_smi_msg *msg; + + ssif_info->req_events = false; + + msg = ipmi_alloc_smi_msg(); + if (!msg) { + ssif_info->ssif_state = SSIF_NORMAL; + return; + } + + ssif_info->curr_msg = msg; + ssif_info->ssif_state = SSIF_GETTING_EVENTS; + ipmi_ssif_unlock_cond(ssif_info, flags); + + msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); + msg->data[1] = IPMI_READ_EVENT_MSG_BUFFER_CMD; + msg->data_size = 2; + + check_start_send(ssif_info, flags, msg); +} + +static void start_recv_msg_fetch(struct ssif_info *ssif_info, + unsigned long *flags) +{ + struct ipmi_smi_msg *msg; + + msg = ipmi_alloc_smi_msg(); + if (!msg) { + ssif_info->ssif_state = SSIF_NORMAL; + return; + } + + ssif_info->curr_msg = msg; + ssif_info->ssif_state = SSIF_GETTING_MESSAGES; + ipmi_ssif_unlock_cond(ssif_info, flags); + + msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2); + msg->data[1] = IPMI_GET_MSG_CMD; + msg->data_size = 2; + + check_start_send(ssif_info, flags, msg); +} + +/* + * Must be called with the message lock held. This will release the + * message lock. Note that the caller will check SSIF_IDLE and start a + * new operation, so there is no need to check for new messages to + * start in here. + */ +static void handle_flags(struct ssif_info *ssif_info, unsigned long *flags) +{ + if (ssif_info->msg_flags & WDT_PRE_TIMEOUT_INT) { + ipmi_smi_t intf = ssif_info->intf; + /* Watchdog pre-timeout */ + ssif_inc_stat(ssif_info, watchdog_pretimeouts); + start_clear_flags(ssif_info, flags); + if (intf) + ipmi_smi_watchdog_pretimeout(intf); + } else if (ssif_info->msg_flags & RECEIVE_MSG_AVAIL) + /* Messages available. */ + start_recv_msg_fetch(ssif_info, flags); + else if (ssif_info->msg_flags & EVENT_MSG_BUFFER_FULL) + /* Events available. */ + start_event_fetch(ssif_info, flags); + else { + ssif_info->ssif_state = SSIF_NORMAL; + ipmi_ssif_unlock_cond(ssif_info, flags); + } +} + +static int ipmi_ssif_thread(void *data) +{ + struct ssif_info *ssif_info = data; + + while (!kthread_should_stop()) { + int result; + + /* Wait for something to do */ + wait_for_completion(&ssif_info->wake_thread); + init_completion(&ssif_info->wake_thread); + + if (ssif_info->stopping) + break; + + if (ssif_info->i2c_read_write == I2C_SMBUS_WRITE) { + result = i2c_smbus_write_block_data( + ssif_info->client, SSIF_IPMI_REQUEST, + ssif_info->i2c_data[0], + ssif_info->i2c_data + 1); + ssif_info->done_handler(ssif_info, result, NULL, 0); + } else { + result = i2c_smbus_read_block_data( + ssif_info->client, SSIF_IPMI_RESPONSE, + ssif_info->i2c_data); + if (result < 0) + ssif_info->done_handler(ssif_info, result, + NULL, 0); + else + ssif_info->done_handler(ssif_info, 0, + ssif_info->i2c_data, + result); + } + } + + return 0; +} + +static int ssif_i2c_send(struct ssif_info *ssif_info, + ssif_i2c_done handler, + int read_write, int command, + unsigned char *data, unsigned int size) +{ + ssif_info->done_handler = handler; + + ssif_info->i2c_read_write = read_write; + ssif_info->i2c_command = command; + ssif_info->i2c_data = data; + ssif_info->i2c_size = size; + complete(&ssif_info->wake_thread); + return 0; +} + + +static void msg_done_handler(struct ssif_info *ssif_info, int result, + unsigned char *data, unsigned int len); + +static void retry_timeout(unsigned long data) +{ + struct ssif_info *ssif_info = (void *) data; + int rv; + + if (ssif_info->stopping) + return; + + ssif_info->rtc_us_timer = 0; + + rv = ssif_i2c_send(ssif_info, msg_done_handler, I2C_SMBUS_READ, + SSIF_IPMI_RESPONSE, + ssif_info->recv, I2C_SMBUS_BLOCK_DATA); + if (rv < 0) { + /* request failed, just return the error. */ + if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) + pr_info("Error from i2c_non_blocking_op(5)\n"); + + msg_done_handler(ssif_info, -EIO, NULL, 0); + } +} + +static int start_resend(struct ssif_info *ssif_info); + +static void msg_done_handler(struct ssif_info *ssif_info, int result, + unsigned char *data, unsigned int len) +{ + struct ipmi_smi_msg *msg; + unsigned long oflags, *flags; + int rv; + + /* + * We are single-threaded here, so no need for a lock until we + * start messing with driver states or the queues. + */ + + if (result < 0) { + ssif_info->retries_left--; + if (ssif_info->retries_left > 0) { + ssif_inc_stat(ssif_info, receive_retries); + + mod_timer(&ssif_info->retry_timer, + jiffies + SSIF_MSG_JIFFIES); + ssif_info->rtc_us_timer = SSIF_MSG_USEC; + return; + } + + ssif_inc_stat(ssif_info, receive_errors); + + if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) + pr_info("Error in msg_done_handler: %d\n", result); + len = 0; + goto continue_op; + } + + if ((len > 1) && (ssif_info->multi_pos == 0) + && (data[0] == 0x00) && (data[1] == 0x01)) { + /* Start of multi-part read. Start the next transaction. */ + int i; + + ssif_inc_stat(ssif_info, received_message_parts); + + /* Remove the multi-part read marker. */ + for (i = 0; i < (len-2); i++) + ssif_info->data[i] = data[i+2]; + len -= 2; + ssif_info->multi_len = len; + ssif_info->multi_pos = 1; + + rv = ssif_i2c_send(ssif_info, msg_done_handler, I2C_SMBUS_READ, + SSIF_IPMI_MULTI_PART_RESPONSE_MIDDLE, + ssif_info->recv, I2C_SMBUS_BLOCK_DATA); + if (rv < 0) { + if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) + pr_info("Error from i2c_non_blocking_op(1)\n"); + + result = -EIO; + } else + return; + } else if (ssif_info->multi_pos) { + /* Middle of multi-part read. Start the next transaction. */ + int i; + unsigned char blocknum; + + if (len == 0) { + result = -EIO; + if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) + pr_info(PFX "Middle message with no data\n"); + + goto continue_op; + } + + blocknum = data[ssif_info->multi_len]; + + if (ssif_info->multi_len+len-1 > IPMI_MAX_MSG_LENGTH) { + /* Received message too big, abort the operation. */ + result = -E2BIG; + if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) + pr_info("Received message too big\n"); + + goto continue_op; + } + + /* Remove the blocknum from the data. */ + for (i = 0; i < (len-1); i++) + ssif_info->data[i+ssif_info->multi_len] = data[i+1]; + len--; + ssif_info->multi_len += len; + if (blocknum == 0xff) { + /* End of read */ + len = ssif_info->multi_len; + data = ssif_info->data; + } else if ((blocknum+1) != ssif_info->multi_pos) { + /* + * Out of sequence block, just abort. Block + * numbers start at zero for the second block, + * but multi_pos starts at one, so the +1. + */ + result = -EIO; + } else { + ssif_inc_stat(ssif_info, received_message_parts); + + ssif_info->multi_pos++; + + rv = ssif_i2c_send(ssif_info, msg_done_handler, + I2C_SMBUS_READ, + SSIF_IPMI_MULTI_PART_RESPONSE_MIDDLE, + ssif_info->recv, + I2C_SMBUS_BLOCK_DATA); + if (rv < 0) { + if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) + pr_info(PFX + "Error from i2c_non_blocking_op(2)\n"); + + result = -EIO; + } else + return; + } + } + + if (result < 0) { + ssif_inc_stat(ssif_info, receive_errors); + } else { + ssif_inc_stat(ssif_info, received_messages); + ssif_inc_stat(ssif_info, received_message_parts); + } + + + continue_op: + if (ssif_info->ssif_debug & SSIF_DEBUG_STATE) + pr_info(PFX "DONE 1: state = %d, result=%d.\n", + ssif_info->ssif_state, result); + + flags = ipmi_ssif_lock_cond(ssif_info, &oflags); + msg = ssif_info->curr_msg; + if (msg) { + msg->rsp_size = len; + if (msg->rsp_size > IPMI_MAX_MSG_LENGTH) + msg->rsp_size = IPMI_MAX_MSG_LENGTH; + memcpy(msg->rsp, data, msg->rsp_size); + ssif_info->curr_msg = NULL; + } + + switch (ssif_info->ssif_state) { + case SSIF_NORMAL: + ipmi_ssif_unlock_cond(ssif_info, flags); + if (!msg) + break; + + if (result < 0) + return_hosed_msg(ssif_info, msg); + else + deliver_recv_msg(ssif_info, msg); + break; + + case SSIF_GETTING_FLAGS: + /* We got the flags from the SSIF, now handle them. */ + if ((result < 0) || (len < 4) || (data[2] != 0)) { + /* + * Error fetching flags, or invalid length, + * just give up for now. + */ + ssif_info->ssif_state = SSIF_NORMAL; + ipmi_ssif_unlock_cond(ssif_info, flags); + pr_warn(PFX "Error getting flags: %d %d, %x\n", + result, len, data[2]); + } else if (data[0] != (IPMI_NETFN_APP_REQUEST | 1) << 2 + || data[1] != IPMI_GET_MSG_FLAGS_CMD) { + pr_warn(PFX "Invalid response getting flags: %x %x\n", + data[0], data[1]); + } else { + ssif_inc_stat(ssif_info, flag_fetches); + ssif_info->msg_flags = data[3]; + handle_flags(ssif_info, flags); + } + break; + + case SSIF_CLEARING_FLAGS: + /* We cleared the flags. */ + if ((result < 0) || (len < 3) || (data[2] != 0)) { + /* Error clearing flags */ + pr_warn(PFX "Error clearing flags: %d %d, %x\n", + result, len, data[2]); + } else if (data[0] != (IPMI_NETFN_APP_REQUEST | 1) << 2 + || data[1] != IPMI_CLEAR_MSG_FLAGS_CMD) { + pr_warn(PFX "Invalid response clearing flags: %x %x\n", + data[0], data[1]); + } + ssif_info->ssif_state = SSIF_NORMAL; + ipmi_ssif_unlock_cond(ssif_info, flags); + break; + + case SSIF_GETTING_EVENTS: + if ((result < 0) || (len < 3) || (msg->rsp[2] != 0)) { + /* Error getting event, probably done. */ + msg->done(msg); + + /* Take off the event flag. */ + ssif_info->msg_flags &= ~EVENT_MSG_BUFFER_FULL; + handle_flags(ssif_info, flags); + } else if (msg->rsp[0] != (IPMI_NETFN_APP_REQUEST | 1) << 2 + || msg->rsp[1] != IPMI_READ_EVENT_MSG_BUFFER_CMD) { + pr_warn(PFX "Invalid response getting events: %x %x\n", + msg->rsp[0], msg->rsp[1]); + msg->done(msg); + /* Take off the event flag. */ + ssif_info->msg_flags &= ~EVENT_MSG_BUFFER_FULL; + handle_flags(ssif_info, flags); + } else { + handle_flags(ssif_info, flags); + ssif_inc_stat(ssif_info, events); + deliver_recv_msg(ssif_info, msg); + } + break; + + case SSIF_GETTING_MESSAGES: + if ((result < 0) || (len < 3) || (msg->rsp[2] != 0)) { + /* Error getting event, probably done. */ + msg->done(msg); + + /* Take off the msg flag. */ + ssif_info->msg_flags &= ~RECEIVE_MSG_AVAIL; + handle_flags(ssif_info, flags); + } else if (msg->rsp[0] != (IPMI_NETFN_APP_REQUEST | 1) << 2 + || msg->rsp[1] != IPMI_GET_MSG_CMD) { + pr_warn(PFX "Invalid response clearing flags: %x %x\n", + msg->rsp[0], msg->rsp[1]); + msg->done(msg); + + /* Take off the msg flag. */ + ssif_info->msg_flags &= ~RECEIVE_MSG_AVAIL; + handle_flags(ssif_info, flags); + } else { + ssif_inc_stat(ssif_info, incoming_messages); + handle_flags(ssif_info, flags); + deliver_recv_msg(ssif_info, msg); + } + break; + } + + flags = ipmi_ssif_lock_cond(ssif_info, &oflags); + if (SSIF_IDLE(ssif_info) && !ssif_info->stopping) { + if (ssif_info->req_events) + start_event_fetch(ssif_info, flags); + else if (ssif_info->req_flags) + start_flag_fetch(ssif_info, flags); + else + start_next_msg(ssif_info, flags); + } else + ipmi_ssif_unlock_cond(ssif_info, flags); + + if (ssif_info->ssif_debug & SSIF_DEBUG_STATE) + pr_info(PFX "DONE 2: state = %d.\n", ssif_info->ssif_state); +} + +static void msg_written_handler(struct ssif_info *ssif_info, int result, + unsigned char *data, unsigned int len) +{ + int rv; + + /* We are single-threaded here, so no need for a lock. */ + if (result < 0) { + ssif_info->retries_left--; + if (ssif_info->retries_left > 0) { + if (!start_resend(ssif_info)) { + ssif_inc_stat(ssif_info, send_retries); + return; + } + /* request failed, just return the error. */ + ssif_inc_stat(ssif_info, send_errors); + + if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) + pr_info(PFX + "Out of retries in msg_written_handler\n"); + msg_done_handler(ssif_info, -EIO, NULL, 0); + return; + } + + ssif_inc_stat(ssif_info, send_errors); + + /* + * Got an error on transmit, let the done routine + * handle it. + */ + if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) + pr_info("Error in msg_written_handler: %d\n", result); + + msg_done_handler(ssif_info, result, NULL, 0); + return; + } + + if (ssif_info->multi_data) { + /* In the middle of a multi-data write. */ + int left; + + ssif_inc_stat(ssif_info, sent_messages_parts); + + left = ssif_info->multi_len - ssif_info->multi_pos; + if (left > 32) + left = 32; + /* Length byte. */ + ssif_info->multi_data[ssif_info->multi_pos] = left; + ssif_info->multi_pos += left; + if (left < 32) + /* + * Write is finished. Note that we must end + * with a write of less than 32 bytes to + * complete the transaction, even if it is + * zero bytes. + */ + ssif_info->multi_data = NULL; + + rv = ssif_i2c_send(ssif_info, msg_written_handler, + I2C_SMBUS_WRITE, + SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE, + ssif_info->multi_data + ssif_info->multi_pos, + I2C_SMBUS_BLOCK_DATA); + if (rv < 0) { + /* request failed, just return the error. */ + ssif_inc_stat(ssif_info, send_errors); + + if (ssif_info->ssif_debug & SSIF_DEBUG_MSG) + pr_info("Error from i2c_non_blocking_op(3)\n"); + msg_done_handler(ssif_info, -EIO, NULL, 0); + } + } else { + ssif_inc_stat(ssif_info, sent_messages); + ssif_inc_stat(ssif_info, sent_messages_parts); + + /* Wait a jiffie then request the next message */ + ssif_info->retries_left = SSIF_RECV_RETRIES; + ssif_info->rtc_us_timer = SSIF_MSG_PART_USEC; + mod_timer(&ssif_info->retry_timer, + jiffies + SSIF_MSG_PART_JIFFIES); + return; + } +} + +static int start_resend(struct ssif_info *ssif_info) +{ + int rv; + int command; + + if (ssif_info->data_len > 32) { + command = SSIF_IPMI_MULTI_PART_REQUEST_START; + ssif_info->multi_data = ssif_info->data; + ssif_info->multi_len = ssif_info->data_len; + /* + * Subtle thing, this is 32, not 33, because we will + * overwrite the thing at position 32 (which was just + * transmitted) with the new length. + */ + ssif_info->multi_pos = 32; + ssif_info->data[0] = 32; + } else { + ssif_info->multi_data = NULL; + command = SSIF_IPMI_REQUEST; + ssif_info->data[0] = ssif_info->data_len; + } + + rv = ssif_i2c_send(ssif_info, msg_written_handler, I2C_SMBUS_WRITE, + command, ssif_info->data, I2C_SMBUS_BLOCK_DATA); + if (rv && (ssif_info->ssif_debug & SSIF_DEBUG_MSG)) + pr_info("Error from i2c_non_blocking_op(4)\n"); + return rv; +} + +static int start_send(struct ssif_info *ssif_info, + unsigned char *data, + unsigned int len) +{ + if (len > IPMI_MAX_MSG_LENGTH) + return -E2BIG; + if (len > ssif_info->max_xmit_msg_size) + return -E2BIG; + + ssif_info->retries_left = SSIF_SEND_RETRIES; + memcpy(ssif_info->data+1, data, len); + ssif_info->data_len = len; + return start_resend(ssif_info); +} + +/* Must be called with the message lock held. */ +static void start_next_msg(struct ssif_info *ssif_info, unsigned long *flags) +{ + struct ipmi_smi_msg *msg; + unsigned long oflags; + + restart: + if (!SSIF_IDLE(ssif_info)) { + ipmi_ssif_unlock_cond(ssif_info, flags); + return; + } + + if (!ssif_info->waiting_msg) { + ssif_info->curr_msg = NULL; + ipmi_ssif_unlock_cond(ssif_info, flags); + } else { + int rv; + + ssif_info->curr_msg = ssif_info->waiting_msg; + ssif_info->waiting_msg = NULL; + ipmi_ssif_unlock_cond(ssif_info, flags); + rv = start_send(ssif_info, + ssif_info->curr_msg->data, + ssif_info->curr_msg->data_size); + if (rv) { + msg = ssif_info->curr_msg; + ssif_info->curr_msg = NULL; + return_hosed_msg(ssif_info, msg); + flags = ipmi_ssif_lock_cond(ssif_info, &oflags); + goto restart; + } + } +} + +static void sender(void *send_info, + struct ipmi_smi_msg *msg) +{ + struct ssif_info *ssif_info = (struct ssif_info *) send_info; + unsigned long oflags, *flags; + + BUG_ON(ssif_info->waiting_msg); + ssif_info->waiting_msg = msg; + + flags = ipmi_ssif_lock_cond(ssif_info, &oflags); + start_next_msg(ssif_info, flags); + + if (ssif_info->ssif_debug & SSIF_DEBUG_TIMING) { + struct timeval t; + + do_gettimeofday(&t); + pr_info("**Enqueue %02x %02x: %ld.%6.6ld\n", + msg->data[0], msg->data[1], t.tv_sec, t.tv_usec); + } +} + +static int get_smi_info(void *send_info, struct ipmi_smi_info *data) +{ + struct ssif_info *ssif_info = send_info; + + data->addr_src = ssif_info->addr_source; + data->dev = &ssif_info->client->dev; + data->addr_info = ssif_info->addr_info; + get_device(data->dev); + + return 0; +} + +/* + * Instead of having our own timer to periodically check the message + * flags, we let the message handler drive us. + */ +static void request_events(void *send_info) +{ + struct ssif_info *ssif_info = (struct ssif_info *) send_info; + unsigned long oflags, *flags; + + if (!ssif_info->has_event_buffer) + return; + + flags = ipmi_ssif_lock_cond(ssif_info, &oflags); + /* + * Request flags first, not events, because the lower layer + * doesn't have a way to send an attention. But make sure + * event checking still happens. + */ + ssif_info->req_events = true; + if (SSIF_IDLE(ssif_info)) + start_flag_fetch(ssif_info, flags); + else { + ssif_info->req_flags = true; + ipmi_ssif_unlock_cond(ssif_info, flags); + } +} + +static int inc_usecount(void *send_info) +{ + struct ssif_info *ssif_info = send_info; + + if (!i2c_get_adapter(ssif_info->client->adapter->nr)) + return -ENODEV; + + i2c_use_client(ssif_info->client); + return 0; +} + +static void dec_usecount(void *send_info) +{ + struct ssif_info *ssif_info = send_info; + + i2c_release_client(ssif_info->client); + i2c_put_adapter(ssif_info->client->adapter); +} + +static int ssif_start_processing(void *send_info, + ipmi_smi_t intf) +{ + struct ssif_info *ssif_info = send_info; + + ssif_info->intf = intf; + + return 0; +} + +#define MAX_SSIF_BMCS 4 + +static unsigned short addr[MAX_SSIF_BMCS]; +static int num_addrs; +module_param_array(addr, ushort, &num_addrs, 0); +MODULE_PARM_DESC(addr, "The addresses to scan for IPMI BMCs on the SSIFs."); + +static char *adapter_name[MAX_SSIF_BMCS]; +static int num_adapter_names; +module_param_array(adapter_name, charp, &num_adapter_names, 0); +MODULE_PARM_DESC(adapter_name, "The string name of the I2C device that has the BMC. By default all devices are scanned."); + +static int slave_addrs[MAX_SSIF_BMCS]; +static int num_slave_addrs; +module_param_array(slave_addrs, int, &num_slave_addrs, 0); +MODULE_PARM_DESC(slave_addrs, + "The default IPMB slave address for the controller."); + +/* + * Bit 0 enables message debugging, bit 1 enables state debugging, and + * bit 2 enables timing debugging. This is an array indexed by + * interface number" + */ +static int dbg[MAX_SSIF_BMCS]; +static int num_dbg; +module_param_array(dbg, int, &num_dbg, 0); +MODULE_PARM_DESC(dbg, "Turn on debugging."); + +static bool ssif_dbg_probe; +module_param_named(dbg_probe, ssif_dbg_probe, bool, 0); +MODULE_PARM_DESC(dbg_probe, "Enable debugging of probing of adapters."); + +static int use_thread; +module_param(use_thread, int, 0); +MODULE_PARM_DESC(use_thread, "Use the thread interface."); + +static bool ssif_tryacpi = 1; +module_param_named(tryacpi, ssif_tryacpi, bool, 0); +MODULE_PARM_DESC(tryacpi, "Setting this to zero will disable the default scan of the interfaces identified via ACPI"); + +static bool ssif_trydmi = 1; +module_param_named(trydmi, ssif_trydmi, bool, 0); +MODULE_PARM_DESC(trydmi, "Setting this to zero will disable the default scan of the interfaces identified via DMI (SMBIOS)"); + +static DEFINE_MUTEX(ssif_infos_mutex); +static LIST_HEAD(ssif_infos); + +static int ssif_remove(struct i2c_client *client) +{ + struct ssif_info *ssif_info = i2c_get_clientdata(client); + int rv; + + if (!ssif_info) + return 0; + + i2c_set_clientdata(client, NULL); + + /* + * After this point, we won't deliver anything asychronously + * to the message handler. We can unregister ourself. + */ + rv = ipmi_unregister_smi(ssif_info->intf); + if (rv) { + pr_err(PFX "Unable to unregister device: errno=%d\n", rv); + return rv; + } + ssif_info->intf = NULL; + + /* make sure the driver is not looking for flags any more. */ + while (ssif_info->ssif_state != SSIF_NORMAL) + schedule_timeout(1); + + ssif_info->stopping = true; + del_timer_sync(&ssif_info->retry_timer); + if (ssif_info->thread) { + complete(&ssif_info->wake_thread); + kthread_stop(ssif_info->thread); + } + + /* + * No message can be outstanding now, we have removed the + * upper layer and it permitted us to do so. + */ + kfree(ssif_info); + return 0; +} + +static int do_cmd(struct i2c_client *client, int len, unsigned char *msg, + int *resp_len, unsigned char *resp) +{ + int retry_cnt; + int ret; + + retry_cnt = SSIF_SEND_RETRIES; + retry1: + ret = i2c_smbus_write_block_data(client, SSIF_IPMI_REQUEST, len, msg); + if (ret) { + retry_cnt--; + if (retry_cnt > 0) + goto retry1; + return -ENODEV; + } + + ret = -ENODEV; + retry_cnt = SSIF_RECV_RETRIES; + while (retry_cnt > 0) { + ret = i2c_smbus_read_block_data(client, SSIF_IPMI_RESPONSE, + resp); + if (ret > 0) + break; + msleep(SSIF_MSG_MSEC); + retry_cnt--; + if (retry_cnt <= 0) + break; + } + + if (ret > 0) { + /* Validate that the response is correct. */ + if (ret < 3 || + (resp[0] != (msg[0] | (1 << 2))) || + (resp[1] != msg[1])) + ret = -EINVAL; + else { + *resp_len = ret; + ret = 0; + } + } + + return ret; +} + +static int ssif_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + unsigned char *resp; + unsigned char msg[3]; + int rv; + int len; + + resp = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL); + if (!resp) + return -ENOMEM; + + /* Do a Get Device ID command, since it is required. */ + msg[0] = IPMI_NETFN_APP_REQUEST << 2; + msg[1] = IPMI_GET_DEVICE_ID_CMD; + rv = do_cmd(client, 2, msg, &len, resp); + if (rv) + rv = -ENODEV; + else + strlcpy(info->type, DEVICE_NAME, I2C_NAME_SIZE); + kfree(resp); + return rv; +} + +static int smi_type_proc_show(struct seq_file *m, void *v) +{ + return seq_puts(m, "ssif\n"); +} + +static int smi_type_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, smi_type_proc_show, inode->i_private); +} + +static const struct file_operations smi_type_proc_ops = { + .open = smi_type_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int smi_stats_proc_show(struct seq_file *m, void *v) +{ + struct ssif_info *ssif_info = m->private; + + seq_printf(m, "sent_messages: %u\n", + ssif_get_stat(ssif_info, sent_messages)); + seq_printf(m, "sent_messages_parts: %u\n", + ssif_get_stat(ssif_info, sent_messages_parts)); + seq_printf(m, "send_retries: %u\n", + ssif_get_stat(ssif_info, send_retries)); + seq_printf(m, "send_errors: %u\n", + ssif_get_stat(ssif_info, send_errors)); + seq_printf(m, "received_messages: %u\n", + ssif_get_stat(ssif_info, received_messages)); + seq_printf(m, "received_message_parts: %u\n", + ssif_get_stat(ssif_info, received_message_parts)); + seq_printf(m, "receive_retries: %u\n", + ssif_get_stat(ssif_info, receive_retries)); + seq_printf(m, "receive_errors: %u\n", + ssif_get_stat(ssif_info, receive_errors)); + seq_printf(m, "flag_fetches: %u\n", + ssif_get_stat(ssif_info, flag_fetches)); + seq_printf(m, "hosed: %u\n", + ssif_get_stat(ssif_info, hosed)); + seq_printf(m, "events: %u\n", + ssif_get_stat(ssif_info, events)); + seq_printf(m, "watchdog_pretimeouts: %u\n", + ssif_get_stat(ssif_info, watchdog_pretimeouts)); + return 0; +} + +static int smi_stats_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, smi_stats_proc_show, PDE_DATA(inode)); +} + +static const struct file_operations smi_stats_proc_ops = { + .open = smi_stats_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct ssif_addr_info *ssif_info_find(unsigned short addr, + char *adapter_name, + bool match_null_name) +{ + struct ssif_addr_info *info, *found = NULL; + +restart: + list_for_each_entry(info, &ssif_infos, link) { + if (info->binfo.addr == addr) { + if (info->adapter_name || adapter_name) { + if (!info->adapter_name != !adapter_name) { + /* One is NULL and one is not */ + continue; + } + if (strcmp(info->adapter_name, adapter_name)) + /* Names to not match */ + continue; + } + found = info; + break; + } + } + + if (!found && match_null_name) { + /* Try to get an exact match first, then try with a NULL name */ + adapter_name = NULL; + match_null_name = false; + goto restart; + } + + return found; +} + +static bool check_acpi(struct ssif_info *ssif_info, struct device *dev) +{ +#ifdef CONFIG_ACPI + acpi_handle acpi_handle; + + acpi_handle = ACPI_HANDLE(dev); + if (acpi_handle) { + ssif_info->addr_source = SI_ACPI; + ssif_info->addr_info.acpi_info.acpi_handle = acpi_handle; + return true; + } +#endif + return false; +} + +static int ssif_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + unsigned char msg[3]; + unsigned char *resp; + struct ssif_info *ssif_info; + int rv = 0; + int len; + int i; + u8 slave_addr = 0; + struct ssif_addr_info *addr_info = NULL; + + + resp = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL); + if (!resp) + return -ENOMEM; + + ssif_info = kzalloc(sizeof(*ssif_info), GFP_KERNEL); + if (!ssif_info) { + kfree(resp); + return -ENOMEM; + } + + if (!check_acpi(ssif_info, &client->dev)) { + addr_info = ssif_info_find(client->addr, client->adapter->name, + true); + if (!addr_info) { + /* Must have come in through sysfs. */ + ssif_info->addr_source = SI_HOTMOD; + } else { + ssif_info->addr_source = addr_info->addr_src; + ssif_info->ssif_debug = addr_info->debug; + ssif_info->addr_info = addr_info->addr_info; + slave_addr = addr_info->slave_addr; + } + } + + pr_info(PFX "Trying %s-specified SSIF interface at i2c address 0x%x, adapter %s, slave address 0x%x\n", + ipmi_addr_src_to_str(ssif_info->addr_source), + client->addr, client->adapter->name, slave_addr); + + /* + * Do a Get Device ID command, since it comes back with some + * useful info. + */ + msg[0] = IPMI_NETFN_APP_REQUEST << 2; + msg[1] = IPMI_GET_DEVICE_ID_CMD; + rv = do_cmd(client, 2, msg, &len, resp); + if (rv) + goto out; + + rv = ipmi_demangle_device_id(resp, len, &ssif_info->device_id); + if (rv) + goto out; + + ssif_info->client = client; + i2c_set_clientdata(client, ssif_info); + + /* Now check for system interface capabilities */ + msg[0] = IPMI_NETFN_APP_REQUEST << 2; + msg[1] = IPMI_GET_SYSTEM_INTERFACE_CAPABILITIES_CMD; + msg[2] = 0; /* SSIF */ + rv = do_cmd(client, 3, msg, &len, resp); + if (!rv && (len >= 3) && (resp[2] == 0)) { + if (len < 7) { + if (ssif_dbg_probe) + pr_info(PFX "SSIF info too short: %d\n", len); + goto no_support; + } + + /* Got a good SSIF response, handle it. */ + ssif_info->max_xmit_msg_size = resp[5]; + ssif_info->max_recv_msg_size = resp[6]; + ssif_info->multi_support = (resp[4] >> 6) & 0x3; + ssif_info->supports_pec = (resp[4] >> 3) & 0x1; + + /* Sanitize the data */ + switch (ssif_info->multi_support) { + case SSIF_NO_MULTI: + if (ssif_info->max_xmit_msg_size > 32) + ssif_info->max_xmit_msg_size = 32; + if (ssif_info->max_recv_msg_size > 32) + ssif_info->max_recv_msg_size = 32; + break; + + case SSIF_MULTI_2_PART: + if (ssif_info->max_xmit_msg_size > 64) + ssif_info->max_xmit_msg_size = 64; + if (ssif_info->max_recv_msg_size > 62) + ssif_info->max_recv_msg_size = 62; + break; + + case SSIF_MULTI_n_PART: + break; + + default: + /* Data is not sane, just give up. */ + goto no_support; + } + } else { + no_support: + /* Assume no multi-part or PEC support */ + pr_info(PFX "Error fetching SSIF: %d %d %2.2x, your system probably doesn't support this command so using defaults\n", + rv, len, resp[2]); + + ssif_info->max_xmit_msg_size = 32; + ssif_info->max_recv_msg_size = 32; + ssif_info->multi_support = SSIF_NO_MULTI; + ssif_info->supports_pec = 0; + } + + /* Make sure the NMI timeout is cleared. */ + msg[0] = IPMI_NETFN_APP_REQUEST << 2; + msg[1] = IPMI_CLEAR_MSG_FLAGS_CMD; + msg[2] = WDT_PRE_TIMEOUT_INT; + rv = do_cmd(client, 3, msg, &len, resp); + if (rv || (len < 3) || (resp[2] != 0)) + pr_warn(PFX "Unable to clear message flags: %d %d %2.2x\n", + rv, len, resp[2]); + + /* Attempt to enable the event buffer. */ + msg[0] = IPMI_NETFN_APP_REQUEST << 2; + msg[1] = IPMI_GET_BMC_GLOBAL_ENABLES_CMD; + rv = do_cmd(client, 2, msg, &len, resp); + if (rv || (len < 4) || (resp[2] != 0)) { + pr_warn(PFX "Error getting global enables: %d %d %2.2x\n", + rv, len, resp[2]); + rv = 0; /* Not fatal */ + goto found; + } + + if (resp[3] & IPMI_BMC_EVT_MSG_BUFF) { + ssif_info->has_event_buffer = true; + /* buffer is already enabled, nothing to do. */ + goto found; + } + + msg[0] = IPMI_NETFN_APP_REQUEST << 2; + msg[1] = IPMI_SET_BMC_GLOBAL_ENABLES_CMD; + msg[2] = resp[3] | IPMI_BMC_EVT_MSG_BUFF; + rv = do_cmd(client, 3, msg, &len, resp); + if (rv || (len < 2)) { + pr_warn(PFX "Error getting global enables: %d %d %2.2x\n", + rv, len, resp[2]); + rv = 0; /* Not fatal */ + goto found; + } + + if (resp[2] == 0) + /* A successful return means the event buffer is supported. */ + ssif_info->has_event_buffer = true; + + found: + ssif_info->intf_num = atomic_inc_return(&next_intf); + + if (ssif_dbg_probe) { + pr_info("ssif_probe: i2c_probe found device at i2c address %x\n", + client->addr); + } + + spin_lock_init(&ssif_info->lock); + ssif_info->ssif_state = SSIF_NORMAL; + init_timer(&ssif_info->retry_timer); + ssif_info->retry_timer.data = (unsigned long) ssif_info; + ssif_info->retry_timer.function = retry_timeout; + + for (i = 0; i < SSIF_NUM_STATS; i++) + atomic_set(&ssif_info->stats[i], 0); + + if (ssif_info->supports_pec) + ssif_info->client->flags |= I2C_CLIENT_PEC; + + ssif_info->handlers.owner = THIS_MODULE; + ssif_info->handlers.start_processing = ssif_start_processing; + ssif_info->handlers.get_smi_info = get_smi_info; + ssif_info->handlers.sender = sender; + ssif_info->handlers.request_events = request_events; + ssif_info->handlers.inc_usecount = inc_usecount; + ssif_info->handlers.dec_usecount = dec_usecount; + + { + unsigned int thread_num; + + thread_num = ((ssif_info->client->adapter->nr << 8) | + ssif_info->client->addr); + init_completion(&ssif_info->wake_thread); + ssif_info->thread = kthread_run(ipmi_ssif_thread, ssif_info, + "kssif%4.4x", thread_num); + if (IS_ERR(ssif_info->thread)) { + rv = PTR_ERR(ssif_info->thread); + dev_notice(&ssif_info->client->dev, + "Could not start kernel thread: error %d\n", + rv); + goto out; + } + } + + rv = ipmi_register_smi(&ssif_info->handlers, + ssif_info, + &ssif_info->device_id, + &ssif_info->client->dev, + slave_addr); + if (rv) { + pr_err(PFX "Unable to register device: error %d\n", rv); + goto out; + } + + rv = ipmi_smi_add_proc_entry(ssif_info->intf, "type", + &smi_type_proc_ops, + ssif_info); + if (rv) { + pr_err(PFX "Unable to create proc entry: %d\n", rv); + goto out_err_unreg; + } + + rv = ipmi_smi_add_proc_entry(ssif_info->intf, "ssif_stats", + &smi_stats_proc_ops, + ssif_info); + if (rv) { + pr_err(PFX "Unable to create proc entry: %d\n", rv); + goto out_err_unreg; + } + + out: + if (rv) + kfree(ssif_info); + kfree(resp); + return rv; + + out_err_unreg: + ipmi_unregister_smi(ssif_info->intf); + goto out; +} + +static int ssif_adapter_handler(struct device *adev, void *opaque) +{ + struct ssif_addr_info *addr_info = opaque; + + if (adev->type != &i2c_adapter_type) + return 0; + + i2c_new_device(to_i2c_adapter(adev), &addr_info->binfo); + + if (!addr_info->adapter_name) + return 1; /* Only try the first I2C adapter by default. */ + return 0; +} + +static int new_ssif_client(int addr, char *adapter_name, + int debug, int slave_addr, + enum ipmi_addr_src addr_src) +{ + struct ssif_addr_info *addr_info; + int rv = 0; + + mutex_lock(&ssif_infos_mutex); + if (ssif_info_find(addr, adapter_name, false)) { + rv = -EEXIST; + goto out_unlock; + } + + addr_info = kzalloc(sizeof(*addr_info), GFP_KERNEL); + if (!addr_info) { + rv = -ENOMEM; + goto out_unlock; + } + + if (adapter_name) { + addr_info->adapter_name = kstrdup(adapter_name, GFP_KERNEL); + if (!addr_info->adapter_name) { + kfree(addr_info); + rv = -ENOMEM; + goto out_unlock; + } + } + + strncpy(addr_info->binfo.type, DEVICE_NAME, + sizeof(addr_info->binfo.type)); + addr_info->binfo.addr = addr; + addr_info->binfo.platform_data = addr_info; + addr_info->debug = debug; + addr_info->slave_addr = slave_addr; + addr_info->addr_src = addr_src; + + list_add_tail(&addr_info->link, &ssif_infos); + + if (initialized) + i2c_for_each_dev(addr_info, ssif_adapter_handler); + /* Otherwise address list will get it */ + +out_unlock: + mutex_unlock(&ssif_infos_mutex); + return rv; +} + +static void free_ssif_clients(void) +{ + struct ssif_addr_info *info, *tmp; + + mutex_lock(&ssif_infos_mutex); + list_for_each_entry_safe(info, tmp, &ssif_infos, link) { + list_del(&info->link); + kfree(info->adapter_name); + kfree(info); + } + mutex_unlock(&ssif_infos_mutex); +} + +static unsigned short *ssif_address_list(void) +{ + struct ssif_addr_info *info; + unsigned int count = 0, i; + unsigned short *address_list; + + list_for_each_entry(info, &ssif_infos, link) + count++; + + address_list = kzalloc(sizeof(*address_list) * (count + 1), GFP_KERNEL); + if (!address_list) + return NULL; + + i = 0; + list_for_each_entry(info, &ssif_infos, link) { + unsigned short addr = info->binfo.addr; + int j; + + for (j = 0; j < i; j++) { + if (address_list[j] == addr) + goto skip_addr; + } + address_list[i] = addr; +skip_addr: + i++; + } + address_list[i] = I2C_CLIENT_END; + + return address_list; +} + +#ifdef CONFIG_ACPI +static struct acpi_device_id ssif_acpi_match[] = { + { "IPI0001", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ssif_acpi_match); + +/* + * Once we get an ACPI failure, we don't try any more, because we go + * through the tables sequentially. Once we don't find a table, there + * are no more. + */ +static int acpi_failure; + +/* + * Defined in the IPMI 2.0 spec. + */ +struct SPMITable { + s8 Signature[4]; + u32 Length; + u8 Revision; + u8 Checksum; + s8 OEMID[6]; + s8 OEMTableID[8]; + s8 OEMRevision[4]; + s8 CreatorID[4]; + s8 CreatorRevision[4]; + u8 InterfaceType; + u8 IPMIlegacy; + s16 SpecificationRevision; + + /* + * Bit 0 - SCI interrupt supported + * Bit 1 - I/O APIC/SAPIC + */ + u8 InterruptType; + + /* + * If bit 0 of InterruptType is set, then this is the SCI + * interrupt in the GPEx_STS register. + */ + u8 GPE; + + s16 Reserved; + + /* + * If bit 1 of InterruptType is set, then this is the I/O + * APIC/SAPIC interrupt. + */ + u32 GlobalSystemInterrupt; + + /* The actual register address. */ + struct acpi_generic_address addr; + + u8 UID[4]; + + s8 spmi_id[1]; /* A '\0' terminated array starts here. */ +}; + +static int try_init_spmi(struct SPMITable *spmi) +{ + unsigned short myaddr; + + if (num_addrs >= MAX_SSIF_BMCS) + return -1; + + if (spmi->IPMIlegacy != 1) { + pr_warn("IPMI: Bad SPMI legacy: %d\n", spmi->IPMIlegacy); + return -ENODEV; + } + + if (spmi->InterfaceType != 4) + return -ENODEV; + + if (spmi->addr.space_id != ACPI_ADR_SPACE_SMBUS) { + pr_warn(PFX "Invalid ACPI SSIF I/O Address type: %d\n", + spmi->addr.space_id); + return -EIO; + } + + myaddr = spmi->addr.address >> 1; + + return new_ssif_client(myaddr, NULL, 0, 0, SI_SPMI); +} + +static void spmi_find_bmc(void) +{ + acpi_status status; + struct SPMITable *spmi; + int i; + + if (acpi_disabled) + return; + + if (acpi_failure) + return; + + for (i = 0; ; i++) { + status = acpi_get_table(ACPI_SIG_SPMI, i+1, + (struct acpi_table_header **)&spmi); + if (status != AE_OK) + return; + + try_init_spmi(spmi); + } +} +#else +static void spmi_find_bmc(void) { } +#endif + +#ifdef CONFIG_DMI +static int decode_dmi(const struct dmi_device *dmi_dev) +{ + struct dmi_header *dm = dmi_dev->device_data; + u8 *data = (u8 *) dm; + u8 len = dm->length; + unsigned short myaddr; + int slave_addr; + + if (num_addrs >= MAX_SSIF_BMCS) + return -1; + + if (len < 9) + return -1; + + if (data[0x04] != 4) /* Not SSIF */ + return -1; + + if ((data[8] >> 1) == 0) { + /* + * Some broken systems put the I2C address in + * the slave address field. We try to + * accommodate them here. + */ + myaddr = data[6] >> 1; + slave_addr = 0; + } else { + myaddr = data[8] >> 1; + slave_addr = data[6]; + } + + return new_ssif_client(myaddr, NULL, 0, 0, SI_SMBIOS); +} + +static void dmi_iterator(void) +{ + const struct dmi_device *dev = NULL; + + while ((dev = dmi_find_device(DMI_DEV_TYPE_IPMI, NULL, dev))) + decode_dmi(dev); +} +#else +static void dmi_iterator(void) { } +#endif + +static const struct i2c_device_id ssif_id[] = { + { DEVICE_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssif_id); + +static struct i2c_driver ssif_i2c_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .owner = THIS_MODULE, + .name = DEVICE_NAME + }, + .probe = ssif_probe, + .remove = ssif_remove, + .id_table = ssif_id, + .detect = ssif_detect +}; + +static int init_ipmi_ssif(void) +{ + int i; + int rv; + + if (initialized) + return 0; + + pr_info("IPMI SSIF Interface driver\n"); + + /* build list for i2c from addr list */ + for (i = 0; i < num_addrs; i++) { + rv = new_ssif_client(addr[i], adapter_name[i], + dbg[i], slave_addrs[i], + SI_HARDCODED); + if (!rv) + pr_err(PFX + "Couldn't add hardcoded device at addr 0x%x\n", + addr[i]); + } + + if (ssif_tryacpi) + ssif_i2c_driver.driver.acpi_match_table = + ACPI_PTR(ssif_acpi_match); + if (ssif_trydmi) + dmi_iterator(); + if (ssif_tryacpi) + spmi_find_bmc(); + + ssif_i2c_driver.address_list = ssif_address_list(); + + rv = i2c_add_driver(&ssif_i2c_driver); + if (!rv) + initialized = true; + + return rv; +} +module_init(init_ipmi_ssif); + +static void cleanup_ipmi_ssif(void) +{ + if (!initialized) + return; + + initialized = false; + + i2c_del_driver(&ssif_i2c_driver); + + free_ssif_clients(); +} +module_exit(cleanup_ipmi_ssif); + +MODULE_AUTHOR("Todd C Davis <todd.c.davis@intel.com>, Corey Minyard <minyard@acm.org>"); +MODULE_DESCRIPTION("IPMI driver for management controllers on a SMBus"); +MODULE_LICENSE("GPL"); |