The IPMI definition of the Block Transfer protocol defines the hardware
registers and behavior in addition to the message format and messaging
semantics. This implements a new protocol that uses IPMI Block Transfer
messages and semantics on top of a standard I2C interface.

Signed-off-by: Brendan Higgins <brendanhigg...@google.com>
---
Changes for v2:
  - None
---
 drivers/char/ipmi/Kconfig       |   4 +
 drivers/char/ipmi/Makefile      |   1 +
 drivers/char/ipmi/ipmi_bt_i2c.c | 452 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 457 insertions(+)
 create mode 100644 drivers/char/ipmi/ipmi_bt_i2c.c

diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
index f6fa056a52fc..a8734a369cb0 100644
--- a/drivers/char/ipmi/Kconfig
+++ b/drivers/char/ipmi/Kconfig
@@ -79,6 +79,10 @@ config IPMI_POWEROFF
          This enables a function to power off the system with IPMI if
         the IPMI management controller is capable of this.
 
+config IPMI_BT_I2C
+       select I2C
+       tristate 'BT IPMI bmc driver over I2c'
+
 endif # IPMI_HANDLER
 
 config ASPEED_BT_IPMI_BMC
diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
index eefb0b301e83..323de0b0b8b5 100644
--- a/drivers/char/ipmi/Makefile
+++ b/drivers/char/ipmi/Makefile
@@ -12,4 +12,5 @@ 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
+obj-$(CONFIG_IPMI_BT_I2C) += ipmi_bt_i2c.o
 obj-$(CONFIG_ASPEED_BT_IPMI_BMC) += bt-bmc.o
diff --git a/drivers/char/ipmi/ipmi_bt_i2c.c b/drivers/char/ipmi/ipmi_bt_i2c.c
new file mode 100644
index 000000000000..94b5c11d23cd
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_bt_i2c.c
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt)        "ipmi-bt-i2c: " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/ipmi_smi.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/types.h>
+
+#define IPMI_BT_I2C_TIMEOUT (msecs_to_jiffies(1000))
+
+/* If we don't have netfn_lun, seq, and cmd, we might as well have nothing. */
+#define IPMI_BT_I2C_LEN_MIN 3
+/* We need at least netfn_lun, seq, cmd, and completion. */
+#define IPMI_BT_I2C_RESPONSE_LEN_MIN 4
+#define IPMI_BT_I2C_MSG_MAX_PAYLOAD_SIZE 252
+
+struct ipmi_bt_i2c_msg {
+       u8 len;
+       u8 netfn_lun;
+       u8 seq;
+       u8 cmd;
+       u8 payload[IPMI_BT_I2C_MSG_MAX_PAYLOAD_SIZE];
+} __packed;
+
+#define IPMI_BT_I2C_MAX_SMI_SIZE 254 /* Need extra byte for seq. */
+#define IPMI_BT_I2C_SMI_MSG_HEADER_SIZE 2
+
+struct ipmi_bt_i2c_smi_msg {
+       u8 netfn_lun;
+       u8 cmd;
+       u8 payload[IPMI_MAX_MSG_LENGTH - 2];
+} __packed;
+
+static inline u32 bt_msg_len(struct ipmi_bt_i2c_msg *bt_request)
+{
+       return bt_request->len + 1;
+}
+
+#define IPMI_BT_I2C_SEQ_MAX 256
+
+struct ipmi_bt_i2c_seq_entry {
+       struct ipmi_smi_msg             *msg;
+       unsigned long                   send_time;
+};
+
+struct ipmi_bt_i2c_master {
+       struct ipmi_device_id           ipmi_id;
+       struct i2c_client               *client;
+       ipmi_smi_t                      intf;
+       spinlock_t                      lock;
+       struct ipmi_bt_i2c_seq_entry    seq_msg_map[IPMI_BT_I2C_SEQ_MAX];
+       struct work_struct              ipmi_bt_i2c_recv_work;
+       struct work_struct              ipmi_bt_i2c_send_work;
+       struct ipmi_smi_msg             *msg_to_send;
+};
+
+static const unsigned long write_timeout = 25;
+
+static int ipmi_bt_i2c_send_request(struct ipmi_bt_i2c_master *master,
+                                   struct ipmi_bt_i2c_msg *request)
+{
+       struct i2c_client *client = master->client;
+       unsigned long timeout, read_time;
+       u8 *buf = (u8 *) request;
+       int ret;
+
+       timeout = jiffies + msecs_to_jiffies(write_timeout);
+       do {
+               read_time = jiffies;
+               ret = i2c_master_send(client, buf, bt_msg_len(request));
+               if (ret >= 0)
+                       return 0;
+               usleep_range(1000, 1500);
+       } while (time_before(read_time, timeout));
+       return ret;
+}
+
+static int ipmi_bt_i2c_receive_response(struct ipmi_bt_i2c_master *master,
+                                       struct ipmi_bt_i2c_msg *response)
+{
+       struct i2c_client *client = master->client;
+       unsigned long timeout, read_time;
+       u8 *buf = (u8 *) response;
+       u8 len = 0;
+       int ret;
+
+       /*
+        * Slave may not NACK when not ready, so we peek at the first byte to
+        * see if it is a valid length.
+        */
+       ret = i2c_master_recv(client, &len, 1);
+       while (ret != 1 || len == 0) {
+               if (ret < 0)
+                       return ret;
+
+               usleep_range(1000, 1500);
+
+               /* Signal received: quit syscall. */
+               if (signal_pending(current))
+                       return -ERESTARTSYS;
+
+               ret = i2c_master_recv(client, &len, 1);
+       }
+
+       timeout = jiffies + msecs_to_jiffies(write_timeout);
+       do {
+               read_time = jiffies;
+               ret = i2c_master_recv(client, buf, len + 1);
+               if (ret >= 0)
+                       return 0;
+               usleep_range(1000, 1500);
+       } while (time_before(read_time, timeout));
+       return ret;
+}
+
+static int ipmi_bt_i2c_start_processing(void *data, ipmi_smi_t intf)
+{
+       struct ipmi_bt_i2c_master *master = data;
+
+       master->intf = intf;
+
+       return 0;
+}
+
+static void __ipmi_bt_i2c_error_reply(struct ipmi_bt_i2c_master *master,
+                                     struct ipmi_smi_msg *msg,
+                                     u8 completion_code)
+{
+       struct ipmi_bt_i2c_smi_msg *response;
+       struct ipmi_bt_i2c_smi_msg *request;
+
+       response = (struct ipmi_bt_i2c_smi_msg *) msg->rsp;
+       request = (struct ipmi_bt_i2c_smi_msg *) msg->data;
+
+       response->netfn_lun = request->netfn_lun | 0x4;
+       response->cmd = request->cmd;
+       response->payload[0] = completion_code;
+       msg->rsp_size = 3;
+       ipmi_smi_msg_received(master->intf, msg);
+}
+
+static void ipmi_bt_i2c_error_reply(struct ipmi_bt_i2c_master *master,
+                                   struct ipmi_smi_msg *msg,
+                                   u8 completion_code)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&master->lock, flags);
+       __ipmi_bt_i2c_error_reply(master, msg, completion_code);
+       spin_unlock_irqrestore(&master->lock, flags);
+}
+
+/*
+ * ipmi_bt_i2c_smi_msg contains a payload and 2 header fields, each 1 byte:
+ * netfn_lun and cmd. They're passed to OpenIPMI within an ipmi_smi_msg struct
+ * along with their length.
+ *
+ * ipmi_bt_i2c_msg contains a payload and 4 header fields: the two above in
+ * addition to seq and len. However, len is not included in the length count so
+ * this message encapsulation is considered 1 byte longer than the other.
+ */
+static u8 ipmi_bt_i2c_smi_to_bt_len(u8 smi_msg_len)
+{
+       /* Only field that BT adds to the header is seq. */
+       return smi_msg_len + 1;
+}
+
+static u8 ipmi_bt_i2c_bt_to_smi_len(struct ipmi_bt_i2c_msg *bt_msg)
+{
+       /* Subtract one byte for seq (opposite of above) */
+       return bt_msg->len - 1;
+}
+
+static size_t ipmi_bt_i2c_payload_len(struct ipmi_bt_i2c_msg *bt_msg)
+{
+       /* Subtract one byte for each: netfn_lun, seq, cmd. */
+       return bt_msg->len - 3;
+}
+
+static bool ipmi_bt_i2c_assign_seq(struct ipmi_bt_i2c_master *master,
+                                  struct ipmi_smi_msg *msg, u8 *ret_seq)
+{
+       struct ipmi_bt_i2c_seq_entry *entry;
+       bool did_cleanup = false;
+       unsigned long flags;
+       u8 seq;
+
+       spin_lock_irqsave(&master->lock, flags);
+retry:
+       for (seq = 0; seq < IPMI_BT_I2C_SEQ_MAX; seq++) {
+               if (!master->seq_msg_map[seq].msg) {
+                       master->seq_msg_map[seq].msg = msg;
+                       master->seq_msg_map[seq].send_time = jiffies;
+                       spin_unlock_irqrestore(&master->lock, flags);
+                       *ret_seq = seq;
+                       return true;
+               }
+       }
+
+       if (did_cleanup) {
+               spin_unlock_irqrestore(&master->lock, flags);
+               return false;
+       }
+
+       /*
+        * TODO: we should do cleanup at times other than only when we run out
+        * of sequence numbers.
+        */
+       for (seq = 0; seq < IPMI_BT_I2C_SEQ_MAX; seq++) {
+               entry = &master->seq_msg_map[seq];
+               if (entry->msg &&
+                   time_after(entry->send_time + IPMI_BT_I2C_TIMEOUT,
+                              jiffies)) {
+                       __ipmi_bt_i2c_error_reply(master, entry->msg,
+                                                 IPMI_TIMEOUT_ERR);
+                       entry->msg = NULL;
+               }
+       }
+       did_cleanup = true;
+       goto retry;
+}
+
+static struct ipmi_smi_msg *ipmi_bt_i2c_find_msg(
+               struct ipmi_bt_i2c_master *master, u8 seq)
+{
+       struct ipmi_smi_msg *msg;
+       unsigned long flags;
+
+       spin_lock_irqsave(&master->lock, flags);
+       msg = master->seq_msg_map[seq].msg;
+       spin_unlock_irqrestore(&master->lock, flags);
+       return msg;
+}
+
+static void ipmi_bt_i2c_free_seq(struct ipmi_bt_i2c_master *master, u8 seq)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&master->lock, flags);
+       master->seq_msg_map[seq].msg = NULL;
+       spin_unlock_irqrestore(&master->lock, flags);
+}
+
+static void ipmi_bt_i2c_send_workfn(struct work_struct *work)
+{
+       struct ipmi_bt_i2c_smi_msg *smi_msg;
+       struct ipmi_bt_i2c_master *master;
+       struct ipmi_bt_i2c_msg bt_msg;
+       struct ipmi_smi_msg *msg;
+       size_t smi_msg_size;
+       unsigned long flags;
+
+       master = container_of(work, struct ipmi_bt_i2c_master,
+                             ipmi_bt_i2c_send_work);
+
+       msg = master->msg_to_send;
+       smi_msg_size = msg->data_size;
+       smi_msg = (struct ipmi_bt_i2c_smi_msg *) msg->data;
+
+       if (smi_msg_size > IPMI_BT_I2C_MAX_SMI_SIZE) {
+               ipmi_bt_i2c_error_reply(master, msg, IPMI_REQ_LEN_EXCEEDED_ERR);
+               return;
+       }
+
+       if (smi_msg_size < IPMI_BT_I2C_SMI_MSG_HEADER_SIZE) {
+               ipmi_bt_i2c_error_reply(master, msg, IPMI_REQ_LEN_INVALID_ERR);
+               return;
+       }
+
+       if (!ipmi_bt_i2c_assign_seq(master, msg, &bt_msg.seq)) {
+               ipmi_bt_i2c_error_reply(master, msg, IPMI_NODE_BUSY_ERR);
+               return;
+       }
+
+       bt_msg.len = ipmi_bt_i2c_smi_to_bt_len(smi_msg_size);
+       bt_msg.netfn_lun = smi_msg->netfn_lun;
+       bt_msg.cmd = smi_msg->cmd;
+       memcpy(bt_msg.payload, smi_msg->payload,
+              ipmi_bt_i2c_payload_len(&bt_msg));
+
+       if (ipmi_bt_i2c_send_request(master, &bt_msg) < 0) {
+               ipmi_bt_i2c_free_seq(master, bt_msg.seq);
+               ipmi_bt_i2c_error_reply(master, msg, IPMI_BUS_ERR);
+       }
+
+       spin_lock_irqsave(&master->lock, flags);
+       master->msg_to_send = NULL;
+       spin_unlock_irqrestore(&master->lock, flags);
+}
+
+void ipmi_bt_i2c_recv_workfn(struct work_struct *work)
+{
+       struct ipmi_bt_i2c_smi_msg *smi_msg;
+       struct ipmi_bt_i2c_master *master;
+       struct ipmi_bt_i2c_msg bt_msg;
+       struct ipmi_smi_msg *msg;
+
+       master = container_of(work, struct ipmi_bt_i2c_master,
+                             ipmi_bt_i2c_recv_work);
+
+       if (ipmi_bt_i2c_receive_response(master, &bt_msg) < 0)
+               return;
+
+       if (bt_msg.len < IPMI_BT_I2C_LEN_MIN)
+               return;
+
+       msg = ipmi_bt_i2c_find_msg(master, bt_msg.seq);
+       if (!msg)
+               return;
+
+       ipmi_bt_i2c_free_seq(master, bt_msg.seq);
+
+       if (bt_msg.len < IPMI_BT_I2C_RESPONSE_LEN_MIN)
+               ipmi_bt_i2c_error_reply(master, msg, IPMI_ERR_MSG_TRUNCATED);
+
+       msg->rsp_size = ipmi_bt_i2c_bt_to_smi_len(&bt_msg);
+       smi_msg = (struct ipmi_bt_i2c_smi_msg *) msg->rsp;
+       smi_msg->netfn_lun = bt_msg.netfn_lun;
+       smi_msg->cmd = bt_msg.cmd;
+       memcpy(smi_msg->payload, bt_msg.payload,
+              ipmi_bt_i2c_payload_len(&bt_msg));
+       ipmi_smi_msg_received(master->intf, msg);
+}
+
+static void ipmi_bt_i2c_sender(void *data, struct ipmi_smi_msg *msg)
+{
+       struct ipmi_bt_i2c_master *master = data;
+       unsigned long flags;
+
+       spin_lock_irqsave(&master->lock, flags);
+       if (master->msg_to_send) {
+               /*
+                * TODO(benjaminfair): Queue messages to send instead of only
+                * keeping one.
+                */
+               __ipmi_bt_i2c_error_reply(master, msg, IPMI_NODE_BUSY_ERR);
+       } else {
+               master->msg_to_send = msg;
+               schedule_work(&master->ipmi_bt_i2c_send_work);
+       }
+       spin_unlock_irqrestore(&master->lock, flags);
+}
+
+static void ipmi_bt_i2c_request_events(void *data)
+{
+       struct ipmi_bt_i2c_master *master = data;
+
+       schedule_work(&master->ipmi_bt_i2c_recv_work);
+}
+
+static void ipmi_bt_i2c_set_run_to_completion(void *data,
+                                             bool run_to_completion)
+{
+}
+
+static void ipmi_bt_i2c_poll(void *data)
+{
+       struct ipmi_bt_i2c_master *master = data;
+
+       schedule_work(&master->ipmi_bt_i2c_recv_work);
+}
+
+static struct ipmi_smi_handlers ipmi_bt_i2c_smi_handlers = {
+       .owner                  = THIS_MODULE,
+       .start_processing       = ipmi_bt_i2c_start_processing,
+       .sender                 = ipmi_bt_i2c_sender,
+       .request_events         = ipmi_bt_i2c_request_events,
+       .set_run_to_completion  = ipmi_bt_i2c_set_run_to_completion,
+       .poll                   = ipmi_bt_i2c_poll,
+};
+
+static int ipmi_bt_i2c_probe(struct i2c_client *client,
+                            const struct i2c_device_id *id)
+{
+       struct ipmi_bt_i2c_master *master;
+       int ret;
+
+       master = devm_kzalloc(&client->dev, sizeof(struct ipmi_bt_i2c_master),
+                             GFP_KERNEL);
+       if (!master)
+               return -ENOMEM;
+
+       spin_lock_init(&master->lock);
+       INIT_WORK(&master->ipmi_bt_i2c_recv_work, ipmi_bt_i2c_recv_workfn);
+       INIT_WORK(&master->ipmi_bt_i2c_send_work, ipmi_bt_i2c_send_workfn);
+       master->client = client;
+       i2c_set_clientdata(client, master);
+
+       /*
+        * TODO(benjaminfair): read ipmi_device_id from BMC to determine version
+        * information and be able to tell multiple BMCs apart
+        */
+       ret = ipmi_register_smi(&ipmi_bt_i2c_smi_handlers, master,
+                               &master->ipmi_id, &client->dev, 0);
+
+       return ret;
+}
+
+static int ipmi_bt_i2c_remove(struct i2c_client *client)
+{
+       struct ipmi_bt_i2c_master *master;
+
+       master = i2c_get_clientdata(client);
+       return ipmi_unregister_smi(master->intf);
+}
+
+static const struct acpi_device_id ipmi_bt_i2c_acpi_id[] = {
+       {"BTMA0001", 0},
+       {},
+};
+MODULE_DEVICE_TABLE(acpi, ipmi_bt_i2c_acpi_id);
+
+static const struct i2c_device_id ipmi_bt_i2c_i2c_id[] = {
+       {"ipmi-bt-i2c", 0},
+       {},
+};
+MODULE_DEVICE_TABLE(i2c, ipmi_bt_i2c_i2c_id);
+
+static struct i2c_driver ipmi_bt_i2c_driver = {
+       .driver = {
+               .name = "ipmi-bt-i2c",
+               .acpi_match_table = ipmi_bt_i2c_acpi_id,
+       },
+       .id_table = ipmi_bt_i2c_i2c_id,
+       .probe = ipmi_bt_i2c_probe,
+       .remove = ipmi_bt_i2c_remove,
+};
+module_i2c_driver(ipmi_bt_i2c_driver);
+
+MODULE_AUTHOR("Brendan Higgins <brendanhigg...@google.com>");
+MODULE_DESCRIPTION("IPMI Block Transfer over I2C.");
+MODULE_LICENSE("GPL v2");
-- 
2.14.0.rc1.383.gd1ce394fe2-goog

Reply via email to