Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit eea0cf3f authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull IPMI driver updates from Corey Minyard:
  - Quite a few bug fixes
  - A new driver for the powernv
  - A new driver for the SMBus interface from the IPMI 2.0 specification

* tag 'for-linus' of git://git.code.sf.net/p/openipmi/linux-ipmi:
  ipmi: Check the BT interrupt enable periodically
  ipmi: Fix attention handling for system interfaces
  ipmi: Periodically check to see if irqs and messages are set right
  drivers/char/ipmi: Add powernv IPMI driver
  ipmi: Add SMBus interface driver (SSIF)
  ipmi: Remove the now unused priority from SMI sender
  ipmi: Remove the now unnecessary message queue
  ipmi: Make the message handler easier to use for SMI interfaces
  ipmi: Move message sending into its own function
  ipmi: rename waiting_msgs to waiting_rcv_msgs
  ipmi: Fix handling of BMC flags
  ipmi: Initialize BMC device attributes
  ipmi: Unregister previously registered driver in error case
  ipmi: Use the proper type for acpi_handle
  ipmi: Fix a bug in hot add/remove
  ipmi: Remove useless sysfs_name parameters
  ipmi: clean up the device handling for the bmc device
  ipmi: Move the address source to string to ipmi-generic code
  ipmi: Ignore SSIF in the PNP handling
parents 823e334e 95c97b59
Loading
Loading
Loading
Loading
+71 −3
Original line number Diff line number Diff line
@@ -42,7 +42,13 @@ The driver interface depends on your hardware. If your system
properly provides the SMBIOS info for IPMI, the driver will detect it
and just work.  If you have a board with a standard interface (These
will generally be either "KCS", "SMIC", or "BT", consult your hardware
manual), choose the 'IPMI SI handler' option.
manual), choose the 'IPMI SI handler' option.  A driver also exists
for direct I2C access to the IPMI management controller.  Some boards
support this, but it is unknown if it will work on every board.  For
this, choose 'IPMI SMBus handler', but be ready to try to do some
figuring to see if it will work on your system if the SMBIOS/APCI
information is wrong or not present.  It is fairly safe to have both
these enabled and let the drivers auto-detect what is present.

You should generally enable ACPI on your system, as systems with IPMI
can have ACPI tables describing them.
@@ -52,7 +58,8 @@ their job correctly, the IPMI controller should be automatically
detected (via ACPI or SMBIOS tables) and should just work.  Sadly,
many boards do not have this information.  The driver attempts
standard defaults, but they may not work.  If you fall into this
situation, you need to read the section below named 'The SI Driver'.
situation, you need to read the section below named 'The SI Driver' or
"The SMBus Driver" on how to hand-configure your system.

IPMI defines a standard watchdog timer.  You can enable this with the
'IPMI Watchdog Timer' config option.  If you compile the driver into
@@ -97,7 +104,12 @@ driver, each open file for this device ties in to the message handler
as an IPMI user.

ipmi_si - A driver for various system interfaces.  This supports KCS,
SMIC, and BT interfaces.
SMIC, and BT interfaces.  Unless you have an SMBus interface or your
own custom interface, you probably need to use this.

ipmi_ssif - A driver for accessing BMCs on the SMBus. It uses the
I2C kernel driver's SMBus interfaces to send and receive IPMI messages
over the SMBus.

ipmi_watchdog - IPMI requires systems to have a very capable watchdog
timer.  This driver implements the standard Linux watchdog timer
@@ -476,6 +488,62 @@ for specifying an interface. Note that when removing an interface,
only the first three parameters (si type, address type, and address)
are used for the comparison.  Any options are ignored for removing.

The SMBus Driver (SSIF)
-----------------------

The SMBus driver allows up to 4 SMBus devices to be configured in the
system.  By default, the driver will only register with something it
finds in DMI or ACPI tables.  You can change this
at module load time (for a module) with:

  modprobe ipmi_ssif.o
	addr=<i2caddr1>[,<i2caddr2>[,...]]
	adapter=<adapter1>[,<adapter2>[...]]
	dbg=<flags1>,<flags2>...
        slave_addrs=<addr1>,<addr2>,...
	[dbg_probe=1]

The addresses are normal I2C addresses.  The adapter is the string
name of the adapter, as shown in /sys/class/i2c-adapter/i2c-<n>/name.
It is *NOT* i2c-<n> itself.

The debug flags are bit flags for each BMC found, they are:
IPMI messages: 1, driver state: 2, timing: 4, I2C probe: 8

Setting dbg_probe to 1 will enable debugging of the probing and
detection process for BMCs on the SMBusses.

The slave_addrs specifies the IPMI address of the local BMC.  This is
usually 0x20 and the driver defaults to that, but in case it's not, it
can be specified when the driver starts up.

Discovering the IPMI compliant BMC on the SMBus can cause devices on
the I2C bus to fail. The SMBus driver writes a "Get Device ID" IPMI
message as a block write to the I2C bus and waits for a response.
This action can be detrimental to some I2C devices. It is highly
recommended that the known I2C address be given to the SMBus driver in
the smb_addr parameter unless you have DMI or ACPI data to tell the
driver what to use.

When compiled into the kernel, the addresses can be specified on the
kernel command line as:

  ipmb_ssif.addr=<i2caddr1>[,<i2caddr2>[...]]
	ipmi_ssif.adapter=<adapter1>[,<adapter2>[...]]
	ipmi_ssif.dbg=<flags1>[,<flags2>[...]]
	ipmi_ssif.dbg_probe=1
        ipmi_ssif.slave_addrs=<addr1>[,<addr2>[...]]

These are the same options as on the module command line.

The I2C driver does not support non-blocking access or polling, so
this driver cannod to IPMI panic events, extend the watchdog at panic
time, or other panic-related IPMI functions without special kernel
patches and driver modifications.  You can get those at the openipmi
web page.

The driver supports a hot add and remove of interfaces through the I2C
sysfs interface.

Other Pieces
------------
+14 −0
Original line number Diff line number Diff line
@@ -62,6 +62,20 @@ config IPMI_SI_PROBE_DEFAULTS
	 only be available on older systems if the "ipmi_si_intf.trydefaults=1"
	 boot argument is passed.

config IPMI_SSIF
       tristate 'IPMI SMBus handler (SSIF)'
       select 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_POWERNV
       depends on PPC_POWERNV
       tristate 'POWERNV (OPAL firmware) IPMI interface'
       help
         Provides a driver for OPAL firmware-based IPMI interfaces.

config IPMI_WATCHDOG
       tristate 'IPMI Watchdog Timer'
       help
+2 −0
Original line number Diff line number Diff line
@@ -7,5 +7,7 @@ ipmi_si-y := ipmi_si_intf.o ipmi_kcs_sm.o ipmi_smic_sm.o ipmi_bt_sm.o
obj-$(CONFIG_IPMI_HANDLER) += ipmi_msghandler.o
obj-$(CONFIG_IPMI_DEVICE_INTERFACE) += ipmi_devintf.o
obj-$(CONFIG_IPMI_SI) += ipmi_si.o
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
+289 −271

File changed.

Preview size limit exceeded, changes collapsed.

+310 −0
Original line number Diff line number Diff line
/*
 * PowerNV OPAL IPMI driver
 *
 * Copyright 2014 IBM Corp.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 */

#define pr_fmt(fmt)        "ipmi-powernv: " fmt

#include <linux/ipmi_smi.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>

#include <asm/opal.h>


struct ipmi_smi_powernv {
	u64			interface_id;
	struct ipmi_device_id	ipmi_id;
	ipmi_smi_t		intf;
	u64			event;
	struct notifier_block	event_nb;

	/**
	 * We assume that there can only be one outstanding request, so
	 * keep the pending message in cur_msg. We protect this from concurrent
	 * updates through send & recv calls, (and consequently opal_msg, which
	 * is in-use when cur_msg is set) with msg_lock
	 */
	spinlock_t		msg_lock;
	struct ipmi_smi_msg	*cur_msg;
	struct opal_ipmi_msg	*opal_msg;
};

static int ipmi_powernv_start_processing(void *send_info, ipmi_smi_t intf)
{
	struct ipmi_smi_powernv *smi = send_info;

	smi->intf = intf;
	return 0;
}

static void send_error_reply(struct ipmi_smi_powernv *smi,
		struct ipmi_smi_msg *msg, u8 completion_code)
{
	msg->rsp[0] = msg->data[0] | 0x4;
	msg->rsp[1] = msg->data[1];
	msg->rsp[2] = completion_code;
	msg->rsp_size = 3;
	ipmi_smi_msg_received(smi->intf, msg);
}

static void ipmi_powernv_send(void *send_info, struct ipmi_smi_msg *msg)
{
	struct ipmi_smi_powernv *smi = send_info;
	struct opal_ipmi_msg *opal_msg;
	unsigned long flags;
	int comp, rc;
	size_t size;

	/* ensure data_len will fit in the opal_ipmi_msg buffer... */
	if (msg->data_size > IPMI_MAX_MSG_LENGTH) {
		comp = IPMI_REQ_LEN_EXCEEDED_ERR;
		goto err;
	}

	/* ... and that we at least have netfn and cmd bytes */
	if (msg->data_size < 2) {
		comp = IPMI_REQ_LEN_INVALID_ERR;
		goto err;
	}

	spin_lock_irqsave(&smi->msg_lock, flags);

	if (smi->cur_msg) {
		comp = IPMI_NODE_BUSY_ERR;
		goto err_unlock;
	}

	/* format our data for the OPAL API */
	opal_msg = smi->opal_msg;
	opal_msg->version = OPAL_IPMI_MSG_FORMAT_VERSION_1;
	opal_msg->netfn = msg->data[0];
	opal_msg->cmd = msg->data[1];
	if (msg->data_size > 2)
		memcpy(opal_msg->data, msg->data + 2, msg->data_size - 2);

	/* data_size already includes the netfn and cmd bytes */
	size = sizeof(*opal_msg) + msg->data_size - 2;

	pr_devel("%s: opal_ipmi_send(0x%llx, %p, %ld)\n", __func__,
			smi->interface_id, opal_msg, size);
	rc = opal_ipmi_send(smi->interface_id, opal_msg, size);
	pr_devel("%s:  -> %d\n", __func__, rc);

	if (!rc) {
		smi->cur_msg = msg;
		spin_unlock_irqrestore(&smi->msg_lock, flags);
		return;
	}

	comp = IPMI_ERR_UNSPECIFIED;
err_unlock:
	spin_unlock_irqrestore(&smi->msg_lock, flags);
err:
	send_error_reply(smi, msg, comp);
}

static int ipmi_powernv_recv(struct ipmi_smi_powernv *smi)
{
	struct opal_ipmi_msg *opal_msg;
	struct ipmi_smi_msg *msg;
	unsigned long flags;
	uint64_t size;
	int rc;

	pr_devel("%s: opal_ipmi_recv(%llx, msg, sz)\n", __func__,
			smi->interface_id);

	spin_lock_irqsave(&smi->msg_lock, flags);

	if (!smi->cur_msg) {
		pr_warn("no current message?\n");
		return 0;
	}

	msg = smi->cur_msg;
	opal_msg = smi->opal_msg;

	size = cpu_to_be64(sizeof(*opal_msg) + IPMI_MAX_MSG_LENGTH);

	rc = opal_ipmi_recv(smi->interface_id,
			opal_msg,
			&size);
	size = be64_to_cpu(size);
	pr_devel("%s:   -> %d (size %lld)\n", __func__,
			rc, rc == 0 ? size : 0);
	if (rc) {
		spin_unlock_irqrestore(&smi->msg_lock, flags);
		ipmi_free_smi_msg(msg);
		return 0;
	}

	if (size < sizeof(*opal_msg)) {
		spin_unlock_irqrestore(&smi->msg_lock, flags);
		pr_warn("unexpected IPMI message size %lld\n", size);
		return 0;
	}

	if (opal_msg->version != OPAL_IPMI_MSG_FORMAT_VERSION_1) {
		spin_unlock_irqrestore(&smi->msg_lock, flags);
		pr_warn("unexpected IPMI message format (version %d)\n",
				opal_msg->version);
		return 0;
	}

	msg->rsp[0] = opal_msg->netfn;
	msg->rsp[1] = opal_msg->cmd;
	if (size > sizeof(*opal_msg))
		memcpy(&msg->rsp[2], opal_msg->data, size - sizeof(*opal_msg));
	msg->rsp_size = 2 + size - sizeof(*opal_msg);

	smi->cur_msg = NULL;
	spin_unlock_irqrestore(&smi->msg_lock, flags);
	ipmi_smi_msg_received(smi->intf, msg);
	return 0;
}

static void ipmi_powernv_request_events(void *send_info)
{
}

static void ipmi_powernv_set_run_to_completion(void *send_info,
		bool run_to_completion)
{
}

static void ipmi_powernv_poll(void *send_info)
{
	struct ipmi_smi_powernv *smi = send_info;

	ipmi_powernv_recv(smi);
}

static struct ipmi_smi_handlers ipmi_powernv_smi_handlers = {
	.owner			= THIS_MODULE,
	.start_processing	= ipmi_powernv_start_processing,
	.sender			= ipmi_powernv_send,
	.request_events		= ipmi_powernv_request_events,
	.set_run_to_completion	= ipmi_powernv_set_run_to_completion,
	.poll			= ipmi_powernv_poll,
};

static int ipmi_opal_event(struct notifier_block *nb,
			  unsigned long events, void *change)
{
	struct ipmi_smi_powernv *smi = container_of(nb,
					struct ipmi_smi_powernv, event_nb);

	if (events & smi->event)
		ipmi_powernv_recv(smi);
	return 0;
}

static int ipmi_powernv_probe(struct platform_device *pdev)
{
	struct ipmi_smi_powernv *ipmi;
	struct device *dev;
	u32 prop;
	int rc;

	if (!pdev || !pdev->dev.of_node)
		return -ENODEV;

	dev = &pdev->dev;

	ipmi = devm_kzalloc(dev, sizeof(*ipmi), GFP_KERNEL);
	if (!ipmi)
		return -ENOMEM;

	spin_lock_init(&ipmi->msg_lock);

	rc = of_property_read_u32(dev->of_node, "ibm,ipmi-interface-id",
			&prop);
	if (rc) {
		dev_warn(dev, "No interface ID property\n");
		goto err_free;
	}
	ipmi->interface_id = prop;

	rc = of_property_read_u32(dev->of_node, "interrupts", &prop);
	if (rc) {
		dev_warn(dev, "No interrupts property\n");
		goto err_free;
	}

	ipmi->event = 1ull << prop;
	ipmi->event_nb.notifier_call = ipmi_opal_event;

	rc = opal_notifier_register(&ipmi->event_nb);
	if (rc) {
		dev_warn(dev, "OPAL notifier registration failed (%d)\n", rc);
		goto err_free;
	}

	ipmi->opal_msg = devm_kmalloc(dev,
			sizeof(*ipmi->opal_msg) + IPMI_MAX_MSG_LENGTH,
			GFP_KERNEL);
	if (!ipmi->opal_msg) {
		rc = -ENOMEM;
		goto err_unregister;
	}

	/* todo: query actual ipmi_device_id */
	rc = ipmi_register_smi(&ipmi_powernv_smi_handlers, ipmi,
			&ipmi->ipmi_id, dev, 0);
	if (rc) {
		dev_warn(dev, "IPMI SMI registration failed (%d)\n", rc);
		goto err_free_msg;
	}

	dev_set_drvdata(dev, ipmi);
	return 0;

err_free_msg:
	devm_kfree(dev, ipmi->opal_msg);
err_unregister:
	opal_notifier_unregister(&ipmi->event_nb);
err_free:
	devm_kfree(dev, ipmi);
	return rc;
}

static int ipmi_powernv_remove(struct platform_device *pdev)
{
	struct ipmi_smi_powernv *smi = dev_get_drvdata(&pdev->dev);

	ipmi_unregister_smi(smi->intf);
	opal_notifier_unregister(&smi->event_nb);
	return 0;
}

static const struct of_device_id ipmi_powernv_match[] = {
	{ .compatible = "ibm,opal-ipmi" },
	{ },
};


static struct platform_driver powernv_ipmi_driver = {
	.driver = {
		.name		= "ipmi-powernv",
		.owner		= THIS_MODULE,
		.of_match_table	= ipmi_powernv_match,
	},
	.probe	= ipmi_powernv_probe,
	.remove	= ipmi_powernv_remove,
};


module_platform_driver(powernv_ipmi_driver);

MODULE_DEVICE_TABLE(of, ipmi_powernv_match);
MODULE_DESCRIPTION("powernv IPMI driver");
MODULE_AUTHOR("Jeremy Kerr <jk@ozlabs.org>");
MODULE_LICENSE("GPL");
Loading