add host-side kernel driver
Adds the host-side kernel driver. It is only included in this
repository for information purposes.
Change-Id: I1c6047bab3fbd1581ef7c3d696cd2afdb2bd3c3f
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/host-kernel/i2c-via-ipmi.c b/host-kernel/i2c-via-ipmi.c
new file mode 100644
index 0000000..0a74e26
--- /dev/null
+++ b/host-kernel/i2c-via-ipmi.c
@@ -0,0 +1,643 @@
+/*
+ * 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");