diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2021-11-01 18:53:03 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2021-11-01 18:53:03 -0700 |
commit | 316b7eaa932d99e6421bee9a89e4f19aefddd88a (patch) | |
tree | 1af5c2b727c58f4a8d5510e533835fd1a4102831 | |
parent | 4dee060625e1095c7065fead542e96ba9504c7eb (diff) | |
parent | f281d010b87454e72475b668ad66e34961f744e0 (diff) | |
download | lwn-316b7eaa932d99e6421bee9a89e4f19aefddd88a.tar.gz lwn-316b7eaa932d99e6421bee9a89e4f19aefddd88a.zip |
Merge tag 'for-linus-5.16-1' of https://github.com/cminyard/linux-ipmi
Pull IPMI driver updates from Corey Minyard:
"A new type of low-level IPMI driver is added for direct communication
over the IPMI message bus without a BMC between the driver and the
bus.
Other than that, lots of little bug fixes and enhancements"
* tag 'for-linus-5.16-1' of https://github.com/cminyard/linux-ipmi:
ipmi: kcs_bmc: Fix a memory leak in the error handling path of 'kcs_bmc_serio_add_device()'
char: ipmi: replace snprintf in show functions with sysfs_emit
ipmi: ipmb: fix dependencies to eliminate build error
ipmi:ipmb: Add OF support
ipmi: bt: Add ast2600 compatible string
ipmi: bt-bmc: Use registers directly
ipmi: ipmb: Fix off-by-one size check on rcvlen
ipmi:ssif: Use depends on, not select, for I2C
ipmi: Add docs for the IPMI IPMB driver
ipmi: Add docs for IPMB direct addressing
ipmi:ipmb: Add initial support for IPMI over IPMB
ipmi: Add support for IPMB direct messages
ipmi: Export ipmb_checksum()
ipmi: Fix a typo
ipmi: Check error code before processing BMC response
ipmi:devintf: Return a proper error when recv buffer too small
ipmi: Disable some operations during a panic
ipmi:watchdog: Set panic count to proper value on a panic
-rw-r--r-- | Documentation/devicetree/bindings/ipmi/aspeed,ast2400-ibt-bmc.txt | 1 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/ipmi/ipmi-ipmb.yaml | 59 | ||||
-rw-r--r-- | Documentation/driver-api/ipmi.rst | 64 | ||||
-rw-r--r-- | drivers/char/ipmi/Kconfig | 11 | ||||
-rw-r--r-- | drivers/char/ipmi/Makefile | 1 | ||||
-rw-r--r-- | drivers/char/ipmi/bt-bmc.c | 69 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_devintf.c | 8 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_ipmb.c | 539 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_msghandler.c | 330 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_si_intf.c | 8 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_ssif.c | 4 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_watchdog.c | 25 | ||||
-rw-r--r-- | drivers/char/ipmi/kcs_bmc_serio.c | 4 | ||||
-rw-r--r-- | include/linux/ipmi.h | 3 | ||||
-rw-r--r-- | include/linux/ipmi_smi.h | 59 | ||||
-rw-r--r-- | include/uapi/linux/ipmi.h | 16 |
16 files changed, 1078 insertions, 123 deletions
diff --git a/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-ibt-bmc.txt b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-ibt-bmc.txt index 028268fd99ee..c9902fd4b38b 100644 --- a/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-ibt-bmc.txt +++ b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-ibt-bmc.txt @@ -9,6 +9,7 @@ Required properties: - compatible : should be one of "aspeed,ast2400-ibt-bmc" "aspeed,ast2500-ibt-bmc" + "aspeed,ast2600-ibt-bmc" - reg: physical address and size of the registers Optional properties: diff --git a/Documentation/devicetree/bindings/ipmi/ipmi-ipmb.yaml b/Documentation/devicetree/bindings/ipmi/ipmi-ipmb.yaml new file mode 100644 index 000000000000..93d8f8e88cf5 --- /dev/null +++ b/Documentation/devicetree/bindings/ipmi/ipmi-ipmb.yaml @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/ipmi/ipmi-ipmb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: IPMI IPMB device bindings + +description: IPMI IPMB device bindings + +maintainers: + - Corey Minyard <cminyard@mvista.com> + +properties: + compatible: + enum: + - ipmi-ipmb + + device_type: + items: + - const: "ipmi" + + reg: + maxItems: 1 + + bmcaddr: + $ref: /schemas/types.yaml#/definitions/uint8 + description: The address of the BMC on the IPMB bus. Defaults to 0x20. + + retry-time: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Time between retries of sends, in milliseconds. Defaults to 250. + + max-retries: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Number of retries before a failure is declared. Defaults to 1. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + ipmi-ipmb@40 { + compatible = "ipmi-ipmb"; + device_type = "ipmi"; + reg = <0x40>; + bmcaddr = /bits/ 8 <0x20>; + retry-time = <250>; + max-retries = <1>; + }; + }; diff --git a/Documentation/driver-api/ipmi.rst b/Documentation/driver-api/ipmi.rst index bc281f10ce4b..e224e47b6b09 100644 --- a/Documentation/driver-api/ipmi.rst +++ b/Documentation/driver-api/ipmi.rst @@ -166,8 +166,8 @@ and the type is IPMI_SYSTEM_INTERFACE_ADDR_TYPE. This is used for talking straight to the BMC on the current card. The channel must be IPMI_BMC_CHANNEL. -Messages that are destined to go out on the IPMB bus use the -IPMI_IPMB_ADDR_TYPE address type. The format is:: +Messages that are destined to go out on the IPMB bus going through the +BMC use the IPMI_IPMB_ADDR_TYPE address type. The format is:: struct ipmi_ipmb_addr { @@ -181,6 +181,23 @@ The "channel" here is generally zero, but some devices support more than one channel, it corresponds to the channel as defined in the IPMI spec. +There is also an IPMB direct address for a situation where the sender +is directly on an IPMB bus and doesn't have to go through the BMC. +You can send messages to a specific management controller (MC) on the +IPMB using the IPMI_IPMB_DIRECT_ADDR_TYPE with the following format:: + + struct ipmi_ipmb_direct_addr + { + int addr_type; + short channel; + unsigned char slave_addr; + unsigned char rq_lun; + unsigned char rs_lun; + }; + +The channel is always zero. You can also receive commands from other +MCs that you have registered to handle and respond to them, so you can +use this to implement a management controller on a bus.. Messages -------- @@ -348,6 +365,10 @@ user may be registered for each netfn/cmd/channel, but different users may register for different commands, or the same command if the channel bitmasks do not overlap. +To respond to a received command, set the response bit in the returned +netfn, use the address from the received message, and use the same +msgid that you got in the receive message. + From userland, equivalent IOCTLs are provided to do these functions. @@ -570,6 +591,45 @@ web page. The driver supports a hot add and remove of interfaces through the I2C sysfs interface. +The IPMI IPMB Driver +-------------------- + +This driver is for supporting a system that sits on an IPMB bus; it +allows the interface to look like a normal IPMI interface. Sending +system interface addressed messages to it will cause the message to go +to the registered BMC on the system (default at IPMI address 0x20). + +It also allows you to directly address other MCs on the bus using the +ipmb direct addressing. You can receive commands from other MCs on +the bus and they will be handled through the normal received command +mechanism described above. + +Parameters are:: + + ipmi_ipmb.bmcaddr=<address to use for system interface addresses messages> + ipmi_ipmb.retry_time_ms=<Time between retries on IPMB> + ipmi_ipmb.max_retries=<Number of times to retry a message> + +Loading the module will not result in the driver automatcially +starting unless there is device tree information setting it up. If +you want to instantiate one of these by hand, do:: + + echo ipmi-ipmb <addr> > /sys/class/i2c-dev/i2c-<n>/device/new_device + +Note that the address you give here is the I2C address, not the IPMI +address. So if you want your MC address to be 0x60, you put 0x30 +here. See the I2C driver info for more details. + +Command bridging to other IPMB busses through this interface does not +work. The receive message queue is not implemented, by design. There +is only one receive message queue on a BMC, and that is meant for the +host drivers, not something on the IPMB bus. + +A BMC may have multiple IPMB busses, which bus your device sits on +depends on how the system is wired. You can fetch the channels with +"ipmitool channel info <n>" where <n> is the channel, with the +channels being 0-7 and try the IPMB channels. + Other Pieces ------------ diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index 249b31197eea..b061e6b513ed 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig @@ -69,12 +69,21 @@ config IPMI_SI config IPMI_SSIF tristate 'IPMI SMBus handler (SSIF)' - select I2C + depends on I2C help Provides a driver for a SMBus interface to a BMC, meaning that you have a driver that must be accessed over an I2C bus instead of a standard interface. This module requires I2C support. +config IPMI_IPMB + tristate 'IPMI IPMB interface' + depends on I2C && I2C_SLAVE + help + Provides a driver for a system running right on the IPMB bus. + It supports normal system interface messages to a BMC on the IPMB + bus, and it also supports direct messaging on the bus using + IPMB direct messages. This module requires I2C support. + config IPMI_POWERNV depends on PPC_POWERNV tristate 'POWERNV (OPAL firmware) IPMI interface' diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile index 84f47d18007f..7ce790efad92 100644 --- a/drivers/char/ipmi/Makefile +++ b/drivers/char/ipmi/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_IPMI_SI) += ipmi_si.o obj-$(CONFIG_IPMI_DMI_DECODE) += ipmi_dmi.o obj-$(CONFIG_IPMI_PLAT_DATA) += ipmi_plat_data.o obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o +obj-$(CONFIG_IPMI_IPMB) += ipmi_ipmb.o obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o diff --git a/drivers/char/ipmi/bt-bmc.c b/drivers/char/ipmi/bt-bmc.c index 6e3d247b55d1..7450904e330a 100644 --- a/drivers/char/ipmi/bt-bmc.c +++ b/drivers/char/ipmi/bt-bmc.c @@ -8,13 +8,11 @@ #include <linux/errno.h> #include <linux/interrupt.h> #include <linux/io.h> -#include <linux/mfd/syscon.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/poll.h> -#include <linux/regmap.h> #include <linux/sched.h> #include <linux/timer.h> @@ -59,8 +57,7 @@ struct bt_bmc { struct device dev; struct miscdevice miscdev; - struct regmap *map; - int offset; + void __iomem *base; int irq; wait_queue_head_t queue; struct timer_list poll_timer; @@ -69,29 +66,14 @@ struct bt_bmc { static atomic_t open_count = ATOMIC_INIT(0); -static const struct regmap_config bt_regmap_cfg = { - .reg_bits = 32, - .val_bits = 32, - .reg_stride = 4, -}; - static u8 bt_inb(struct bt_bmc *bt_bmc, int reg) { - uint32_t val = 0; - int rc; - - rc = regmap_read(bt_bmc->map, bt_bmc->offset + reg, &val); - WARN(rc != 0, "regmap_read() failed: %d\n", rc); - - return rc == 0 ? (u8) val : 0; + return readb(bt_bmc->base + reg); } static void bt_outb(struct bt_bmc *bt_bmc, u8 data, int reg) { - int rc; - - rc = regmap_write(bt_bmc->map, bt_bmc->offset + reg, data); - WARN(rc != 0, "regmap_write() failed: %d\n", rc); + writeb(data, bt_bmc->base + reg); } static void clr_rd_ptr(struct bt_bmc *bt_bmc) @@ -376,18 +358,15 @@ static irqreturn_t bt_bmc_irq(int irq, void *arg) { struct bt_bmc *bt_bmc = arg; u32 reg; - int rc; - rc = regmap_read(bt_bmc->map, bt_bmc->offset + BT_CR2, ®); - if (rc) - return IRQ_NONE; + reg = readl(bt_bmc->base + BT_CR2); reg &= BT_CR2_IRQ_H2B | BT_CR2_IRQ_HBUSY; if (!reg) return IRQ_NONE; /* ack pending IRQs */ - regmap_write(bt_bmc->map, bt_bmc->offset + BT_CR2, reg); + writel(reg, bt_bmc->base + BT_CR2); wake_up(&bt_bmc->queue); return IRQ_HANDLED; @@ -398,6 +377,7 @@ static int bt_bmc_config_irq(struct bt_bmc *bt_bmc, { struct device *dev = &pdev->dev; int rc; + u32 reg; bt_bmc->irq = platform_get_irq_optional(pdev, 0); if (bt_bmc->irq < 0) @@ -417,11 +397,11 @@ static int bt_bmc_config_irq(struct bt_bmc *bt_bmc, * will be cleared (along with B2H) when we can write the next * message to the BT buffer */ - rc = regmap_update_bits(bt_bmc->map, bt_bmc->offset + BT_CR1, - (BT_CR1_IRQ_H2B | BT_CR1_IRQ_HBUSY), - (BT_CR1_IRQ_H2B | BT_CR1_IRQ_HBUSY)); + reg = readl(bt_bmc->base + BT_CR1); + reg |= BT_CR1_IRQ_H2B | BT_CR1_IRQ_HBUSY; + writel(reg, bt_bmc->base + BT_CR1); - return rc; + return 0; } static int bt_bmc_probe(struct platform_device *pdev) @@ -439,25 +419,9 @@ static int bt_bmc_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, bt_bmc); - bt_bmc->map = syscon_node_to_regmap(pdev->dev.parent->of_node); - if (IS_ERR(bt_bmc->map)) { - void __iomem *base; - - /* - * Assume it's not the MFD-based devicetree description, in - * which case generate a regmap ourselves - */ - base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(base)) - return PTR_ERR(base); - - bt_bmc->map = devm_regmap_init_mmio(dev, base, &bt_regmap_cfg); - bt_bmc->offset = 0; - } else { - rc = of_property_read_u32(dev->of_node, "reg", &bt_bmc->offset); - if (rc) - return rc; - } + bt_bmc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(bt_bmc->base)) + return PTR_ERR(bt_bmc->base); mutex_init(&bt_bmc->mutex); init_waitqueue_head(&bt_bmc->queue); @@ -483,12 +447,12 @@ static int bt_bmc_probe(struct platform_device *pdev) add_timer(&bt_bmc->poll_timer); } - regmap_write(bt_bmc->map, bt_bmc->offset + BT_CR0, - (BT_IO_BASE << BT_CR0_IO_BASE) | + writel((BT_IO_BASE << BT_CR0_IO_BASE) | (BT_IRQ << BT_CR0_IRQ) | BT_CR0_EN_CLR_SLV_RDP | BT_CR0_EN_CLR_SLV_WRP | - BT_CR0_ENABLE_IBT); + BT_CR0_ENABLE_IBT, + bt_bmc->base + BT_CR0); clr_b_busy(bt_bmc); @@ -508,6 +472,7 @@ static int bt_bmc_remove(struct platform_device *pdev) static const struct of_device_id bt_bmc_match[] = { { .compatible = "aspeed,ast2400-ibt-bmc" }, { .compatible = "aspeed,ast2500-ibt-bmc" }, + { .compatible = "aspeed,ast2600-ibt-bmc" }, { }, }; diff --git a/drivers/char/ipmi/ipmi_devintf.c b/drivers/char/ipmi/ipmi_devintf.c index 3dd1d5abb298..d160fa4c73fe 100644 --- a/drivers/char/ipmi/ipmi_devintf.c +++ b/drivers/char/ipmi/ipmi_devintf.c @@ -247,11 +247,13 @@ static int handle_recv(struct ipmi_file_private *priv, if (msg->msg.data_len > 0) { if (rsp->msg.data_len < msg->msg.data_len) { - rv2 = -EMSGSIZE; - if (trunc) + if (trunc) { + rv2 = -EMSGSIZE; msg->msg.data_len = rsp->msg.data_len; - else + } else { + rv = -EMSGSIZE; goto recv_putback_on_err; + } } if (copy_to_user(rsp->msg.data, diff --git a/drivers/char/ipmi/ipmi_ipmb.c b/drivers/char/ipmi/ipmi_ipmb.c new file mode 100644 index 000000000000..ba0c2d2c6bbe --- /dev/null +++ b/drivers/char/ipmi/ipmi_ipmb.c @@ -0,0 +1,539 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Driver to talk to a remote management controller on IPMB. + */ + +#include <linux/acpi.h> +#include <linux/errno.h> +#include <linux/i2c.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/semaphore.h> +#include <linux/kthread.h> +#include <linux/wait.h> +#include <linux/ipmi_msgdefs.h> +#include <linux/ipmi_smi.h> + +#define DEVICE_NAME "ipmi-ipmb" + +static int bmcaddr = 0x20; +module_param(bmcaddr, int, 0644); +MODULE_PARM_DESC(bmcaddr, "Address to use for BMC."); + +static unsigned int retry_time_ms = 250; +module_param(retry_time_ms, uint, 0644); +MODULE_PARM_DESC(max_retries, "Timeout time between retries, in milliseconds."); + +static unsigned int max_retries = 1; +module_param(max_retries, uint, 0644); +MODULE_PARM_DESC(max_retries, "Max resends of a command before timing out."); + +/* Add room for the two slave addresses, two checksums, and rqSeq. */ +#define IPMB_MAX_MSG_LEN (IPMI_MAX_MSG_LENGTH + 5) + +struct ipmi_ipmb_dev { + struct ipmi_smi *intf; + struct i2c_client *client; + + struct ipmi_smi_handlers handlers; + + bool ready; + + u8 curr_seq; + + u8 bmcaddr; + u32 retry_time_ms; + u32 max_retries; + + struct ipmi_smi_msg *next_msg; + struct ipmi_smi_msg *working_msg; + + /* Transmit thread. */ + struct task_struct *thread; + struct semaphore wake_thread; + struct semaphore got_rsp; + spinlock_t lock; + bool stopping; + + u8 xmitmsg[IPMB_MAX_MSG_LEN]; + unsigned int xmitlen; + + u8 rcvmsg[IPMB_MAX_MSG_LEN]; + unsigned int rcvlen; + bool overrun; +}; + +static bool valid_ipmb(struct ipmi_ipmb_dev *iidev) +{ + u8 *msg = iidev->rcvmsg; + u8 netfn; + + if (iidev->overrun) + return false; + + /* Minimum message size. */ + if (iidev->rcvlen < 7) + return false; + + /* Is it a response? */ + netfn = msg[1] >> 2; + if (netfn & 1) { + /* Response messages have an added completion code. */ + if (iidev->rcvlen < 8) + return false; + } + + if (ipmb_checksum(msg, 3) != 0) + return false; + if (ipmb_checksum(msg + 3, iidev->rcvlen - 3) != 0) + return false; + + return true; +} + +static void ipmi_ipmb_check_msg_done(struct ipmi_ipmb_dev *iidev) +{ + struct ipmi_smi_msg *imsg = NULL; + u8 *msg = iidev->rcvmsg; + bool is_cmd; + unsigned long flags; + + if (iidev->rcvlen == 0) + return; + if (!valid_ipmb(iidev)) + goto done; + + is_cmd = ((msg[1] >> 2) & 1) == 0; + + if (is_cmd) { + /* Ignore commands until we are up. */ + if (!iidev->ready) + goto done; + + /* It's a command, allocate a message for it. */ + imsg = ipmi_alloc_smi_msg(); + if (!imsg) + goto done; + imsg->type = IPMI_SMI_MSG_TYPE_IPMB_DIRECT; + imsg->data_size = 0; + } else { + spin_lock_irqsave(&iidev->lock, flags); + if (iidev->working_msg) { + u8 seq = msg[4] >> 2; + bool xmit_rsp = (iidev->working_msg->data[0] >> 2) & 1; + + /* + * Responses should carry the sequence we sent + * them with. If it's a transmitted response, + * ignore it. And if the message hasn't been + * transmitted, ignore it. + */ + if (!xmit_rsp && seq == iidev->curr_seq) { + iidev->curr_seq = (iidev->curr_seq + 1) & 0x3f; + + imsg = iidev->working_msg; + iidev->working_msg = NULL; + } + } + spin_unlock_irqrestore(&iidev->lock, flags); + } + + if (!imsg) + goto done; + + if (imsg->type == IPMI_SMI_MSG_TYPE_IPMB_DIRECT) { + imsg->rsp[0] = msg[1]; /* NetFn/LUN */ + /* + * Keep the source address, rqSeq. Drop the trailing + * checksum. + */ + memcpy(imsg->rsp + 1, msg + 3, iidev->rcvlen - 4); + imsg->rsp_size = iidev->rcvlen - 3; + } else { + imsg->rsp[0] = msg[1]; /* NetFn/LUN */ + /* + * Skip the source address, rqSeq. Drop the trailing + * checksum. + */ + memcpy(imsg->rsp + 1, msg + 5, iidev->rcvlen - 6); + imsg->rsp_size = iidev->rcvlen - 5; + } + ipmi_smi_msg_received(iidev->intf, imsg); + if (!is_cmd) + up(&iidev->got_rsp); + +done: + iidev->overrun = false; + iidev->rcvlen = 0; +} + +/* + * The IPMB protocol only supports i2c writes so there is no need to + * support I2C_SLAVE_READ* events, except to know if the other end has + * issued a read without going to stop mode. + */ +static int ipmi_ipmb_slave_cb(struct i2c_client *client, + enum i2c_slave_event event, u8 *val) +{ + struct ipmi_ipmb_dev *iidev = i2c_get_clientdata(client); + + switch (event) { + case I2C_SLAVE_WRITE_REQUESTED: + ipmi_ipmb_check_msg_done(iidev); + /* + * First byte is the slave address, to ease the checksum + * calculation. + */ + iidev->rcvmsg[0] = client->addr << 1; + iidev->rcvlen = 1; + break; + + case I2C_SLAVE_WRITE_RECEIVED: + if (iidev->rcvlen >= sizeof(iidev->rcvmsg)) + iidev->overrun = true; + else + iidev->rcvmsg[iidev->rcvlen++] = *val; + break; + + case I2C_SLAVE_READ_REQUESTED: + case I2C_SLAVE_STOP: + ipmi_ipmb_check_msg_done(iidev); + break; + + case I2C_SLAVE_READ_PROCESSED: + break; + } + + return 0; +} + +static void ipmi_ipmb_send_response(struct ipmi_ipmb_dev *iidev, + struct ipmi_smi_msg *msg, u8 cc) +{ + if ((msg->data[0] >> 2) & 1) { + /* + * It's a response being sent, we needto return a + * response response. Fake a send msg command + * response with channel 0. This will always be ipmb + * direct. + */ + msg->data[0] = (IPMI_NETFN_APP_REQUEST | 1) << 2; + msg->data[3] = IPMI_SEND_MSG_CMD; + msg->data[4] = cc; + msg->data_size = 5; + } + msg->rsp[0] = msg->data[0] | (1 << 2); + if (msg->type == IPMI_SMI_MSG_TYPE_IPMB_DIRECT) { + msg->rsp[1] = msg->data[1]; + msg->rsp[2] = msg->data[2]; + msg->rsp[3] = msg->data[3]; + msg->rsp[4] = cc; + msg->rsp_size = 5; + } else { + msg->rsp[1] = msg->data[1]; + msg->rsp[2] = cc; + msg->rsp_size = 3; + } + ipmi_smi_msg_received(iidev->intf, msg); +} + +static void ipmi_ipmb_format_for_xmit(struct ipmi_ipmb_dev *iidev, + struct ipmi_smi_msg *msg) +{ + if (msg->type == IPMI_SMI_MSG_TYPE_IPMB_DIRECT) { + iidev->xmitmsg[0] = msg->data[1]; + iidev->xmitmsg[1] = msg->data[0]; + memcpy(iidev->xmitmsg + 4, msg->data + 2, msg->data_size - 2); + iidev->xmitlen = msg->data_size + 2; + } else { + iidev->xmitmsg[0] = iidev->bmcaddr; + iidev->xmitmsg[1] = msg->data[0]; + iidev->xmitmsg[4] = 0; + memcpy(iidev->xmitmsg + 5, msg->data + 1, msg->data_size - 1); + iidev->xmitlen = msg->data_size + 4; + } + iidev->xmitmsg[3] = iidev->client->addr << 1; + if (((msg->data[0] >> 2) & 1) == 0) + /* If it's a command, put in our own sequence number. */ + iidev->xmitmsg[4] = ((iidev->xmitmsg[4] & 0x03) | + (iidev->curr_seq << 2)); + + /* Now add on the final checksums. */ + iidev->xmitmsg[2] = ipmb_checksum(iidev->xmitmsg, 2); + iidev->xmitmsg[iidev->xmitlen] = + ipmb_checksum(iidev->xmitmsg + 3, iidev->xmitlen - 3); + iidev->xmitlen++; +} + +static int ipmi_ipmb_thread(void *data) +{ + struct ipmi_ipmb_dev *iidev = data; + + while (!kthread_should_stop()) { + long ret; + struct i2c_msg i2c_msg; + struct ipmi_smi_msg *msg = NULL; + unsigned long flags; + unsigned int retries = 0; + + /* Wait for a message to send */ + ret = down_interruptible(&iidev->wake_thread); + if (iidev->stopping) + break; + if (ret) + continue; + + spin_lock_irqsave(&iidev->lock, flags); + if (iidev->next_msg) { + msg = iidev->next_msg; + iidev->next_msg = NULL; + } + spin_unlock_irqrestore(&iidev->lock, flags); + if (!msg) + continue; + + ipmi_ipmb_format_for_xmit(iidev, msg); + +retry: + i2c_msg.len = iidev->xmitlen - 1; + if (i2c_msg.len > 32) { + ipmi_ipmb_send_response(iidev, msg, + IPMI_REQ_LEN_EXCEEDED_ERR); + continue; + } + + i2c_msg.addr = iidev->xmitmsg[0] >> 1; + i2c_msg.flags = 0; + i2c_msg.buf = iidev->xmitmsg + 1; + + /* Rely on i2c_transfer for a barrier. */ + iidev->working_msg = msg; + + ret = i2c_transfer(iidev->client->adapter, &i2c_msg, 1); + + if ((msg->data[0] >> 2) & 1) { + /* + * It's a response, nothing will be returned + * by the other end. + */ + + iidev->working_msg = NULL; + ipmi_ipmb_send_response(iidev, msg, + ret < 0 ? IPMI_BUS_ERR : 0); + continue; + } + if (ret < 0) { + iidev->working_msg = NULL; + ipmi_ipmb_send_response(iidev, msg, IPMI_BUS_ERR); + continue; + } + + /* A command was sent, wait for its response. */ + ret = down_timeout(&iidev->got_rsp, + msecs_to_jiffies(iidev->retry_time_ms)); + + /* + * Grab the message if we can. If the handler hasn't + * already handled it, the message will still be there. + */ + spin_lock_irqsave(&iidev->lock, flags); + msg = iidev->working_msg; + iidev->working_msg = NULL; + spin_unlock_irqrestore(&iidev->lock, flags); + + if (!msg && ret) { + /* + * If working_msg is not set and we timed out, + * that means the message grabbed by + * check_msg_done before we could grab it + * here. Wait again for check_msg_done to up + * the semaphore. + */ + down(&iidev->got_rsp); + } else if (msg && ++retries <= iidev->max_retries) { + spin_lock_irqsave(&iidev->lock, flags); + iidev->working_msg = msg; + spin_unlock_irqrestore(&iidev->lock, flags); + goto retry; + } + + if (msg) + ipmi_ipmb_send_response(iidev, msg, IPMI_TIMEOUT_ERR); + } + + if (iidev->next_msg) + /* Return an unspecified error. */ + ipmi_ipmb_send_response(iidev, iidev->next_msg, 0xff); + + return 0; +} + +static int ipmi_ipmb_start_processing(void *send_info, + struct ipmi_smi *new_intf) +{ + struct ipmi_ipmb_dev *iidev = send_info; + + iidev->intf = new_intf; + iidev->ready = true; + return 0; +} + +static void ipmi_ipmb_stop_thread(struct ipmi_ipmb_dev *iidev) +{ + if (iidev->thread) { + struct task_struct *t = iidev->thread; + + iidev->thread = NULL; + iidev->stopping = true; + up(&iidev->wake_thread); + up(&iidev->got_rsp); + kthread_stop(t); + } +} + +static void ipmi_ipmb_shutdown(void *send_info) +{ + struct ipmi_ipmb_dev *iidev = send_info; + + ipmi_ipmb_stop_thread(iidev); +} + +static void ipmi_ipmb_sender(void *send_info, + struct ipmi_smi_msg *msg) +{ + struct ipmi_ipmb_dev *iidev = send_info; + unsigned long flags; + + spin_lock_irqsave(&iidev->lock, flags); + BUG_ON(iidev->next_msg); + + iidev->next_msg = msg; + spin_unlock_irqrestore(&iidev->lock, flags); + + up(&iidev->wake_thread); +} + +static void ipmi_ipmb_request_events(void *send_info) +{ + /* We don't fetch events here. */ +} + +static int ipmi_ipmb_remove(struct i2c_client *client) +{ + struct ipmi_ipmb_dev *iidev = i2c_get_clientdata(client); + + if (iidev->client) { + iidev->client = NULL; + i2c_slave_unregister(client); + } + ipmi_ipmb_stop_thread(iidev); + + return 0; +} + +static int ipmi_ipmb_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ipmi_ipmb_dev *iidev; + int rv; + + iidev = devm_kzalloc(&client->dev, sizeof(*iidev), GFP_KERNEL); + if (!iidev) + return -ENOMEM; + + if (of_property_read_u8(dev->of_node, "bmcaddr", &iidev->bmcaddr) != 0) + iidev->bmcaddr = bmcaddr; + if (iidev->bmcaddr == 0 || iidev->bmcaddr & 1) { + /* Can't have the write bit set. */ + dev_notice(&client->dev, + "Invalid bmc address value %2.2x\n", iidev->bmcaddr); + return -EINVAL; + } + + if (of_property_read_u32(dev->of_node, "retry-time", + &iidev->retry_time_ms) != 0) + iidev->retry_time_ms = retry_time_ms; + + if (of_property_read_u32(dev->of_node, "max-retries", + &iidev->max_retries) != 0) + iidev->max_retries = max_retries; + + i2c_set_clientdata(client, iidev); + client->flags |= I2C_CLIENT_SLAVE; + + rv = i2c_slave_register(client, ipmi_ipmb_slave_cb); + if (rv) + return rv; + + iidev->client = client; + + iidev->handlers.flags = IPMI_SMI_CAN_HANDLE_IPMB_DIRECT; + iidev->handlers.start_processing = ipmi_ipmb_start_processing; + iidev->handlers.shutdown = ipmi_ipmb_shutdown; + iidev->handlers.sender = ipmi_ipmb_sender; + iidev->handlers.request_events = ipmi_ipmb_request_events; + + spin_lock_init(&iidev->lock); + sema_init(&iidev->wake_thread, 0); + sema_init(&iidev->got_rsp, 0); + + iidev->thread = kthread_run(ipmi_ipmb_thread, iidev, + "kipmb%4.4x", client->addr); + if (IS_ERR(iidev->thread)) { + rv = PTR_ERR(iidev->thread); + dev_notice(&client->dev, + "Could not start kernel thread: error %d\n", rv); + goto out_err; + } + + rv = ipmi_register_smi(&iidev->handlers, + iidev, + &client->dev, + iidev->bmcaddr); + if (rv) + goto out_err; + + return 0; + +out_err: + ipmi_ipmb_remove(client); + return rv; +} + +#ifdef CONFIG_OF +static const struct of_device_id of_ipmi_ipmb_match[] = { + { .type = "ipmi", .compatible = DEVICE_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_ipmi_ipmb_match); +#else +#define of_ipmi_ipmb_match NULL +#endif + +static const struct i2c_device_id ipmi_ipmb_id[] = { + { DEVICE_NAME, 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ipmi_ipmb_id); + +static struct i2c_driver ipmi_ipmb_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = DEVICE_NAME, + .of_match_table = of_ipmi_ipmb_match, + }, + .probe = ipmi_ipmb_probe, + .remove = ipmi_ipmb_remove, + .id_table = ipmi_ipmb_id, +}; +module_i2c_driver(ipmi_ipmb_driver); + +MODULE_AUTHOR("Corey Minyard"); +MODULE_DESCRIPTION("IPMI IPMB driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index e96cb5c4f97a..deed355422f4 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -653,6 +653,11 @@ static int is_ipmb_bcast_addr(struct ipmi_addr *addr) return addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE; } +static int is_ipmb_direct_addr(struct ipmi_addr *addr) +{ + return addr->addr_type == IPMI_IPMB_DIRECT_ADDR_TYPE; +} + static void free_recv_msg_list(struct list_head *q) { struct ipmi_recv_msg *msg, *msg2; @@ -805,6 +810,17 @@ ipmi_addr_equal(struct ipmi_addr *addr1, struct ipmi_addr *addr2) && (ipmb_addr1->lun == ipmb_addr2->lun)); } + if (is_ipmb_direct_addr(addr1)) { + struct ipmi_ipmb_direct_addr *daddr1 + = (struct ipmi_ipmb_direct_addr *) addr1; + struct ipmi_ipmb_direct_addr *daddr2 + = (struct ipmi_ipmb_direct_addr *) addr2; + + return daddr1->slave_addr == daddr2->slave_addr && + daddr1->rq_lun == daddr2->rq_lun && + daddr1->rs_lun == daddr2->rs_lun; + } + if (is_lan_addr(addr1)) { struct ipmi_lan_addr *lan_addr1 = (struct ipmi_lan_addr *) addr1; @@ -843,6 +859,23 @@ int ipmi_validate_addr(struct ipmi_addr *addr, int len) return 0; } + if (is_ipmb_direct_addr(addr)) { + struct ipmi_ipmb_direct_addr *daddr = (void *) addr; + + if (addr->channel != 0) + return -EINVAL; + if (len < sizeof(struct ipmi_ipmb_direct_addr)) + return -EINVAL; + + if (daddr->slave_addr & 0x01) + return -EINVAL; + if (daddr->rq_lun >= 4) + return -EINVAL; + if (daddr->rs_lun >= 4) + return -EINVAL; + return 0; + } + if (is_lan_addr(addr)) { if (len < sizeof(struct ipmi_lan_addr)) return -EINVAL; @@ -862,6 +895,9 @@ unsigned int ipmi_addr_length(int addr_type) || (addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)) return sizeof(struct ipmi_ipmb_addr); + if (addr_type == IPMI_IPMB_DIRECT_ADDR_TYPE) + return sizeof(struct ipmi_ipmb_direct_addr); + if (addr_type == IPMI_LAN_ADDR_TYPE) return sizeof(struct ipmi_lan_addr); @@ -1710,7 +1746,7 @@ int ipmi_unregister_for_cmd(struct ipmi_user *user, } EXPORT_SYMBOL(ipmi_unregister_for_cmd); -static unsigned char +unsigned char ipmb_checksum(unsigned char *data, int size) { unsigned char csum = 0; @@ -1720,6 +1756,7 @@ ipmb_checksum(unsigned char *data, int size) return -csum; } +EXPORT_SYMBOL(ipmb_checksum); static inline void format_ipmb_msg(struct ipmi_smi_msg *smi_msg, struct kernel_ipmi_msg *msg, @@ -2051,6 +2088,58 @@ out_err: return rv; } +static int i_ipmi_req_ipmb_direct(struct ipmi_smi *intf, + struct ipmi_addr *addr, + long msgid, + struct kernel_ipmi_msg *msg, + struct ipmi_smi_msg *smi_msg, + struct ipmi_recv_msg *recv_msg, + unsigned char source_lun) +{ + struct ipmi_ipmb_direct_addr *daddr; + bool is_cmd = !(recv_msg->msg.netfn & 0x1); + + if (!(intf->handlers->flags & IPMI_SMI_CAN_HANDLE_IPMB_DIRECT)) + return -EAFNOSUPPORT; + + /* Responses must have a completion code. */ + if (!is_cmd && msg->data_len < 1) { + ipmi_inc_stat(intf, sent_invalid_commands); + return -EINVAL; + } + + if ((msg->data_len + 4) > IPMI_MAX_MSG_LENGTH) { + ipmi_inc_stat(intf, sent_invalid_commands); + return -EMSGSIZE; + } + + daddr = (struct ipmi_ipmb_direct_addr *) addr; + if (daddr->rq_lun > 3 || daddr->rs_lun > 3) { + ipmi_inc_stat(intf, sent_invalid_commands); + return -EINVAL; + } + + smi_msg->type = IPMI_SMI_MSG_TYPE_IPMB_DIRECT; + smi_msg->msgid = msgid; + + if (is_cmd) { + smi_msg->data[0] = msg->netfn << 2 | daddr->rs_lun; + smi_msg->data[2] = recv_msg->msgid << 2 | daddr->rq_lun; + } else { + smi_msg->data[0] = msg->netfn << 2 | daddr->rq_lun; + smi_msg->data[2] = recv_msg->msgid << 2 | daddr->rs_lun; + } + smi_msg->data[1] = daddr->slave_addr; + smi_msg->data[3] = msg->cmd; + + memcpy(smi_msg->data + 4, msg->data, msg->data_len); + smi_msg->data_size = msg->data_len + 4; + + smi_msg->user_data = recv_msg; + + return 0; +} + static int i_ipmi_req_lan(struct ipmi_smi *intf, struct ipmi_addr *addr, long msgid, @@ -2240,6 +2329,9 @@ static int i_ipmi_request(struct ipmi_user *user, rv = i_ipmi_req_ipmb(intf, addr, msgid, msg, smi_msg, recv_msg, source_address, source_lun, retries, retry_time_ms); + } else if (is_ipmb_direct_addr(addr)) { + rv = i_ipmi_req_ipmb_direct(intf, addr, msgid, msg, smi_msg, + recv_msg, source_lun); } else if (is_lan_addr(addr)) { rv = i_ipmi_req_lan(intf, addr, msgid, msg, smi_msg, recv_msg, source_lun, retries, retry_time_ms); @@ -2369,6 +2461,13 @@ static void bmc_device_id_handler(struct ipmi_smi *intf, return; } + if (msg->msg.data[0]) { + dev_warn(intf->si_dev, "device id fetch failed: 0x%2.2x\n", + msg->msg.data[0]); + intf->bmc->dyn_id_set = 0; + goto out; + } + rv = ipmi_demangle_device_id(msg->msg.netfn, msg->msg.cmd, msg->msg.data, msg->msg.data_len, &intf->bmc->fetch_id); if (rv) { @@ -2384,7 +2483,7 @@ static void bmc_device_id_handler(struct ipmi_smi *intf, smp_wmb(); intf->bmc->dyn_id_set = 1; } - +out: wake_up(&intf->waitq); } @@ -2617,7 +2716,7 @@ static ssize_t device_id_show(struct device *dev, if (rv) return rv; - return snprintf(buf, 10, "%u\n", id.device_id); + return sysfs_emit(buf, "%u\n", id.device_id); } static DEVICE_ATTR_RO(device_id); @@ -2633,7 +2732,7 @@ static ssize_t provides_device_sdrs_show(struct device *dev, if (rv) return rv; - return snprintf(buf, 10, "%u\n", (id.device_revision & 0x80) >> 7); + return sysfs_emit(buf, "%u\n", (id.device_revision & 0x80) >> 7); } static DEVICE_ATTR_RO(provides_device_sdrs); @@ -2648,7 +2747,7 @@ static ssize_t revision_show(struct device *dev, struct device_attribute *attr, if (rv) return rv; - return snprintf(buf, 20, "%u\n", id.device_revision & 0x0F); + return sysfs_emit(buf, "%u\n", id.device_revision & 0x0F); } static DEVICE_ATTR_RO(revision); @@ -2664,7 +2763,7 @@ static ssize_t firmware_revision_show(struct device *dev, if (rv) return rv; - return snprintf(buf, 20, "%u.%x\n", id.firmware_revision_1, + return sysfs_emit(buf, "%u.%x\n", id.firmware_revision_1, id.firmware_revision_2); } static DEVICE_ATTR_RO(firmware_revision); @@ -2681,7 +2780,7 @@ static ssize_t ipmi_version_show(struct device *dev, if (rv) return rv; - return snprintf(buf, 20, "%u.%u\n", + return sysfs_emit(buf, "%u.%u\n", ipmi_version_major(&id), ipmi_version_minor(&id)); } @@ -2699,7 +2798,7 @@ static ssize_t add_dev_support_show(struct device *dev, if (rv) return rv; - return snprintf(buf, 10, "0x%02x\n", id.additional_device_support); + return sysfs_emit(buf, "0x%02x\n", id.additional_device_support); } static DEVICE_ATTR(additional_device_support, S_IRUGO, add_dev_support_show, NULL); @@ -2716,7 +2815,7 @@ static ssize_t manufacturer_id_show(struct device *dev, if (rv) return rv; - return snprintf(buf, 20, "0x%6.6x\n", id.manufacturer_id); + return sysfs_emit(buf, "0x%6.6x\n", id.manufacturer_id); } static DEVICE_ATTR_RO(manufacturer_id); @@ -2732,7 +2831,7 @@ static ssize_t product_id_show(struct device *dev, if (rv) return rv; - return snprintf(buf, 10, "0x%4.4x\n", id.product_id); + return sysfs_emit(buf, "0x%4.4x\n", id.product_id); } static DEVICE_ATTR_RO(product_id); @@ -2748,7 +2847,7 @@ static ssize_t aux_firmware_rev_show(struct device *dev, if (rv) return rv; - return snprintf(buf, 21, "0x%02x 0x%02x 0x%02x 0x%02x\n", + return sysfs_emit(buf, "0x%02x 0x%02x 0x%02x 0x%02x\n", id.aux_firmware_revision[3], id.aux_firmware_revision[2], id.aux_firmware_revision[1], @@ -2770,7 +2869,7 @@ static ssize_t guid_show(struct device *dev, struct device_attribute *attr, if (!guid_set) return -ENOENT; - return snprintf(buf, UUID_STRING_LEN + 1 + 1, "%pUl\n", &guid); + return sysfs_emit(buf, "%pUl\n", &guid); } static DEVICE_ATTR_RO(guid); @@ -3794,6 +3893,123 @@ static int handle_ipmb_get_msg_cmd(struct ipmi_smi *intf, return rv; } +static int handle_ipmb_direct_rcv_cmd(struct ipmi_smi *intf, + struct ipmi_smi_msg *msg) +{ + struct cmd_rcvr *rcvr; + int rv = 0; + struct ipmi_user *user = NULL; + struct ipmi_ipmb_direct_addr *daddr; + struct ipmi_recv_msg *recv_msg; + unsigned char netfn = msg->rsp[0] >> 2; + unsigned char cmd = msg->rsp[3]; + + rcu_read_lock(); + /* We always use channel 0 for direct messages. */ + rcvr = find_cmd_rcvr(intf, netfn, cmd, 0); + if (rcvr) { + user = rcvr->user; + kref_get(&user->refcount); + } else + user = NULL; + rcu_read_unlock(); + + if (user == NULL) { + /* We didn't find a user, deliver an error response. */ + ipmi_inc_stat(intf, unhandled_commands); + + msg->data[0] = ((netfn + 1) << 2) | (msg->rsp[4] & 0x3); + msg->data[1] = msg->rsp[2]; + msg->data[2] = msg->rsp[4] & ~0x3; + msg->data[3] = cmd; + msg->data[4] = IPMI_INVALID_CMD_COMPLETION_CODE; + msg->data_size = 5; + + rcu_read_lock(); + if (!intf->in_shutdown) { + smi_send(intf, intf->handlers, msg, 0); + /* + * We used the message, so return the value + * that causes it to not be freed or + * queued. + */ + rv = -1; + } + rcu_read_unlock(); + } else { + recv_msg = ipmi_alloc_recv_msg(); + if (!recv_msg) { + /* + * We couldn't allocate memory for the + * message, so requeue it for handling + * later. + */ + rv = 1; + kref_put(&user->refcount, free_user); + } else { + /* Extract the source address from the data. */ + daddr = (struct ipmi_ipmb_direct_addr *)&recv_msg->addr; + daddr->addr_type = IPMI_IPMB_DIRECT_ADDR_TYPE; + daddr->channel = 0; + daddr->slave_addr = msg->rsp[1]; + daddr->rs_lun = msg->rsp[0] & 3; + daddr->rq_lun = msg->rsp[2] & 3; + + /* + * Extract the rest of the message information + * from the IPMB header. + */ + recv_msg->user = user; + recv_msg->recv_type = IPMI_CMD_RECV_TYPE; + recv_msg->msgid = (msg->rsp[2] >> 2); + recv_msg->msg.netfn = msg->rsp[0] >> 2; + recv_msg->msg.cmd = msg->rsp[3]; + recv_msg->msg.data = recv_msg->msg_data; + + recv_msg->msg.data_len = msg->rsp_size - 4; + memcpy(recv_msg->msg_data, msg->rsp + 4, + msg->rsp_size - 4); + if (deliver_response(intf, recv_msg)) + ipmi_inc_stat(intf, unhandled_commands); + else + ipmi_inc_stat(intf, handled_commands); + } + } + + return rv; +} + +static int handle_ipmb_direct_rcv_rsp(struct ipmi_smi *intf, + struct ipmi_smi_msg *msg) +{ + struct ipmi_recv_msg *recv_msg; + struct ipmi_ipmb_direct_addr *daddr; + + recv_msg = (struct ipmi_recv_msg *) msg->user_data; + if (recv_msg == NULL) { + dev_warn(intf->si_dev, + "IPMI message received with no owner. This could be because of a malformed message, or because of a hardware error. Contact your hardware vendor for assistance.\n"); + return 0; + } + + recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE; + recv_msg->msgid = msg->msgid; + daddr = (struct ipmi_ipmb_direct_addr *) &recv_msg->addr; + daddr->addr_type = IPMI_IPMB_DIRECT_ADDR_TYPE; + daddr->channel = 0; + daddr->slave_addr = msg->rsp[1]; + daddr->rq_lun = msg->rsp[0] & 3; + daddr->rs_lun = msg->rsp[2] & 3; + recv_msg->msg.netfn = msg->rsp[0] >> 2; + recv_msg->msg.cmd = msg->rsp[3]; + memcpy(recv_msg->msg_data, &msg->rsp[4], msg->rsp_size - 4); + recv_msg->msg.data = recv_msg->msg_data; + recv_msg->msg.data_len = msg->rsp_size - 4; + deliver_local_response(intf, recv_msg); + + return 0; +} + static int handle_lan_get_msg_rsp(struct ipmi_smi *intf, struct ipmi_smi_msg *msg) { @@ -4219,18 +4435,40 @@ static int handle_bmc_rsp(struct ipmi_smi *intf, static int handle_one_recv_msg(struct ipmi_smi *intf, struct ipmi_smi_msg *msg) { - int requeue; + int requeue = 0; int chan; + unsigned char cc; + bool is_cmd = !((msg->rsp[0] >> 2) & 1); pr_debug("Recv: %*ph\n", msg->rsp_size, msg->rsp); - if ((msg->data_size >= 2) + if (msg->rsp_size < 2) { + /* Message is too small to be correct. */ + dev_warn(intf->si_dev, + "BMC returned too small a message for netfn %x cmd %x, got %d bytes\n", + (msg->data[0] >> 2) | 1, msg->data[1], msg->rsp_size); + +return_unspecified: + /* Generate an error response for the message. */ + msg->rsp[0] = msg->data[0] | (1 << 2); + msg->rsp[1] = msg->data[1]; + msg->rsp[2] = IPMI_ERR_UNSPECIFIED; + msg->rsp_size = 3; + } else if (msg->type == IPMI_SMI_MSG_TYPE_IPMB_DIRECT) { + /* commands must have at least 3 bytes, responses 4. */ + if (is_cmd && (msg->rsp_size < 3)) { + ipmi_inc_stat(intf, invalid_commands); + goto out; + } + if (!is_cmd && (msg->rsp_size < 4)) + goto return_unspecified; + } else if ((msg->data_size >= 2) && (msg->data[0] == (IPMI_NETFN_APP_REQUEST << 2)) && (msg->data[1] == IPMI_SEND_MSG_CMD) && (msg->user_data == NULL)) { if (intf->in_shutdown) - goto free_msg; + goto out; /* * This is the local response to a command send, start @@ -4265,21 +4503,6 @@ static int handle_one_recv_msg(struct ipmi_smi *intf, } else /* The message was sent, start the timer. */ intf_start_seq_timer(intf, msg->msgid); -free_msg: - requeue = 0; - goto out; - - } else if (msg->rsp_size < 2) { - /* Message is too small to be correct. */ - dev_warn(intf->si_dev, - "BMC returned too small a message for netfn %x cmd %x, got %d bytes\n", - (msg->data[0] >> 2) | 1, msg->data[1], msg->rsp_size); - - /* Generate an error response for the message. */ - msg->rsp[0] = msg->data[0] | (1 << 2); - msg->rsp[1] = msg->data[1]; - msg->rsp[2] = IPMI_ERR_UNSPECIFIED; - msg->rsp_size = 3; } else if (((msg->rsp[0] >> 2) != ((msg->data[0] >> 2) | 1)) || (msg->rsp[1] != msg->data[1])) { /* @@ -4291,39 +4514,46 @@ free_msg: (msg->data[0] >> 2) | 1, msg->data[1], msg->rsp[0] >> 2, msg->rsp[1]); - /* Generate an error response for the message. */ - msg->rsp[0] = msg->data[0] | (1 << 2); - msg->rsp[1] = msg->data[1]; - msg->rsp[2] = IPMI_ERR_UNSPECIFIED; - msg->rsp_size = 3; + goto return_unspecified; } - if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2)) - && (msg->rsp[1] == IPMI_SEND_MSG_CMD) - && (msg->user_data != NULL)) { + if (msg->type == IPMI_SMI_MSG_TYPE_IPMB_DIRECT) { + if ((msg->data[0] >> 2) & 1) { + /* It's a response to a sent response. */ + chan = 0; + cc = msg->rsp[4]; + goto process_response_response; + } + if (is_cmd) + requeue = handle_ipmb_direct_rcv_cmd(intf, msg); + else + requeue = handle_ipmb_direct_rcv_rsp(intf, msg); + } else if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2)) + && (msg->rsp[1] == IPMI_SEND_MSG_CMD) + && (msg->user_data != NULL)) { /* * It's a response to a response we sent. For this we * deliver a send message response to the user. */ - struct ipmi_recv_msg *recv_msg = msg->user_data; - - requeue = 0; - if (msg->rsp_size < 2) - /* Message is too small to be correct. */ - goto out; + struct ipmi_recv_msg *recv_msg; chan = msg->data[2] & 0x0f; if (chan >= IPMI_MAX_CHANNELS) /* Invalid channel number */ goto out; + cc = msg->rsp[2]; +process_response_response: + recv_msg = msg->user_data; + + requeue = 0; if (!recv_msg) goto out; recv_msg->recv_type = IPMI_RESPONSE_RESPONSE_TYPE; recv_msg->msg.data = recv_msg->msg_data; + recv_msg->msg_data[0] = cc; recv_msg->msg.data_len = 1; - recv_msg->msg_data[0] = msg->rsp[2]; deliver_local_response(intf, recv_msg); } else if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2)) && (msg->rsp[1] == IPMI_GET_MSG_CMD)) { @@ -4789,7 +5019,9 @@ static atomic_t recv_msg_inuse_count = ATOMIC_INIT(0); static void free_smi_msg(struct ipmi_smi_msg *msg) { atomic_dec(&smi_msg_inuse_count); - kfree(msg); + /* Try to keep as much stuff out of the panic path as possible. */ + if (!oops_in_progress) + kfree(msg); } struct ipmi_smi_msg *ipmi_alloc_smi_msg(void) @@ -4808,7 +5040,9 @@ EXPORT_SYMBOL(ipmi_alloc_smi_msg); static void free_recv_msg(struct ipmi_recv_msg *msg) { atomic_dec(&recv_msg_inuse_count); - kfree(msg); + /* Try to keep as much stuff out of the panic path as possible. */ + if (!oops_in_progress) + kfree(msg); } static struct ipmi_recv_msg *ipmi_alloc_recv_msg(void) @@ -4826,7 +5060,7 @@ static struct ipmi_recv_msg *ipmi_alloc_recv_msg(void) void ipmi_free_recv_msg(struct ipmi_recv_msg *msg) { - if (msg->user) + if (msg->user && !oops_in_progress) kref_put(&msg->user->refcount, free_user); msg->done(msg); } diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c index 6f3272b58ced..64dedb3ef8ec 100644 --- a/drivers/char/ipmi/ipmi_si_intf.c +++ b/drivers/char/ipmi/ipmi_si_intf.c @@ -1603,7 +1603,7 @@ static ssize_t name##_show(struct device *dev, \ { \ struct smi_info *smi_info = dev_get_drvdata(dev); \ \ - return snprintf(buf, 10, "%u\n", smi_get_stat(smi_info, name)); \ + return sysfs_emit(buf, "%u\n", smi_get_stat(smi_info, name)); \ } \ static DEVICE_ATTR_RO(name) @@ -1613,7 +1613,7 @@ static ssize_t type_show(struct device *dev, { struct smi_info *smi_info = dev_get_drvdata(dev); - return snprintf(buf, 10, "%s\n", si_to_str[smi_info->io.si_type]); + return sysfs_emit(buf, "%s\n", si_to_str[smi_info->io.si_type]); } static DEVICE_ATTR_RO(type); @@ -1624,7 +1624,7 @@ static ssize_t interrupts_enabled_show(struct device *dev, struct smi_info *smi_info = dev_get_drvdata(dev); int enabled = smi_info->io.irq && !smi_info->interrupt_disabled; - return snprintf(buf, 10, "%d\n", enabled); + return sysfs_emit(buf, "%d\n", enabled); } static DEVICE_ATTR_RO(interrupts_enabled); @@ -1646,7 +1646,7 @@ static ssize_t params_show(struct device *dev, { struct smi_info *smi_info = dev_get_drvdata(dev); - return snprintf(buf, 200, + return sysfs_emit(buf, "%s,%s,0x%lx,rsp=%d,rsi=%d,rsh=%d,irq=%d,ipmb=%d\n", si_to_str[smi_info->io.si_type], addr_space_to_str[smi_info->io.addr_space], diff --git a/drivers/char/ipmi/ipmi_ssif.c b/drivers/char/ipmi/ipmi_ssif.c index 20d5af92966d..0c62e578749e 100644 --- a/drivers/char/ipmi/ipmi_ssif.c +++ b/drivers/char/ipmi/ipmi_ssif.c @@ -1190,7 +1190,7 @@ static ssize_t ipmi_##name##_show(struct device *dev, \ { \ struct ssif_info *ssif_info = dev_get_drvdata(dev); \ \ - return snprintf(buf, 10, "%u\n", ssif_get_stat(ssif_info, name));\ + return sysfs_emit(buf, "%u\n", ssif_get_stat(ssif_info, name));\ } \ static DEVICE_ATTR(name, S_IRUGO, ipmi_##name##_show, NULL) @@ -1198,7 +1198,7 @@ static ssize_t ipmi_type_show(struct device *dev, struct device_attribute *attr, char *buf) { - return snprintf(buf, 10, "ssif\n"); + return sysfs_emit(buf, "ssif\n"); } static DEVICE_ATTR(type, S_IRUGO, ipmi_type_show, NULL); diff --git a/drivers/char/ipmi/ipmi_watchdog.c b/drivers/char/ipmi/ipmi_watchdog.c index e4ff3b50de7f..883b4a341012 100644 --- a/drivers/char/ipmi/ipmi_watchdog.c +++ b/drivers/char/ipmi/ipmi_watchdog.c @@ -342,13 +342,17 @@ static atomic_t msg_tofree = ATOMIC_INIT(0); static DECLARE_COMPLETION(msg_wait); static void msg_free_smi(struct ipmi_smi_msg *msg) { - if (atomic_dec_and_test(&msg_tofree)) - complete(&msg_wait); + if (atomic_dec_and_test(&msg_tofree)) { + if (!oops_in_progress) + complete(&msg_wait); + } } static void msg_free_recv(struct ipmi_recv_msg *msg) { - if (atomic_dec_and_test(&msg_tofree)) - complete(&msg_wait); + if (atomic_dec_and_test(&msg_tofree)) { + if (!oops_in_progress) + complete(&msg_wait); + } } static struct ipmi_smi_msg smi_msg = { .done = msg_free_smi @@ -434,8 +438,10 @@ static int _ipmi_set_timeout(int do_heartbeat) rv = __ipmi_set_timeout(&smi_msg, &recv_msg, &send_heartbeat_now); - if (rv) + if (rv) { + atomic_set(&msg_tofree, 0); return rv; + } wait_for_completion(&msg_wait); @@ -497,7 +503,7 @@ static void panic_halt_ipmi_heartbeat(void) msg.cmd = IPMI_WDOG_RESET_TIMER; msg.data = NULL; msg.data_len = 0; - atomic_inc(&panic_done_count); + atomic_add(2, &panic_done_count); rv = ipmi_request_supply_msgs(watchdog_user, (struct ipmi_addr *) &addr, 0, @@ -507,7 +513,7 @@ static void panic_halt_ipmi_heartbeat(void) &panic_halt_heartbeat_recv_msg, 1); if (rv) - atomic_dec(&panic_done_count); + atomic_sub(2, &panic_done_count); } static struct ipmi_smi_msg panic_halt_smi_msg = { @@ -531,12 +537,12 @@ static void panic_halt_ipmi_set_timeout(void) /* Wait for the messages to be free. */ while (atomic_read(&panic_done_count) != 0) ipmi_poll_interface(watchdog_user); - atomic_inc(&panic_done_count); + atomic_add(2, &panic_done_count); rv = __ipmi_set_timeout(&panic_halt_smi_msg, &panic_halt_recv_msg, &send_heartbeat_now); if (rv) { - atomic_dec(&panic_done_count); + atomic_sub(2, &panic_done_count); pr_warn("Unable to extend the watchdog timeout\n"); } else { if (send_heartbeat_now) @@ -580,6 +586,7 @@ restart: &recv_msg, 1); if (rv) { + atomic_set(&msg_tofree, 0); pr_warn("heartbeat send failure: %d\n", rv); return rv; } diff --git a/drivers/char/ipmi/kcs_bmc_serio.c b/drivers/char/ipmi/kcs_bmc_serio.c index 7948cabde50b..7e2067628a6c 100644 --- a/drivers/char/ipmi/kcs_bmc_serio.c +++ b/drivers/char/ipmi/kcs_bmc_serio.c @@ -73,10 +73,12 @@ static int kcs_bmc_serio_add_device(struct kcs_bmc_device *kcs_bmc) struct serio *port; priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; /* Use kzalloc() as the allocation is cleaned up with kfree() via serio_unregister_port() */ port = kzalloc(sizeof(*port), GFP_KERNEL); - if (!(priv && port)) + if (!port) return -ENOMEM; port->id.type = SERIO_8042; diff --git a/include/linux/ipmi.h b/include/linux/ipmi.h index 52850a02a3d0..163831a087ef 100644 --- a/include/linux/ipmi.h +++ b/include/linux/ipmi.h @@ -335,4 +335,7 @@ extern int ipmi_get_smi_info(int if_num, struct ipmi_smi_info *data); #define GET_DEVICE_ID_MAX_RETRY 5 +/* Helper function for computing the IPMB checksum of some data. */ +unsigned char ipmb_checksum(unsigned char *data, int size); + #endif /* __LINUX_IPMI_H */ diff --git a/include/linux/ipmi_smi.h b/include/linux/ipmi_smi.h index deec18b8944a..9277d21c2690 100644 --- a/include/linux/ipmi_smi.h +++ b/include/linux/ipmi_smi.h @@ -39,6 +39,59 @@ struct ipmi_smi; #define IPMI_WATCH_MASK_CHECK_COMMANDS (1 << 2) /* + * SMI messages + * + * When communicating with an SMI, messages come in two formats: + * + * * Normal (to a BMC over a BMC interface) + * + * * IPMB (over a IPMB to another MC) + * + * When normal, commands are sent using the format defined by a + * standard message over KCS (NetFn must be even): + * + * +-----------+-----+------+ + * | NetFn/LUN | Cmd | Data | + * +-----------+-----+------+ + * + * And responses, similarly, with an completion code added (NetFn must + * be odd): + * + * +-----------+-----+------+------+ + * | NetFn/LUN | Cmd | CC | Data | + * +-----------+-----+------+------+ + * + * With normal messages, only commands are sent and only responses are + * received. + * + * In IPMB mode, we are acting as an IPMB device. Commands will be in + * the following format (NetFn must be even): + * + * +-------------+------+-------------+-----+------+ + * | NetFn/rsLUN | Addr | rqSeq/rqLUN | Cmd | Data | + * +-------------+------+-------------+-----+------+ + * + * Responses will using the following format: + * + * +-------------+------+-------------+-----+------+------+ + * | NetFn/rqLUN | Addr | rqSeq/rsLUN | Cmd | CC | Data | + * +-------------+------+-------------+-----+------+------+ + * + * This is similar to the format defined in the IPMB manual section + * 2.11.1 with the checksums and the first address removed. Also, the + * address is always the remote address. + * + * IPMB messages can be commands and responses in both directions. + * Received commands are handled as received commands from the message + * queue. + */ + +enum ipmi_smi_msg_type { + IPMI_SMI_MSG_TYPE_NORMAL = 0, + IPMI_SMI_MSG_TYPE_IPMB_DIRECT +}; + +/* * Messages to/from the lower layer. The smi interface will take one * of these to send. After the send has occurred and a response has * been received, it will report this same data structure back up to @@ -54,6 +107,8 @@ struct ipmi_smi; struct ipmi_smi_msg { struct list_head link; + enum ipmi_smi_msg_type type; + long msgid; void *user_data; @@ -73,6 +128,10 @@ struct ipmi_smi_msg { struct ipmi_smi_handlers { struct module *owner; + /* Capabilities of the SMI. */ +#define IPMI_SMI_CAN_HANDLE_IPMB_DIRECT (1 << 0) + unsigned int flags; + /* * The low-level interface cannot start sending messages to * the upper layer until this function is called. This may diff --git a/include/uapi/linux/ipmi.h b/include/uapi/linux/ipmi.h index 32d148309b16..966c3070959b 100644 --- a/include/uapi/linux/ipmi.h +++ b/include/uapi/linux/ipmi.h @@ -81,6 +81,20 @@ struct ipmi_ipmb_addr { }; /* + * Used for messages received directly from an IPMB that have not gone + * through a MC. This is for systems that sit right on an IPMB so + * they can receive commands and respond to them. + */ +#define IPMI_IPMB_DIRECT_ADDR_TYPE 0x81 +struct ipmi_ipmb_direct_addr { + int addr_type; + short channel; + unsigned char slave_addr; + unsigned char rs_lun; + unsigned char rq_lun; +}; + +/* * A LAN Address. This is an address to/from a LAN interface bridged * by the BMC, not an address actually out on the LAN. * @@ -158,7 +172,7 @@ struct kernel_ipmi_msg { * is used for the receive in-kernel interface and in the receive * IOCTL. * - * The "IPMI_RESPONSE_RESPNOSE_TYPE" is a little strange sounding, but + * The "IPMI_RESPONSE_RESPONSE_TYPE" is a little strange sounding, but * it allows you to get the message results when you send a response * message. */ |