| /* |
| * I2C Bus driver proxy for BMC I2C bus access. |
| * |
| * For hosts incorporating a BMC, I2C resources are typically connected to |
| * the BMC, not the host. This module creates host proxies for the BMC's |
| * virtual I2C adapters. |
| * |
| * At heart, each proxy is a virtual i2c-adapter, and so supports: |
| * . raw I2C I/O - as used by i2c-tools, for example |
| * . dynamic i2c device creation |
| * |
| * These proxies and the IPMI extensions underpinning them directly support |
| * the I2C_RDWR interface; Linux' SMBUS emulation layer supports SMBUS calls |
| * on top of that. |
| * |
| * Each proxy device connects to exactly one i2c adapter at the BMC, |
| * but you can create many proxies if you have many adapters to reach. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/completion.h> |
| #include <linux/i2c.h> |
| #include <linux/ipmi.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_platform.h> |
| #include <linux/platform_device.h> |
| #include <linux/printk.h> |
| #include <linux/spinlock.h> |
| #include <linux/stddef.h> |
| #include <linux/types.h> |
| #include <stdarg.h> |
| |
| /* TODO(peterh): move to include/uapi/linux/ipmi_msgdefs.h */ |
| #define IPMI_NETFN_OEM_GROUP_REQUEST 0x2e |
| #define IPMI_NETFN_OEM_GROUP_RESPONSE 0x2f |
| |
| /* Specific IANA OEM Numbers for this functionality. */ |
| #define IPMI_OEM_GOOG 11129 |
| #define IPMI_OEM_OPENBMC 49871 |
| |
| /* Specific OEM Message for this functionality. */ |
| #define OPENBMC_I2C_OEM_IPMI_CMD 2 |
| |
| /* OEM IPMI Request header consists of 3-byte IANA OEN */ |
| #define OEM_IPMI_REQ_HDR_OEN 0 |
| #define OEM_IPMI_REQ_HDR_LEN 3 |
| |
| /* OEM IPMI Reply header consists of CC and 3 byte IANA OEN */ |
| #define OEM_IPMI_REPLY_HDR_CC 0 |
| #define OEM_IPMI_REPLY_HDR_OEN 1 |
| #define OEM_IPMI_REPLY_HDR_LEN 4 |
| |
| /* I2C OEM IPMI Request header */ |
| #define I2C_OEM_IPMI_REQ_BUS 0 |
| #define I2C_OEM_IPMI_REQ_FLAGS 1 |
| #define I2C_OEM_IPMI_REQ_FLAG_PEC (1 << 7) |
| #define I2C_OEM_IPMI_REQ_HDR_LEN 2 |
| /* Followed by one or more steps. */ |
| |
| /* I2C OEM IPMI Request step - optional repeated element. */ |
| #define I2C_OEM_IPMI_STEP_DEV_AND_DIR 0 |
| #define I2C_OEM_IPMI_STEP_IS_READ (1 << 0) |
| #define I2C_OEM_IPMI_STEP_DEV(dandd) ((dandd) >> 1) |
| #define I2C_OEM_IPMI_STEP_FLAGS 1 |
| #define I2C_OEM_IPMI_STEP_FLAG_RECV_LEN (1 << 7) |
| #define I2C_OEM_IPMI_STEP_PARM 2 |
| #define I2C_OEM_IPMI_STEP_HDR_LEN 3 |
| /* Followed by parm bytes of wr data if ! IS_READ */ |
| |
| /* |
| * I2C OEM IPMI Reply consists of OEM IPMI reply header, |
| * followed by every byte read, in requested order. |
| */ |
| |
| #define IPMI_MAX_REQ_LEN 60 |
| #define IPMI_MAX_STEP_LEN 34 |
| |
| /* via-ipmi as little-endian hex-ascii */ |
| #define I2C_VIA_IPMI_BUS_MAGIC 0x696d70692d616976 |
| |
| /** |
| * struct i2c_via_ipmi_bus - per proxy state. |
| |
| * @magic: Pattern usable to verify void pointer conversion validity. |
| * @node: Embedded list pointers for this structure. |
| * @pdev: Parent i2c-via-ipmi platform device. |
| * @remote_bus_id: Which BMC i2c adapter number to proxy. |
| * @adap: Embedded i2c adapter. |
| * @lock: Spinlock protecting proxy from ioctl vs. reply races. |
| * @ipmi_seq: Nonce to use as msgId for IPMI request/reply; message |
| * handler rejects mismatches as stale replies. |
| * @cmd_complete: Completion struct for this proxy. |
| * @cmd_rc: Completion code from IPMI reply. |
| * @msgs: Pointer to array of messages as given to i2c xfer handler. |
| * @msgs_count:`Number of msgs[] to process as given to i2c xfer handler. |
| * @if_num: IPMI interface number to use for BMC messaging. |
| * @user: IPMI user for routing requests and replies. |
| * @req_addr: IPMI address to which request is addressed. |
| * (Always local BMC in this version.) |
| * @req: IPMI request structure. |
| * @req_data: Reserved for IPMI request data bytes. |
| * |
| * This structure holds the state of each BMC i2c proxy adapter during its |
| * lifetime. |
| * |
| * The adap property allows this proxy to act as an i2c adapter, while |
| * IPMI user and related addressing information tie the proxy to the |
| * IPMI messaging service, and thence the BMC. |
| * |
| * The msgs and req related properties convery i2c xfer arguments to the |
| * reply handler, while the completion properties flow back from the reply |
| * handler to the i2c xfer operation that launched the request. |
| */ |
| struct i2c_via_ipmi_bus { |
| /* private: internal use only */ |
| u64 magic; |
| struct list_head node; |
| struct platform_device *pdev; |
| unsigned remote_bus_id; |
| struct i2c_adapter adap; |
| spinlock_t lock; |
| long ipmi_seq; |
| struct completion cmd_complete; |
| int cmd_rc; |
| struct i2c_msg *msgs; |
| size_t msgs_count; |
| unsigned if_num; |
| ipmi_user_t user; |
| struct ipmi_addr req_addr; |
| struct kernel_ipmi_msg req; |
| unsigned char req_data[256]; |
| }; |
| |
| #define ADAP_NAME_MAX sizeof(((struct i2c_via_ipmi_bus *)0)->adap.name) |
| |
| static LIST_HEAD(proxies); |
| |
| static char *proxy_invalid_reason(struct i2c_via_ipmi_bus *bus) |
| { |
| if (!bus) |
| return kasprintf(GFP_KERNEL, "is nullptr"); |
| if (bus->magic != I2C_VIA_IPMI_BUS_MAGIC) |
| return kasprintf(GFP_KERNEL, "has bad magic %#llx", bus->magic); |
| return NULL; |
| } |
| |
| static void i2c_via_ipmi_reply_handler(struct ipmi_recv_msg *reply, |
| void *user_msg_data) |
| { |
| const unsigned char *buf, *oen, *next_byte, *buf_end; |
| struct i2c_via_ipmi_bus *bus; |
| unsigned char recv_len; |
| struct i2c_msg *msg; |
| struct device *dev; |
| char *reason; |
| u32 oem_code; |
| int cmd_rc; |
| size_t i; |
| /* |
| * If message isn't a reply to our ipmi_request_settime, |
| * then user_msg_data is unlikely to point to our i2c_via_ipmi_bus, |
| * so confirm everything we can. |
| */ |
| bus = user_msg_data; |
| reason = proxy_invalid_reason(bus); |
| if (reason) { |
| pr_err("%s: user_msg_data %s", __func__, reason); |
| kfree(reason); |
| goto not_our_reply; |
| } |
| dev = &bus->adap.dev; |
| if (!(reply->user && |
| reply->user_msg_data == user_msg_data && |
| reply->msg.netfn == IPMI_NETFN_OEM_GROUP_RESPONSE && |
| reply->msg.cmd == OPENBMC_I2C_OEM_IPMI_CMD && |
| /* Check response length for error state or valid OEM packet */ |
| ((reply->msg.data_len > 0 && |
| (reply->msg.data[OEM_IPMI_REPLY_HDR_CC] != IPMI_CC_NO_ERROR)) || |
| (reply->msg.data_len >= OEM_IPMI_REPLY_HDR_LEN)))) { |
| dev_info(dev, "%s: apparent non-reply?", __func__); |
| goto not_our_reply; |
| } |
| if (!spin_trylock(&bus->lock)) { |
| dev_info(dev, "%s: drop seq=%ld, mutex busy.", |
| __func__, reply->msgid); |
| goto not_our_reply; |
| } |
| if (reply->msgid != bus->ipmi_seq) { |
| spin_unlock(&bus->lock); |
| dev_info(dev, "%s: drop seq=%ld, want seq=%ld.", |
| __func__, reply->msgid, bus->ipmi_seq); |
| goto not_our_reply; |
| } |
| buf = reply->msg.data; |
| buf_end = buf + reply->msg.data_len; |
| |
| /* |
| * Extract CC and IANA OEN from OEM IPMI Reply header. |
| */ |
| cmd_rc = buf[OEM_IPMI_REPLY_HDR_CC]; |
| /* |
| * If we get an error return, accept the reply and return the error |
| * up the I2C stack. |
| */ |
| if (cmd_rc != IPMI_CC_NO_ERROR) |
| goto accept_reply; |
| |
| oen = &buf[OEM_IPMI_REPLY_HDR_OEN]; |
| oem_code = ((((u32)oen[2] << 8) | oen[1]) << 8) | oen[0]; |
| next_byte = buf + OEM_IPMI_REPLY_HDR_LEN; |
| if (oem_code != IPMI_OEM_OPENBMC) { |
| spin_unlock(&bus->lock); |
| dev_info(dev, "%s: wrong OEM Enterprise Number %u", |
| __func__, oem_code); |
| goto not_our_reply; |
| } |
| /* |
| * Note: having confirmed it's a reply to our request, |
| * any return past here should complete that request. |
| */ |
| /* |
| * BMC I/O seems to have worked - that's all for writes; if there |
| * are read steps, the rest of the reply caries the bytes read. |
| * Check the whole reply before trusting any of it - if it is |
| * the wrong size for this request, call it an I/O error. |
| */ |
| for (i = 0; i < bus->msgs_count; ++i) { |
| msg = &bus->msgs[i]; |
| if (!(msg->flags & I2C_M_RD)) |
| continue; |
| if (msg->flags & I2C_M_RECV_LEN) { |
| if (next_byte >= buf_end) { |
| bus->cmd_rc = -EPROTO; |
| spin_unlock(&bus->lock); |
| dev_err(dev, "%s[%zu]: response omits RECV_LEN", |
| __func__, i); |
| goto reject_reply; |
| } |
| /* |
| * Limit net payload to SMBUS maximum. |
| * To avoid overrun, buffer should be able to hold |
| * union i2c_smbus_data for this kind of step. |
| */ |
| recv_len = *next_byte; |
| if (recv_len > I2C_SMBUS_BLOCK_MAX) { |
| bus->cmd_rc = -EPROTO; |
| spin_unlock(&bus->lock); |
| dev_err(dev, "%s[%zu]: recv_len=%u, max is %d", |
| __func__, i, recv_len, |
| I2C_SMBUS_BLOCK_MAX); |
| goto reject_reply; |
| } |
| msg->len = recv_len + |
| (msg->flags & I2C_CLIENT_PEC ? 2 : 1); |
| } |
| next_byte += msg->len; |
| if (next_byte > buf_end) { |
| bus->cmd_rc = -EPROTO; |
| spin_unlock(&bus->lock); |
| dev_err(dev, "%s[%zu]: response too small", |
| __func__, i); |
| goto reject_reply; |
| } |
| } |
| if (next_byte < buf_end) { |
| bus->cmd_rc = -EPROTO; |
| spin_unlock(&bus->lock); |
| dev_err(dev, "%s: response len=%d, expected=%zd", |
| __func__, reply->msg.data_len, buf_end - next_byte); |
| goto reject_reply; |
| } |
| /* |
| * Everything checks out, copy results back to caller. |
| */ |
| next_byte = buf + 4; |
| for (i = 0; i < bus->msgs_count; ++i) { |
| msg = &bus->msgs[i]; |
| if (msg->flags & I2C_M_RD) { |
| memcpy(msg->buf, next_byte, msg->len); |
| next_byte += msg->len; |
| } |
| } |
| |
| accept_reply: |
| bus->cmd_rc = cmd_rc; |
| spin_unlock(&bus->lock); |
| |
| reject_reply: |
| complete(&bus->cmd_complete); |
| |
| not_our_reply: |
| reply->done(reply); |
| return; |
| } |
| |
| static struct ipmi_user_hndl i2c_via_ipmi_ipmi_handlers = { |
| .ipmi_recv_hndl = i2c_via_ipmi_reply_handler, |
| }; |
| |
| static u32 i2c_via_ipmi_functionality(struct i2c_adapter *adap) |
| { |
| return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; |
| } |
| |
| static int i2c_via_ipmi_xfer(struct i2c_adapter *adap, |
| struct i2c_msg *msgs, int num) |
| { |
| unsigned char *next_byte, *oen, *msg_hdr, *step_hdr; |
| struct i2c_via_ipmi_bus *bus = adap->algo_data; |
| struct device *dev = &adap->dev; |
| struct kernel_ipmi_msg *req; |
| const struct i2c_msg *msg; |
| char *reason; |
| int cmd_rc; |
| size_t i; |
| int rc; |
| |
| reason = proxy_invalid_reason(bus); |
| if (reason) { |
| dev_err(dev, "%s: adap.algo_data %s", __func__, reason); |
| kfree(reason); |
| return -EFAULT; |
| } |
| /* |
| * Setup kernel message structure |
| */ |
| req = &bus->req; |
| next_byte = bus->req_data; |
| req->data = next_byte; |
| /* |
| * Route message to specific OEM command handler. |
| */ |
| req->netfn = IPMI_NETFN_OEM_GROUP_REQUEST; |
| req->cmd = OPENBMC_I2C_OEM_IPMI_CMD; |
| /* |
| * IANA OEN is the only content of OEM IPMI Request header. |
| */ |
| oen = &next_byte[OEM_IPMI_REQ_HDR_OEN]; |
| oen[0] = (unsigned char)(IPMI_OEM_OPENBMC & 255); |
| oen[1] = (unsigned char)((IPMI_OEM_OPENBMC >> 8) & 255); |
| oen[2] = (unsigned char)((IPMI_OEM_OPENBMC >> 16) & 255); |
| next_byte += OEM_IPMI_REQ_HDR_LEN; |
| /* |
| * I2C OEM Message header: bus id & overall flag byte. |
| */ |
| msg_hdr = next_byte; |
| msg_hdr[I2C_OEM_IPMI_REQ_BUS] = bus->remote_bus_id; |
| msg_hdr[I2C_OEM_IPMI_REQ_FLAGS] = 0; |
| next_byte += I2C_OEM_IPMI_REQ_HDR_LEN; |
| /* |
| * Foreach i2c message, append a step. |
| */ |
| for (i = 0; i < num; ++i) { |
| msg = &msgs[i]; |
| if (msg->len > IPMI_MAX_STEP_LEN) { |
| dev_err(dev, "%s[%zu](%#x): len=%d, max=%d", |
| __func__, i, msg->addr, |
| msg->len, IPMI_MAX_STEP_LEN); |
| return -EMSGSIZE; |
| } |
| /* |
| * Step header - if write step, payload will follow. |
| */ |
| step_hdr = next_byte; |
| next_byte += I2C_OEM_IPMI_STEP_HDR_LEN; |
| if (next_byte - msg_hdr > IPMI_MAX_REQ_LEN) { |
| dev_err(dev, "%s: request too big.", __func__); |
| return -EMSGSIZE; |
| } |
| step_hdr[I2C_OEM_IPMI_STEP_DEV_AND_DIR] = msg->addr << 1; |
| step_hdr[I2C_OEM_IPMI_STEP_FLAGS] = 0; |
| step_hdr[I2C_OEM_IPMI_STEP_PARM] = msg->len; |
| if (!(msg->flags & I2C_M_RD)) { |
| if (msg->flags & I2C_M_RECV_LEN) { |
| dev_err(dev, "%s[%zu](%#x): RECV_LEN for WR?", |
| __func__, i, msg->addr); |
| return -EINVAL; |
| } |
| if (next_byte + msg->len - msg_hdr > IPMI_MAX_REQ_LEN) { |
| dev_err(dev, "%s[%zu](%#x): request too big.", |
| __func__, i, msg->addr); |
| return -EMSGSIZE; |
| } |
| if (msg->len) { |
| memcpy(next_byte, msg->buf, msg->len); |
| next_byte += msg->len; |
| } |
| dev_dbg(dev, "%s[%zu](%#x): write %d bytes.", |
| __func__, i, msg->addr, msg->len); |
| continue; |
| } |
| step_hdr[0] |= 1; /* Set rd bit */ |
| if (msg->flags & I2C_M_RECV_LEN) { |
| step_hdr[I2C_OEM_IPMI_STEP_FLAGS] |= |
| I2C_OEM_IPMI_STEP_FLAG_RECV_LEN; |
| if (msg->len == 2) |
| msg_hdr[I2C_OEM_IPMI_REQ_FLAGS] |= |
| I2C_OEM_IPMI_REQ_FLAG_PEC; |
| dev_dbg(dev, "%s[%zu](%#x): read count+%d bytes.", |
| __func__, i, msg->addr, msg->len); |
| continue; |
| } |
| dev_dbg(dev, "%s[%zu](%#x): read %d bytes.", |
| __func__, i, msg->addr, msg->len); |
| } |
| req->data_len = next_byte - bus->req_data; |
| dev_dbg(dev, "%s: Sending %d step, %d byte request.", |
| __func__, num, req->data_len); |
| /* |
| * Completion routines need key xfer parameters. |
| */ |
| bus->msgs = msgs; |
| bus->msgs_count = num; |
| |
| /* |
| * IPMI handler modifies the same proxy object to return results. |
| * Concurrent proxy access is protected by two properties: |
| * bus->ipmi_seq, a unique sequence number, and |
| * bus->lock, a regular spinlock. |
| * The ipmi_request_settime() call carries ipmi_seq as its msgId; |
| * the IPMI handler checks for exact match to ensure it is a reply |
| * to THIS request. Upon match, it copies answers back & completes |
| * the request. Upon mismatch, proxy state is not modified in any |
| * way, and only lock, impi_seq, and dev are referenced. |
| * This side, the ioctl handler, advances ipmi_seq immediately after |
| * completion or timeout, so any later response will be dropped. |
| * Upon timeout, the cmd_cc state seeded here will remain. |
| * The spinlock is held here while advancing ipmi_seq, and in the |
| * IPMI resonse handler while relying upon the match. |
| */ |
| bus->cmd_rc = -ETIMEDOUT; |
| reinit_completion(&bus->cmd_complete); |
| rc = ipmi_request_settime(bus->user, &bus->req_addr, bus->ipmi_seq, |
| req, bus, 0, -1, 0); |
| if (rc < 0) { |
| dev_err(dev, "%s: ipmi_request_settime returned %d.", |
| __func__, rc); |
| return rc; |
| } |
| wait_for_completion_killable_timeout(&bus->cmd_complete, |
| bus->adap.timeout); |
| spin_lock(&bus->lock); |
| bus->ipmi_seq += 1; |
| cmd_rc = bus->cmd_rc; |
| spin_unlock(&bus->lock); |
| if (!cmd_rc) { |
| dev_dbg(dev, "%s: returning num=%d.", __func__, num); |
| return num; |
| } |
| if (cmd_rc == -ETIMEDOUT) { |
| dev_err(dev, "%s: returning -ETIMEDOUT.", __func__); |
| return cmd_rc; |
| } |
| if (cmd_rc == -EPROTO) { |
| dev_err(dev, "%s: returning -EPROTO.", __func__); |
| return -EPROTO; |
| } |
| if (cmd_rc > 0) { |
| dev_dbg(dev, "%s: cmd_rc=%d, so returning -EPROTO.", |
| __func__, cmd_rc); |
| return -EPROTO; |
| } |
| dev_dbg(dev, "%s: returning cmd_rc=%d.", __func__, cmd_rc); |
| return cmd_rc; |
| } |
| |
| static const struct i2c_algorithm i2c_via_ipmi_algo = { |
| .functionality = i2c_via_ipmi_functionality, |
| .master_xfer = i2c_via_ipmi_xfer, |
| }; |
| |
| static int i2c_via_ipmi_create(struct platform_device *pdev, |
| const char *name, |
| unsigned bmc_bus_id, |
| struct i2c_via_ipmi_bus **new_bus) |
| { |
| struct device *parent = &pdev->dev; |
| struct i2c_via_ipmi_bus *bus = NULL; |
| int rc; |
| |
| bus = devm_kzalloc(parent, sizeof(*bus), GFP_KERNEL); |
| if (!bus) |
| return -ENOMEM; |
| bus->magic = I2C_VIA_IPMI_BUS_MAGIC; |
| INIT_LIST_HEAD(&bus->node); |
| bus->pdev = pdev; |
| bus->remote_bus_id = bmc_bus_id; |
| spin_lock_init(&bus->lock); |
| bus->ipmi_seq = 0x40000000L; |
| init_completion(&bus->cmd_complete); |
| bus->req_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; |
| bus->req_addr.channel = IPMI_BMC_CHANNEL; |
| dev_dbg(parent, "%s: name=%s, bmc_bus_id=%u", |
| __func__, name, bmc_bus_id); |
| rc = ipmi_create_user(bus->if_num, &i2c_via_ipmi_ipmi_handlers, |
| bus, &bus->user); |
| if (rc) { |
| dev_err(parent, "%s: ipmi_create_user(%d, ...) returned %d.", |
| __func__, bus->if_num, rc); |
| goto free_instance; |
| } |
| |
| bus->adap.owner = THIS_MODULE; |
| strncpy(bus->adap.name, name, sizeof(bus->adap.name)); |
| bus->adap.retries = 0; |
| /* The BMC's i2c timeout is (and likely will remain) 5s, therefore |
| * this host timeout should be set longer to avoid it timing out |
| * ahead of the BMC's chance. |
| */ |
| bus->adap.timeout = 10 * HZ; |
| bus->adap.algo = &i2c_via_ipmi_algo; |
| bus->adap.algo_data = bus; |
| if (pdev) |
| bus->adap.dev.parent = &pdev->dev; |
| rc = i2c_add_adapter(&bus->adap); |
| if (rc < 0) { |
| dev_err(parent, "%s: i2c_add_adapter(%d, ...) returned %d.", |
| __func__, bmc_bus_id, rc); |
| goto free_instance; |
| } |
| |
| list_add_tail(&bus->node, &proxies); |
| if (new_bus) |
| *new_bus = bus; |
| return 0; |
| |
| free_instance: |
| devm_kfree(parent, bus); |
| return rc; |
| } |
| |
| static int i2c_via_ipmi_destroy(struct i2c_via_ipmi_bus *bus) |
| { |
| struct device *parent = &bus->pdev->dev; |
| |
| dev_dbg(parent, "%s: name=%s, bmc_bus_id=%u", |
| __func__, bus->adap.name, bus->remote_bus_id); |
| i2c_del_adapter(&bus->adap); |
| list_del(&bus->node); |
| devm_kfree(parent, bus); |
| return 0; |
| } |
| |
| static int i2c_via_ipmi_probe(struct platform_device *pdev) |
| { |
| char name[ADAP_NAME_MAX]; |
| unsigned bus_id; |
| int rc; |
| |
| /* |
| * Create requested population of proxies. |
| */ |
| for (bus_id = 0; bus_id < CONFIG_I2C_VIA_IPMI_AUTOPROXIES; ++bus_id) { |
| snprintf(name, sizeof(name), "bmc%u", bus_id); |
| rc = i2c_via_ipmi_create(pdev, name, bus_id, NULL); |
| if (rc) |
| return rc; |
| } |
| return 0; |
| } |
| |
| static int i2c_via_ipmi_remove(struct platform_device *pdev) |
| { |
| struct i2c_via_ipmi_bus *bus, *safe; |
| int pass = 0; |
| |
| /* |
| * Destroy all proxies in reverse order. |
| */ |
| list_for_each_entry_safe_reverse(bus, safe, &proxies, node) { |
| i2c_via_ipmi_destroy(bus); |
| ++pass; |
| } |
| dev_dbg(&pdev->dev, "%s: removed %d proxies.", __func__, pass); |
| return 0; |
| } |
| |
| static void i2c_via_ipmi_release(struct device *__unused) |
| { |
| /* |
| * This function releases the resources associated with a device. Since |
| * our device struct is statically allocated, we don't need to do |
| * anything here. |
| */ |
| } |
| |
| static struct platform_driver i2c_via_ipmi_driver = { |
| .driver = { |
| .name = "i2c-via-ipmi", |
| }, |
| .probe = i2c_via_ipmi_probe, |
| .remove = i2c_via_ipmi_remove, |
| }; |
| |
| static struct platform_device i2c_via_ipmi_pdev = { |
| .name = "i2c-via-ipmi", |
| .id = PLATFORM_DEVID_NONE, |
| .dev.release = i2c_via_ipmi_release, |
| }; |
| |
| int i2c_via_ipmi_init(void) |
| { |
| int rc; |
| |
| pr_debug("%s: entering.", __func__); |
| rc = platform_driver_register(&i2c_via_ipmi_driver); |
| if (rc) { |
| pr_err("couldn't register i2c-via-ipmi platform driver\n"); |
| return rc; |
| } |
| rc = platform_device_register(&i2c_via_ipmi_pdev); |
| if (rc) { |
| pr_err("couldn't register i2c-via-ipmi platform device\n"); |
| goto unregister_pdrv; |
| } |
| pr_debug("%s: returning 0.", __func__); |
| return 0; |
| |
| unregister_pdrv: |
| platform_driver_unregister(&i2c_via_ipmi_driver); |
| return rc; |
| } |
| |
| void i2c_via_ipmi_exit(void) |
| { |
| pr_debug("%s: entering.", __func__); |
| platform_device_unregister(&i2c_via_ipmi_pdev); |
| platform_driver_unregister(&i2c_via_ipmi_driver); |
| pr_debug("%s: returning.", __func__); |
| } |
| |
| subsys_initcall(i2c_via_ipmi_init); |
| module_exit(i2c_via_ipmi_exit); |
| |
| MODULE_AUTHOR("Peter Hanson <peterh@google.com>"); |
| MODULE_DESCRIPTION("I2C Bus driver proxy for BMC I2C bus access."); |
| MODULE_LICENSE("GPL v2"); |