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. This protocol
has the same BMC side file system interface as "ipmi-bt-host".

Signed-off-by: Brendan Higgins <brendanhigg...@google.com>
---
 drivers/char/Kconfig                    |   1 +
 drivers/char/Makefile                   |   1 +
 drivers/char/ipmi_bmc/Kconfig           |  22 ++
 drivers/char/ipmi_bmc/Makefile          |   5 +
 drivers/char/ipmi_bmc/ipmi_bmc_bt_i2c.c | 346 ++++++++++++++++++++++++++++++++
 include/linux/ipmi_bmc.h                |  76 +++++++
 6 files changed, 451 insertions(+)
 create mode 100644 drivers/char/ipmi_bmc/Kconfig
 create mode 100644 drivers/char/ipmi_bmc/Makefile
 create mode 100644 drivers/char/ipmi_bmc/ipmi_bmc_bt_i2c.c
 create mode 100644 include/linux/ipmi_bmc.h

diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index ccd239ab879f..2a6ca2325a45 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -195,6 +195,7 @@ config POWERNV_OP_PANEL
          If unsure, say M here to build it as a module called powernv-op-panel.
 
 source "drivers/char/ipmi/Kconfig"
+source "drivers/char/ipmi_bmc/Kconfig"
 
 config DS1620
        tristate "NetWinder thermometer support"
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 53e33720818c..9e143186fa30 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -58,4 +58,5 @@ js-rtc-y = rtc.o
 
 obj-$(CONFIG_TILE_SROM)                += tile-srom.o
 obj-$(CONFIG_XILLYBUS)         += xillybus/
+obj-$(CONFIG_IPMI_BMC)         += ipmi_bmc/
 obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o
diff --git a/drivers/char/ipmi_bmc/Kconfig b/drivers/char/ipmi_bmc/Kconfig
new file mode 100644
index 000000000000..26c8e0cb765c
--- /dev/null
+++ b/drivers/char/ipmi_bmc/Kconfig
@@ -0,0 +1,22 @@
+#
+# IPMI BMC configuration
+#
+
+menuconfig IPMI_BMC
+       tristate 'IPMI BMC core'
+       help
+         This enables the BMC-side IPMI drivers.
+
+         If unsure, say N.
+
+if IPMI_BMC
+
+config IPMI_BMC_BT_I2C
+       depends on I2C
+       select I2C_SLAVE
+       tristate 'Generic I2C BT IPMI BMC driver'
+       help
+         Provides a driver that uses IPMI Block Transfer messages and
+         semantics on top of plain old I2C.
+
+endif # IPMI_BMC
diff --git a/drivers/char/ipmi_bmc/Makefile b/drivers/char/ipmi_bmc/Makefile
new file mode 100644
index 000000000000..dfe5128f8158
--- /dev/null
+++ b/drivers/char/ipmi_bmc/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the ipmi bmc drivers.
+#
+
+obj-$(CONFIG_IPMI_BMC_BT_I2C) += ipmi_bmc_bt_i2c.o
diff --git a/drivers/char/ipmi_bmc/ipmi_bmc_bt_i2c.c 
b/drivers/char/ipmi_bmc/ipmi_bmc_bt_i2c.c
new file mode 100644
index 000000000000..686b83fa42a4
--- /dev/null
+++ b/drivers/char/ipmi_bmc/ipmi_bmc_bt_i2c.c
@@ -0,0 +1,346 @@
+/*
+ * 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.
+ */
+
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/ipmi_bmc.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/wait.h>
+
+#define PFX "IPMI BMC BT-I2C: "
+
+/*
+ * TODO: This is "bt-host" to match the bt-host driver; however, I think this 
is
+ * unclear in the context of a CPU side driver. Should probably name this
+ * and the DEVICE_NAME in bt-host to something like "bt-bmc" or "bt-slave".
+ */
+#define DEVICE_NAME    "ipmi-bt-host"
+
+static const unsigned long request_queue_max_len = 256;
+
+struct bt_request_elem {
+       struct list_head        list;
+       struct bt_msg           request;
+};
+
+struct bt_i2c_slave {
+       struct i2c_client       *client;
+       struct miscdevice       miscdev;
+       struct bt_msg           request;
+       struct list_head        request_queue;
+       atomic_t                request_queue_len;
+       struct bt_msg           response;
+       bool                    response_in_progress;
+       size_t                  msg_idx;
+       spinlock_t              lock;
+       wait_queue_head_t       wait_queue;
+       struct mutex            file_mutex;
+};
+
+static int receive_bt_request(struct bt_i2c_slave *bt_slave, bool non_blocking,
+                             struct bt_msg *bt_request)
+{
+       int res;
+       unsigned long flags;
+       struct bt_request_elem *queue_elem;
+
+       if (!non_blocking) {
+try_again:
+               res = wait_event_interruptible(
+                               bt_slave->wait_queue,
+                               atomic_read(&bt_slave->request_queue_len));
+               if (res)
+                       return res;
+       }
+
+       spin_lock_irqsave(&bt_slave->lock, flags);
+       if (!atomic_read(&bt_slave->request_queue_len)) {
+               spin_unlock_irqrestore(&bt_slave->lock, flags);
+               if (non_blocking)
+                       return -EAGAIN;
+               goto try_again;
+       }
+
+       if (list_empty(&bt_slave->request_queue)) {
+               pr_err(PFX "request_queue was empty despite nonzero 
request_queue_len\n");
+               return -EIO;
+       }
+       queue_elem = list_first_entry(&bt_slave->request_queue,
+                                     struct bt_request_elem, list);
+       memcpy(bt_request, &queue_elem->request, sizeof(*bt_request));
+       list_del(&queue_elem->list);
+       kfree(queue_elem);
+       atomic_dec(&bt_slave->request_queue_len);
+       spin_unlock_irqrestore(&bt_slave->lock, flags);
+       return 0;
+}
+
+static int send_bt_response(struct bt_i2c_slave *bt_slave, bool non_blocking,
+                           struct bt_msg *bt_response)
+{
+       int res;
+       unsigned long flags;
+
+       if (!non_blocking) {
+try_again:
+               res = wait_event_interruptible(bt_slave->wait_queue,
+                                              !bt_slave->response_in_progress);
+               if (res)
+                       return res;
+       }
+
+       spin_lock_irqsave(&bt_slave->lock, flags);
+       if (bt_slave->response_in_progress) {
+               spin_unlock_irqrestore(&bt_slave->lock, flags);
+               if (non_blocking)
+                       return -EAGAIN;
+               goto try_again;
+       }
+
+       memcpy(&bt_slave->response, bt_response, sizeof(*bt_response));
+       bt_slave->response_in_progress = true;
+       spin_unlock_irqrestore(&bt_slave->lock, flags);
+       return 0;
+}
+
+static inline struct bt_i2c_slave *to_bt_i2c_slave(struct file *file)
+{
+       return container_of(file->private_data, struct bt_i2c_slave, miscdev);
+}
+
+static ssize_t bt_read(struct file *file, char __user *buf, size_t count,
+                      loff_t *ppos)
+{
+       struct bt_i2c_slave *bt_slave = to_bt_i2c_slave(file);
+       struct bt_msg msg;
+       ssize_t ret;
+
+       mutex_lock(&bt_slave->file_mutex);
+       ret = receive_bt_request(bt_slave, file->f_flags & O_NONBLOCK, &msg);
+       if (ret < 0)
+               goto out;
+       count = min_t(size_t, count, bt_msg_len(&msg));
+       if (copy_to_user(buf, &msg, count)) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+out:
+       mutex_unlock(&bt_slave->file_mutex);
+       if (ret < 0)
+               return ret;
+       else
+               return count;
+}
+
+static ssize_t bt_write(struct file *file, const char __user *buf, size_t 
count,
+                       loff_t *ppos)
+{
+       struct bt_i2c_slave *bt_slave = to_bt_i2c_slave(file);
+       struct bt_msg msg;
+       ssize_t ret;
+
+       if (count > sizeof(msg))
+               return -EINVAL;
+
+       if (copy_from_user(&msg, buf, count) || count < bt_msg_len(&msg))
+               return -EINVAL;
+
+       mutex_lock(&bt_slave->file_mutex);
+       ret = send_bt_response(bt_slave, file->f_flags & O_NONBLOCK, &msg);
+       mutex_unlock(&bt_slave->file_mutex);
+
+       if (ret < 0)
+               return ret;
+       else
+               return count;
+}
+
+static unsigned int bt_poll(struct file *file, poll_table *wait)
+{
+       struct bt_i2c_slave *bt_slave = to_bt_i2c_slave(file);
+       unsigned int mask = 0;
+
+       mutex_lock(&bt_slave->file_mutex);
+       poll_wait(file, &bt_slave->wait_queue, wait);
+
+       if (atomic_read(&bt_slave->request_queue_len))
+               mask |= POLLIN;
+       if (!bt_slave->response_in_progress)
+               mask |= POLLOUT;
+       mutex_unlock(&bt_slave->file_mutex);
+       return mask;
+}
+
+static const struct file_operations bt_fops = {
+       .owner          = THIS_MODULE,
+       .read           = bt_read,
+       .write          = bt_write,
+       .poll           = bt_poll,
+};
+
+/* Called with bt_slave->lock held. */
+static int handle_request(struct bt_i2c_slave *bt_slave)
+{
+       struct bt_request_elem *queue_elem;
+
+       if (atomic_read(&bt_slave->request_queue_len) >= request_queue_max_len)
+               return -EFAULT;
+       queue_elem = kmalloc(sizeof(*queue_elem), GFP_KERNEL);
+       if (!queue_elem)
+               return -ENOMEM;
+       memcpy(&queue_elem->request, &bt_slave->request, sizeof(struct bt_msg));
+       list_add(&queue_elem->list, &bt_slave->request_queue);
+       atomic_inc(&bt_slave->request_queue_len);
+       wake_up_all(&bt_slave->wait_queue);
+       return 0;
+}
+
+/* Called with bt_slave->lock held. */
+static int complete_response(struct bt_i2c_slave *bt_slave)
+{
+       /* Invalidate response in buffer to denote it having been sent. */
+       bt_slave->response.len = 0;
+       bt_slave->response_in_progress = false;
+       wake_up_all(&bt_slave->wait_queue);
+       return 0;
+}
+
+static int bt_i2c_slave_cb(struct i2c_client *client,
+                          enum i2c_slave_event event, u8 *val)
+{
+       struct bt_i2c_slave *bt_slave = i2c_get_clientdata(client);
+       u8 *buf;
+
+       spin_lock(&bt_slave->lock);
+       switch (event) {
+       case I2C_SLAVE_WRITE_REQUESTED:
+               bt_slave->msg_idx = 0;
+               break;
+
+       case I2C_SLAVE_WRITE_RECEIVED:
+               buf = (u8 *) &bt_slave->request;
+               if (bt_slave->msg_idx >= sizeof(struct bt_msg))
+                       break;
+
+               buf[bt_slave->msg_idx++] = *val;
+               if (bt_slave->msg_idx >= bt_msg_len(&bt_slave->request))
+                       handle_request(bt_slave);
+               break;
+
+       case I2C_SLAVE_READ_REQUESTED:
+               buf = (u8 *) &bt_slave->response;
+               bt_slave->msg_idx = 0;
+               *val = buf[bt_slave->msg_idx];
+               /*
+                * Do not increment buffer_idx here, because we don't know if
+                * this byte will be actually used. Read Linux I2C slave docs
+                * for details.
+                */
+               break;
+
+       case I2C_SLAVE_READ_PROCESSED:
+               buf = (u8 *) &bt_slave->response;
+               if (bt_slave->response.len &&
+                   bt_slave->msg_idx < bt_msg_len(&bt_slave->response)) {
+                       *val = buf[++bt_slave->msg_idx];
+               } else {
+                       *val = 0;
+               }
+               if (bt_slave->msg_idx + 1 >= bt_msg_len(&bt_slave->response))
+                       complete_response(bt_slave);
+               break;
+
+       case I2C_SLAVE_STOP:
+               bt_slave->msg_idx = 0;
+               break;
+
+       default:
+               break;
+       }
+       spin_unlock(&bt_slave->lock);
+
+       return 0;
+}
+
+static int bt_i2c_probe(struct i2c_client *client,
+                       const struct i2c_device_id *id)
+{
+       struct bt_i2c_slave *bt_slave;
+       int ret;
+
+       bt_slave = devm_kzalloc(&client->dev, sizeof(*bt_slave),
+                               GFP_KERNEL);
+       if (!bt_slave)
+               return -ENOMEM;
+
+       spin_lock_init(&bt_slave->lock);
+       init_waitqueue_head(&bt_slave->wait_queue);
+       atomic_set(&bt_slave->request_queue_len, 0);
+       bt_slave->response_in_progress = false;
+       INIT_LIST_HEAD(&bt_slave->request_queue);
+
+       mutex_init(&bt_slave->file_mutex);
+
+       bt_slave->miscdev.minor = MISC_DYNAMIC_MINOR;
+       bt_slave->miscdev.name = DEVICE_NAME;
+       bt_slave->miscdev.fops = &bt_fops;
+       bt_slave->miscdev.parent = &client->dev;
+       ret = misc_register(&bt_slave->miscdev);
+       if (ret)
+               return ret;
+
+       bt_slave->client = client;
+       i2c_set_clientdata(client, bt_slave);
+       ret = i2c_slave_register(client, bt_i2c_slave_cb);
+       if (ret) {
+               misc_deregister(&bt_slave->miscdev);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int bt_i2c_remove(struct i2c_client *client)
+{
+       struct bt_i2c_slave *bt_slave = i2c_get_clientdata(client);
+
+       i2c_slave_unregister(client);
+       misc_deregister(&bt_slave->miscdev);
+       return 0;
+}
+
+static const struct i2c_device_id bt_i2c_id[] = {
+       {"ipmi-bmc-bt-i2c", 0},
+       {},
+};
+MODULE_DEVICE_TABLE(i2c, bt_i2c_id);
+
+static struct i2c_driver bt_i2c_driver = {
+       .driver = {
+               .name           = "ipmi-bmc-bt-i2c",
+       },
+       .probe          = bt_i2c_probe,
+       .remove         = bt_i2c_remove,
+       .id_table       = bt_i2c_id,
+};
+module_i2c_driver(bt_i2c_driver);
+
+MODULE_AUTHOR("Brendan Higgins <brendanhigg...@google.com>");
+MODULE_DESCRIPTION("BMC-side IPMI Block Transfer over I2C.");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/ipmi_bmc.h b/include/linux/ipmi_bmc.h
new file mode 100644
index 000000000000..d0885c0bf190
--- /dev/null
+++ b/include/linux/ipmi_bmc.h
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+#ifndef __LINUX_IPMI_BMC_H
+#define __LINUX_IPMI_BMC_H
+
+#include <linux/bug.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+#define BT_MSG_PAYLOAD_LEN_MAX 252
+
+/**
+ * struct bt_msg - Block Transfer IPMI message.
+ * @len: Length of the message, not including this field.
+ * @netfn_lun: 6-bit netfn field definining the category of message and 2-bit
+ *             lun field used for routing.
+ * @seq: Sequence number used to associate requests with responses.
+ * @cmd: Command within a netfn category.
+ * @payload: Variable length field. May have specific requirements based on
+ *           netfn/cmd pair.
+ *
+ * Use bt_msg_len() to determine the total length of a message (including
+ * the @len field) rather than reading it directly.
+ */
+struct bt_msg {
+       u8 len;
+       u8 netfn_lun;
+       u8 seq;
+       u8 cmd;
+       u8 payload[BT_MSG_PAYLOAD_LEN_MAX];
+} __packed;
+
+/**
+ * bt_msg_len() - Determine the total length of a Block Transfer message.
+ * @bt_msg: Pointer to the message.
+ *
+ * This function calculates the length of an IPMI Block Transfer message
+ * including the length field itself.
+ *
+ * Return: Length of @bt_msg.
+ */
+static inline u32 bt_msg_len(struct bt_msg *bt_msg)
+{
+       return bt_msg->len + 1;
+}
+
+/**
+ * bt_msg_payload_to_len() - Calculate the len field of a Block Transfer 
message
+ *                           given the length of the payload.
+ * @payload_len: Length of the payload.
+ *
+ * Return: len field of the Block Transfer message which contains this payload.
+ */
+static inline u8 bt_msg_payload_to_len(u8 payload_len)
+{
+       if (unlikely(payload_len > BT_MSG_PAYLOAD_LEN_MAX)) {
+               payload_len = BT_MSG_PAYLOAD_LEN_MAX;
+               WARN(1, "BT message payload is too large. Truncating to %u.\n",
+                    BT_MSG_PAYLOAD_LEN_MAX);
+       }
+       return payload_len + 3;
+}
+
+#endif /* __LINUX_IPMI_BMC_H */
-- 
2.14.0.rc1.383.gd1ce394fe2-goog


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Openipmi-developer mailing list
Openipmi-developer@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openipmi-developer

Reply via email to