[PATCH v2 2/4] ipmi: bt-i2c: added IPMI Block Transfer over I2C host side

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




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 <brendanhiggins@xxxxxxxxxx>
---
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 <brendanhiggins@xxxxxxxxxx>");
+MODULE_DESCRIPTION("IPMI Block Transfer over I2C.");
+MODULE_LICENSE("GPL v2");
-- 
2.14.0.rc1.383.gd1ce394fe2-goog

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux