[PATCH 3/3] ipmi: Add timeout waiting for channel information

From: Markus Boehme
Date: Mon Sep 07 2020 - 12:27:51 EST


We have observed hosts with misbehaving BMCs that receive a Get Channel
Info command but don't respond. This leads to an indefinite wait in the
ipmi_msghandler's __scan_channels function, showing up as hung task
messages for modprobe.

Add a timeout waiting for the channel scan to complete. If the scan
fails to complete within that time, treat that like IPMI 1.0 and only
assume the presence of the primary IPMB channel at channel number 0.

Signed-off-by: Stefan Nuernberger <snu@xxxxxxxxxx>
Signed-off-by: Markus Boehme <markubo@xxxxxxxxxx>
---
drivers/char/ipmi/ipmi_msghandler.c | 72 ++++++++++++++++++++-----------------
1 file changed, 39 insertions(+), 33 deletions(-)

diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c
index 2a2e8b2..9de9ba6 100644
--- a/drivers/char/ipmi/ipmi_msghandler.c
+++ b/drivers/char/ipmi/ipmi_msghandler.c
@@ -3315,46 +3315,52 @@ channel_handler(struct ipmi_smi *intf, struct ipmi_recv_msg *msg)
*/
static int __scan_channels(struct ipmi_smi *intf, struct ipmi_device_id *id)
{
- int rv;
+ long rv;
+ unsigned int set;

- if (ipmi_version_major(id) > 1
- || (ipmi_version_major(id) == 1
- && ipmi_version_minor(id) >= 5)) {
- unsigned int set;
+ if (ipmi_version_major(id) == 1 && ipmi_version_minor(id) < 5) {
+ set = intf->curr_working_cset;
+ goto single_ipmb_channel;
+ }

- /*
- * Start scanning the channels to see what is
- * available.
- */
- set = !intf->curr_working_cset;
- intf->curr_working_cset = set;
- memset(&intf->wchannels[set], 0,
- sizeof(struct ipmi_channel_set));
-
- intf->null_user_handler = channel_handler;
- intf->curr_channel = 0;
- rv = send_channel_info_cmd(intf, 0);
- if (rv) {
- dev_warn(intf->si_dev,
- "Error sending channel information for channel 0, %d\n",
- rv);
- intf->null_user_handler = NULL;
- return -EIO;
- }
+ /*
+ * Start scanning the channels to see what is
+ * available.
+ */
+ set = !intf->curr_working_cset;
+ intf->curr_working_cset = set;
+ memset(&intf->wchannels[set], 0, sizeof(struct ipmi_channel_set));

- /* Wait for the channel info to be read. */
- wait_event(intf->waitq, intf->channels_ready);
+ intf->null_user_handler = channel_handler;
+ intf->curr_channel = 0;
+ rv = send_channel_info_cmd(intf, 0);
+ if (rv) {
+ dev_warn(intf->si_dev,
+ "Error sending channel information for channel 0, %ld\n",
+ rv);
intf->null_user_handler = NULL;
- } else {
- unsigned int set = intf->curr_working_cset;
+ return -EIO;
+ }

- /* Assume a single IPMB channel at zero. */
- intf->wchannels[set].c[0].medium = IPMI_CHANNEL_MEDIUM_IPMB;
- intf->wchannels[set].c[0].protocol = IPMI_CHANNEL_PROTOCOL_IPMB;
- intf->channel_list = intf->wchannels + set;
- intf->channels_ready = true;
+ /* Wait for the channel info to be read. */
+ rv = wait_event_timeout(intf->waitq, intf->channels_ready, 5 * HZ);
+ if (rv == 0) {
+ dev_warn(intf->si_dev,
+ "Timed out waiting for channel information. Assuming a single IPMB channel at 0.\n");
+ goto single_ipmb_channel;
}

+ goto out;
+
+single_ipmb_channel:
+ /* Assume a single IPMB channel at zero. */
+ intf->wchannels[set].c[0].medium = IPMI_CHANNEL_MEDIUM_IPMB;
+ intf->wchannels[set].c[0].protocol = IPMI_CHANNEL_PROTOCOL_IPMB;
+ intf->channel_list = intf->wchannels + set;
+ intf->channels_ready = true;
+
+out:
+ intf->null_user_handler = NULL;
return 0;
}

--
2.7.4