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> --- 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