|  | // SPDX-License-Identifier: GPL-2.0 | 
|  |  | 
|  | /* | 
|  | * Driver to talk to a remote management controller on IPMB. | 
|  | */ | 
|  |  | 
|  | #include <linux/acpi.h> | 
|  | #include <linux/errno.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/miscdevice.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/mutex.h> | 
|  | #include <linux/poll.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/semaphore.h> | 
|  | #include <linux/kthread.h> | 
|  | #include <linux/wait.h> | 
|  | #include <linux/ipmi_msgdefs.h> | 
|  | #include <linux/ipmi_smi.h> | 
|  |  | 
|  | #define DEVICE_NAME "ipmi-ipmb" | 
|  |  | 
|  | static int bmcaddr = 0x20; | 
|  | module_param(bmcaddr, int, 0644); | 
|  | MODULE_PARM_DESC(bmcaddr, "Address to use for BMC."); | 
|  |  | 
|  | static unsigned int retry_time_ms = 250; | 
|  | module_param(retry_time_ms, uint, 0644); | 
|  | MODULE_PARM_DESC(retry_time_ms, "Timeout time between retries, in milliseconds."); | 
|  |  | 
|  | static unsigned int max_retries = 1; | 
|  | module_param(max_retries, uint, 0644); | 
|  | MODULE_PARM_DESC(max_retries, "Max resends of a command before timing out."); | 
|  |  | 
|  | /* Add room for the two slave addresses, two checksums, and rqSeq. */ | 
|  | #define IPMB_MAX_MSG_LEN (IPMI_MAX_MSG_LENGTH + 5) | 
|  |  | 
|  | struct ipmi_ipmb_dev { | 
|  | struct ipmi_smi *intf; | 
|  | struct i2c_client *client; | 
|  | struct i2c_client *slave; | 
|  |  | 
|  | struct ipmi_smi_handlers handlers; | 
|  |  | 
|  | bool ready; | 
|  |  | 
|  | u8 curr_seq; | 
|  |  | 
|  | u8 bmcaddr; | 
|  | u32 retry_time_ms; | 
|  | u32 max_retries; | 
|  |  | 
|  | struct ipmi_smi_msg *next_msg; | 
|  | struct ipmi_smi_msg *working_msg; | 
|  |  | 
|  | /* Transmit thread. */ | 
|  | struct task_struct *thread; | 
|  | struct semaphore wake_thread; | 
|  | struct semaphore got_rsp; | 
|  | spinlock_t lock; | 
|  | bool stopping; | 
|  |  | 
|  | u8 xmitmsg[IPMB_MAX_MSG_LEN]; | 
|  | unsigned int xmitlen; | 
|  |  | 
|  | u8 rcvmsg[IPMB_MAX_MSG_LEN]; | 
|  | unsigned int rcvlen; | 
|  | bool overrun; | 
|  | }; | 
|  |  | 
|  | static bool valid_ipmb(struct ipmi_ipmb_dev *iidev) | 
|  | { | 
|  | u8 *msg = iidev->rcvmsg; | 
|  | u8 netfn; | 
|  |  | 
|  | if (iidev->overrun) | 
|  | return false; | 
|  |  | 
|  | /* Minimum message size. */ | 
|  | if (iidev->rcvlen < 7) | 
|  | return false; | 
|  |  | 
|  | /* Is it a response? */ | 
|  | netfn = msg[1] >> 2; | 
|  | if (netfn & 1) { | 
|  | /* Response messages have an added completion code. */ | 
|  | if (iidev->rcvlen < 8) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (ipmb_checksum(msg, 3) != 0) | 
|  | return false; | 
|  | if (ipmb_checksum(msg + 3, iidev->rcvlen - 3) != 0) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static void ipmi_ipmb_check_msg_done(struct ipmi_ipmb_dev *iidev) | 
|  | { | 
|  | struct ipmi_smi_msg *imsg = NULL; | 
|  | u8 *msg = iidev->rcvmsg; | 
|  | bool is_cmd; | 
|  | unsigned long flags; | 
|  |  | 
|  | if (iidev->rcvlen == 0) | 
|  | return; | 
|  | if (!valid_ipmb(iidev)) | 
|  | goto done; | 
|  |  | 
|  | is_cmd = ((msg[1] >> 2) & 1) == 0; | 
|  |  | 
|  | if (is_cmd) { | 
|  | /* Ignore commands until we are up. */ | 
|  | if (!iidev->ready) | 
|  | goto done; | 
|  |  | 
|  | /* It's a command, allocate a message for it. */ | 
|  | imsg = ipmi_alloc_smi_msg(); | 
|  | if (!imsg) | 
|  | goto done; | 
|  | imsg->type = IPMI_SMI_MSG_TYPE_IPMB_DIRECT; | 
|  | imsg->data_size = 0; | 
|  | } else { | 
|  | spin_lock_irqsave(&iidev->lock, flags); | 
|  | if (iidev->working_msg) { | 
|  | u8 seq = msg[4] >> 2; | 
|  | bool xmit_rsp = (iidev->working_msg->data[0] >> 2) & 1; | 
|  |  | 
|  | /* | 
|  | * Responses should carry the sequence we sent | 
|  | * them with.  If it's a transmitted response, | 
|  | * ignore it.  And if the message hasn't been | 
|  | * transmitted, ignore it. | 
|  | */ | 
|  | if (!xmit_rsp && seq == iidev->curr_seq) { | 
|  | iidev->curr_seq = (iidev->curr_seq + 1) & 0x3f; | 
|  |  | 
|  | imsg = iidev->working_msg; | 
|  | iidev->working_msg = NULL; | 
|  | } | 
|  | } | 
|  | spin_unlock_irqrestore(&iidev->lock, flags); | 
|  | } | 
|  |  | 
|  | if (!imsg) | 
|  | goto done; | 
|  |  | 
|  | if (imsg->type == IPMI_SMI_MSG_TYPE_IPMB_DIRECT) { | 
|  | imsg->rsp[0] = msg[1]; /* NetFn/LUN */ | 
|  | /* | 
|  | * Keep the source address, rqSeq.  Drop the trailing | 
|  | * checksum. | 
|  | */ | 
|  | memcpy(imsg->rsp + 1, msg + 3, iidev->rcvlen - 4); | 
|  | imsg->rsp_size = iidev->rcvlen - 3; | 
|  | } else { | 
|  | imsg->rsp[0] = msg[1]; /* NetFn/LUN */ | 
|  | /* | 
|  | * Skip the source address, rqSeq.  Drop the trailing | 
|  | * checksum. | 
|  | */ | 
|  | memcpy(imsg->rsp + 1, msg + 5, iidev->rcvlen - 6); | 
|  | imsg->rsp_size = iidev->rcvlen - 5; | 
|  | } | 
|  | ipmi_smi_msg_received(iidev->intf, imsg); | 
|  | if (!is_cmd) | 
|  | up(&iidev->got_rsp); | 
|  |  | 
|  | done: | 
|  | iidev->overrun = false; | 
|  | iidev->rcvlen = 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The IPMB protocol only supports i2c writes so there is no need to | 
|  | * support I2C_SLAVE_READ* events, except to know if the other end has | 
|  | * issued a read without going to stop mode. | 
|  | */ | 
|  | static int ipmi_ipmb_slave_cb(struct i2c_client *client, | 
|  | enum i2c_slave_event event, u8 *val) | 
|  | { | 
|  | struct ipmi_ipmb_dev *iidev = i2c_get_clientdata(client); | 
|  |  | 
|  | switch (event) { | 
|  | case I2C_SLAVE_WRITE_REQUESTED: | 
|  | ipmi_ipmb_check_msg_done(iidev); | 
|  | /* | 
|  | * First byte is the slave address, to ease the checksum | 
|  | * calculation. | 
|  | */ | 
|  | iidev->rcvmsg[0] = client->addr << 1; | 
|  | iidev->rcvlen = 1; | 
|  | break; | 
|  |  | 
|  | case I2C_SLAVE_WRITE_RECEIVED: | 
|  | if (iidev->rcvlen >= sizeof(iidev->rcvmsg)) | 
|  | iidev->overrun = true; | 
|  | else | 
|  | iidev->rcvmsg[iidev->rcvlen++] = *val; | 
|  | break; | 
|  |  | 
|  | case I2C_SLAVE_READ_REQUESTED: | 
|  | case I2C_SLAVE_STOP: | 
|  | ipmi_ipmb_check_msg_done(iidev); | 
|  | break; | 
|  |  | 
|  | case I2C_SLAVE_READ_PROCESSED: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void ipmi_ipmb_send_response(struct ipmi_ipmb_dev *iidev, | 
|  | struct ipmi_smi_msg *msg, u8 cc) | 
|  | { | 
|  | if ((msg->data[0] >> 2) & 1) { | 
|  | /* | 
|  | * It's a response being sent, we need to return a | 
|  | * response to the response.  Fake a send msg command | 
|  | * response with channel 0.  This will always be ipmb | 
|  | * direct. | 
|  | */ | 
|  | msg->data[0] = (IPMI_NETFN_APP_REQUEST | 1) << 2; | 
|  | msg->data[3] = IPMI_SEND_MSG_CMD; | 
|  | msg->data[4] = cc; | 
|  | msg->data_size = 5; | 
|  | } | 
|  | msg->rsp[0] = msg->data[0] | (1 << 2); | 
|  | if (msg->type == IPMI_SMI_MSG_TYPE_IPMB_DIRECT) { | 
|  | msg->rsp[1] = msg->data[1]; | 
|  | msg->rsp[2] = msg->data[2]; | 
|  | msg->rsp[3] = msg->data[3]; | 
|  | msg->rsp[4] = cc; | 
|  | msg->rsp_size = 5; | 
|  | } else { | 
|  | msg->rsp[1] = msg->data[1]; | 
|  | msg->rsp[2] = cc; | 
|  | msg->rsp_size = 3; | 
|  | } | 
|  | ipmi_smi_msg_received(iidev->intf, msg); | 
|  | } | 
|  |  | 
|  | static void ipmi_ipmb_format_for_xmit(struct ipmi_ipmb_dev *iidev, | 
|  | struct ipmi_smi_msg *msg) | 
|  | { | 
|  | if (msg->type == IPMI_SMI_MSG_TYPE_IPMB_DIRECT) { | 
|  | iidev->xmitmsg[0] = msg->data[1]; | 
|  | iidev->xmitmsg[1] = msg->data[0]; | 
|  | memcpy(iidev->xmitmsg + 4, msg->data + 2, msg->data_size - 2); | 
|  | iidev->xmitlen = msg->data_size + 2; | 
|  | } else { | 
|  | iidev->xmitmsg[0] = iidev->bmcaddr; | 
|  | iidev->xmitmsg[1] = msg->data[0]; | 
|  | iidev->xmitmsg[4] = 0; | 
|  | memcpy(iidev->xmitmsg + 5, msg->data + 1, msg->data_size - 1); | 
|  | iidev->xmitlen = msg->data_size + 4; | 
|  | } | 
|  | iidev->xmitmsg[3] = iidev->slave->addr << 1; | 
|  | if (((msg->data[0] >> 2) & 1) == 0) | 
|  | /* If it's a command, put in our own sequence number. */ | 
|  | iidev->xmitmsg[4] = ((iidev->xmitmsg[4] & 0x03) | | 
|  | (iidev->curr_seq << 2)); | 
|  |  | 
|  | /* Now add on the final checksums. */ | 
|  | iidev->xmitmsg[2] = ipmb_checksum(iidev->xmitmsg, 2); | 
|  | iidev->xmitmsg[iidev->xmitlen] = | 
|  | ipmb_checksum(iidev->xmitmsg + 3, iidev->xmitlen - 3); | 
|  | iidev->xmitlen++; | 
|  | } | 
|  |  | 
|  | static int ipmi_ipmb_thread(void *data) | 
|  | { | 
|  | struct ipmi_ipmb_dev *iidev = data; | 
|  |  | 
|  | while (!kthread_should_stop()) { | 
|  | long ret; | 
|  | struct i2c_msg i2c_msg; | 
|  | struct ipmi_smi_msg *msg = NULL; | 
|  | unsigned long flags; | 
|  | unsigned int retries = 0; | 
|  |  | 
|  | /* Wait for a message to send */ | 
|  | ret = down_interruptible(&iidev->wake_thread); | 
|  | if (iidev->stopping) | 
|  | break; | 
|  | if (ret) | 
|  | continue; | 
|  |  | 
|  | spin_lock_irqsave(&iidev->lock, flags); | 
|  | if (iidev->next_msg) { | 
|  | msg = iidev->next_msg; | 
|  | iidev->next_msg = NULL; | 
|  | } | 
|  | spin_unlock_irqrestore(&iidev->lock, flags); | 
|  | if (!msg) | 
|  | continue; | 
|  |  | 
|  | ipmi_ipmb_format_for_xmit(iidev, msg); | 
|  |  | 
|  | retry: | 
|  | i2c_msg.len = iidev->xmitlen - 1; | 
|  | if (i2c_msg.len > 32) { | 
|  | ipmi_ipmb_send_response(iidev, msg, | 
|  | IPMI_REQ_LEN_EXCEEDED_ERR); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | i2c_msg.addr = iidev->xmitmsg[0] >> 1; | 
|  | i2c_msg.flags = 0; | 
|  | i2c_msg.buf = iidev->xmitmsg + 1; | 
|  |  | 
|  | /* Rely on i2c_transfer for a barrier. */ | 
|  | iidev->working_msg = msg; | 
|  |  | 
|  | ret = i2c_transfer(iidev->client->adapter, &i2c_msg, 1); | 
|  |  | 
|  | if ((msg->data[0] >> 2) & 1) { | 
|  | /* | 
|  | * It's a response, nothing will be returned | 
|  | * by the other end. | 
|  | */ | 
|  |  | 
|  | iidev->working_msg = NULL; | 
|  | ipmi_ipmb_send_response(iidev, msg, | 
|  | ret < 0 ? IPMI_BUS_ERR : 0); | 
|  | continue; | 
|  | } | 
|  | if (ret < 0) { | 
|  | iidev->working_msg = NULL; | 
|  | ipmi_ipmb_send_response(iidev, msg, IPMI_BUS_ERR); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* A command was sent, wait for its response. */ | 
|  | ret = down_timeout(&iidev->got_rsp, | 
|  | msecs_to_jiffies(iidev->retry_time_ms)); | 
|  |  | 
|  | /* | 
|  | * Grab the message if we can.  If the handler hasn't | 
|  | * already handled it, the message will still be there. | 
|  | */ | 
|  | spin_lock_irqsave(&iidev->lock, flags); | 
|  | msg = iidev->working_msg; | 
|  | iidev->working_msg = NULL; | 
|  | spin_unlock_irqrestore(&iidev->lock, flags); | 
|  |  | 
|  | if (!msg && ret) { | 
|  | /* | 
|  | * If working_msg is not set and we timed out, | 
|  | * that means the message grabbed by | 
|  | * check_msg_done before we could grab it | 
|  | * here.  Wait again for check_msg_done to up | 
|  | * the semaphore. | 
|  | */ | 
|  | down(&iidev->got_rsp); | 
|  | } else if (msg && ++retries <= iidev->max_retries) { | 
|  | spin_lock_irqsave(&iidev->lock, flags); | 
|  | iidev->working_msg = msg; | 
|  | spin_unlock_irqrestore(&iidev->lock, flags); | 
|  | goto retry; | 
|  | } | 
|  |  | 
|  | if (msg) | 
|  | ipmi_ipmb_send_response(iidev, msg, IPMI_TIMEOUT_ERR); | 
|  | } | 
|  |  | 
|  | if (iidev->next_msg) | 
|  | /* Return an unspecified error. */ | 
|  | ipmi_ipmb_send_response(iidev, iidev->next_msg, 0xff); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int ipmi_ipmb_start_processing(void *send_info, | 
|  | struct ipmi_smi *new_intf) | 
|  | { | 
|  | struct ipmi_ipmb_dev *iidev = send_info; | 
|  |  | 
|  | iidev->intf = new_intf; | 
|  | iidev->ready = true; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void ipmi_ipmb_stop_thread(struct ipmi_ipmb_dev *iidev) | 
|  | { | 
|  | if (iidev->thread) { | 
|  | struct task_struct *t = iidev->thread; | 
|  |  | 
|  | iidev->thread = NULL; | 
|  | iidev->stopping = true; | 
|  | up(&iidev->wake_thread); | 
|  | up(&iidev->got_rsp); | 
|  | kthread_stop(t); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void ipmi_ipmb_shutdown(void *send_info) | 
|  | { | 
|  | struct ipmi_ipmb_dev *iidev = send_info; | 
|  |  | 
|  | ipmi_ipmb_stop_thread(iidev); | 
|  | } | 
|  |  | 
|  | static void ipmi_ipmb_sender(void *send_info, | 
|  | struct ipmi_smi_msg *msg) | 
|  | { | 
|  | struct ipmi_ipmb_dev *iidev = send_info; | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&iidev->lock, flags); | 
|  | BUG_ON(iidev->next_msg); | 
|  |  | 
|  | iidev->next_msg = msg; | 
|  | spin_unlock_irqrestore(&iidev->lock, flags); | 
|  |  | 
|  | up(&iidev->wake_thread); | 
|  | } | 
|  |  | 
|  | static void ipmi_ipmb_request_events(void *send_info) | 
|  | { | 
|  | /* We don't fetch events here. */ | 
|  | } | 
|  |  | 
|  | static void ipmi_ipmb_cleanup(struct ipmi_ipmb_dev *iidev) | 
|  | { | 
|  | if (iidev->slave) { | 
|  | i2c_slave_unregister(iidev->slave); | 
|  | if (iidev->slave != iidev->client) | 
|  | i2c_unregister_device(iidev->slave); | 
|  | } | 
|  | iidev->slave = NULL; | 
|  | iidev->client = NULL; | 
|  | ipmi_ipmb_stop_thread(iidev); | 
|  | } | 
|  |  | 
|  | static void ipmi_ipmb_remove(struct i2c_client *client) | 
|  | { | 
|  | struct ipmi_ipmb_dev *iidev = i2c_get_clientdata(client); | 
|  |  | 
|  | ipmi_ipmb_cleanup(iidev); | 
|  | ipmi_unregister_smi(iidev->intf); | 
|  | } | 
|  |  | 
|  | static int ipmi_ipmb_probe(struct i2c_client *client) | 
|  | { | 
|  | struct device *dev = &client->dev; | 
|  | struct ipmi_ipmb_dev *iidev; | 
|  | struct device_node *slave_np; | 
|  | struct i2c_adapter *slave_adap = NULL; | 
|  | struct i2c_client *slave = NULL; | 
|  | int rv; | 
|  |  | 
|  | iidev = devm_kzalloc(&client->dev, sizeof(*iidev), GFP_KERNEL); | 
|  | if (!iidev) | 
|  | return -ENOMEM; | 
|  |  | 
|  | if (of_property_read_u8(dev->of_node, "bmcaddr", &iidev->bmcaddr) != 0) | 
|  | iidev->bmcaddr = bmcaddr; | 
|  | if (iidev->bmcaddr == 0 || iidev->bmcaddr & 1) { | 
|  | /* Can't have the write bit set. */ | 
|  | dev_notice(&client->dev, | 
|  | "Invalid bmc address value %2.2x\n", iidev->bmcaddr); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (of_property_read_u32(dev->of_node, "retry-time", | 
|  | &iidev->retry_time_ms) != 0) | 
|  | iidev->retry_time_ms = retry_time_ms; | 
|  |  | 
|  | if (of_property_read_u32(dev->of_node, "max-retries", | 
|  | &iidev->max_retries) != 0) | 
|  | iidev->max_retries = max_retries; | 
|  |  | 
|  | slave_np = of_parse_phandle(dev->of_node, "slave-dev", 0); | 
|  | if (slave_np) { | 
|  | slave_adap = of_get_i2c_adapter_by_node(slave_np); | 
|  | of_node_put(slave_np); | 
|  | if (!slave_adap) { | 
|  | dev_notice(&client->dev, | 
|  | "Could not find slave adapter\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  |  | 
|  | iidev->client = client; | 
|  |  | 
|  | if (slave_adap) { | 
|  | struct i2c_board_info binfo; | 
|  |  | 
|  | memset(&binfo, 0, sizeof(binfo)); | 
|  | strscpy(binfo.type, "ipmb-slave", I2C_NAME_SIZE); | 
|  | binfo.addr = client->addr; | 
|  | binfo.flags = I2C_CLIENT_SLAVE; | 
|  | slave = i2c_new_client_device(slave_adap, &binfo); | 
|  | i2c_put_adapter(slave_adap); | 
|  | if (IS_ERR(slave)) { | 
|  | rv = PTR_ERR(slave); | 
|  | dev_notice(&client->dev, | 
|  | "Could not allocate slave device: %d\n", rv); | 
|  | return rv; | 
|  | } | 
|  | i2c_set_clientdata(slave, iidev); | 
|  | } else { | 
|  | slave = client; | 
|  | } | 
|  | i2c_set_clientdata(client, iidev); | 
|  | slave->flags |= I2C_CLIENT_SLAVE; | 
|  |  | 
|  | rv = i2c_slave_register(slave, ipmi_ipmb_slave_cb); | 
|  | if (rv) | 
|  | goto out_err; | 
|  | iidev->slave = slave; | 
|  | slave = NULL; | 
|  |  | 
|  | iidev->handlers.flags = IPMI_SMI_CAN_HANDLE_IPMB_DIRECT; | 
|  | iidev->handlers.start_processing = ipmi_ipmb_start_processing; | 
|  | iidev->handlers.shutdown = ipmi_ipmb_shutdown; | 
|  | iidev->handlers.sender = ipmi_ipmb_sender; | 
|  | iidev->handlers.request_events = ipmi_ipmb_request_events; | 
|  |  | 
|  | spin_lock_init(&iidev->lock); | 
|  | sema_init(&iidev->wake_thread, 0); | 
|  | sema_init(&iidev->got_rsp, 0); | 
|  |  | 
|  | iidev->thread = kthread_run(ipmi_ipmb_thread, iidev, | 
|  | "kipmb%4.4x", client->addr); | 
|  | if (IS_ERR(iidev->thread)) { | 
|  | rv = PTR_ERR(iidev->thread); | 
|  | dev_notice(&client->dev, | 
|  | "Could not start kernel thread: error %d\n", rv); | 
|  | goto out_err; | 
|  | } | 
|  |  | 
|  | rv = ipmi_register_smi(&iidev->handlers, | 
|  | iidev, | 
|  | &client->dev, | 
|  | iidev->bmcaddr); | 
|  | if (rv) | 
|  | goto out_err; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | out_err: | 
|  | if (slave && slave != client) | 
|  | i2c_unregister_device(slave); | 
|  | ipmi_ipmb_cleanup(iidev); | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_OF | 
|  | static const struct of_device_id of_ipmi_ipmb_match[] = { | 
|  | { .type = "ipmi", .compatible = DEVICE_NAME }, | 
|  | {}, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, of_ipmi_ipmb_match); | 
|  | #else | 
|  | #define of_ipmi_ipmb_match NULL | 
|  | #endif | 
|  |  | 
|  | static const struct i2c_device_id ipmi_ipmb_id[] = { | 
|  | { DEVICE_NAME }, | 
|  | {} | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(i2c, ipmi_ipmb_id); | 
|  |  | 
|  | static struct i2c_driver ipmi_ipmb_driver = { | 
|  | .class		= I2C_CLASS_HWMON, | 
|  | .driver = { | 
|  | .name = DEVICE_NAME, | 
|  | .of_match_table = of_ipmi_ipmb_match, | 
|  | }, | 
|  | .probe		= ipmi_ipmb_probe, | 
|  | .remove		= ipmi_ipmb_remove, | 
|  | .id_table	= ipmi_ipmb_id, | 
|  | }; | 
|  | module_i2c_driver(ipmi_ipmb_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Corey Minyard"); | 
|  | MODULE_DESCRIPTION("IPMI IPMB driver"); | 
|  | MODULE_LICENSE("GPL v2"); |