Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# IPMI device configuration
#
menuconfig IPMI_HANDLER
tristate 'IPMI top-level message handler'
depends on HAS_IOMEM
select IPMI_DMI_DECODE if DMI
help
This enables the central IPMI message handler, required for IPMI
to work.
IPMI is a standard for managing sensors (temperature,
voltage, etc.) in a system.
See <file:Documentation/driver-api/ipmi.rst> for more details on the driver.
If unsure, say N.
config IPMI_DMI_DECODE
select IPMI_PLAT_DATA
bool
config IPMI_PLAT_DATA
bool
if IPMI_HANDLER
config IPMI_PANIC_EVENT
bool 'Generate a panic event to all BMCs on a panic'
help
When a panic occurs, this will cause the IPMI message handler to,
by default, generate an IPMI event describing the panic to each
interface registered with the message handler. This is always
available, the module parameter for ipmi_msghandler named
panic_op can be set to "event" to chose this value, this config
simply causes the default value to be set to "event".
config IPMI_PANIC_STRING
bool 'Generate OEM events containing the panic string'
depends on IPMI_PANIC_EVENT
help
When a panic occurs, this will cause the IPMI message handler to,
by default, generate IPMI OEM type f0 events holding the IPMB
address of the panic generator (byte 4 of the event), a sequence
number for the string (byte 5 of the event) and part of the
string (the rest of the event). Bytes 1, 2, and 3 are the normal
usage for an OEM event. You can fetch these events and use the
sequence numbers to piece the string together. This config
parameter sets the default value to generate these events,
the module parameter for ipmi_msghandler named panic_op can
be set to "string" to chose this value, this config simply
causes the default value to be set to "string".
config IPMI_DEVICE_INTERFACE
tristate 'Device interface for IPMI'
help
This provides an IOCTL interface to the IPMI message handler so
userland processes may use IPMI. It supports poll() and select().
config IPMI_SI
tristate 'IPMI System Interface handler'
select IPMI_PLAT_DATA
help
Provides a driver for System Interfaces (KCS, SMIC, BT).
Currently, only KCS and SMIC are supported. If
you are using IPMI, you should probably say "y" here.
config IPMI_SSIF
tristate 'IPMI SMBus handler (SSIF)'
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_LS2K
bool 'Loongson-2K IPMI interface'
depends on LOONGARCH
select MFD_LS2K_BMC_CORE
help
Provides a driver for Loongson-2K IPMI interfaces.
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
This enables the IPMI watchdog timer.
config IPMI_POWEROFF
tristate 'IPMI Poweroff'
help
This enables a function to power off the system with IPMI if
the IPMI management controller is capable of this.
endif # IPMI_HANDLER
config IPMI_KCS_BMC
tristate
config ASPEED_KCS_IPMI_BMC
depends on ARCH_ASPEED || COMPILE_TEST
select IPMI_KCS_BMC
select REGMAP_MMIO
tristate "Aspeed KCS IPMI BMC driver"
help
Provides a driver for the KCS (Keyboard Controller Style) IPMI
interface found on Aspeed SOCs (AST2400 and AST2500).
The driver implements the BMC side of the KCS contorller, it
provides the access of KCS IO space for BMC side.
config NPCM7XX_KCS_IPMI_BMC
depends on ARCH_NPCM || COMPILE_TEST
select IPMI_KCS_BMC
select REGMAP_MMIO
tristate "NPCM KCS IPMI BMC driver"
help
Provides a driver for the KCS (Keyboard Controller Style) IPMI
interface found on Nuvoton NPCM SOCs.
The driver implements the BMC side of the KCS contorller, it
provides the access of KCS IO space for BMC side.
This support is also available as a module. If so, the module
will be called kcs_bmc_npcm7xx.
config IPMI_KCS_BMC_CDEV_IPMI
depends on IPMI_KCS_BMC
tristate "IPMI character device interface for BMC KCS devices"
help
Provides a BMC-side character device implementing IPMI
semantics for KCS IPMI devices.
Say YES if you wish to expose KCS devices on the BMC for IPMI
purposes.
This support is also available as a module. The module will be
called kcs_bmc_cdev_ipmi.
config IPMI_KCS_BMC_SERIO
depends on IPMI_KCS_BMC && SERIO
tristate "SerIO adaptor for BMC KCS devices"
help
Adapts the BMC KCS device for the SerIO subsystem. This allows users
to take advantage of userspace interfaces provided by SerIO where
appropriate.
Say YES if you wish to expose KCS devices on the BMC via SerIO
interfaces.
This support is also available as a module. The module will be
called kcs_bmc_serio.
config ASPEED_BT_IPMI_BMC
depends on ARCH_ASPEED || COMPILE_TEST
depends on MFD_SYSCON
select REGMAP_MMIO
tristate "BT IPMI bmc driver"
help
Provides a driver for the BT (Block Transfer) IPMI interface
found on Aspeed SOCs (AST2400 and AST2500). The driver
implements the BMC side of the BT interface.
config SSIF_IPMI_BMC
tristate "SSIF IPMI BMC driver"
depends on I2C && I2C_SLAVE
help
This enables the IPMI SMBus system interface (SSIF) at the
management (BMC) side.
The driver implements the BMC side of the SMBus system
interface (SSIF).
config SSIF_IPMI_BMC_KUNIT_TEST
bool "KUnit tests for SSIF IPMI BMC driver" if !KUNIT_ALL_TESTS
depends on KUNIT=y
depends on SSIF_IPMI_BMC
default KUNIT_ALL_TESTS
help
This option builds unit tests that exercise the SSIF BMC state
machine, including request handling, response transmission,
and error paths such as aborted or truncated transfers.
config IPMB_DEVICE_INTERFACE
tristate 'IPMB Interface handler'
depends on I2C
depends on I2C_SLAVE
help
Provides a driver for a device (Satellite MC) to
receive requests and send responses back to the BMC via
the IPMB interface. This module requires I2C support.
+31
View File
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the ipmi drivers.
#
ipmi_si-y := ipmi_si_intf.o ipmi_kcs_sm.o ipmi_smic_sm.o ipmi_bt_sm.o \
ipmi_si_hotmod.o ipmi_si_hardcode.o ipmi_si_platform.o \
ipmi_si_mem_io.o
ipmi_si-$(CONFIG_HAS_IOPORT) += ipmi_si_port_io.o
ipmi_si-$(CONFIG_PCI) += ipmi_si_pci.o
ipmi_si-$(CONFIG_IPMI_LS2K) += ipmi_si_ls2k.o
ipmi_si-$(CONFIG_PARISC) += ipmi_si_parisc.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_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
obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o
obj-$(CONFIG_IPMI_KCS_BMC_SERIO) += kcs_bmc_serio.o
obj-$(CONFIG_IPMI_KCS_BMC_CDEV_IPMI) += kcs_bmc_cdev_ipmi.o
obj-$(CONFIG_ASPEED_BT_IPMI_BMC) += bt-bmc.o
obj-$(CONFIG_ASPEED_KCS_IPMI_BMC) += kcs_bmc_aspeed.o
obj-$(CONFIG_NPCM7XX_KCS_IPMI_BMC) += kcs_bmc_npcm7xx.o
obj-$(CONFIG_IPMB_DEVICE_INTERFACE) += ipmb_dev_int.o
obj-$(CONFIG_SSIF_IPMI_BMC) += ssif_bmc.o
+492
View File
@@ -0,0 +1,492 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2015-2016, IBM Corporation.
*/
#include <linux/atomic.h>
#include <linux/bt-bmc.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/timer.h>
/*
* This is a BMC device used to communicate to the host
*/
#define DEVICE_NAME "ipmi-bt-host"
#define BT_IO_BASE 0xe4
#define BT_IRQ 10
#define BT_CR0 0x0
#define BT_CR0_IO_BASE 16
#define BT_CR0_IRQ 12
#define BT_CR0_EN_CLR_SLV_RDP 0x8
#define BT_CR0_EN_CLR_SLV_WRP 0x4
#define BT_CR0_ENABLE_IBT 0x1
#define BT_CR1 0x4
#define BT_CR1_IRQ_H2B 0x01
#define BT_CR1_IRQ_HBUSY 0x40
#define BT_CR2 0x8
#define BT_CR2_IRQ_H2B 0x01
#define BT_CR2_IRQ_HBUSY 0x40
#define BT_CR3 0xc
#define BT_CTRL 0x10
#define BT_CTRL_B_BUSY 0x80
#define BT_CTRL_H_BUSY 0x40
#define BT_CTRL_OEM0 0x20
#define BT_CTRL_SMS_ATN 0x10
#define BT_CTRL_B2H_ATN 0x08
#define BT_CTRL_H2B_ATN 0x04
#define BT_CTRL_CLR_RD_PTR 0x02
#define BT_CTRL_CLR_WR_PTR 0x01
#define BT_BMC2HOST 0x14
#define BT_INTMASK 0x18
#define BT_INTMASK_B2H_IRQEN 0x01
#define BT_INTMASK_B2H_IRQ 0x02
#define BT_INTMASK_BMC_HWRST 0x80
#define BT_BMC_BUFFER_SIZE 256
struct bt_bmc {
struct device dev;
struct miscdevice miscdev;
void __iomem *base;
int irq;
wait_queue_head_t queue;
struct timer_list poll_timer;
struct mutex mutex;
};
static atomic_t open_count = ATOMIC_INIT(0);
static u8 bt_inb(struct bt_bmc *bt_bmc, int reg)
{
return readb(bt_bmc->base + reg);
}
static void bt_outb(struct bt_bmc *bt_bmc, u8 data, int reg)
{
writeb(data, bt_bmc->base + reg);
}
static void clr_rd_ptr(struct bt_bmc *bt_bmc)
{
bt_outb(bt_bmc, BT_CTRL_CLR_RD_PTR, BT_CTRL);
}
static void clr_wr_ptr(struct bt_bmc *bt_bmc)
{
bt_outb(bt_bmc, BT_CTRL_CLR_WR_PTR, BT_CTRL);
}
static void clr_h2b_atn(struct bt_bmc *bt_bmc)
{
bt_outb(bt_bmc, BT_CTRL_H2B_ATN, BT_CTRL);
}
static void set_b_busy(struct bt_bmc *bt_bmc)
{
if (!(bt_inb(bt_bmc, BT_CTRL) & BT_CTRL_B_BUSY))
bt_outb(bt_bmc, BT_CTRL_B_BUSY, BT_CTRL);
}
static void clr_b_busy(struct bt_bmc *bt_bmc)
{
if (bt_inb(bt_bmc, BT_CTRL) & BT_CTRL_B_BUSY)
bt_outb(bt_bmc, BT_CTRL_B_BUSY, BT_CTRL);
}
static void set_b2h_atn(struct bt_bmc *bt_bmc)
{
bt_outb(bt_bmc, BT_CTRL_B2H_ATN, BT_CTRL);
}
static u8 bt_read(struct bt_bmc *bt_bmc)
{
return bt_inb(bt_bmc, BT_BMC2HOST);
}
static ssize_t bt_readn(struct bt_bmc *bt_bmc, u8 *buf, size_t n)
{
int i;
for (i = 0; i < n; i++)
buf[i] = bt_read(bt_bmc);
return n;
}
static void bt_write(struct bt_bmc *bt_bmc, u8 c)
{
bt_outb(bt_bmc, c, BT_BMC2HOST);
}
static ssize_t bt_writen(struct bt_bmc *bt_bmc, u8 *buf, size_t n)
{
int i;
for (i = 0; i < n; i++)
bt_write(bt_bmc, buf[i]);
return n;
}
static void set_sms_atn(struct bt_bmc *bt_bmc)
{
bt_outb(bt_bmc, BT_CTRL_SMS_ATN, BT_CTRL);
}
static struct bt_bmc *file_bt_bmc(struct file *file)
{
return container_of(file->private_data, struct bt_bmc, miscdev);
}
static int bt_bmc_open(struct inode *inode, struct file *file)
{
struct bt_bmc *bt_bmc = file_bt_bmc(file);
if (atomic_inc_return(&open_count) == 1) {
clr_b_busy(bt_bmc);
return 0;
}
atomic_dec(&open_count);
return -EBUSY;
}
/*
* The BT (Block Transfer) interface means that entire messages are
* buffered by the host before a notification is sent to the BMC that
* there is data to be read. The first byte is the length and the
* message data follows. The read operation just tries to capture the
* whole before returning it to userspace.
*
* BT Message format :
*
* Byte 1 Byte 2 Byte 3 Byte 4 Byte 5:N
* Length NetFn/LUN Seq Cmd Data
*
*/
static ssize_t bt_bmc_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct bt_bmc *bt_bmc = file_bt_bmc(file);
u8 len;
int len_byte = 1;
u8 kbuffer[BT_BMC_BUFFER_SIZE];
ssize_t ret = 0;
ssize_t nread;
WARN_ON(*ppos);
if (wait_event_interruptible(bt_bmc->queue,
bt_inb(bt_bmc, BT_CTRL) & BT_CTRL_H2B_ATN))
return -ERESTARTSYS;
mutex_lock(&bt_bmc->mutex);
if (unlikely(!(bt_inb(bt_bmc, BT_CTRL) & BT_CTRL_H2B_ATN))) {
ret = -EIO;
goto out_unlock;
}
set_b_busy(bt_bmc);
clr_h2b_atn(bt_bmc);
clr_rd_ptr(bt_bmc);
/*
* The BT frames start with the message length, which does not
* include the length byte.
*/
kbuffer[0] = bt_read(bt_bmc);
len = kbuffer[0];
/* We pass the length back to userspace as well */
if (len + 1 > count)
len = count - 1;
while (len) {
nread = min_t(ssize_t, len, sizeof(kbuffer) - len_byte);
bt_readn(bt_bmc, kbuffer + len_byte, nread);
if (copy_to_user(buf, kbuffer, nread + len_byte)) {
ret = -EFAULT;
break;
}
len -= nread;
buf += nread + len_byte;
ret += nread + len_byte;
len_byte = 0;
}
clr_b_busy(bt_bmc);
out_unlock:
mutex_unlock(&bt_bmc->mutex);
return ret;
}
/*
* BT Message response format :
*
* Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6:N
* Length NetFn/LUN Seq Cmd Code Data
*/
static ssize_t bt_bmc_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct bt_bmc *bt_bmc = file_bt_bmc(file);
u8 kbuffer[BT_BMC_BUFFER_SIZE];
ssize_t ret = 0;
ssize_t nwritten;
/*
* send a minimum response size
*/
if (count < 5)
return -EINVAL;
WARN_ON(*ppos);
/*
* There's no interrupt for clearing bmc busy so we have to
* poll
*/
if (wait_event_interruptible(bt_bmc->queue,
!(bt_inb(bt_bmc, BT_CTRL) &
(BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN))))
return -ERESTARTSYS;
mutex_lock(&bt_bmc->mutex);
if (unlikely(bt_inb(bt_bmc, BT_CTRL) &
(BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN))) {
ret = -EIO;
goto out_unlock;
}
clr_wr_ptr(bt_bmc);
while (count) {
nwritten = min_t(ssize_t, count, sizeof(kbuffer));
if (copy_from_user(&kbuffer, buf, nwritten)) {
ret = -EFAULT;
break;
}
bt_writen(bt_bmc, kbuffer, nwritten);
count -= nwritten;
buf += nwritten;
ret += nwritten;
}
set_b2h_atn(bt_bmc);
out_unlock:
mutex_unlock(&bt_bmc->mutex);
return ret;
}
static long bt_bmc_ioctl(struct file *file, unsigned int cmd,
unsigned long param)
{
struct bt_bmc *bt_bmc = file_bt_bmc(file);
switch (cmd) {
case BT_BMC_IOCTL_SMS_ATN:
set_sms_atn(bt_bmc);
return 0;
}
return -EINVAL;
}
static int bt_bmc_release(struct inode *inode, struct file *file)
{
struct bt_bmc *bt_bmc = file_bt_bmc(file);
atomic_dec(&open_count);
set_b_busy(bt_bmc);
return 0;
}
static __poll_t bt_bmc_poll(struct file *file, poll_table *wait)
{
struct bt_bmc *bt_bmc = file_bt_bmc(file);
__poll_t mask = 0;
u8 ctrl;
poll_wait(file, &bt_bmc->queue, wait);
ctrl = bt_inb(bt_bmc, BT_CTRL);
if (ctrl & BT_CTRL_H2B_ATN)
mask |= EPOLLIN;
if (!(ctrl & (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN)))
mask |= EPOLLOUT;
return mask;
}
static const struct file_operations bt_bmc_fops = {
.owner = THIS_MODULE,
.open = bt_bmc_open,
.read = bt_bmc_read,
.write = bt_bmc_write,
.release = bt_bmc_release,
.poll = bt_bmc_poll,
.unlocked_ioctl = bt_bmc_ioctl,
};
static void poll_timer(struct timer_list *t)
{
struct bt_bmc *bt_bmc = timer_container_of(bt_bmc, t, poll_timer);
bt_bmc->poll_timer.expires += msecs_to_jiffies(500);
wake_up(&bt_bmc->queue);
add_timer(&bt_bmc->poll_timer);
}
static irqreturn_t bt_bmc_irq(int irq, void *arg)
{
struct bt_bmc *bt_bmc = arg;
u32 reg;
reg = readl(bt_bmc->base + BT_CR2);
reg &= BT_CR2_IRQ_H2B | BT_CR2_IRQ_HBUSY;
if (!reg)
return IRQ_NONE;
/* ack pending IRQs */
writel(reg, bt_bmc->base + BT_CR2);
wake_up(&bt_bmc->queue);
return IRQ_HANDLED;
}
static int bt_bmc_config_irq(struct bt_bmc *bt_bmc,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int rc;
u32 reg;
bt_bmc->irq = platform_get_irq_optional(pdev, 0);
if (bt_bmc->irq < 0)
return bt_bmc->irq;
rc = devm_request_irq(dev, bt_bmc->irq, bt_bmc_irq, IRQF_SHARED,
DEVICE_NAME, bt_bmc);
if (rc < 0) {
dev_warn(dev, "Unable to request IRQ %d\n", bt_bmc->irq);
bt_bmc->irq = rc;
return rc;
}
/*
* Configure IRQs on the bmc clearing the H2B and HBUSY bits;
* H2B will be asserted when the bmc has data for us; HBUSY
* will be cleared (along with B2H) when we can write the next
* message to the BT buffer
*/
reg = readl(bt_bmc->base + BT_CR1);
reg |= BT_CR1_IRQ_H2B | BT_CR1_IRQ_HBUSY;
writel(reg, bt_bmc->base + BT_CR1);
return 0;
}
static int bt_bmc_probe(struct platform_device *pdev)
{
struct bt_bmc *bt_bmc;
struct device *dev;
int rc;
dev = &pdev->dev;
dev_info(dev, "Found bt bmc device\n");
bt_bmc = devm_kzalloc(dev, sizeof(*bt_bmc), GFP_KERNEL);
if (!bt_bmc)
return -ENOMEM;
dev_set_drvdata(&pdev->dev, bt_bmc);
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);
bt_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
bt_bmc->miscdev.name = DEVICE_NAME;
bt_bmc->miscdev.fops = &bt_bmc_fops;
bt_bmc->miscdev.parent = dev;
rc = misc_register(&bt_bmc->miscdev);
if (rc) {
dev_err(dev, "Unable to register misc device\n");
return rc;
}
bt_bmc_config_irq(bt_bmc, pdev);
if (bt_bmc->irq >= 0) {
dev_info(dev, "Using IRQ %d\n", bt_bmc->irq);
} else {
dev_info(dev, "No IRQ; using timer\n");
timer_setup(&bt_bmc->poll_timer, poll_timer, 0);
bt_bmc->poll_timer.expires = jiffies + msecs_to_jiffies(10);
add_timer(&bt_bmc->poll_timer);
}
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_bmc->base + BT_CR0);
clr_b_busy(bt_bmc);
return 0;
}
static void bt_bmc_remove(struct platform_device *pdev)
{
struct bt_bmc *bt_bmc = dev_get_drvdata(&pdev->dev);
misc_deregister(&bt_bmc->miscdev);
if (bt_bmc->irq < 0)
timer_delete_sync(&bt_bmc->poll_timer);
}
static const struct of_device_id bt_bmc_match[] = {
{ .compatible = "aspeed,ast2400-ibt-bmc" },
{ .compatible = "aspeed,ast2500-ibt-bmc" },
{ .compatible = "aspeed,ast2600-ibt-bmc" },
{ },
};
static struct platform_driver bt_bmc_driver = {
.driver = {
.name = DEVICE_NAME,
.of_match_table = bt_bmc_match,
},
.probe = bt_bmc_probe,
.remove = bt_bmc_remove,
};
module_platform_driver(bt_bmc_driver);
MODULE_DEVICE_TABLE(of, bt_bmc_match);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alistair Popple <alistair@popple.id.au>");
MODULE_DESCRIPTION("Linux device interface to the IPMI BT interface");
@@ -0,0 +1,382 @@
// SPDX-License-Identifier: GPL-2.0
/*
* IPMB driver to receive a request and send a response
*
* Copyright (C) 2019 Mellanox Techologies, Ltd.
*
* This was inspired by Brendan Higgins' ipmi-bmc-bt-i2c driver.
*/
#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/wait.h>
#define MAX_MSG_LEN 240
#define IPMB_REQUEST_LEN_MIN 7
#define NETFN_RSP_BIT_MASK 0x4
#define REQUEST_QUEUE_MAX_LEN 256
#define IPMB_MSG_LEN_IDX 0
#define RQ_SA_8BIT_IDX 1
#define NETFN_LUN_IDX 2
#define GET_7BIT_ADDR(addr_8bit) (addr_8bit >> 1)
#define GET_8BIT_ADDR(addr_7bit) ((addr_7bit << 1) & 0xff)
#define IPMB_MSG_PAYLOAD_LEN_MAX (MAX_MSG_LEN - IPMB_REQUEST_LEN_MIN - 1)
#define SMBUS_MSG_HEADER_LENGTH 2
#define SMBUS_MSG_IDX_OFFSET (SMBUS_MSG_HEADER_LENGTH + 1)
struct ipmb_msg {
u8 len;
u8 rs_sa;
u8 netfn_rs_lun;
u8 checksum1;
u8 rq_sa;
u8 rq_seq_rq_lun;
u8 cmd;
u8 payload[IPMB_MSG_PAYLOAD_LEN_MAX];
/* checksum2 is included in payload */
} __packed;
struct ipmb_request_elem {
struct list_head list;
struct ipmb_msg request;
};
struct ipmb_dev {
struct i2c_client *client;
struct miscdevice miscdev;
struct ipmb_msg request;
struct list_head request_queue;
atomic_t request_queue_len;
size_t msg_idx;
spinlock_t lock;
wait_queue_head_t wait_queue;
struct mutex file_mutex;
bool is_i2c_protocol;
};
static inline struct ipmb_dev *to_ipmb_dev(struct file *file)
{
return container_of(file->private_data, struct ipmb_dev, miscdev);
}
static ssize_t ipmb_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
struct ipmb_dev *ipmb_dev = to_ipmb_dev(file);
struct ipmb_request_elem *queue_elem;
struct ipmb_msg msg;
ssize_t ret = 0;
memset(&msg, 0, sizeof(msg));
spin_lock_irq(&ipmb_dev->lock);
while (list_empty(&ipmb_dev->request_queue)) {
spin_unlock_irq(&ipmb_dev->lock);
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
ret = wait_event_interruptible(ipmb_dev->wait_queue,
!list_empty(&ipmb_dev->request_queue));
if (ret)
return ret;
spin_lock_irq(&ipmb_dev->lock);
}
queue_elem = list_first_entry(&ipmb_dev->request_queue,
struct ipmb_request_elem, list);
memcpy(&msg, &queue_elem->request, sizeof(msg));
list_del(&queue_elem->list);
kfree(queue_elem);
atomic_dec(&ipmb_dev->request_queue_len);
spin_unlock_irq(&ipmb_dev->lock);
count = min_t(size_t, count, msg.len + 1);
if (copy_to_user(buf, &msg, count))
ret = -EFAULT;
return ret < 0 ? ret : count;
}
static int ipmb_i2c_write(struct i2c_client *client, u8 *msg, u8 addr)
{
struct i2c_msg i2c_msg;
/*
* subtract 1 byte (rq_sa) from the length of the msg passed to
* raw i2c_transfer
*/
i2c_msg.len = msg[IPMB_MSG_LEN_IDX] - 1;
/* Assign message to buffer except first 2 bytes (length and address) */
i2c_msg.buf = msg + 2;
i2c_msg.addr = addr;
i2c_msg.flags = client->flags & I2C_CLIENT_PEC;
return i2c_transfer(client->adapter, &i2c_msg, 1);
}
static ssize_t ipmb_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct ipmb_dev *ipmb_dev = to_ipmb_dev(file);
u8 rq_sa, netf_rq_lun, msg_len;
struct i2c_client *temp_client;
u8 msg[MAX_MSG_LEN];
ssize_t ret;
if (count > sizeof(msg))
return -EINVAL;
if (copy_from_user(&msg, buf, count))
return -EFAULT;
if (count < msg[0])
return -EINVAL;
rq_sa = GET_7BIT_ADDR(msg[RQ_SA_8BIT_IDX]);
netf_rq_lun = msg[NETFN_LUN_IDX];
/* Check i2c block transfer vs smbus */
if (ipmb_dev->is_i2c_protocol) {
ret = ipmb_i2c_write(ipmb_dev->client, msg, rq_sa);
return (ret == 1) ? count : ret;
}
/*
* subtract rq_sa and netf_rq_lun from the length of the msg. Fill the
* temporary client. Note that its use is an exception for IPMI.
*/
msg_len = msg[IPMB_MSG_LEN_IDX] - SMBUS_MSG_HEADER_LENGTH;
temp_client = kmemdup(ipmb_dev->client, sizeof(*temp_client), GFP_KERNEL);
if (!temp_client)
return -ENOMEM;
temp_client->addr = rq_sa;
ret = i2c_smbus_write_block_data(temp_client, netf_rq_lun, msg_len,
msg + SMBUS_MSG_IDX_OFFSET);
kfree(temp_client);
return ret < 0 ? ret : count;
}
static __poll_t ipmb_poll(struct file *file, poll_table *wait)
{
struct ipmb_dev *ipmb_dev = to_ipmb_dev(file);
__poll_t mask = EPOLLOUT;
mutex_lock(&ipmb_dev->file_mutex);
poll_wait(file, &ipmb_dev->wait_queue, wait);
if (atomic_read(&ipmb_dev->request_queue_len))
mask |= EPOLLIN;
mutex_unlock(&ipmb_dev->file_mutex);
return mask;
}
static const struct file_operations ipmb_fops = {
.owner = THIS_MODULE,
.read = ipmb_read,
.write = ipmb_write,
.poll = ipmb_poll,
};
/* Called with ipmb_dev->lock held. */
static void ipmb_handle_request(struct ipmb_dev *ipmb_dev)
{
struct ipmb_request_elem *queue_elem;
if (atomic_read(&ipmb_dev->request_queue_len) >=
REQUEST_QUEUE_MAX_LEN)
return;
queue_elem = kmalloc_obj(*queue_elem, GFP_ATOMIC);
if (!queue_elem)
return;
memcpy(&queue_elem->request, &ipmb_dev->request,
sizeof(struct ipmb_msg));
list_add(&queue_elem->list, &ipmb_dev->request_queue);
atomic_inc(&ipmb_dev->request_queue_len);
wake_up_all(&ipmb_dev->wait_queue);
}
static u8 ipmb_verify_checksum1(struct ipmb_dev *ipmb_dev, u8 rs_sa)
{
/* The 8 lsb of the sum is 0 when the checksum is valid */
return (rs_sa + ipmb_dev->request.netfn_rs_lun +
ipmb_dev->request.checksum1);
}
/*
* Verify if message has proper ipmb header with minimum length
* and correct checksum byte.
*/
static bool is_ipmb_msg(struct ipmb_dev *ipmb_dev, u8 rs_sa)
{
if ((ipmb_dev->msg_idx >= IPMB_REQUEST_LEN_MIN) &&
(!ipmb_verify_checksum1(ipmb_dev, rs_sa)))
return true;
return false;
}
/*
* The IPMB protocol only supports I2C Writes so there is no need
* to support I2C_SLAVE_READ* events.
* This i2c callback function only monitors IPMB request messages
* and adds them in a queue, so that they can be handled by
* receive_ipmb_request.
*/
static int ipmb_slave_cb(struct i2c_client *client,
enum i2c_slave_event event, u8 *val)
{
struct ipmb_dev *ipmb_dev = i2c_get_clientdata(client);
u8 *buf = (u8 *)&ipmb_dev->request;
unsigned long flags;
spin_lock_irqsave(&ipmb_dev->lock, flags);
switch (event) {
case I2C_SLAVE_WRITE_REQUESTED:
memset(&ipmb_dev->request, 0, sizeof(ipmb_dev->request));
ipmb_dev->msg_idx = 0;
/*
* At index 0, ipmb_msg stores the length of msg,
* skip it for now.
* The len will be populated once the whole
* buf is populated.
*
* The I2C bus driver's responsibility is to pass the
* data bytes to the backend driver; it does not
* forward the i2c slave address.
* Since the first byte in the IPMB message is the
* address of the responder, it is the responsibility
* of the IPMB driver to format the message properly.
* So this driver prepends the address of the responder
* to the received i2c data before the request message
* is handled in userland.
*/
buf[++ipmb_dev->msg_idx] = GET_8BIT_ADDR(client->addr);
break;
case I2C_SLAVE_WRITE_RECEIVED:
if (ipmb_dev->msg_idx >= sizeof(struct ipmb_msg) - 1)
break;
buf[++ipmb_dev->msg_idx] = *val;
break;
case I2C_SLAVE_STOP:
ipmb_dev->request.len = ipmb_dev->msg_idx;
if (is_ipmb_msg(ipmb_dev, GET_8BIT_ADDR(client->addr)))
ipmb_handle_request(ipmb_dev);
break;
default:
break;
}
spin_unlock_irqrestore(&ipmb_dev->lock, flags);
return 0;
}
static int ipmb_probe(struct i2c_client *client)
{
struct ipmb_dev *ipmb_dev;
int ret;
ipmb_dev = devm_kzalloc(&client->dev, sizeof(*ipmb_dev),
GFP_KERNEL);
if (!ipmb_dev)
return -ENOMEM;
spin_lock_init(&ipmb_dev->lock);
init_waitqueue_head(&ipmb_dev->wait_queue);
atomic_set(&ipmb_dev->request_queue_len, 0);
INIT_LIST_HEAD(&ipmb_dev->request_queue);
mutex_init(&ipmb_dev->file_mutex);
ipmb_dev->miscdev.minor = MISC_DYNAMIC_MINOR;
ipmb_dev->miscdev.name = devm_kasprintf(&client->dev, GFP_KERNEL,
"%s%d", "ipmb-",
client->adapter->nr);
if (!ipmb_dev->miscdev.name)
return -ENOMEM;
ipmb_dev->miscdev.fops = &ipmb_fops;
ipmb_dev->miscdev.parent = &client->dev;
ret = misc_register(&ipmb_dev->miscdev);
if (ret)
return ret;
ipmb_dev->is_i2c_protocol
= device_property_read_bool(&client->dev, "i2c-protocol");
ipmb_dev->client = client;
i2c_set_clientdata(client, ipmb_dev);
ret = i2c_slave_register(client, ipmb_slave_cb);
if (ret) {
misc_deregister(&ipmb_dev->miscdev);
return ret;
}
return 0;
}
static void ipmb_remove(struct i2c_client *client)
{
struct ipmb_dev *ipmb_dev = i2c_get_clientdata(client);
i2c_slave_unregister(client);
misc_deregister(&ipmb_dev->miscdev);
}
static const struct i2c_device_id ipmb_id[] = {
{ .name = "ipmb-dev" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ipmb_id);
#ifdef CONFIG_ACPI
static const struct acpi_device_id acpi_ipmb_id[] = {
{ "IPMB0001", 0 },
{},
};
MODULE_DEVICE_TABLE(acpi, acpi_ipmb_id);
#endif
static struct i2c_driver ipmb_driver = {
.driver = {
.name = "ipmb-dev",
.acpi_match_table = ACPI_PTR(acpi_ipmb_id),
},
.probe = ipmb_probe,
.remove = ipmb_remove,
.id_table = ipmb_id,
};
module_i2c_driver(ipmb_driver);
MODULE_AUTHOR("Mellanox Technologies");
MODULE_DESCRIPTION("IPMB driver");
MODULE_LICENSE("GPL v2");
+696
View File
@@ -0,0 +1,696 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* ipmi_bt_sm.c
*
* The state machine for an Open IPMI BT sub-driver under ipmi_si.c, part
* of the driver architecture at http://sourceforge.net/projects/openipmi
*
* Author: Rocky Craig <first.last@hp.com>
*/
#define DEBUG /* So dev_dbg() is always available. */
#include <linux/kernel.h> /* For printk. */
#include <linux/string.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/ipmi_msgdefs.h> /* for completion codes */
#include "ipmi_si_sm.h"
#define BT_DEBUG_OFF 0 /* Used in production */
#define BT_DEBUG_ENABLE 1 /* Generic messages */
#define BT_DEBUG_MSG 2 /* Prints all request/response buffers */
#define BT_DEBUG_STATES 4 /* Verbose look at state changes */
/*
* BT_DEBUG_OFF must be zero to correspond to the default uninitialized
* value
*/
static int bt_debug; /* 0 == BT_DEBUG_OFF */
module_param(bt_debug, int, 0644);
MODULE_PARM_DESC(bt_debug, "debug bitmask, 1=enable, 2=messages, 4=states");
/*
* Typical "Get BT Capabilities" values are 2-3 retries, 5-10 seconds,
* and 64 byte buffers. However, one HP implementation wants 255 bytes of
* buffer (with a documented message of 160 bytes) so go for the max.
* Since the Open IPMI architecture is single-message oriented at this
* stage, the queue depth of BT is of no concern.
*/
#define BT_NORMAL_TIMEOUT 5 /* seconds */
#define BT_NORMAL_RETRY_LIMIT 2
#define BT_RESET_DELAY 6 /* seconds after warm reset */
/*
* States are written in chronological order and usually cover
* multiple rows of the state table discussion in the IPMI spec.
*/
enum bt_states {
BT_STATE_IDLE = 0, /* Order is critical in this list */
BT_STATE_XACTION_START,
BT_STATE_WRITE_BYTES,
BT_STATE_WRITE_CONSUME,
BT_STATE_READ_WAIT,
BT_STATE_CLEAR_B2H,
BT_STATE_READ_BYTES,
BT_STATE_RESET1, /* These must come last */
BT_STATE_RESET2,
BT_STATE_RESET3,
BT_STATE_RESTART,
BT_STATE_PRINTME,
BT_STATE_LONG_BUSY /* BT doesn't get hosed :-) */
};
/*
* Macros seen at the end of state "case" blocks. They help with legibility
* and debugging.
*/
#define BT_STATE_CHANGE(X, Y) { bt->state = X; return Y; }
#define BT_SI_SM_RETURN(Y) { last_printed = BT_STATE_PRINTME; return Y; }
struct si_sm_data {
enum bt_states state;
unsigned char seq; /* BT sequence number */
struct si_sm_io *io;
unsigned char write_data[IPMI_MAX_MSG_LENGTH + 2]; /* +2 for memcpy */
int write_count;
unsigned char read_data[IPMI_MAX_MSG_LENGTH + 2]; /* +2 for memcpy */
int read_count;
int truncated;
long timeout; /* microseconds countdown */
int error_retries; /* end of "common" fields */
int nonzero_status; /* hung BMCs stay all 0 */
enum bt_states complete; /* to divert the state machine */
long BT_CAP_req2rsp;
int BT_CAP_retries; /* Recommended retries */
};
#define BT_CLR_WR_PTR 0x01 /* See IPMI 1.5 table 11.6.4 */
#define BT_CLR_RD_PTR 0x02
#define BT_H2B_ATN 0x04
#define BT_B2H_ATN 0x08
#define BT_SMS_ATN 0x10
#define BT_OEM0 0x20
#define BT_H_BUSY 0x40
#define BT_B_BUSY 0x80
/*
* Some bits are toggled on each write: write once to set it, once
* more to clear it; writing a zero does nothing. To absolutely
* clear it, check its state and write if set. This avoids the "get
* current then use as mask" scheme to modify one bit. Note that the
* variable "bt" is hardcoded into these macros.
*/
#define BT_STATUS bt->io->inputb(bt->io, 0)
#define BT_CONTROL(x) bt->io->outputb(bt->io, 0, x)
#define BMC2HOST bt->io->inputb(bt->io, 1)
#define HOST2BMC(x) bt->io->outputb(bt->io, 1, x)
#define BT_INTMASK_R bt->io->inputb(bt->io, 2)
#define BT_INTMASK_W(x) bt->io->outputb(bt->io, 2, x)
/*
* Convenience routines for debugging. These are not multi-open safe!
* Note the macros have hardcoded variables in them.
*/
static char *state2txt(unsigned char state)
{
switch (state) {
case BT_STATE_IDLE: return("IDLE");
case BT_STATE_XACTION_START: return("XACTION");
case BT_STATE_WRITE_BYTES: return("WR_BYTES");
case BT_STATE_WRITE_CONSUME: return("WR_CONSUME");
case BT_STATE_READ_WAIT: return("RD_WAIT");
case BT_STATE_CLEAR_B2H: return("CLEAR_B2H");
case BT_STATE_READ_BYTES: return("RD_BYTES");
case BT_STATE_RESET1: return("RESET1");
case BT_STATE_RESET2: return("RESET2");
case BT_STATE_RESET3: return("RESET3");
case BT_STATE_RESTART: return("RESTART");
case BT_STATE_LONG_BUSY: return("LONG_BUSY");
}
return("BAD STATE");
}
#define STATE2TXT state2txt(bt->state)
static char *status2txt(unsigned char status)
{
/*
* This cannot be called by two threads at the same time and
* the buffer is always consumed immediately, so the static is
* safe to use.
*/
static char buf[40];
strcpy(buf, "[ ");
if (status & BT_B_BUSY)
strcat(buf, "B_BUSY ");
if (status & BT_H_BUSY)
strcat(buf, "H_BUSY ");
if (status & BT_OEM0)
strcat(buf, "OEM0 ");
if (status & BT_SMS_ATN)
strcat(buf, "SMS ");
if (status & BT_B2H_ATN)
strcat(buf, "B2H ");
if (status & BT_H2B_ATN)
strcat(buf, "H2B ");
strcat(buf, "]");
return buf;
}
#define STATUS2TXT status2txt(status)
/* called externally at insmod time, and internally on cleanup */
static unsigned int bt_init_data(struct si_sm_data *bt, struct si_sm_io *io)
{
memset(bt, 0, sizeof(struct si_sm_data));
if (bt->io != io) {
/* external: one-time only things */
bt->io = io;
bt->seq = 0;
}
bt->state = BT_STATE_IDLE; /* start here */
bt->complete = BT_STATE_IDLE; /* end here */
bt->BT_CAP_req2rsp = BT_NORMAL_TIMEOUT * USEC_PER_SEC;
bt->BT_CAP_retries = BT_NORMAL_RETRY_LIMIT;
return 3; /* We claim 3 bytes of space; ought to check SPMI table */
}
/* Jam a completion code (probably an error) into a response */
static void force_result(struct si_sm_data *bt, unsigned char completion_code)
{
bt->read_data[0] = 4; /* # following bytes */
bt->read_data[1] = bt->write_data[1] | 4; /* Odd NetFn/LUN */
bt->read_data[2] = bt->write_data[2]; /* seq (ignored) */
bt->read_data[3] = bt->write_data[3]; /* Command */
bt->read_data[4] = completion_code;
bt->read_count = 5;
}
/* The upper state machine starts here */
static int bt_start_transaction(struct si_sm_data *bt,
unsigned char *data,
unsigned int size)
{
unsigned int i;
if (size < 2)
return IPMI_REQ_LEN_INVALID_ERR;
if (size > IPMI_MAX_MSG_LENGTH)
return IPMI_REQ_LEN_EXCEEDED_ERR;
if (bt->state == BT_STATE_LONG_BUSY)
return IPMI_NODE_BUSY_ERR;
if (bt->state != BT_STATE_IDLE) {
dev_warn(bt->io->dev, "BT in invalid state %d\n", bt->state);
return IPMI_NOT_IN_MY_STATE_ERR;
}
if (bt_debug & BT_DEBUG_MSG) {
dev_dbg(bt->io->dev, "+++++++++++++++++ New command\n");
dev_dbg(bt->io->dev, "NetFn/LUN CMD [%d data]:", size - 2);
for (i = 0; i < size; i ++)
pr_cont(" %02x", data[i]);
pr_cont("\n");
}
bt->write_data[0] = size + 1; /* all data plus seq byte */
bt->write_data[1] = *data; /* NetFn/LUN */
bt->write_data[2] = bt->seq++;
memcpy(bt->write_data + 3, data + 1, size - 1);
bt->write_count = size + 2;
bt->error_retries = 0;
bt->nonzero_status = 0;
bt->truncated = 0;
bt->state = BT_STATE_XACTION_START;
bt->timeout = bt->BT_CAP_req2rsp;
force_result(bt, IPMI_ERR_UNSPECIFIED);
return 0;
}
/*
* After the upper state machine has been told SI_SM_TRANSACTION_COMPLETE
* it calls this. Strip out the length and seq bytes.
*/
static int bt_get_result(struct si_sm_data *bt,
unsigned char *data,
unsigned int length)
{
int i, msg_len;
msg_len = bt->read_count - 2; /* account for length & seq */
if (msg_len < 3 || msg_len > IPMI_MAX_MSG_LENGTH) {
force_result(bt, IPMI_ERR_UNSPECIFIED);
msg_len = 3;
}
data[0] = bt->read_data[1];
data[1] = bt->read_data[3];
if (length < msg_len || bt->truncated) {
data[2] = IPMI_ERR_MSG_TRUNCATED;
msg_len = 3;
} else
memcpy(data + 2, bt->read_data + 4, msg_len - 2);
if (bt_debug & BT_DEBUG_MSG) {
dev_dbg(bt->io->dev, "result %d bytes:", msg_len);
for (i = 0; i < msg_len; i++)
pr_cont(" %02x", data[i]);
pr_cont("\n");
}
return msg_len;
}
/* This bit's functionality is optional */
#define BT_BMC_HWRST 0x80
static void reset_flags(struct si_sm_data *bt)
{
if (bt_debug)
dev_dbg(bt->io->dev, "flag reset %s\n", status2txt(BT_STATUS));
if (BT_STATUS & BT_H_BUSY)
BT_CONTROL(BT_H_BUSY); /* force clear */
BT_CONTROL(BT_CLR_WR_PTR); /* always reset */
BT_CONTROL(BT_SMS_ATN); /* always clear */
BT_INTMASK_W(BT_BMC_HWRST);
}
/*
* Get rid of an unwanted/stale response. This should only be needed for
* BMCs that support multiple outstanding requests.
*/
static void drain_BMC2HOST(struct si_sm_data *bt)
{
int i, size;
if (!(BT_STATUS & BT_B2H_ATN)) /* Not signalling a response */
return;
BT_CONTROL(BT_H_BUSY); /* now set */
BT_CONTROL(BT_B2H_ATN); /* always clear */
BT_STATUS; /* pause */
BT_CONTROL(BT_B2H_ATN); /* some BMCs are stubborn */
BT_CONTROL(BT_CLR_RD_PTR); /* always reset */
if (bt_debug)
dev_dbg(bt->io->dev, "stale response %s; ",
status2txt(BT_STATUS));
size = BMC2HOST;
for (i = 0; i < size ; i++)
BMC2HOST;
BT_CONTROL(BT_H_BUSY); /* now clear */
if (bt_debug)
pr_cont("drained %d bytes\n", size + 1);
}
static inline void write_all_bytes(struct si_sm_data *bt)
{
int i;
if (bt_debug & BT_DEBUG_MSG) {
dev_dbg(bt->io->dev, "write %d bytes seq=0x%02X",
bt->write_count, bt->seq);
for (i = 0; i < bt->write_count; i++)
pr_cont(" %02x", bt->write_data[i]);
pr_cont("\n");
}
for (i = 0; i < bt->write_count; i++)
HOST2BMC(bt->write_data[i]);
}
static inline int read_all_bytes(struct si_sm_data *bt)
{
unsigned int i;
/*
* length is "framing info", minimum = 4: NetFn, Seq, Cmd, cCode.
* Keep layout of first four bytes aligned with write_data[]
*/
bt->read_data[0] = BMC2HOST;
bt->read_count = bt->read_data[0];
if (bt->read_count < 4 || bt->read_count >= IPMI_MAX_MSG_LENGTH) {
if (bt_debug & BT_DEBUG_MSG)
dev_dbg(bt->io->dev,
"bad raw rsp len=%d\n", bt->read_count);
bt->truncated = 1;
return 1; /* let next XACTION START clean it up */
}
for (i = 1; i <= bt->read_count; i++)
bt->read_data[i] = BMC2HOST;
bt->read_count++; /* Account internally for length byte */
if (bt_debug & BT_DEBUG_MSG) {
int max = bt->read_count;
dev_dbg(bt->io->dev,
"got %d bytes seq=0x%02X", max, bt->read_data[2]);
if (max > 16)
max = 16;
for (i = 0; i < max; i++)
pr_cont(" %02x", bt->read_data[i]);
pr_cont("%s\n", bt->read_count == max ? "" : " ...");
}
/* per the spec, the (NetFn[1], Seq[2], Cmd[3]) tuples must match */
if ((bt->read_data[3] == bt->write_data[3]) &&
(bt->read_data[2] == bt->write_data[2]) &&
((bt->read_data[1] & 0xF8) == (bt->write_data[1] & 0xF8)))
return 1;
if (bt_debug & BT_DEBUG_MSG)
dev_dbg(bt->io->dev,
"IPMI BT: bad packet: want 0x(%02X, %02X, %02X) got (%02X, %02X, %02X)\n",
bt->write_data[1] | 0x04, bt->write_data[2],
bt->write_data[3],
bt->read_data[1], bt->read_data[2], bt->read_data[3]);
return 0;
}
/* Restart if retries are left, or return an error completion code */
static enum si_sm_result error_recovery(struct si_sm_data *bt,
unsigned char status,
unsigned char cCode)
{
char *reason;
bt->timeout = bt->BT_CAP_req2rsp;
switch (cCode) {
case IPMI_TIMEOUT_ERR:
reason = "timeout";
break;
default:
reason = "internal error";
break;
}
dev_warn(bt->io->dev, "IPMI BT: %s in %s %s ", /* open-ended line */
reason, STATE2TXT, STATUS2TXT);
/*
* Per the IPMI spec, retries are based on the sequence number
* known only to this module, so manage a restart here.
*/
(bt->error_retries)++;
if (bt->error_retries < bt->BT_CAP_retries) {
pr_cont("%d retries left\n",
bt->BT_CAP_retries - bt->error_retries);
bt->state = BT_STATE_RESTART;
return SI_SM_CALL_WITHOUT_DELAY;
}
dev_warn(bt->io->dev, "failed %d retries, sending error response\n",
bt->BT_CAP_retries);
if (!bt->nonzero_status)
dev_err(bt->io->dev, "stuck, try power cycle\n");
/* this is most likely during insmod */
else if (bt->seq <= (unsigned char)(bt->BT_CAP_retries & 0xFF)) {
dev_warn(bt->io->dev, "BT reset (takes 5 secs)\n");
bt->state = BT_STATE_RESET1;
return SI_SM_CALL_WITHOUT_DELAY;
}
/*
* Concoct a useful error message, set up the next state, and
* be done with this sequence.
*/
bt->state = BT_STATE_IDLE;
switch (cCode) {
case IPMI_TIMEOUT_ERR:
if (status & BT_B_BUSY) {
cCode = IPMI_NODE_BUSY_ERR;
bt->state = BT_STATE_LONG_BUSY;
}
break;
default:
break;
}
force_result(bt, cCode);
return SI_SM_TRANSACTION_COMPLETE;
}
/* Check status and (usually) take action and change this state machine. */
static enum si_sm_result bt_event(struct si_sm_data *bt, long time)
{
unsigned char status;
static enum bt_states last_printed = BT_STATE_PRINTME;
int i;
status = BT_STATUS;
bt->nonzero_status |= status;
if ((bt_debug & BT_DEBUG_STATES) && (bt->state != last_printed)) {
dev_dbg(bt->io->dev, "BT: %s %s TO=%ld - %ld\n",
STATE2TXT,
STATUS2TXT,
bt->timeout,
time);
last_printed = bt->state;
}
/*
* Commands that time out may still (eventually) provide a response.
* This stale response will get in the way of a new response so remove
* it if possible (hopefully during IDLE). Even if it comes up later
* it will be rejected by its (now-forgotten) seq number.
*/
if ((bt->state < BT_STATE_WRITE_BYTES) && (status & BT_B2H_ATN)) {
drain_BMC2HOST(bt);
BT_SI_SM_RETURN(SI_SM_CALL_WITH_DELAY);
}
if ((bt->state != BT_STATE_IDLE) &&
(bt->state < BT_STATE_PRINTME)) {
/* check timeout */
bt->timeout -= time;
if ((bt->timeout < 0) && (bt->state < BT_STATE_RESET1))
return error_recovery(bt,
status,
IPMI_TIMEOUT_ERR);
}
switch (bt->state) {
/*
* Idle state first checks for asynchronous messages from another
* channel, then does some opportunistic housekeeping.
*/
case BT_STATE_IDLE:
if (status & BT_SMS_ATN) {
BT_CONTROL(BT_SMS_ATN); /* clear it */
return SI_SM_ATTN;
}
if (status & BT_H_BUSY) /* clear a leftover H_BUSY */
BT_CONTROL(BT_H_BUSY);
BT_SI_SM_RETURN(SI_SM_IDLE);
case BT_STATE_XACTION_START:
if (status & (BT_B_BUSY | BT_H2B_ATN))
BT_SI_SM_RETURN(SI_SM_CALL_WITH_DELAY);
if (BT_STATUS & BT_H_BUSY)
BT_CONTROL(BT_H_BUSY); /* force clear */
BT_STATE_CHANGE(BT_STATE_WRITE_BYTES,
SI_SM_CALL_WITHOUT_DELAY);
case BT_STATE_WRITE_BYTES:
if (status & BT_H_BUSY)
BT_CONTROL(BT_H_BUSY); /* clear */
BT_CONTROL(BT_CLR_WR_PTR);
write_all_bytes(bt);
BT_CONTROL(BT_H2B_ATN); /* can clear too fast to catch */
BT_STATE_CHANGE(BT_STATE_WRITE_CONSUME,
SI_SM_CALL_WITHOUT_DELAY);
case BT_STATE_WRITE_CONSUME:
if (status & (BT_B_BUSY | BT_H2B_ATN))
BT_SI_SM_RETURN(SI_SM_CALL_WITH_DELAY);
BT_STATE_CHANGE(BT_STATE_READ_WAIT,
SI_SM_CALL_WITHOUT_DELAY);
/* Spinning hard can suppress B2H_ATN and force a timeout */
case BT_STATE_READ_WAIT:
if (!(status & BT_B2H_ATN))
BT_SI_SM_RETURN(SI_SM_CALL_WITH_DELAY);
BT_CONTROL(BT_H_BUSY); /* set */
/*
* Uncached, ordered writes should just proceed serially but
* some BMCs don't clear B2H_ATN with one hit. Fast-path a
* workaround without too much penalty to the general case.
*/
BT_CONTROL(BT_B2H_ATN); /* clear it to ACK the BMC */
BT_STATE_CHANGE(BT_STATE_CLEAR_B2H,
SI_SM_CALL_WITHOUT_DELAY);
case BT_STATE_CLEAR_B2H:
if (status & BT_B2H_ATN) {
/* keep hitting it */
BT_CONTROL(BT_B2H_ATN);
BT_SI_SM_RETURN(SI_SM_CALL_WITH_DELAY);
}
BT_STATE_CHANGE(BT_STATE_READ_BYTES,
SI_SM_CALL_WITHOUT_DELAY);
case BT_STATE_READ_BYTES:
if (!(status & BT_H_BUSY))
/* check in case of retry */
BT_CONTROL(BT_H_BUSY);
BT_CONTROL(BT_CLR_RD_PTR); /* start of BMC2HOST buffer */
i = read_all_bytes(bt); /* true == packet seq match */
BT_CONTROL(BT_H_BUSY); /* NOW clear */
if (!i) /* Not my message */
BT_STATE_CHANGE(BT_STATE_READ_WAIT,
SI_SM_CALL_WITHOUT_DELAY);
bt->state = bt->complete;
return bt->state == BT_STATE_IDLE ? /* where to next? */
SI_SM_TRANSACTION_COMPLETE : /* normal */
SI_SM_CALL_WITHOUT_DELAY; /* Startup magic */
case BT_STATE_LONG_BUSY: /* For example: after FW update */
if (!(status & BT_B_BUSY)) {
reset_flags(bt); /* next state is now IDLE */
bt_init_data(bt, bt->io);
}
return SI_SM_CALL_WITH_DELAY; /* No repeat printing */
case BT_STATE_RESET1:
reset_flags(bt);
drain_BMC2HOST(bt);
BT_STATE_CHANGE(BT_STATE_RESET2,
SI_SM_CALL_WITH_DELAY);
case BT_STATE_RESET2: /* Send a soft reset */
BT_CONTROL(BT_CLR_WR_PTR);
HOST2BMC(3); /* number of bytes following */
HOST2BMC(0x18); /* NetFn/LUN == Application, LUN 0 */
HOST2BMC(42); /* Sequence number */
HOST2BMC(3); /* Cmd == Soft reset */
BT_CONTROL(BT_H2B_ATN);
bt->timeout = BT_RESET_DELAY * USEC_PER_SEC;
BT_STATE_CHANGE(BT_STATE_RESET3,
SI_SM_CALL_WITH_DELAY);
case BT_STATE_RESET3: /* Hold off everything for a bit */
if (bt->timeout > 0)
return SI_SM_CALL_WITH_DELAY;
drain_BMC2HOST(bt);
BT_STATE_CHANGE(BT_STATE_RESTART,
SI_SM_CALL_WITH_DELAY);
case BT_STATE_RESTART: /* don't reset retries or seq! */
bt->read_count = 0;
bt->nonzero_status = 0;
bt->timeout = bt->BT_CAP_req2rsp;
BT_STATE_CHANGE(BT_STATE_XACTION_START,
SI_SM_CALL_WITH_DELAY);
default: /* should never occur */
return error_recovery(bt,
status,
IPMI_ERR_UNSPECIFIED);
}
return SI_SM_CALL_WITH_DELAY;
}
static int bt_detect(struct si_sm_data *bt)
{
unsigned char GetBT_CAP[] = { 0x18, 0x36 };
unsigned char BT_CAP[8];
enum si_sm_result smi_result;
int rv;
/*
* It's impossible for the BT status and interrupt registers to be
* all 1's, (assuming a properly functioning, self-initialized BMC)
* but that's what you get from reading a bogus address, so we
* test that first. The calling routine uses negative logic.
*/
if ((BT_STATUS == 0xFF) && (BT_INTMASK_R == 0xFF))
return 1;
reset_flags(bt);
/*
* Try getting the BT capabilities here.
*/
rv = bt_start_transaction(bt, GetBT_CAP, sizeof(GetBT_CAP));
if (rv) {
dev_warn(bt->io->dev,
"Can't start capabilities transaction: %d\n", rv);
goto out_no_bt_cap;
}
smi_result = SI_SM_CALL_WITHOUT_DELAY;
for (;;) {
if (smi_result == SI_SM_CALL_WITH_DELAY ||
smi_result == SI_SM_CALL_WITH_TICK_DELAY) {
schedule_timeout_uninterruptible(1);
smi_result = bt_event(bt, jiffies_to_usecs(1));
} else if (smi_result == SI_SM_CALL_WITHOUT_DELAY) {
smi_result = bt_event(bt, 0);
} else
break;
}
rv = bt_get_result(bt, BT_CAP, sizeof(BT_CAP));
bt_init_data(bt, bt->io);
if (rv < 8) {
dev_warn(bt->io->dev, "bt cap response too short: %d\n", rv);
goto out_no_bt_cap;
}
if (BT_CAP[2]) {
dev_warn(bt->io->dev, "Error fetching bt cap: %x\n", BT_CAP[2]);
out_no_bt_cap:
dev_warn(bt->io->dev, "using default values\n");
} else {
bt->BT_CAP_req2rsp = BT_CAP[6] * USEC_PER_SEC;
bt->BT_CAP_retries = BT_CAP[7];
}
dev_info(bt->io->dev, "req2rsp=%ld secs retries=%d\n",
bt->BT_CAP_req2rsp / USEC_PER_SEC, bt->BT_CAP_retries);
return 0;
}
static void bt_cleanup(struct si_sm_data *bt)
{
}
static int bt_size(void)
{
return sizeof(struct si_sm_data);
}
const struct si_sm_handlers bt_smi_handlers = {
.init_data = bt_init_data,
.start_transaction = bt_start_transaction,
.get_result = bt_get_result,
.event = bt_event,
.detect = bt_detect,
.cleanup = bt_cleanup,
.size = bt_size,
};
@@ -0,0 +1,907 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* ipmi_devintf.c
*
* Linux device interface for the IPMI message handler.
*
* Author: MontaVista Software, Inc.
* Corey Minyard <minyard@mvista.com>
* source@mvista.com
*
* Copyright 2002 MontaVista Software Inc.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/errno.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/ipmi.h>
#include <linux/mutex.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/compat.h>
struct ipmi_file_private
{
struct ipmi_user *user;
spinlock_t recv_msg_lock;
struct list_head recv_msgs;
struct fasync_struct *fasync_queue;
wait_queue_head_t wait;
struct mutex recv_mutex;
int default_retries;
unsigned int default_retry_time_ms;
};
static void file_receive_handler(struct ipmi_recv_msg *msg,
void *handler_data)
{
struct ipmi_file_private *priv = handler_data;
int was_empty;
unsigned long flags;
spin_lock_irqsave(&priv->recv_msg_lock, flags);
was_empty = list_empty(&priv->recv_msgs);
list_add_tail(&msg->link, &priv->recv_msgs);
spin_unlock_irqrestore(&priv->recv_msg_lock, flags);
if (was_empty) {
wake_up_interruptible(&priv->wait);
kill_fasync(&priv->fasync_queue, SIGIO, POLL_IN);
}
}
static __poll_t ipmi_poll(struct file *file, poll_table *wait)
{
struct ipmi_file_private *priv = file->private_data;
__poll_t mask = 0;
unsigned long flags;
poll_wait(file, &priv->wait, wait);
spin_lock_irqsave(&priv->recv_msg_lock, flags);
if (!list_empty(&priv->recv_msgs))
mask |= (EPOLLIN | EPOLLRDNORM);
spin_unlock_irqrestore(&priv->recv_msg_lock, flags);
return mask;
}
static int ipmi_fasync(int fd, struct file *file, int on)
{
struct ipmi_file_private *priv = file->private_data;
return fasync_helper(fd, file, on, &priv->fasync_queue);
}
static const struct ipmi_user_hndl ipmi_hndlrs =
{
.ipmi_recv_hndl = file_receive_handler,
};
static int ipmi_open(struct inode *inode, struct file *file)
{
int if_num = iminor(inode);
int rv;
struct ipmi_file_private *priv;
priv = kmalloc_obj(*priv);
if (!priv)
return -ENOMEM;
rv = ipmi_create_user(if_num,
&ipmi_hndlrs,
priv,
&priv->user);
if (rv) {
kfree(priv);
goto out;
}
file->private_data = priv;
spin_lock_init(&priv->recv_msg_lock);
INIT_LIST_HEAD(&priv->recv_msgs);
init_waitqueue_head(&priv->wait);
priv->fasync_queue = NULL;
mutex_init(&priv->recv_mutex);
/* Use the low-level defaults. */
priv->default_retries = -1;
priv->default_retry_time_ms = 0;
out:
return rv;
}
static int ipmi_release(struct inode *inode, struct file *file)
{
struct ipmi_file_private *priv = file->private_data;
struct ipmi_recv_msg *msg, *next;
ipmi_destroy_user(priv->user);
list_for_each_entry_safe(msg, next, &priv->recv_msgs, link)
ipmi_free_recv_msg(msg);
kfree(priv);
return 0;
}
static int handle_send_req(struct ipmi_user *user,
struct ipmi_req *req,
int retries,
unsigned int retry_time_ms)
{
int rv;
struct ipmi_addr addr;
struct kernel_ipmi_msg msg;
if (req->addr_len > sizeof(struct ipmi_addr))
return -EINVAL;
if (copy_from_user(&addr, req->addr, req->addr_len))
return -EFAULT;
msg.netfn = req->msg.netfn;
msg.cmd = req->msg.cmd;
msg.data_len = req->msg.data_len;
msg.data = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL);
if (!msg.data)
return -ENOMEM;
/* From here out we cannot return, we must jump to "out" for
error exits to free msgdata. */
rv = ipmi_validate_addr(&addr, req->addr_len);
if (rv)
goto out;
if (req->msg.data != NULL) {
if (req->msg.data_len > IPMI_MAX_MSG_LENGTH) {
rv = -EMSGSIZE;
goto out;
}
if (copy_from_user(msg.data,
req->msg.data,
req->msg.data_len)) {
rv = -EFAULT;
goto out;
}
} else {
msg.data_len = 0;
}
rv = ipmi_request_settime(user,
&addr,
req->msgid,
&msg,
NULL,
0,
retries,
retry_time_ms);
out:
kfree(msg.data);
return rv;
}
static int handle_recv(struct ipmi_file_private *priv,
bool trunc, struct ipmi_recv *rsp,
int (*copyout)(struct ipmi_recv *, void __user *),
void __user *to)
{
int addr_len;
struct list_head *entry;
struct ipmi_recv_msg *msg;
unsigned long flags;
int rv = 0, rv2 = 0;
/* We claim a mutex because we don't want two
users getting something from the queue at a time.
Since we have to release the spinlock before we can
copy the data to the user, it's possible another
user will grab something from the queue, too. Then
the messages might get out of order if something
fails and the message gets put back onto the
queue. This mutex prevents that problem. */
mutex_lock(&priv->recv_mutex);
/* Grab the message off the list. */
spin_lock_irqsave(&priv->recv_msg_lock, flags);
if (list_empty(&(priv->recv_msgs))) {
spin_unlock_irqrestore(&priv->recv_msg_lock, flags);
rv = -EAGAIN;
goto recv_err;
}
entry = priv->recv_msgs.next;
msg = list_entry(entry, struct ipmi_recv_msg, link);
list_del(entry);
spin_unlock_irqrestore(&priv->recv_msg_lock, flags);
addr_len = ipmi_addr_length(msg->addr.addr_type);
if (rsp->addr_len < addr_len) {
rv = -EINVAL;
goto recv_putback_on_err;
}
if (copy_to_user(rsp->addr, &msg->addr, addr_len)) {
rv = -EFAULT;
goto recv_putback_on_err;
}
rsp->addr_len = addr_len;
rsp->recv_type = msg->recv_type;
rsp->msgid = msg->msgid;
rsp->msg.netfn = msg->msg.netfn;
rsp->msg.cmd = msg->msg.cmd;
if (msg->msg.data_len > 0) {
if (rsp->msg.data_len < msg->msg.data_len) {
if (trunc) {
rv2 = -EMSGSIZE;
msg->msg.data_len = rsp->msg.data_len;
} else {
rv = -EMSGSIZE;
goto recv_putback_on_err;
}
}
if (copy_to_user(rsp->msg.data,
msg->msg.data,
msg->msg.data_len)) {
rv = -EFAULT;
goto recv_putback_on_err;
}
rsp->msg.data_len = msg->msg.data_len;
} else {
rsp->msg.data_len = 0;
}
rv = copyout(rsp, to);
if (rv)
goto recv_putback_on_err;
mutex_unlock(&priv->recv_mutex);
ipmi_free_recv_msg(msg);
return rv2;
recv_putback_on_err:
/* If we got an error, put the message back onto
the head of the queue. */
spin_lock_irqsave(&priv->recv_msg_lock, flags);
list_add(entry, &priv->recv_msgs);
spin_unlock_irqrestore(&priv->recv_msg_lock, flags);
recv_err:
mutex_unlock(&priv->recv_mutex);
return rv;
}
static int copyout_recv(struct ipmi_recv *rsp, void __user *to)
{
return copy_to_user(to, rsp, sizeof(struct ipmi_recv)) ? -EFAULT : 0;
}
static long ipmi_ioctl(struct file *file,
unsigned int cmd,
unsigned long data)
{
int rv = -EINVAL;
struct ipmi_file_private *priv = file->private_data;
void __user *arg = (void __user *)data;
switch (cmd)
{
case IPMICTL_SEND_COMMAND:
{
struct ipmi_req req;
int retries;
unsigned int retry_time_ms;
if (copy_from_user(&req, arg, sizeof(req))) {
rv = -EFAULT;
break;
}
mutex_lock(&priv->recv_mutex);
retries = priv->default_retries;
retry_time_ms = priv->default_retry_time_ms;
mutex_unlock(&priv->recv_mutex);
rv = handle_send_req(priv->user, &req, retries, retry_time_ms);
break;
}
case IPMICTL_SEND_COMMAND_SETTIME:
{
struct ipmi_req_settime req;
if (copy_from_user(&req, arg, sizeof(req))) {
rv = -EFAULT;
break;
}
rv = handle_send_req(priv->user,
&req.req,
req.retries,
req.retry_time_ms);
break;
}
case IPMICTL_RECEIVE_MSG:
case IPMICTL_RECEIVE_MSG_TRUNC:
{
struct ipmi_recv rsp;
if (copy_from_user(&rsp, arg, sizeof(rsp)))
rv = -EFAULT;
else
rv = handle_recv(priv, cmd == IPMICTL_RECEIVE_MSG_TRUNC,
&rsp, copyout_recv, arg);
break;
}
case IPMICTL_REGISTER_FOR_CMD:
{
struct ipmi_cmdspec val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_register_for_cmd(priv->user, val.netfn, val.cmd,
IPMI_CHAN_ALL);
break;
}
case IPMICTL_UNREGISTER_FOR_CMD:
{
struct ipmi_cmdspec val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_unregister_for_cmd(priv->user, val.netfn, val.cmd,
IPMI_CHAN_ALL);
break;
}
case IPMICTL_REGISTER_FOR_CMD_CHANS:
{
struct ipmi_cmdspec_chans val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_register_for_cmd(priv->user, val.netfn, val.cmd,
val.chans);
break;
}
case IPMICTL_UNREGISTER_FOR_CMD_CHANS:
{
struct ipmi_cmdspec_chans val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_unregister_for_cmd(priv->user, val.netfn, val.cmd,
val.chans);
break;
}
case IPMICTL_SET_GETS_EVENTS_CMD:
{
int val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_set_gets_events(priv->user, val);
break;
}
/* The next four are legacy, not per-channel. */
case IPMICTL_SET_MY_ADDRESS_CMD:
{
unsigned int val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_set_my_address(priv->user, 0, val);
break;
}
case IPMICTL_GET_MY_ADDRESS_CMD:
{
unsigned int val;
unsigned char rval;
rv = ipmi_get_my_address(priv->user, 0, &rval);
if (rv)
break;
val = rval;
if (copy_to_user(arg, &val, sizeof(val))) {
rv = -EFAULT;
break;
}
break;
}
case IPMICTL_SET_MY_LUN_CMD:
{
unsigned int val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_set_my_LUN(priv->user, 0, val);
break;
}
case IPMICTL_GET_MY_LUN_CMD:
{
unsigned int val;
unsigned char rval;
rv = ipmi_get_my_LUN(priv->user, 0, &rval);
if (rv)
break;
val = rval;
if (copy_to_user(arg, &val, sizeof(val))) {
rv = -EFAULT;
break;
}
break;
}
case IPMICTL_SET_MY_CHANNEL_ADDRESS_CMD:
{
struct ipmi_channel_lun_address_set val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
return ipmi_set_my_address(priv->user, val.channel, val.value);
}
case IPMICTL_GET_MY_CHANNEL_ADDRESS_CMD:
{
struct ipmi_channel_lun_address_set val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_get_my_address(priv->user, val.channel, &val.value);
if (rv)
break;
if (copy_to_user(arg, &val, sizeof(val))) {
rv = -EFAULT;
break;
}
break;
}
case IPMICTL_SET_MY_CHANNEL_LUN_CMD:
{
struct ipmi_channel_lun_address_set val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_set_my_LUN(priv->user, val.channel, val.value);
break;
}
case IPMICTL_GET_MY_CHANNEL_LUN_CMD:
{
struct ipmi_channel_lun_address_set val;
if (copy_from_user(&val, arg, sizeof(val))) {
rv = -EFAULT;
break;
}
rv = ipmi_get_my_LUN(priv->user, val.channel, &val.value);
if (rv)
break;
if (copy_to_user(arg, &val, sizeof(val))) {
rv = -EFAULT;
break;
}
break;
}
case IPMICTL_SET_TIMING_PARMS_CMD:
{
struct ipmi_timing_parms parms;
if (copy_from_user(&parms, arg, sizeof(parms))) {
rv = -EFAULT;
break;
}
mutex_lock(&priv->recv_mutex);
priv->default_retries = parms.retries;
priv->default_retry_time_ms = parms.retry_time_ms;
mutex_unlock(&priv->recv_mutex);
rv = 0;
break;
}
case IPMICTL_GET_TIMING_PARMS_CMD:
{
struct ipmi_timing_parms parms;
mutex_lock(&priv->recv_mutex);
parms.retries = priv->default_retries;
parms.retry_time_ms = priv->default_retry_time_ms;
mutex_unlock(&priv->recv_mutex);
if (copy_to_user(arg, &parms, sizeof(parms))) {
rv = -EFAULT;
break;
}
rv = 0;
break;
}
case IPMICTL_GET_MAINTENANCE_MODE_CMD:
{
int mode;
mode = ipmi_get_maintenance_mode(priv->user);
if (copy_to_user(arg, &mode, sizeof(mode))) {
rv = -EFAULT;
break;
}
rv = 0;
break;
}
case IPMICTL_SET_MAINTENANCE_MODE_CMD:
{
int mode;
if (copy_from_user(&mode, arg, sizeof(mode))) {
rv = -EFAULT;
break;
}
rv = ipmi_set_maintenance_mode(priv->user, mode);
break;
}
default:
rv = -ENOTTY;
break;
}
return rv;
}
#ifdef CONFIG_COMPAT
/*
* The following code contains code for supporting 32-bit compatible
* ioctls on 64-bit kernels. This allows running 32-bit apps on the
* 64-bit kernel
*/
#define COMPAT_IPMICTL_SEND_COMMAND \
_IOR(IPMI_IOC_MAGIC, 13, struct compat_ipmi_req)
#define COMPAT_IPMICTL_SEND_COMMAND_SETTIME \
_IOR(IPMI_IOC_MAGIC, 21, struct compat_ipmi_req_settime)
#define COMPAT_IPMICTL_RECEIVE_MSG \
_IOWR(IPMI_IOC_MAGIC, 12, struct compat_ipmi_recv)
#define COMPAT_IPMICTL_RECEIVE_MSG_TRUNC \
_IOWR(IPMI_IOC_MAGIC, 11, struct compat_ipmi_recv)
struct compat_ipmi_msg {
u8 netfn;
u8 cmd;
u16 data_len;
compat_uptr_t data;
};
struct compat_ipmi_req {
compat_uptr_t addr;
compat_uint_t addr_len;
compat_long_t msgid;
struct compat_ipmi_msg msg;
};
struct compat_ipmi_recv {
compat_int_t recv_type;
compat_uptr_t addr;
compat_uint_t addr_len;
compat_long_t msgid;
struct compat_ipmi_msg msg;
};
struct compat_ipmi_req_settime {
struct compat_ipmi_req req;
compat_int_t retries;
compat_uint_t retry_time_ms;
};
/*
* Define some helper functions for copying IPMI data
*/
static void get_compat_ipmi_msg(struct ipmi_msg *p64,
struct compat_ipmi_msg *p32)
{
p64->netfn = p32->netfn;
p64->cmd = p32->cmd;
p64->data_len = p32->data_len;
p64->data = compat_ptr(p32->data);
}
static void get_compat_ipmi_req(struct ipmi_req *p64,
struct compat_ipmi_req *p32)
{
p64->addr = compat_ptr(p32->addr);
p64->addr_len = p32->addr_len;
p64->msgid = p32->msgid;
get_compat_ipmi_msg(&p64->msg, &p32->msg);
}
static void get_compat_ipmi_req_settime(struct ipmi_req_settime *p64,
struct compat_ipmi_req_settime *p32)
{
get_compat_ipmi_req(&p64->req, &p32->req);
p64->retries = p32->retries;
p64->retry_time_ms = p32->retry_time_ms;
}
static void get_compat_ipmi_recv(struct ipmi_recv *p64,
struct compat_ipmi_recv *p32)
{
memset(p64, 0, sizeof(struct ipmi_recv));
p64->recv_type = p32->recv_type;
p64->addr = compat_ptr(p32->addr);
p64->addr_len = p32->addr_len;
p64->msgid = p32->msgid;
get_compat_ipmi_msg(&p64->msg, &p32->msg);
}
static int copyout_recv32(struct ipmi_recv *p64, void __user *to)
{
struct compat_ipmi_recv v32;
memset(&v32, 0, sizeof(struct compat_ipmi_recv));
v32.recv_type = p64->recv_type;
v32.addr = ptr_to_compat(p64->addr);
v32.addr_len = p64->addr_len;
v32.msgid = p64->msgid;
v32.msg.netfn = p64->msg.netfn;
v32.msg.cmd = p64->msg.cmd;
v32.msg.data_len = p64->msg.data_len;
v32.msg.data = ptr_to_compat(p64->msg.data);
return copy_to_user(to, &v32, sizeof(v32)) ? -EFAULT : 0;
}
/*
* Handle compatibility ioctls
*/
static long compat_ipmi_ioctl(struct file *filep, unsigned int cmd,
unsigned long arg)
{
struct ipmi_file_private *priv = filep->private_data;
switch(cmd) {
case COMPAT_IPMICTL_SEND_COMMAND:
{
struct ipmi_req rp;
struct compat_ipmi_req r32;
int retries;
unsigned int retry_time_ms;
if (copy_from_user(&r32, compat_ptr(arg), sizeof(r32)))
return -EFAULT;
get_compat_ipmi_req(&rp, &r32);
mutex_lock(&priv->recv_mutex);
retries = priv->default_retries;
retry_time_ms = priv->default_retry_time_ms;
mutex_unlock(&priv->recv_mutex);
return handle_send_req(priv->user, &rp,
retries, retry_time_ms);
}
case COMPAT_IPMICTL_SEND_COMMAND_SETTIME:
{
struct ipmi_req_settime sp;
struct compat_ipmi_req_settime sp32;
if (copy_from_user(&sp32, compat_ptr(arg), sizeof(sp32)))
return -EFAULT;
get_compat_ipmi_req_settime(&sp, &sp32);
return handle_send_req(priv->user, &sp.req,
sp.retries, sp.retry_time_ms);
}
case COMPAT_IPMICTL_RECEIVE_MSG:
case COMPAT_IPMICTL_RECEIVE_MSG_TRUNC:
{
struct ipmi_recv recv64;
struct compat_ipmi_recv recv32;
if (copy_from_user(&recv32, compat_ptr(arg), sizeof(recv32)))
return -EFAULT;
get_compat_ipmi_recv(&recv64, &recv32);
return handle_recv(priv,
cmd == COMPAT_IPMICTL_RECEIVE_MSG_TRUNC,
&recv64, copyout_recv32, compat_ptr(arg));
}
default:
return ipmi_ioctl(filep, cmd, arg);
}
}
#endif
static const struct file_operations ipmi_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = ipmi_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_ipmi_ioctl,
#endif
.open = ipmi_open,
.release = ipmi_release,
.fasync = ipmi_fasync,
.poll = ipmi_poll,
.llseek = noop_llseek,
};
#define DEVICE_NAME "ipmidev"
static int ipmi_major;
module_param(ipmi_major, int, 0);
MODULE_PARM_DESC(ipmi_major, "Sets the major number of the IPMI device. By"
" default, or if you set it to zero, it will choose the next"
" available device. Setting it to -1 will disable the"
" interface. Other values will set the major device number"
" to that value.");
/* Keep track of the devices that are registered. */
struct ipmi_reg_list {
dev_t dev;
struct list_head link;
};
static LIST_HEAD(reg_list);
static DEFINE_MUTEX(reg_list_mutex);
static const struct class ipmi_class = {
.name = "ipmi",
};
static void ipmi_new_smi(int if_num, struct device *device)
{
dev_t dev = MKDEV(ipmi_major, if_num);
struct ipmi_reg_list *entry;
entry = kmalloc_obj(*entry);
if (!entry) {
pr_err("ipmi_devintf: Unable to create the ipmi class device link\n");
return;
}
entry->dev = dev;
mutex_lock(&reg_list_mutex);
device_create(&ipmi_class, device, dev, NULL, "ipmi%d", if_num);
list_add(&entry->link, &reg_list);
mutex_unlock(&reg_list_mutex);
}
static void ipmi_smi_gone(int if_num)
{
dev_t dev = MKDEV(ipmi_major, if_num);
struct ipmi_reg_list *entry;
mutex_lock(&reg_list_mutex);
list_for_each_entry(entry, &reg_list, link) {
if (entry->dev == dev) {
list_del(&entry->link);
kfree(entry);
break;
}
}
device_destroy(&ipmi_class, dev);
mutex_unlock(&reg_list_mutex);
}
static struct ipmi_smi_watcher smi_watcher =
{
.owner = THIS_MODULE,
.new_smi = ipmi_new_smi,
.smi_gone = ipmi_smi_gone,
};
static int __init init_ipmi_devintf(void)
{
int rv;
if (ipmi_major < 0)
return -EINVAL;
pr_info("ipmi device interface\n");
rv = class_register(&ipmi_class);
if (rv)
return rv;
rv = register_chrdev(ipmi_major, DEVICE_NAME, &ipmi_fops);
if (rv < 0) {
class_unregister(&ipmi_class);
pr_err("ipmi: can't get major %d\n", ipmi_major);
return rv;
}
if (ipmi_major == 0) {
ipmi_major = rv;
}
rv = ipmi_smi_watcher_register(&smi_watcher);
if (rv) {
unregister_chrdev(ipmi_major, DEVICE_NAME);
class_unregister(&ipmi_class);
pr_warn("ipmi: can't register smi watcher\n");
return rv;
}
return 0;
}
module_init(init_ipmi_devintf);
static void __exit cleanup_ipmi(void)
{
struct ipmi_reg_list *entry, *entry2;
mutex_lock(&reg_list_mutex);
list_for_each_entry_safe(entry, entry2, &reg_list, link) {
list_del(&entry->link);
device_destroy(&ipmi_class, entry->dev);
kfree(entry);
}
mutex_unlock(&reg_list_mutex);
class_unregister(&ipmi_class);
ipmi_smi_watcher_unregister(&smi_watcher);
unregister_chrdev(ipmi_major, DEVICE_NAME);
}
module_exit(cleanup_ipmi);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Corey Minyard <minyard@mvista.com>");
MODULE_DESCRIPTION("Linux device interface for the IPMI message handler.");
+223
View File
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* A hack to create a platform device from a DMI entry. This will
* allow autoloading of the IPMI drive based on SMBIOS entries.
*/
#define pr_fmt(fmt) "%s" fmt, "ipmi:dmi: "
#define dev_fmt pr_fmt
#include <linux/ipmi.h>
#include <linux/init.h>
#include <linux/dmi.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include "ipmi_dmi.h"
#include "ipmi_plat_data.h"
#define IPMI_DMI_TYPE_KCS 0x01
#define IPMI_DMI_TYPE_SMIC 0x02
#define IPMI_DMI_TYPE_BT 0x03
#define IPMI_DMI_TYPE_SSIF 0x04
struct ipmi_dmi_info {
enum si_type si_type;
unsigned int space; /* addr space for si, intf# for ssif */
unsigned long addr;
u8 slave_addr;
struct ipmi_dmi_info *next;
};
static struct ipmi_dmi_info *ipmi_dmi_infos;
static int ipmi_dmi_nr __initdata;
static void __init dmi_add_platform_ipmi(unsigned long base_addr,
unsigned int space,
u8 slave_addr,
int irq,
int offset,
int type)
{
const char *name;
struct ipmi_dmi_info *info;
struct ipmi_plat_data p;
memset(&p, 0, sizeof(p));
name = "dmi-ipmi-si";
p.iftype = IPMI_PLAT_IF_SI;
switch (type) {
case IPMI_DMI_TYPE_SSIF:
name = "dmi-ipmi-ssif";
p.iftype = IPMI_PLAT_IF_SSIF;
p.type = SI_TYPE_INVALID;
break;
case IPMI_DMI_TYPE_BT:
p.type = SI_BT;
break;
case IPMI_DMI_TYPE_KCS:
p.type = SI_KCS;
break;
case IPMI_DMI_TYPE_SMIC:
p.type = SI_SMIC;
break;
default:
pr_err("Invalid IPMI type: %d\n", type);
return;
}
p.addr = base_addr;
p.space = space;
p.regspacing = offset;
p.irq = irq;
p.slave_addr = slave_addr;
p.addr_source = SI_SMBIOS;
info = kmalloc_obj(*info);
if (!info) {
pr_warn("Could not allocate dmi info\n");
} else {
info->si_type = p.type;
info->space = space;
info->addr = base_addr;
info->slave_addr = slave_addr;
info->next = ipmi_dmi_infos;
ipmi_dmi_infos = info;
}
if (ipmi_platform_add(name, ipmi_dmi_nr, &p))
ipmi_dmi_nr++;
}
/*
* Look up the slave address for a given interface. This is here
* because ACPI doesn't have a slave address while SMBIOS does, but we
* prefer using ACPI so the ACPI code can use the IPMI namespace.
* This function allows an ACPI-specified IPMI device to look up the
* slave address from the DMI table.
*/
int ipmi_dmi_get_slave_addr(enum si_type si_type, unsigned int space,
unsigned long base_addr)
{
struct ipmi_dmi_info *info = ipmi_dmi_infos;
while (info) {
if (info->si_type == si_type &&
info->space == space &&
info->addr == base_addr)
return info->slave_addr;
info = info->next;
}
return 0;
}
EXPORT_SYMBOL(ipmi_dmi_get_slave_addr);
#define DMI_IPMI_MIN_LENGTH 0x10
#define DMI_IPMI_VER2_LENGTH 0x12
#define DMI_IPMI_TYPE 4
#define DMI_IPMI_SLAVEADDR 6
#define DMI_IPMI_ADDR 8
#define DMI_IPMI_ACCESS 0x10
#define DMI_IPMI_IRQ 0x11
#define DMI_IPMI_IO_MASK 0xfffe
static void __init dmi_decode_ipmi(const struct dmi_header *dm)
{
const u8 *data = (const u8 *) dm;
int space = IPMI_IO_ADDR_SPACE;
unsigned long base_addr;
u8 len = dm->length;
u8 slave_addr;
int irq = 0, offset = 0;
int type;
if (len < DMI_IPMI_MIN_LENGTH)
return;
type = data[DMI_IPMI_TYPE];
slave_addr = data[DMI_IPMI_SLAVEADDR];
memcpy(&base_addr, data + DMI_IPMI_ADDR, sizeof(unsigned long));
if (!base_addr) {
pr_err("Base address is zero, assuming no IPMI interface\n");
return;
}
if (len >= DMI_IPMI_VER2_LENGTH) {
if (type == IPMI_DMI_TYPE_SSIF) {
space = 0; /* Match I2C interface 0. */
base_addr = data[DMI_IPMI_ADDR] >> 1;
if (base_addr == 0) {
/*
* Some broken systems put the I2C address in
* the slave address field. We try to
* accommodate them here.
*/
base_addr = data[DMI_IPMI_SLAVEADDR] >> 1;
slave_addr = 0;
}
} else {
if (base_addr & 1) {
/* I/O */
base_addr &= DMI_IPMI_IO_MASK;
} else {
/* Memory */
space = IPMI_MEM_ADDR_SPACE;
}
/*
* If bit 4 of byte 0x10 is set, then the lsb
* for the address is odd.
*/
base_addr |= (data[DMI_IPMI_ACCESS] >> 4) & 1;
irq = data[DMI_IPMI_IRQ];
/*
* The top two bits of byte 0x10 hold the
* register spacing.
*/
switch ((data[DMI_IPMI_ACCESS] >> 6) & 3) {
case 0: /* Byte boundaries */
offset = 1;
break;
case 1: /* 32-bit boundaries */
offset = 4;
break;
case 2: /* 16-byte boundaries */
offset = 16;
break;
default:
pr_err("Invalid offset: 0\n");
return;
}
}
} else {
/* Old DMI spec. */
/*
* Note that technically, the lower bit of the base
* address should be 1 if the address is I/O and 0 if
* the address is in memory. So many systems get that
* wrong (and all that I have seen are I/O) so we just
* ignore that bit and assume I/O. Systems that use
* memory should use the newer spec, anyway.
*/
base_addr = base_addr & DMI_IPMI_IO_MASK;
offset = 1;
}
dmi_add_platform_ipmi(base_addr, space, slave_addr, irq,
offset, type);
}
static int __init scan_for_dmi_ipmi(void)
{
const struct dmi_device *dev = NULL;
while ((dev = dmi_find_device(DMI_DEV_TYPE_IPMI, NULL, dev)))
dmi_decode_ipmi((const struct dmi_header *) dev->device_data);
return 0;
}
subsys_initcall(scan_for_dmi_ipmi);
+10
View File
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* DMI defines for use by IPMI
*/
#include "ipmi_si.h"
#ifdef CONFIG_IPMI_DMI_DECODE
int ipmi_dmi_get_slave_addr(enum si_type si_type, unsigned int space,
unsigned long base_addr);
#endif
+588
View File
@@ -0,0 +1,588 @@
// 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(retry_time_ms, "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 i2c_client *slave;
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:
*val = 0xff;
ipmi_ipmb_check_msg_done(iidev);
break;
case I2C_SLAVE_STOP:
ipmi_ipmb_check_msg_done(iidev);
break;
case I2C_SLAVE_READ_PROCESSED:
*val = 0xff;
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 need to return a
* response to the 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->slave->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 int 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);
return IPMI_CC_NO_ERROR;
}
static void ipmi_ipmb_request_events(void *send_info)
{
/* We don't fetch events here. */
}
static void ipmi_ipmb_cleanup(struct ipmi_ipmb_dev *iidev)
{
if (iidev->slave) {
i2c_slave_unregister(iidev->slave);
if (iidev->slave != iidev->client)
i2c_unregister_device(iidev->slave);
}
iidev->slave = NULL;
iidev->client = NULL;
ipmi_ipmb_stop_thread(iidev);
}
static void ipmi_ipmb_remove(struct i2c_client *client)
{
struct ipmi_ipmb_dev *iidev = i2c_get_clientdata(client);
ipmi_ipmb_cleanup(iidev);
ipmi_unregister_smi(iidev->intf);
}
static int ipmi_ipmb_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct ipmi_ipmb_dev *iidev;
struct device_node *slave_np;
struct i2c_adapter *slave_adap = NULL;
struct i2c_client *slave = NULL;
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;
slave_np = of_parse_phandle(dev->of_node, "slave-dev", 0);
if (slave_np) {
slave_adap = of_get_i2c_adapter_by_node(slave_np);
of_node_put(slave_np);
if (!slave_adap) {
dev_notice(&client->dev,
"Could not find slave adapter\n");
return -EINVAL;
}
}
iidev->client = client;
if (slave_adap) {
struct i2c_board_info binfo;
memset(&binfo, 0, sizeof(binfo));
strscpy(binfo.type, "ipmb-slave", I2C_NAME_SIZE);
binfo.addr = client->addr;
binfo.flags = I2C_CLIENT_SLAVE;
slave = i2c_new_client_device(slave_adap, &binfo);
i2c_put_adapter(slave_adap);
if (IS_ERR(slave)) {
rv = PTR_ERR(slave);
dev_notice(&client->dev,
"Could not allocate slave device: %d\n", rv);
return rv;
}
i2c_set_clientdata(slave, iidev);
} else {
slave = client;
}
i2c_set_clientdata(client, iidev);
slave->flags |= I2C_CLIENT_SLAVE;
rv = i2c_slave_register(slave, ipmi_ipmb_slave_cb);
if (rv)
goto out_err;
iidev->slave = slave;
slave = NULL;
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:
if (slave && slave != client)
i2c_unregister_device(slave);
ipmi_ipmb_cleanup(iidev);
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[] = {
{ .name = DEVICE_NAME },
{ }
};
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");
+536
View File
@@ -0,0 +1,536 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* ipmi_kcs_sm.c
*
* State machine for handling IPMI KCS interfaces.
*
* Author: MontaVista Software, Inc.
* Corey Minyard <minyard@mvista.com>
* source@mvista.com
*
* Copyright 2002 MontaVista Software Inc.
*/
/*
* This state machine is taken from the state machine in the IPMI spec,
* pretty much verbatim. If you have questions about the states, see
* that document.
*/
#define DEBUG /* So dev_dbg() is always available. */
#include <linux/kernel.h> /* For printk. */
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/string.h>
#include <linux/jiffies.h>
#include <linux/ipmi_msgdefs.h> /* for completion codes */
#include "ipmi_si_sm.h"
/* kcs_debug is a bit-field
* KCS_DEBUG_ENABLE - turned on for now
* KCS_DEBUG_MSG - commands and their responses
* KCS_DEBUG_STATES - state machine
*/
#define KCS_DEBUG_STATES 4
#define KCS_DEBUG_MSG 2
#define KCS_DEBUG_ENABLE 1
static int kcs_debug;
module_param(kcs_debug, int, 0644);
MODULE_PARM_DESC(kcs_debug, "debug bitmask, 1=enable, 2=messages, 4=states");
/* The states the KCS driver may be in. */
enum kcs_states {
/* The KCS interface is currently doing nothing. */
KCS_IDLE,
/*
* We are starting an operation. The data is in the output
* buffer, but nothing has been done to the interface yet. This
* was added to the state machine in the spec to wait for the
* initial IBF.
*/
KCS_START_OP,
/* We have written a write cmd to the interface. */
KCS_WAIT_WRITE_START,
/* We are writing bytes to the interface. */
KCS_WAIT_WRITE,
/*
* We have written the write end cmd to the interface, and
* still need to write the last byte.
*/
KCS_WAIT_WRITE_END,
/* We are waiting to read data from the interface. */
KCS_WAIT_READ,
/*
* State to transition to the error handler, this was added to
* the state machine in the spec to be sure IBF was there.
*/
KCS_ERROR0,
/*
* First stage error handler, wait for the interface to
* respond.
*/
KCS_ERROR1,
/*
* The abort cmd has been written, wait for the interface to
* respond.
*/
KCS_ERROR2,
/*
* We wrote some data to the interface, wait for it to switch
* to read mode.
*/
KCS_ERROR3,
/* The hardware failed to follow the state machine. */
KCS_HOSED
};
#define MAX_KCS_READ_SIZE IPMI_MAX_MSG_LENGTH
#define MAX_KCS_WRITE_SIZE IPMI_MAX_MSG_LENGTH
/* Timeouts in microseconds. */
#define IBF_RETRY_TIMEOUT (5*USEC_PER_SEC)
#define OBF_RETRY_TIMEOUT (5*USEC_PER_SEC)
#define MAX_ERROR_RETRIES 2
#define ERROR0_OBF_WAIT_JIFFIES (2*HZ)
struct si_sm_data {
enum kcs_states state;
struct si_sm_io *io;
unsigned char write_data[MAX_KCS_WRITE_SIZE];
int write_pos;
int write_count;
int orig_write_count;
unsigned char read_data[MAX_KCS_READ_SIZE];
int read_pos;
int truncated;
unsigned int error_retries;
long ibf_timeout;
long obf_timeout;
unsigned long error0_timeout;
};
static unsigned int init_kcs_data(struct si_sm_data *kcs,
struct si_sm_io *io)
{
kcs->state = KCS_IDLE;
kcs->io = io;
kcs->write_pos = 0;
kcs->write_count = 0;
kcs->orig_write_count = 0;
kcs->read_pos = 0;
kcs->error_retries = 0;
kcs->truncated = 0;
kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
kcs->obf_timeout = OBF_RETRY_TIMEOUT;
/* Reserve 2 I/O bytes. */
return 2;
}
static inline unsigned char read_status(struct si_sm_data *kcs)
{
return kcs->io->inputb(kcs->io, 1);
}
static inline unsigned char read_data(struct si_sm_data *kcs)
{
return kcs->io->inputb(kcs->io, 0);
}
static inline void write_cmd(struct si_sm_data *kcs, unsigned char data)
{
kcs->io->outputb(kcs->io, 1, data);
}
static inline void write_data(struct si_sm_data *kcs, unsigned char data)
{
kcs->io->outputb(kcs->io, 0, data);
}
/* Control codes. */
#define KCS_GET_STATUS_ABORT 0x60
#define KCS_WRITE_START 0x61
#define KCS_WRITE_END 0x62
#define KCS_READ_BYTE 0x68
/* Status bits. */
#define GET_STATUS_STATE(status) (((status) >> 6) & 0x03)
#define KCS_IDLE_STATE 0
#define KCS_READ_STATE 1
#define KCS_WRITE_STATE 2
#define KCS_ERROR_STATE 3
#define GET_STATUS_ATN(status) ((status) & 0x04)
#define GET_STATUS_IBF(status) ((status) & 0x02)
#define GET_STATUS_OBF(status) ((status) & 0x01)
static inline void write_next_byte(struct si_sm_data *kcs)
{
write_data(kcs, kcs->write_data[kcs->write_pos]);
(kcs->write_pos)++;
(kcs->write_count)--;
}
static inline void start_error_recovery(struct si_sm_data *kcs, char *reason)
{
(kcs->error_retries)++;
if (kcs->error_retries > MAX_ERROR_RETRIES) {
if (kcs_debug & KCS_DEBUG_ENABLE)
dev_dbg(kcs->io->dev, "ipmi_kcs_sm: kcs hosed: %s\n",
reason);
kcs->state = KCS_HOSED;
} else {
kcs->error0_timeout = jiffies + ERROR0_OBF_WAIT_JIFFIES;
kcs->state = KCS_ERROR0;
}
}
static inline void read_next_byte(struct si_sm_data *kcs)
{
if (kcs->read_pos >= MAX_KCS_READ_SIZE) {
/* Throw the data away and mark it truncated. */
read_data(kcs);
kcs->truncated = 1;
} else {
kcs->read_data[kcs->read_pos] = read_data(kcs);
(kcs->read_pos)++;
}
write_data(kcs, KCS_READ_BYTE);
}
static inline int check_ibf(struct si_sm_data *kcs, unsigned char status,
long time)
{
if (GET_STATUS_IBF(status)) {
kcs->ibf_timeout -= time;
if (kcs->ibf_timeout < 0) {
start_error_recovery(kcs, "IBF not ready in time");
kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
return 1;
}
return 0;
}
kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
return 1;
}
static inline int check_obf(struct si_sm_data *kcs, unsigned char status,
long time)
{
if (!GET_STATUS_OBF(status)) {
kcs->obf_timeout -= time;
if (kcs->obf_timeout < 0) {
kcs->obf_timeout = OBF_RETRY_TIMEOUT;
start_error_recovery(kcs, "OBF not ready in time");
return 1;
}
return 0;
}
kcs->obf_timeout = OBF_RETRY_TIMEOUT;
return 1;
}
static void clear_obf(struct si_sm_data *kcs, unsigned char status)
{
if (GET_STATUS_OBF(status))
read_data(kcs);
}
static void restart_kcs_transaction(struct si_sm_data *kcs)
{
kcs->write_count = kcs->orig_write_count;
kcs->write_pos = 0;
kcs->read_pos = 0;
kcs->state = KCS_WAIT_WRITE_START;
kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
kcs->obf_timeout = OBF_RETRY_TIMEOUT;
write_cmd(kcs, KCS_WRITE_START);
}
static int start_kcs_transaction(struct si_sm_data *kcs, unsigned char *data,
unsigned int size)
{
unsigned int i;
if (size < 2)
return IPMI_REQ_LEN_INVALID_ERR;
if (size > MAX_KCS_WRITE_SIZE)
return IPMI_REQ_LEN_EXCEEDED_ERR;
if ((kcs->state != KCS_IDLE) && (kcs->state != KCS_HOSED)) {
dev_warn(kcs->io->dev, "KCS in invalid state %d\n", kcs->state);
return IPMI_NOT_IN_MY_STATE_ERR;
}
if (kcs_debug & KCS_DEBUG_MSG) {
dev_dbg(kcs->io->dev, "%s -", __func__);
for (i = 0; i < size; i++)
pr_cont(" %02x", data[i]);
pr_cont("\n");
}
kcs->error_retries = 0;
memcpy(kcs->write_data, data, size);
kcs->write_count = size;
kcs->orig_write_count = size;
kcs->write_pos = 0;
kcs->read_pos = 0;
kcs->state = KCS_START_OP;
kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
kcs->obf_timeout = OBF_RETRY_TIMEOUT;
return 0;
}
static int get_kcs_result(struct si_sm_data *kcs, unsigned char *data,
unsigned int length)
{
if (length < kcs->read_pos) {
kcs->read_pos = length;
kcs->truncated = 1;
}
memcpy(data, kcs->read_data, kcs->read_pos);
if ((length >= 3) && (kcs->read_pos < 3)) {
/* Guarantee that we return at least 3 bytes, with an
error in the third byte if it is too short. */
data[2] = IPMI_ERR_UNSPECIFIED;
kcs->read_pos = 3;
}
if (kcs->truncated) {
/*
* Report a truncated error. We might overwrite
* another error, but that's too bad, the user needs
* to know it was truncated.
*/
data[2] = IPMI_ERR_MSG_TRUNCATED;
kcs->truncated = 0;
}
return kcs->read_pos;
}
/*
* This implements the state machine defined in the IPMI manual, see
* that for details on how this works. Divide that flowchart into
* sections delimited by "Wait for IBF" and this will become clear.
*/
static enum si_sm_result kcs_event(struct si_sm_data *kcs, long time)
{
unsigned char status;
unsigned char state;
status = read_status(kcs);
if (kcs_debug & KCS_DEBUG_STATES)
dev_dbg(kcs->io->dev,
"KCS: State = %d, %x\n", kcs->state, status);
/* All states wait for ibf, so just do it here. */
if (!check_ibf(kcs, status, time))
return SI_SM_CALL_WITH_DELAY;
/* Just about everything looks at the KCS state, so grab that, too. */
state = GET_STATUS_STATE(status);
switch (kcs->state) {
case KCS_IDLE:
/* If there's and interrupt source, turn it off. */
clear_obf(kcs, status);
if (GET_STATUS_ATN(status))
return SI_SM_ATTN;
else
return SI_SM_IDLE;
case KCS_START_OP:
if (state != KCS_IDLE_STATE) {
start_error_recovery(kcs,
"State machine not idle at start");
break;
}
clear_obf(kcs, status);
write_cmd(kcs, KCS_WRITE_START);
kcs->state = KCS_WAIT_WRITE_START;
break;
case KCS_WAIT_WRITE_START:
if (state != KCS_WRITE_STATE) {
start_error_recovery(
kcs,
"Not in write state at write start");
break;
}
read_data(kcs);
if (kcs->write_count == 1) {
write_cmd(kcs, KCS_WRITE_END);
kcs->state = KCS_WAIT_WRITE_END;
} else {
write_next_byte(kcs);
kcs->state = KCS_WAIT_WRITE;
}
break;
case KCS_WAIT_WRITE:
if (state != KCS_WRITE_STATE) {
start_error_recovery(kcs,
"Not in write state for write");
break;
}
clear_obf(kcs, status);
if (kcs->write_count == 1) {
write_cmd(kcs, KCS_WRITE_END);
kcs->state = KCS_WAIT_WRITE_END;
} else {
write_next_byte(kcs);
}
break;
case KCS_WAIT_WRITE_END:
if (state != KCS_WRITE_STATE) {
start_error_recovery(kcs,
"Not in write state"
" for write end");
break;
}
clear_obf(kcs, status);
write_next_byte(kcs);
kcs->state = KCS_WAIT_READ;
break;
case KCS_WAIT_READ:
if ((state != KCS_READ_STATE) && (state != KCS_IDLE_STATE)) {
start_error_recovery(
kcs,
"Not in read or idle in read state");
break;
}
if (state == KCS_READ_STATE) {
if (!check_obf(kcs, status, time))
return SI_SM_CALL_WITH_DELAY;
read_next_byte(kcs);
} else {
/*
* We don't implement this exactly like the state
* machine in the spec. Some broken hardware
* does not write the final dummy byte to the
* read register. Thus obf will never go high
* here. We just go straight to idle, and we
* handle clearing out obf in idle state if it
* happens to come in.
*/
clear_obf(kcs, status);
kcs->orig_write_count = 0;
kcs->state = KCS_IDLE;
return SI_SM_TRANSACTION_COMPLETE;
}
break;
case KCS_ERROR0:
clear_obf(kcs, status);
status = read_status(kcs);
if (GET_STATUS_OBF(status))
/* controller isn't responding */
if (time_before(jiffies, kcs->error0_timeout))
return SI_SM_CALL_WITH_TICK_DELAY;
write_cmd(kcs, KCS_GET_STATUS_ABORT);
kcs->state = KCS_ERROR1;
break;
case KCS_ERROR1:
clear_obf(kcs, status);
write_data(kcs, 0);
kcs->state = KCS_ERROR2;
break;
case KCS_ERROR2:
if (state != KCS_READ_STATE) {
start_error_recovery(kcs,
"Not in read state for error2");
break;
}
if (!check_obf(kcs, status, time))
return SI_SM_CALL_WITH_DELAY;
clear_obf(kcs, status);
write_data(kcs, KCS_READ_BYTE);
kcs->state = KCS_ERROR3;
break;
case KCS_ERROR3:
if (state != KCS_IDLE_STATE) {
start_error_recovery(kcs,
"Not in idle state for error3");
break;
}
if (!check_obf(kcs, status, time))
return SI_SM_CALL_WITH_DELAY;
clear_obf(kcs, status);
if (kcs->orig_write_count) {
restart_kcs_transaction(kcs);
} else {
kcs->state = KCS_IDLE;
return SI_SM_TRANSACTION_COMPLETE;
}
break;
case KCS_HOSED:
break;
}
if (kcs->state == KCS_HOSED) {
init_kcs_data(kcs, kcs->io);
return SI_SM_HOSED;
}
return SI_SM_CALL_WITHOUT_DELAY;
}
static int kcs_size(void)
{
return sizeof(struct si_sm_data);
}
static int kcs_detect(struct si_sm_data *kcs)
{
/*
* It's impossible for the KCS status register to be all 1's,
* (assuming a properly functioning, self-initialized BMC)
* but that's what you get from reading a bogus address, so we
* test that first.
*/
if (read_status(kcs) == 0xff)
return 1;
return 0;
}
static void kcs_cleanup(struct si_sm_data *kcs)
{
}
const struct si_sm_handlers kcs_smi_handlers = {
.init_data = init_kcs_data,
.start_transaction = start_kcs_transaction,
.get_result = get_kcs_result,
.event = kcs_event,
.detect = kcs_detect,
.cleanup = kcs_cleanup,
.size = kcs_size,
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Add an IPMI platform device.
*/
#include <linux/platform_device.h>
#include "ipmi_plat_data.h"
#include "ipmi_si.h"
struct platform_device *ipmi_platform_add(const char *name, unsigned int inst,
struct ipmi_plat_data *p)
{
struct platform_device *pdev;
unsigned int num_r = 1, size = 0, pidx = 0;
struct resource r[4];
struct property_entry pr[6];
u32 flags;
int rv;
memset(pr, 0, sizeof(pr));
memset(r, 0, sizeof(r));
if (p->iftype == IPMI_PLAT_IF_SI) {
if (p->type == SI_BT)
size = 3;
else if (p->type != SI_TYPE_INVALID)
size = 2;
if (p->regsize == 0)
p->regsize = DEFAULT_REGSIZE;
if (p->regspacing == 0)
p->regspacing = p->regsize;
pr[pidx++] = PROPERTY_ENTRY_U8("ipmi-type", p->type);
} else if (p->iftype == IPMI_PLAT_IF_SSIF) {
pr[pidx++] = PROPERTY_ENTRY_U16("i2c-addr", p->addr);
}
if (p->slave_addr)
pr[pidx++] = PROPERTY_ENTRY_U8("slave-addr", p->slave_addr);
pr[pidx++] = PROPERTY_ENTRY_U8("addr-source", p->addr_source);
if (p->regshift)
pr[pidx++] = PROPERTY_ENTRY_U8("reg-shift", p->regshift);
pr[pidx++] = PROPERTY_ENTRY_U8("reg-size", p->regsize);
/* Last entry must be left NULL to terminate it. */
pdev = platform_device_alloc(name, inst);
if (!pdev) {
pr_err("Error allocating IPMI platform device %s.%d\n",
name, inst);
return NULL;
}
if (size == 0)
/* An invalid or SSIF interface, no resources. */
goto add_properties;
/*
* Register spacing is derived from the resources in
* the IPMI platform code.
*/
if (p->space == IPMI_IO_ADDR_SPACE)
flags = IORESOURCE_IO;
else
flags = IORESOURCE_MEM;
r[0].start = p->addr;
r[0].end = r[0].start + p->regsize - 1;
r[0].name = "IPMI Address 1";
r[0].flags = flags;
if (size > 1) {
r[1].start = r[0].start + p->regspacing;
r[1].end = r[1].start + p->regsize - 1;
r[1].name = "IPMI Address 2";
r[1].flags = flags;
num_r++;
}
if (size > 2) {
r[2].start = r[1].start + p->regspacing;
r[2].end = r[2].start + p->regsize - 1;
r[2].name = "IPMI Address 3";
r[2].flags = flags;
num_r++;
}
if (p->irq) {
r[num_r].start = p->irq;
r[num_r].end = p->irq;
r[num_r].name = "IPMI IRQ";
r[num_r].flags = IORESOURCE_IRQ;
num_r++;
}
rv = platform_device_add_resources(pdev, r, num_r);
if (rv) {
dev_err(&pdev->dev,
"Unable to add hard-code resources: %d\n", rv);
goto err;
}
add_properties:
rv = device_create_managed_software_node(&pdev->dev, pr, NULL);
if (rv) {
dev_err(&pdev->dev,
"Unable to add hard-code properties: %d\n", rv);
goto err;
}
rv = platform_device_add(pdev);
if (rv) {
dev_err(&pdev->dev,
"Unable to add hard-code device: %d\n", rv);
goto err;
}
return pdev;
err:
platform_device_put(pdev);
return NULL;
}
EXPORT_SYMBOL(ipmi_platform_add);
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Generic code to add IPMI platform devices.
*/
#include <linux/ipmi.h>
enum ipmi_plat_interface_type { IPMI_PLAT_IF_SI, IPMI_PLAT_IF_SSIF };
struct ipmi_plat_data {
enum ipmi_plat_interface_type iftype;
unsigned int type; /* si_type for si, SI_INVALID for others */
unsigned int space; /* addr_space for si, intf# for ssif. */
unsigned long addr;
unsigned int regspacing;
unsigned int regsize;
unsigned int regshift;
unsigned int irq;
unsigned int slave_addr;
enum ipmi_addr_src addr_source;
};
struct platform_device *ipmi_platform_add(const char *name, unsigned int inst,
struct ipmi_plat_data *p);
@@ -0,0 +1,315 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* PowerNV OPAL IPMI driver
*
* Copyright 2014 IBM Corp.
*/
#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 <linux/of_irq.h>
#include <linux/interrupt.h>
#include <asm/opal.h>
struct ipmi_smi_powernv {
u64 interface_id;
struct ipmi_smi *intf;
unsigned int irq;
/**
* 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, struct ipmi_smi *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 int 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) {
comp = IPMI_ERR_UNSPECIFIED;
goto err_unlock;
}
smi->cur_msg = msg;
spin_unlock_irqrestore(&smi->msg_lock, flags);
return IPMI_CC_NO_ERROR;
err_unlock:
spin_unlock_irqrestore(&smi->msg_lock, flags);
err:
return 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) {
spin_unlock_irqrestore(&smi->msg_lock, flags);
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) {
/* If came via the poll, and response was not yet ready */
if (rc == OPAL_EMPTY) {
spin_unlock_irqrestore(&smi->msg_lock, flags);
return 0;
}
smi->cur_msg = NULL;
spin_unlock_irqrestore(&smi->msg_lock, flags);
send_error_reply(smi, msg, IPMI_ERR_UNSPECIFIED);
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 const 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 irqreturn_t ipmi_opal_event(int irq, void *data)
{
struct ipmi_smi_powernv *smi = data;
ipmi_powernv_recv(smi);
return IRQ_HANDLED;
}
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->irq = irq_of_parse_and_map(dev->of_node, 0);
if (!ipmi->irq) {
dev_info(dev, "Unable to map irq from device tree\n");
ipmi->irq = opal_event_request(prop);
}
rc = request_irq(ipmi->irq, ipmi_opal_event, IRQ_TYPE_LEVEL_HIGH,
"opal-ipmi", ipmi);
if (rc) {
dev_warn(dev, "Unable to request irq\n");
goto err_dispose;
}
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;
}
rc = ipmi_register_smi(&ipmi_powernv_smi_handlers, ipmi, 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:
free_irq(ipmi->irq, ipmi);
err_dispose:
irq_dispose_mapping(ipmi->irq);
err_free:
devm_kfree(dev, ipmi);
return rc;
}
static void ipmi_powernv_remove(struct platform_device *pdev)
{
struct ipmi_smi_powernv *smi = dev_get_drvdata(&pdev->dev);
ipmi_unregister_smi(smi->intf);
free_irq(smi->irq, smi);
irq_dispose_mapping(smi->irq);
}
static const struct of_device_id ipmi_powernv_match[] = {
{ .compatible = "ibm,opal-ipmi" },
{ },
};
static struct platform_driver powernv_ipmi_driver = {
.driver = {
.name = "ipmi-powernv",
.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");
@@ -0,0 +1,719 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* ipmi_poweroff.c
*
* MontaVista IPMI Poweroff extension to sys_reboot
*
* Author: MontaVista Software, Inc.
* Steven Dake <sdake@mvista.com>
* Corey Minyard <cminyard@mvista.com>
* source@mvista.com
*
* Copyright 2002,2004 MontaVista Software Inc.
*/
#define pr_fmt(fmt) "IPMI poweroff: " fmt
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/completion.h>
#include <linux/pm.h>
#include <linux/kdev_t.h>
#include <linux/ipmi.h>
#include <linux/ipmi_smi.h>
static void ipmi_po_smi_gone(int if_num);
static void ipmi_po_new_smi(int if_num, struct device *device);
/* Definitions for controlling power off (if the system supports it). It
* conveniently matches the IPMI chassis control values. */
#define IPMI_CHASSIS_POWER_DOWN 0 /* power down, the default. */
#define IPMI_CHASSIS_POWER_CYCLE 0x02 /* power cycle */
/* the IPMI data command */
static int poweroff_powercycle;
/* Which interface to use, -1 means the first we see. */
static int ifnum_to_use = -1;
/* Our local state. */
static int ready;
static struct ipmi_user *ipmi_user;
static int ipmi_ifnum;
static void (*specific_poweroff_func)(struct ipmi_user *user);
/* Holds the old poweroff function so we can restore it on removal. */
static void (*old_poweroff_func)(void);
static int set_param_ifnum(const char *val, const struct kernel_param *kp)
{
int rv = param_set_int(val, kp);
if (rv)
return rv;
if ((ifnum_to_use < 0) || (ifnum_to_use == ipmi_ifnum))
return 0;
ipmi_po_smi_gone(ipmi_ifnum);
ipmi_po_new_smi(ifnum_to_use, NULL);
return 0;
}
module_param_call(ifnum_to_use, set_param_ifnum, param_get_int,
&ifnum_to_use, 0644);
MODULE_PARM_DESC(ifnum_to_use, "The interface number to use for the watchdog "
"timer. Setting to -1 defaults to the first registered "
"interface");
/* parameter definition to allow user to flag power cycle */
module_param(poweroff_powercycle, int, 0644);
MODULE_PARM_DESC(poweroff_powercycle,
" Set to non-zero to enable power cycle instead of power"
" down. Power cycle is contingent on hardware support,"
" otherwise it defaults back to power down.");
/* Stuff from the get device id command. */
static unsigned int mfg_id;
static unsigned int prod_id;
static unsigned char capabilities;
static unsigned char ipmi_version;
/*
* We use our own messages for this operation, we don't let the system
* allocate them, since we may be in a panic situation. The whole
* thing is single-threaded, anyway, so multiple messages are not
* required.
*/
static atomic_t dummy_count = ATOMIC_INIT(0);
static void dummy_smi_free(struct ipmi_smi_msg *msg)
{
atomic_dec(&dummy_count);
}
static void dummy_recv_free(struct ipmi_recv_msg *msg)
{
atomic_dec(&dummy_count);
}
static struct ipmi_smi_msg halt_smi_msg = INIT_IPMI_SMI_MSG(dummy_smi_free);
static struct ipmi_recv_msg halt_recv_msg = INIT_IPMI_RECV_MSG(dummy_recv_free);
/*
* Code to send a message and wait for the response.
*/
static void receive_handler(struct ipmi_recv_msg *recv_msg, void *handler_data)
{
struct completion *comp = recv_msg->user_msg_data;
if (comp)
complete(comp);
}
static const struct ipmi_user_hndl ipmi_poweroff_handler = {
.ipmi_recv_hndl = receive_handler
};
static int ipmi_request_wait_for_response(struct ipmi_user *user,
struct ipmi_addr *addr,
struct kernel_ipmi_msg *send_msg)
{
int rv;
struct completion comp;
init_completion(&comp);
rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, &comp,
&halt_smi_msg, &halt_recv_msg, 0);
if (rv)
return rv;
wait_for_completion(&comp);
return halt_recv_msg.msg.data[0];
}
/* Wait for message to complete, spinning. */
static int ipmi_request_in_rc_mode(struct ipmi_user *user,
struct ipmi_addr *addr,
struct kernel_ipmi_msg *send_msg)
{
int rv;
atomic_set(&dummy_count, 2);
rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, NULL,
&halt_smi_msg, &halt_recv_msg, 0);
if (rv) {
atomic_set(&dummy_count, 0);
return rv;
}
/*
* Spin until our message is done.
*/
while (atomic_read(&dummy_count) > 0) {
ipmi_poll_interface(user);
cpu_relax();
}
return halt_recv_msg.msg.data[0];
}
/*
* ATCA Support
*/
#define IPMI_NETFN_ATCA 0x2c
#define IPMI_ATCA_SET_POWER_CMD 0x11
#define IPMI_ATCA_GET_ADDR_INFO_CMD 0x01
#define IPMI_PICMG_ID 0
#define IPMI_NETFN_OEM 0x2e
#define IPMI_ATCA_PPS_GRACEFUL_RESTART 0x11
#define IPMI_ATCA_PPS_IANA "\x00\x40\x0A"
#define IPMI_MOTOROLA_MANUFACTURER_ID 0x0000A1
#define IPMI_MOTOROLA_PPS_IPMC_PRODUCT_ID 0x0051
static void (*atca_oem_poweroff_hook)(struct ipmi_user *user);
static void pps_poweroff_atca(struct ipmi_user *user)
{
struct ipmi_system_interface_addr smi_addr;
struct kernel_ipmi_msg send_msg;
int rv;
/*
* Configure IPMI address for local access
*/
smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
smi_addr.channel = IPMI_BMC_CHANNEL;
smi_addr.lun = 0;
pr_info("PPS powerdown hook used\n");
send_msg.netfn = IPMI_NETFN_OEM;
send_msg.cmd = IPMI_ATCA_PPS_GRACEFUL_RESTART;
send_msg.data = IPMI_ATCA_PPS_IANA;
send_msg.data_len = 3;
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv && rv != IPMI_UNKNOWN_ERR_COMPLETION_CODE)
pr_err("Unable to send ATCA, IPMI error 0x%x\n", rv);
return;
}
static int ipmi_atca_detect(struct ipmi_user *user)
{
struct ipmi_system_interface_addr smi_addr;
struct kernel_ipmi_msg send_msg;
int rv;
unsigned char data[1];
/*
* Configure IPMI address for local access
*/
smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
smi_addr.channel = IPMI_BMC_CHANNEL;
smi_addr.lun = 0;
/*
* Use get address info to check and see if we are ATCA
*/
send_msg.netfn = IPMI_NETFN_ATCA;
send_msg.cmd = IPMI_ATCA_GET_ADDR_INFO_CMD;
data[0] = IPMI_PICMG_ID;
send_msg.data = data;
send_msg.data_len = sizeof(data);
rv = ipmi_request_wait_for_response(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
pr_info("ATCA Detect mfg 0x%X prod 0x%X\n", mfg_id, prod_id);
if ((mfg_id == IPMI_MOTOROLA_MANUFACTURER_ID)
&& (prod_id == IPMI_MOTOROLA_PPS_IPMC_PRODUCT_ID)) {
pr_info("Installing Pigeon Point Systems Poweroff Hook\n");
atca_oem_poweroff_hook = pps_poweroff_atca;
}
return !rv;
}
static void ipmi_poweroff_atca(struct ipmi_user *user)
{
struct ipmi_system_interface_addr smi_addr;
struct kernel_ipmi_msg send_msg;
int rv;
unsigned char data[4];
/*
* Configure IPMI address for local access
*/
smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
smi_addr.channel = IPMI_BMC_CHANNEL;
smi_addr.lun = 0;
pr_info("Powering down via ATCA power command\n");
/*
* Power down
*/
send_msg.netfn = IPMI_NETFN_ATCA;
send_msg.cmd = IPMI_ATCA_SET_POWER_CMD;
data[0] = IPMI_PICMG_ID;
data[1] = 0; /* FRU id */
data[2] = 0; /* Power Level */
data[3] = 0; /* Don't change saved presets */
send_msg.data = data;
send_msg.data_len = sizeof(data);
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
/*
* At this point, the system may be shutting down, and most
* serial drivers (if used) will have interrupts turned off
* it may be better to ignore IPMI_UNKNOWN_ERR_COMPLETION_CODE
* return code
*/
if (rv && rv != IPMI_UNKNOWN_ERR_COMPLETION_CODE) {
pr_err("Unable to send ATCA powerdown message, IPMI error 0x%x\n",
rv);
goto out;
}
if (atca_oem_poweroff_hook)
atca_oem_poweroff_hook(user);
out:
return;
}
/*
* CPI1 Support
*/
#define IPMI_NETFN_OEM_1 0xf8
#define OEM_GRP_CMD_SET_RESET_STATE 0x84
#define OEM_GRP_CMD_SET_POWER_STATE 0x82
#define IPMI_NETFN_OEM_8 0xf8
#define OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL 0x80
#define OEM_GRP_CMD_GET_SLOT_GA 0xa3
#define IPMI_NETFN_SENSOR_EVT 0x10
#define IPMI_CMD_GET_EVENT_RECEIVER 0x01
#define IPMI_CPI1_PRODUCT_ID 0x000157
#define IPMI_CPI1_MANUFACTURER_ID 0x0108
static int ipmi_cpi1_detect(struct ipmi_user *user)
{
return ((mfg_id == IPMI_CPI1_MANUFACTURER_ID)
&& (prod_id == IPMI_CPI1_PRODUCT_ID));
}
static void ipmi_poweroff_cpi1(struct ipmi_user *user)
{
struct ipmi_system_interface_addr smi_addr;
struct ipmi_ipmb_addr ipmb_addr;
struct kernel_ipmi_msg send_msg;
int rv;
unsigned char data[1];
int slot;
unsigned char hotswap_ipmb;
unsigned char aer_addr;
unsigned char aer_lun;
/*
* Configure IPMI address for local access
*/
smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
smi_addr.channel = IPMI_BMC_CHANNEL;
smi_addr.lun = 0;
pr_info("Powering down via CPI1 power command\n");
/*
* Get IPMI ipmb address
*/
send_msg.netfn = IPMI_NETFN_OEM_8 >> 2;
send_msg.cmd = OEM_GRP_CMD_GET_SLOT_GA;
send_msg.data = NULL;
send_msg.data_len = 0;
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv)
goto out;
slot = halt_recv_msg.msg.data[1];
hotswap_ipmb = (slot > 9) ? (0xb0 + 2 * slot) : (0xae + 2 * slot);
/*
* Get active event receiver
*/
send_msg.netfn = IPMI_NETFN_SENSOR_EVT >> 2;
send_msg.cmd = IPMI_CMD_GET_EVENT_RECEIVER;
send_msg.data = NULL;
send_msg.data_len = 0;
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv)
goto out;
aer_addr = halt_recv_msg.msg.data[1];
aer_lun = halt_recv_msg.msg.data[2];
/*
* Setup IPMB address target instead of local target
*/
ipmb_addr.addr_type = IPMI_IPMB_ADDR_TYPE;
ipmb_addr.channel = 0;
ipmb_addr.slave_addr = aer_addr;
ipmb_addr.lun = aer_lun;
/*
* Send request hotswap control to remove blade from dpv
*/
send_msg.netfn = IPMI_NETFN_OEM_8 >> 2;
send_msg.cmd = OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL;
send_msg.data = &hotswap_ipmb;
send_msg.data_len = 1;
ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &ipmb_addr,
&send_msg);
/*
* Set reset asserted
*/
send_msg.netfn = IPMI_NETFN_OEM_1 >> 2;
send_msg.cmd = OEM_GRP_CMD_SET_RESET_STATE;
send_msg.data = data;
data[0] = 1; /* Reset asserted state */
send_msg.data_len = 1;
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv)
goto out;
/*
* Power down
*/
send_msg.netfn = IPMI_NETFN_OEM_1 >> 2;
send_msg.cmd = OEM_GRP_CMD_SET_POWER_STATE;
send_msg.data = data;
data[0] = 1; /* Power down state */
send_msg.data_len = 1;
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv)
goto out;
out:
return;
}
/*
* ipmi_dell_chassis_detect()
* Dell systems with IPMI < 1.5 don't set the chassis capability bit
* but they can handle a chassis poweroff or powercycle command.
*/
#define DELL_IANA_MFR_ID {0xA2, 0x02, 0x00}
static int ipmi_dell_chassis_detect(struct ipmi_user *user)
{
const char ipmi_version_major = ipmi_version & 0xF;
const char ipmi_version_minor = (ipmi_version >> 4) & 0xF;
const char mfr[3] = DELL_IANA_MFR_ID;
if (!memcmp(mfr, &mfg_id, sizeof(mfr)) &&
ipmi_version_major <= 1 &&
ipmi_version_minor < 5)
return 1;
return 0;
}
/*
* ipmi_hp_chassis_detect()
* HP PA-RISC servers rp3410/rp3440, the C8000 workstation and the rx2600 and
* zx6000 machines support IPMI vers 1 and don't set the chassis capability bit
* but they can handle a chassis poweroff or powercycle command.
*/
#define HP_IANA_MFR_ID 0x0b
#define HP_BMC_PROD_ID 0x8201
static int ipmi_hp_chassis_detect(struct ipmi_user *user)
{
if (mfg_id == HP_IANA_MFR_ID
&& prod_id == HP_BMC_PROD_ID
&& ipmi_version == 1)
return 1;
return 0;
}
/*
* Standard chassis support
*/
#define IPMI_NETFN_CHASSIS_REQUEST 0
#define IPMI_CHASSIS_CONTROL_CMD 0x02
static int ipmi_chassis_detect(struct ipmi_user *user)
{
/* Chassis support, use it. */
return (capabilities & 0x80);
}
static void ipmi_poweroff_chassis(struct ipmi_user *user)
{
struct ipmi_system_interface_addr smi_addr;
struct kernel_ipmi_msg send_msg;
int rv;
unsigned char data[1];
/*
* Configure IPMI address for local access
*/
smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
smi_addr.channel = IPMI_BMC_CHANNEL;
smi_addr.lun = 0;
powercyclefailed:
pr_info("Powering %s via IPMI chassis control command\n",
(poweroff_powercycle ? "cycle" : "down"));
/*
* Power down
*/
send_msg.netfn = IPMI_NETFN_CHASSIS_REQUEST;
send_msg.cmd = IPMI_CHASSIS_CONTROL_CMD;
if (poweroff_powercycle)
data[0] = IPMI_CHASSIS_POWER_CYCLE;
else
data[0] = IPMI_CHASSIS_POWER_DOWN;
send_msg.data = data;
send_msg.data_len = sizeof(data);
rv = ipmi_request_in_rc_mode(user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv) {
if (poweroff_powercycle) {
/* power cycle failed, default to power down */
pr_err("Unable to send chassis power cycle message, IPMI error 0x%x\n",
rv);
poweroff_powercycle = 0;
goto powercyclefailed;
}
pr_err("Unable to send chassis power down message, IPMI error 0x%x\n",
rv);
}
}
/* Table of possible power off functions. */
struct poweroff_function {
char *platform_type;
int (*detect)(struct ipmi_user *user);
void (*poweroff_func)(struct ipmi_user *user);
};
static struct poweroff_function poweroff_functions[] = {
{ .platform_type = "ATCA",
.detect = ipmi_atca_detect,
.poweroff_func = ipmi_poweroff_atca },
{ .platform_type = "CPI1",
.detect = ipmi_cpi1_detect,
.poweroff_func = ipmi_poweroff_cpi1 },
{ .platform_type = "chassis",
.detect = ipmi_dell_chassis_detect,
.poweroff_func = ipmi_poweroff_chassis },
{ .platform_type = "chassis",
.detect = ipmi_hp_chassis_detect,
.poweroff_func = ipmi_poweroff_chassis },
/* Chassis should generally be last, other things should override
it. */
{ .platform_type = "chassis",
.detect = ipmi_chassis_detect,
.poweroff_func = ipmi_poweroff_chassis },
};
#define NUM_PO_FUNCS ARRAY_SIZE(poweroff_functions)
/* Called on a powerdown request. */
static void ipmi_poweroff_function(void)
{
if (!ready)
return;
/* Use run-to-completion mode, since interrupts may be off. */
specific_poweroff_func(ipmi_user);
}
/* Wait for an IPMI interface to be installed, the first one installed
will be grabbed by this code and used to perform the powerdown. */
static void ipmi_po_new_smi(int if_num, struct device *device)
{
struct ipmi_system_interface_addr smi_addr;
struct kernel_ipmi_msg send_msg;
int rv;
int i;
if (ready)
return;
if ((ifnum_to_use >= 0) && (ifnum_to_use != if_num))
return;
rv = ipmi_create_user(if_num, &ipmi_poweroff_handler, NULL,
&ipmi_user);
if (rv) {
pr_err("could not create IPMI user, error %d\n", rv);
return;
}
ipmi_ifnum = if_num;
/*
* Do a get device ide and store some results, since this is
* used by several functions.
*/
smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
smi_addr.channel = IPMI_BMC_CHANNEL;
smi_addr.lun = 0;
send_msg.netfn = IPMI_NETFN_APP_REQUEST;
send_msg.cmd = IPMI_GET_DEVICE_ID_CMD;
send_msg.data = NULL;
send_msg.data_len = 0;
rv = ipmi_request_wait_for_response(ipmi_user,
(struct ipmi_addr *) &smi_addr,
&send_msg);
if (rv) {
pr_err("Unable to send IPMI get device id info, IPMI error 0x%x\n",
rv);
goto out_err;
}
if (halt_recv_msg.msg.data_len < 12) {
pr_err("(chassis) IPMI get device id info too short, was %d bytes, needed %d bytes\n",
halt_recv_msg.msg.data_len, 12);
goto out_err;
}
mfg_id = (halt_recv_msg.msg.data[7]
| (halt_recv_msg.msg.data[8] << 8)
| (halt_recv_msg.msg.data[9] << 16));
prod_id = (halt_recv_msg.msg.data[10]
| (halt_recv_msg.msg.data[11] << 8));
capabilities = halt_recv_msg.msg.data[6];
ipmi_version = halt_recv_msg.msg.data[5];
/* Scan for a poweroff method */
for (i = 0; i < NUM_PO_FUNCS; i++) {
if (poweroff_functions[i].detect(ipmi_user))
goto found;
}
out_err:
pr_err("Unable to find a poweroff function that will work, giving up\n");
ipmi_destroy_user(ipmi_user);
return;
found:
pr_info("Found a %s style poweroff function\n",
poweroff_functions[i].platform_type);
specific_poweroff_func = poweroff_functions[i].poweroff_func;
old_poweroff_func = pm_power_off;
pm_power_off = ipmi_poweroff_function;
ready = 1;
}
static void ipmi_po_smi_gone(int if_num)
{
if (!ready)
return;
if (ipmi_ifnum != if_num)
return;
ready = 0;
ipmi_destroy_user(ipmi_user);
pm_power_off = old_poweroff_func;
}
static struct ipmi_smi_watcher smi_watcher = {
.owner = THIS_MODULE,
.new_smi = ipmi_po_new_smi,
.smi_gone = ipmi_po_smi_gone
};
#ifdef CONFIG_PROC_FS
#include <linux/sysctl.h>
static const struct ctl_table ipmi_table[] = {
{ .procname = "poweroff_powercycle",
.data = &poweroff_powercycle,
.maxlen = sizeof(poweroff_powercycle),
.mode = 0644,
.proc_handler = proc_dointvec },
};
static struct ctl_table_header *ipmi_table_header;
#endif /* CONFIG_PROC_FS */
/*
* Startup and shutdown functions.
*/
static int __init ipmi_poweroff_init(void)
{
int rv;
pr_info("Copyright (C) 2004 MontaVista Software - IPMI Powerdown via sys_reboot\n");
if (poweroff_powercycle)
pr_info("Power cycle is enabled\n");
#ifdef CONFIG_PROC_FS
ipmi_table_header = register_sysctl("dev/ipmi", ipmi_table);
if (!ipmi_table_header) {
pr_err("Unable to register powercycle sysctl\n");
rv = -ENOMEM;
goto out_err;
}
#endif
rv = ipmi_smi_watcher_register(&smi_watcher);
#ifdef CONFIG_PROC_FS
if (rv) {
unregister_sysctl_table(ipmi_table_header);
pr_err("Unable to register SMI watcher: %d\n", rv);
goto out_err;
}
out_err:
#endif
return rv;
}
#ifdef MODULE
static void __exit ipmi_poweroff_cleanup(void)
{
#ifdef CONFIG_PROC_FS
unregister_sysctl_table(ipmi_table_header);
#endif
ipmi_smi_watcher_unregister(&smi_watcher);
if (ready) {
ipmi_destroy_user(ipmi_user);
pm_power_off = old_poweroff_func;
}
}
module_exit(ipmi_poweroff_cleanup);
#endif
module_init(ipmi_poweroff_init);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Corey Minyard <minyard@mvista.com>");
MODULE_DESCRIPTION("IPMI Poweroff extension to sys_reboot");
+122
View File
@@ -0,0 +1,122 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* ipmi_si.h
*
* Interface from the device-specific interfaces (OF, DMI, ACPI, PCI,
* etc) to the base ipmi system interface code.
*/
#ifndef __IPMI_SI_H__
#define __IPMI_SI_H__
#include <linux/ipmi.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#define SI_DEVICE_NAME "ipmi_si"
#define DEFAULT_REGSPACING 1
#define DEFAULT_REGSIZE 1
/* Numbers in this enumerator should be mapped to si_to_str[] */
enum si_type {
SI_TYPE_INVALID, SI_KCS, SI_SMIC, SI_BT, SI_TYPE_MAX
};
/* Array is defined in the ipmi_si_intf.c */
extern const char *const si_to_str[];
struct ipmi_match_info {
enum si_type type;
};
extern const struct ipmi_match_info ipmi_kcs_si_info;
extern const struct ipmi_match_info ipmi_smic_si_info;
extern const struct ipmi_match_info ipmi_bt_si_info;
enum ipmi_addr_space {
IPMI_IO_ADDR_SPACE, IPMI_MEM_ADDR_SPACE
};
/*
* The structure for doing I/O in the state machine. The state
* machine doesn't have the actual I/O routines, they are done through
* this interface.
*/
struct si_sm_io {
unsigned char (*inputb)(const struct si_sm_io *io, unsigned int offset);
void (*outputb)(const struct si_sm_io *io,
unsigned int offset,
unsigned char b);
/*
* Generic info used by the actual handling routines, the
* state machine shouldn't touch these.
*/
void __iomem *addr;
unsigned int regspacing;
unsigned int regsize;
unsigned int regshift;
enum ipmi_addr_space addr_space;
unsigned long addr_data;
enum ipmi_addr_src addr_source; /* ACPI, PCI, SMBIOS, hardcode, etc. */
union ipmi_smi_info_union addr_info;
int (*io_setup)(struct si_sm_io *info);
void (*io_cleanup)(struct si_sm_io *info);
unsigned int io_size;
int irq;
int (*irq_setup)(struct si_sm_io *io);
void *irq_handler_data;
void (*irq_cleanup)(struct si_sm_io *io);
u8 slave_addr;
const struct ipmi_match_info *si_info;
struct device *dev;
};
int ipmi_si_add_smi(struct si_sm_io *io);
irqreturn_t ipmi_si_irq_handler(int irq, void *data);
void ipmi_irq_start_cleanup(struct si_sm_io *io);
int ipmi_std_irq_setup(struct si_sm_io *io);
void ipmi_irq_finish_setup(struct si_sm_io *io);
void ipmi_si_remove_by_dev(struct device *dev);
struct device *ipmi_si_remove_by_data(int addr_space, enum si_type si_type,
unsigned long addr);
void ipmi_hardcode_init(void);
void ipmi_si_hardcode_exit(void);
void ipmi_si_hotmod_exit(void);
int ipmi_si_hardcode_match(int addr_space, unsigned long addr);
void ipmi_si_platform_init(void);
void ipmi_si_platform_shutdown(void);
void ipmi_remove_platform_device_by_name(char *name);
extern struct platform_driver ipmi_platform_driver;
#ifdef CONFIG_PCI
void ipmi_si_pci_init(void);
void ipmi_si_pci_shutdown(void);
#else
static inline void ipmi_si_pci_init(void) { }
static inline void ipmi_si_pci_shutdown(void) { }
#endif
#ifdef CONFIG_IPMI_LS2K
void ipmi_si_ls2k_init(void);
void ipmi_si_ls2k_shutdown(void);
#else
static inline void ipmi_si_ls2k_init(void) { }
static inline void ipmi_si_ls2k_shutdown(void) { }
#endif
#ifdef CONFIG_PARISC
void ipmi_si_parisc_init(void);
void ipmi_si_parisc_shutdown(void);
#else
static inline void ipmi_si_parisc_init(void) { }
static inline void ipmi_si_parisc_shutdown(void) { }
#endif
int ipmi_si_port_setup(struct si_sm_io *io);
int ipmi_si_mem_setup(struct si_sm_io *io);
#endif /* __IPMI_SI_H__ */
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: GPL-2.0+
#define pr_fmt(fmt) "ipmi_hardcode: " fmt
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include "ipmi_si.h"
#include "ipmi_plat_data.h"
/*
* There can be 4 IO ports passed in (with or without IRQs), 4 addresses,
* a default IO port, and 1 ACPI/SPMI address. That sets SI_MAX_DRIVERS.
*/
#define SI_MAX_PARMS 4
#define MAX_SI_TYPE_STR 30
static char si_type_str[MAX_SI_TYPE_STR] __initdata;
static unsigned long addrs[SI_MAX_PARMS];
static unsigned int num_addrs;
static unsigned int ports[SI_MAX_PARMS];
static unsigned int num_ports;
static int irqs[SI_MAX_PARMS] __initdata;
static unsigned int num_irqs __initdata;
static int regspacings[SI_MAX_PARMS] __initdata;
static unsigned int num_regspacings __initdata;
static int regsizes[SI_MAX_PARMS] __initdata;
static unsigned int num_regsizes __initdata;
static int regshifts[SI_MAX_PARMS] __initdata;
static unsigned int num_regshifts __initdata;
static int slave_addrs[SI_MAX_PARMS] __initdata;
static unsigned int num_slave_addrs __initdata;
module_param_string(type, si_type_str, MAX_SI_TYPE_STR, 0);
MODULE_PARM_DESC(type,
"Defines the type of each interface, each interface separated by commas. The types are 'kcs', 'smic', and 'bt'. For example si_type=kcs,bt will set the first interface to kcs and the second to bt");
module_param_hw_array(addrs, ulong, iomem, &num_addrs, 0);
MODULE_PARM_DESC(addrs,
"Sets the memory address of each interface, the addresses separated by commas. Only use if an interface is in memory. Otherwise, set it to zero or leave it blank.");
module_param_hw_array(ports, uint, ioport, &num_ports, 0);
MODULE_PARM_DESC(ports,
"Sets the port address of each interface, the addresses separated by commas. Only use if an interface is a port. Otherwise, set it to zero or leave it blank.");
module_param_hw_array(irqs, int, irq, &num_irqs, 0);
MODULE_PARM_DESC(irqs,
"Sets the interrupt of each interface, the addresses separated by commas. Only use if an interface has an interrupt. Otherwise, set it to zero or leave it blank.");
module_param_hw_array(regspacings, int, other, &num_regspacings, 0);
MODULE_PARM_DESC(regspacings,
"The number of bytes between the start address and each successive register used by the interface. For instance, if the start address is 0xca2 and the spacing is 2, then the second address is at 0xca4. Defaults to 1.");
module_param_hw_array(regsizes, int, other, &num_regsizes, 0);
MODULE_PARM_DESC(regsizes,
"The size of the specific IPMI register in bytes. This should generally be 1, 2, 4, or 8 for an 8-bit, 16-bit, 32-bit, or 64-bit register. Use this if you the 8-bit IPMI register has to be read from a larger register.");
module_param_hw_array(regshifts, int, other, &num_regshifts, 0);
MODULE_PARM_DESC(regshifts,
"The amount to shift the data read from the. IPMI register, in bits. For instance, if the data is read from a 32-bit word and the IPMI data is in bit 8-15, then the shift would be 8");
module_param_hw_array(slave_addrs, int, other, &num_slave_addrs, 0);
MODULE_PARM_DESC(slave_addrs,
"Set the default IPMB slave address for the controller. Normally this is 0x20, but can be overridden by this parm. This is an array indexed by interface number.");
static void __init ipmi_hardcode_init_one(const char *si_type_str,
unsigned int i,
unsigned long addr,
enum ipmi_addr_space addr_space)
{
struct ipmi_plat_data p;
int t;
memset(&p, 0, sizeof(p));
p.iftype = IPMI_PLAT_IF_SI;
if (!si_type_str || !*si_type_str) {
p.type = SI_KCS;
} else {
t = match_string(si_to_str, -1, si_type_str);
if (t < 0) {
pr_warn("Interface type specified for interface %d, was invalid: %s\n",
i, si_type_str);
return;
}
p.type = t;
}
p.regsize = regsizes[i];
p.regspacing = regspacings[i];
p.slave_addr = slave_addrs[i];
p.addr_source = SI_HARDCODED;
p.regshift = regshifts[i];
p.addr = addr;
p.space = addr_space;
ipmi_platform_add("hardcode-ipmi-si", i, &p);
}
void __init ipmi_hardcode_init(void)
{
unsigned int i;
char *str;
char *si_type[SI_MAX_PARMS];
memset(si_type, 0, sizeof(si_type));
/* Parse out the si_type string into its components. */
str = si_type_str;
if (*str != '\0') {
for (i = 0; (i < SI_MAX_PARMS) && (*str != '\0'); i++) {
si_type[i] = str;
str = strchr(str, ',');
if (str) {
*str = '\0';
str++;
} else {
break;
}
}
}
for (i = 0; i < SI_MAX_PARMS; i++) {
if (i < num_ports && ports[i])
ipmi_hardcode_init_one(si_type[i], i, ports[i],
IPMI_IO_ADDR_SPACE);
if (i < num_addrs && addrs[i])
ipmi_hardcode_init_one(si_type[i], i, addrs[i],
IPMI_MEM_ADDR_SPACE);
}
}
void ipmi_si_hardcode_exit(void)
{
ipmi_remove_platform_device_by_name("hardcode-ipmi-si");
}
/*
* Returns true of the given address exists as a hardcoded address,
* false if not.
*/
int ipmi_si_hardcode_match(int addr_space, unsigned long addr)
{
unsigned int i;
if (addr_space == IPMI_IO_ADDR_SPACE) {
for (i = 0; i < num_ports; i++) {
if (ports[i] == addr)
return 1;
}
} else {
for (i = 0; i < num_addrs; i++) {
if (addrs[i] == addr)
return 1;
}
}
return 0;
}
@@ -0,0 +1,237 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* ipmi_si_hotmod.c
*
* Handling for dynamically adding/removing IPMI devices through
* a module parameter (and thus sysfs).
*/
#define pr_fmt(fmt) "ipmi_hotmod: " fmt
#include <linux/moduleparam.h>
#include <linux/ipmi.h>
#include <linux/atomic.h>
#include "ipmi_si.h"
#include "ipmi_plat_data.h"
static int hotmod_handler(const char *val, const struct kernel_param *kp);
module_param_call(hotmod, hotmod_handler, NULL, NULL, 0200);
MODULE_PARM_DESC(hotmod,
"Add and remove interfaces. See Documentation/driver-api/ipmi.rst in the kernel sources for the gory details.");
/*
* Parms come in as <op1>[:op2[:op3...]]. ops are:
* add|remove,kcs|bt|smic,mem|i/o,<address>[,<opt1>[,<opt2>[,...]]]
* Options are:
* rsp=<regspacing>
* rsi=<regsize>
* rsh=<regshift>
* irq=<irq>
* ipmb=<ipmb addr>
*/
enum hotmod_op { HM_ADD, HM_REMOVE };
struct hotmod_vals {
const char *name;
const int val;
};
static const struct hotmod_vals hotmod_ops[] = {
{ "add", HM_ADD },
{ "remove", HM_REMOVE },
{ NULL }
};
static const struct hotmod_vals hotmod_si[] = {
{ "kcs", SI_KCS },
{ "smic", SI_SMIC },
{ "bt", SI_BT },
{ NULL }
};
static const struct hotmod_vals hotmod_as[] = {
{ "mem", IPMI_MEM_ADDR_SPACE },
{ "i/o", IPMI_IO_ADDR_SPACE },
{ NULL }
};
static int parse_str(const struct hotmod_vals *v, unsigned int *val, char *name,
const char **curr)
{
char *s;
int i;
s = strchr(*curr, ',');
if (!s) {
pr_warn("No hotmod %s given\n", name);
return -EINVAL;
}
*s = '\0';
s++;
for (i = 0; v[i].name; i++) {
if (strcmp(*curr, v[i].name) == 0) {
*val = v[i].val;
*curr = s;
return 0;
}
}
pr_warn("Invalid hotmod %s '%s'\n", name, *curr);
return -EINVAL;
}
static int check_hotmod_int_op(const char *curr, const char *option,
const char *name, unsigned int *val)
{
char *n;
if (strcmp(curr, name) == 0) {
if (!option) {
pr_warn("No option given for '%s'\n", curr);
return -EINVAL;
}
*val = simple_strtoul(option, &n, 0);
if ((*n != '\0') || (*option == '\0')) {
pr_warn("Bad option given for '%s'\n", curr);
return -EINVAL;
}
return 1;
}
return 0;
}
static int parse_hotmod_str(const char *curr, enum hotmod_op *op,
struct ipmi_plat_data *h)
{
char *s, *o;
int rv;
unsigned int ival;
h->iftype = IPMI_PLAT_IF_SI;
rv = parse_str(hotmod_ops, &ival, "operation", &curr);
if (rv)
return rv;
*op = ival;
rv = parse_str(hotmod_si, &ival, "interface type", &curr);
if (rv)
return rv;
h->type = ival;
rv = parse_str(hotmod_as, &ival, "address space", &curr);
if (rv)
return rv;
h->space = ival;
s = strchr(curr, ',');
if (s) {
*s = '\0';
s++;
}
rv = kstrtoul(curr, 0, &h->addr);
if (rv) {
pr_warn("Invalid hotmod address '%s': %d\n", curr, rv);
return rv;
}
while (s) {
curr = s;
s = strchr(curr, ',');
if (s) {
*s = '\0';
s++;
}
o = strchr(curr, '=');
if (o) {
*o = '\0';
o++;
}
rv = check_hotmod_int_op(curr, o, "rsp", &h->regspacing);
if (rv < 0)
return rv;
else if (rv)
continue;
rv = check_hotmod_int_op(curr, o, "rsi", &h->regsize);
if (rv < 0)
return rv;
else if (rv)
continue;
rv = check_hotmod_int_op(curr, o, "rsh", &h->regshift);
if (rv < 0)
return rv;
else if (rv)
continue;
rv = check_hotmod_int_op(curr, o, "irq", &h->irq);
if (rv < 0)
return rv;
else if (rv)
continue;
rv = check_hotmod_int_op(curr, o, "ipmb", &h->slave_addr);
if (rv < 0)
return rv;
else if (rv)
continue;
pr_warn("Invalid hotmod option '%s'\n", curr);
return -EINVAL;
}
h->addr_source = SI_HOTMOD;
return 0;
}
static atomic_t hotmod_nr;
static int hotmod_handler(const char *val, const struct kernel_param *kp)
{
int rv;
struct ipmi_plat_data h;
char *str, *curr, *next;
str = kstrdup(val, GFP_KERNEL);
if (!str)
return -ENOMEM;
/* Kill any trailing spaces, as we can get a "\n" from echo. */
for (curr = strstrip(str); curr; curr = next) {
enum hotmod_op op;
next = strchr(curr, ':');
if (next) {
*next = '\0';
next++;
}
memset(&h, 0, sizeof(h));
rv = parse_hotmod_str(curr, &op, &h);
if (rv)
goto out;
if (op == HM_ADD) {
ipmi_platform_add("hotmod-ipmi-si",
atomic_inc_return(&hotmod_nr),
&h);
} else {
struct device *dev;
dev = ipmi_si_remove_by_data(h.space, h.type, h.addr);
if (dev && dev_is_platform(dev)) {
struct platform_device *pdev;
pdev = to_platform_device(dev);
if (strcmp(pdev->name, "hotmod-ipmi-si") == 0)
platform_device_unregister(pdev);
}
put_device(dev);
}
}
rv = strlen(val);
out:
kfree(str);
return rv;
}
void ipmi_si_hotmod_exit(void)
{
ipmi_remove_platform_device_by_name("hotmod-ipmi-si");
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Driver for Loongson-2K BMC IPMI interface
*
* Copyright (C) 2024-2025 Loongson Technology Corporation Limited.
*
* Authors:
* Chong Qiao <qiaochong@loongson.cn>
* Binbin Zhou <zhoubinbin@loongson.cn>
*/
#include <linux/bitfield.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/types.h>
#include "ipmi_si.h"
#define LS2K_KCS_FIFO_IBFH 0x0
#define LS2K_KCS_FIFO_IBFT 0x1
#define LS2K_KCS_FIFO_OBFH 0x2
#define LS2K_KCS_FIFO_OBFT 0x3
/* KCS registers */
#define LS2K_KCS_REG_STS 0x4
#define LS2K_KCS_REG_DATA_OUT 0x5
#define LS2K_KCS_REG_DATA_IN 0x6
#define LS2K_KCS_REG_CMD 0x8
#define LS2K_KCS_CMD_DATA 0xa
#define LS2K_KCS_VERSION 0xb
#define LS2K_KCS_WR_REQ 0xc
#define LS2K_KCS_WR_ACK 0x10
#define LS2K_KCS_STS_OBF BIT(0)
#define LS2K_KCS_STS_IBF BIT(1)
#define LS2K_KCS_STS_SMS_ATN BIT(2)
#define LS2K_KCS_STS_CMD BIT(3)
#define LS2K_KCS_DATA_MASK (LS2K_KCS_STS_OBF | LS2K_KCS_STS_IBF | LS2K_KCS_STS_CMD)
static bool ls2k_registered;
static unsigned char ls2k_mem_inb_v0(const struct si_sm_io *io, unsigned int offset)
{
void __iomem *addr = io->addr;
int reg_offset;
if (offset & BIT(0)) {
reg_offset = LS2K_KCS_REG_STS;
} else {
writeb(readb(addr + LS2K_KCS_REG_STS) & ~LS2K_KCS_STS_OBF, addr + LS2K_KCS_REG_STS);
reg_offset = LS2K_KCS_REG_DATA_OUT;
}
return readb(addr + reg_offset);
}
static unsigned char ls2k_mem_inb_v1(const struct si_sm_io *io, unsigned int offset)
{
void __iomem *addr = io->addr;
unsigned char inb = 0, cmd;
bool obf, ibf;
obf = readb(addr + LS2K_KCS_FIFO_OBFH) ^ readb(addr + LS2K_KCS_FIFO_OBFT);
ibf = readb(addr + LS2K_KCS_FIFO_IBFH) ^ readb(addr + LS2K_KCS_FIFO_IBFT);
cmd = readb(addr + LS2K_KCS_CMD_DATA);
if (offset & BIT(0)) {
inb = readb(addr + LS2K_KCS_REG_STS) & ~LS2K_KCS_DATA_MASK;
inb |= FIELD_PREP(LS2K_KCS_STS_OBF, obf)
| FIELD_PREP(LS2K_KCS_STS_IBF, ibf)
| FIELD_PREP(LS2K_KCS_STS_CMD, cmd);
} else {
inb = readb(addr + LS2K_KCS_REG_DATA_OUT);
writeb(readb(addr + LS2K_KCS_FIFO_OBFH), addr + LS2K_KCS_FIFO_OBFT);
}
return inb;
}
static void ls2k_mem_outb_v0(const struct si_sm_io *io, unsigned int offset,
unsigned char val)
{
void __iomem *addr = io->addr;
unsigned char sts = readb(addr + LS2K_KCS_REG_STS);
int reg_offset;
if (sts & LS2K_KCS_STS_IBF)
return;
if (offset & BIT(0)) {
reg_offset = LS2K_KCS_REG_CMD;
sts |= LS2K_KCS_STS_CMD;
} else {
reg_offset = LS2K_KCS_REG_DATA_IN;
sts &= ~LS2K_KCS_STS_CMD;
}
writew(val, addr + reg_offset);
writeb(sts | LS2K_KCS_STS_IBF, addr + LS2K_KCS_REG_STS);
writel(readl(addr + LS2K_KCS_WR_REQ) + 1, addr + LS2K_KCS_WR_REQ);
}
static void ls2k_mem_outb_v1(const struct si_sm_io *io, unsigned int offset,
unsigned char val)
{
void __iomem *addr = io->addr;
unsigned char ibfh, ibft;
int reg_offset;
ibfh = readb(addr + LS2K_KCS_FIFO_IBFH);
ibft = readb(addr + LS2K_KCS_FIFO_IBFT);
if (ibfh ^ ibft)
return;
reg_offset = (offset & BIT(0)) ? LS2K_KCS_REG_CMD : LS2K_KCS_REG_DATA_IN;
writew(val, addr + reg_offset);
writeb(offset & BIT(0), addr + LS2K_KCS_CMD_DATA);
writeb(!ibft, addr + LS2K_KCS_FIFO_IBFH);
writel(readl(addr + LS2K_KCS_WR_REQ) + 1, addr + LS2K_KCS_WR_REQ);
}
static void ls2k_mem_cleanup(struct si_sm_io *io)
{
if (io->addr)
iounmap(io->addr);
}
static int ipmi_ls2k_mem_setup(struct si_sm_io *io)
{
unsigned char version;
io->addr = ioremap(io->addr_data, io->regspacing);
if (!io->addr)
return -EIO;
version = readb(io->addr + LS2K_KCS_VERSION);
io->inputb = version ? ls2k_mem_inb_v1 : ls2k_mem_inb_v0;
io->outputb = version ? ls2k_mem_outb_v1 : ls2k_mem_outb_v0;
io->io_cleanup = ls2k_mem_cleanup;
return 0;
}
static int ipmi_ls2k_probe(struct platform_device *pdev)
{
struct si_sm_io io;
memset(&io, 0, sizeof(io));
io.si_info = &ipmi_kcs_si_info;
io.io_setup = ipmi_ls2k_mem_setup;
io.addr_data = pdev->resource[0].start;
io.regspacing = resource_size(&pdev->resource[0]);
io.dev = &pdev->dev;
dev_dbg(&pdev->dev, "addr 0x%lx, spacing %d.\n", io.addr_data, io.regspacing);
return ipmi_si_add_smi(&io);
}
static void ipmi_ls2k_remove(struct platform_device *pdev)
{
ipmi_si_remove_by_dev(&pdev->dev);
}
static struct platform_driver ipmi_ls2k_platform_driver = {
.driver = {
.name = "ls2k-ipmi-si",
},
.probe = ipmi_ls2k_probe,
.remove = ipmi_ls2k_remove,
};
void ipmi_si_ls2k_init(void)
{
platform_driver_register(&ipmi_ls2k_platform_driver);
ls2k_registered = true;
}
void ipmi_si_ls2k_shutdown(void)
{
if (ls2k_registered)
platform_driver_unregister(&ipmi_ls2k_platform_driver);
}
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: GPL-2.0+
#include <linux/io.h>
#include "ipmi_si.h"
static unsigned char intf_mem_inb(const struct si_sm_io *io,
unsigned int offset)
{
return readb((io->addr)+(offset * io->regspacing));
}
static void intf_mem_outb(const struct si_sm_io *io, unsigned int offset,
unsigned char b)
{
writeb(b, (io->addr)+(offset * io->regspacing));
}
static unsigned char intf_mem_inw(const struct si_sm_io *io,
unsigned int offset)
{
return (readw((io->addr)+(offset * io->regspacing)) >> io->regshift)
& 0xff;
}
static void intf_mem_outw(const struct si_sm_io *io, unsigned int offset,
unsigned char b)
{
writeb(b << io->regshift, (io->addr)+(offset * io->regspacing));
}
static unsigned char intf_mem_inl(const struct si_sm_io *io,
unsigned int offset)
{
return (readl((io->addr)+(offset * io->regspacing)) >> io->regshift)
& 0xff;
}
static void intf_mem_outl(const struct si_sm_io *io, unsigned int offset,
unsigned char b)
{
writel(b << io->regshift, (io->addr)+(offset * io->regspacing));
}
#ifdef readq
static unsigned char mem_inq(const struct si_sm_io *io, unsigned int offset)
{
return (readq((io->addr)+(offset * io->regspacing)) >> io->regshift)
& 0xff;
}
static void mem_outq(const struct si_sm_io *io, unsigned int offset,
unsigned char b)
{
writeq((u64)b << io->regshift, (io->addr)+(offset * io->regspacing));
}
#endif
static void mem_region_cleanup(struct si_sm_io *io, int num)
{
unsigned long addr = io->addr_data;
int idx;
for (idx = 0; idx < num; idx++)
release_mem_region(addr + idx * io->regspacing,
io->regsize);
}
static void mem_cleanup(struct si_sm_io *io)
{
if (io->addr) {
iounmap(io->addr);
mem_region_cleanup(io, io->io_size);
}
}
int ipmi_si_mem_setup(struct si_sm_io *io)
{
unsigned long addr = io->addr_data;
int mapsize, idx;
if (!addr)
return -ENODEV;
/*
* Figure out the actual readb/readw/readl/etc routine to use based
* upon the register size.
*/
switch (io->regsize) {
case 1:
io->inputb = intf_mem_inb;
io->outputb = intf_mem_outb;
break;
case 2:
io->inputb = intf_mem_inw;
io->outputb = intf_mem_outw;
break;
case 4:
io->inputb = intf_mem_inl;
io->outputb = intf_mem_outl;
break;
#ifdef readq
case 8:
io->inputb = mem_inq;
io->outputb = mem_outq;
break;
#endif
default:
dev_warn(io->dev, "Invalid register size: %d\n",
io->regsize);
return -EINVAL;
}
/*
* Some BIOSes reserve disjoint memory regions in their ACPI
* tables. This causes problems when trying to request the
* entire region. Therefore we must request each register
* separately.
*/
for (idx = 0; idx < io->io_size; idx++) {
if (request_mem_region(addr + idx * io->regspacing,
io->regsize, SI_DEVICE_NAME) == NULL) {
/* Undo allocations */
mem_region_cleanup(io, idx);
return -EIO;
}
}
/*
* Calculate the total amount of memory to claim. This is an
* unusual looking calculation, but it avoids claiming any
* more memory than it has to. It will claim everything
* between the first address to the end of the last full
* register.
*/
mapsize = ((io->io_size * io->regspacing)
- (io->regspacing - io->regsize));
io->addr = ioremap(addr, mapsize);
if (io->addr == NULL) {
mem_region_cleanup(io, io->io_size);
return -EIO;
}
io->io_cleanup = mem_cleanup;
return 0;
}
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0+
#include <linux/module.h>
#include <asm/hardware.h> /* for register_parisc_driver() stuff */
#include <asm/parisc-device.h>
#include "ipmi_si.h"
static bool parisc_registered;
static int __init ipmi_parisc_probe(struct parisc_device *dev)
{
struct si_sm_io io;
memset(&io, 0, sizeof(io));
io.si_info = &ipmi_kcs_si_info;
io.addr_source = SI_DEVICETREE;
io.addr_space = IPMI_MEM_ADDR_SPACE;
io.addr_data = dev->hpa.start;
io.regsize = 1;
io.regspacing = 1;
io.regshift = 0;
io.irq = 0; /* no interrupt */
io.irq_setup = NULL;
io.dev = &dev->dev;
dev_dbg(&dev->dev, "addr 0x%lx\n", io.addr_data);
return ipmi_si_add_smi(&io);
}
static void __exit ipmi_parisc_remove(struct parisc_device *dev)
{
ipmi_si_remove_by_dev(&dev->dev);
}
static const struct parisc_device_id ipmi_parisc_tbl[] __initconst = {
{ HPHW_MC, HVERSION_REV_ANY_ID, 0x004, 0xC0 },
{ 0, }
};
MODULE_DEVICE_TABLE(parisc, ipmi_parisc_tbl);
static struct parisc_driver ipmi_parisc_driver __refdata = {
.name = "ipmi",
.id_table = ipmi_parisc_tbl,
.probe = ipmi_parisc_probe,
.remove = __exit_p(ipmi_parisc_remove),
};
void ipmi_si_parisc_init(void)
{
register_parisc_driver(&ipmi_parisc_driver);
parisc_registered = true;
}
void ipmi_si_parisc_shutdown(void)
{
if (parisc_registered)
unregister_parisc_driver(&ipmi_parisc_driver);
}
+165
View File
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* ipmi_si_pci.c
*
* Handling for IPMI devices on the PCI bus.
*/
#define pr_fmt(fmt) "ipmi_pci: " fmt
#include <linux/module.h>
#include <linux/pci.h>
#include "ipmi_si.h"
static bool pci_registered;
static bool si_trypci = true;
module_param_named(trypci, si_trypci, bool, 0);
MODULE_PARM_DESC(trypci,
"Setting this to zero will disable the default scan of the interfaces identified via pci");
#define PCI_DEVICE_ID_HP_MMC 0x121A
static int ipmi_pci_probe_regspacing(struct si_sm_io *io)
{
unsigned char status;
int regspacing;
if (io->si_info->type != SI_KCS)
return DEFAULT_REGSPACING;
io->regsize = DEFAULT_REGSIZE;
io->regshift = 0;
/* detect 1, 4, 16byte spacing */
for (regspacing = DEFAULT_REGSPACING; regspacing <= 16;) {
io->regspacing = regspacing;
if (io->io_setup(io)) {
dev_err(io->dev, "Could not setup I/O space\n");
return DEFAULT_REGSPACING;
}
/* write invalid cmd */
io->outputb(io, 1, 0x10);
/* read status back */
status = io->inputb(io, 1);
io->io_cleanup(io);
if (status)
return regspacing;
regspacing *= 4;
}
return DEFAULT_REGSPACING;
}
static struct pci_device_id ipmi_pci_blacklist[] = {
/*
* This is a "Virtual IPMI device", whatever that is. It appears
* as a KCS device by the class, but it is not one.
*/
{ PCI_VDEVICE(REALTEK, 0x816c) },
{ 0, }
};
static int ipmi_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
int rv;
struct si_sm_io io;
if (pci_match_id(ipmi_pci_blacklist, pdev))
return -ENODEV;
memset(&io, 0, sizeof(io));
io.addr_source = SI_PCI;
dev_info(&pdev->dev, "probing via PCI");
switch (pdev->class) {
case PCI_CLASS_SERIAL_IPMI_SMIC:
io.si_info = &ipmi_smic_si_info;
break;
case PCI_CLASS_SERIAL_IPMI_KCS:
io.si_info = &ipmi_kcs_si_info;
break;
case PCI_CLASS_SERIAL_IPMI_BT:
io.si_info = &ipmi_bt_si_info;
break;
default:
dev_info(&pdev->dev, "Unknown IPMI class: %x\n", pdev->class);
return -ENOMEM;
}
rv = pcim_enable_device(pdev);
if (rv) {
dev_err(&pdev->dev, "couldn't enable PCI device\n");
return rv;
}
if (pci_resource_flags(pdev, 0) & IORESOURCE_IO) {
if (!IS_ENABLED(CONFIG_HAS_IOPORT))
return -ENXIO;
io.addr_space = IPMI_IO_ADDR_SPACE;
io.io_setup = ipmi_si_port_setup;
} else {
io.addr_space = IPMI_MEM_ADDR_SPACE;
io.io_setup = ipmi_si_mem_setup;
}
io.addr_data = pci_resource_start(pdev, 0);
io.dev = &pdev->dev;
io.regspacing = ipmi_pci_probe_regspacing(&io);
io.regsize = DEFAULT_REGSIZE;
io.regshift = 0;
io.irq = pdev->irq;
if (io.irq)
io.irq_setup = ipmi_std_irq_setup;
dev_info(&pdev->dev, "%pR regsize %u spacing %u irq %d\n",
&pdev->resource[0], io.regsize, io.regspacing, io.irq);
return ipmi_si_add_smi(&io);
}
static void ipmi_pci_remove(struct pci_dev *pdev)
{
ipmi_si_remove_by_dev(&pdev->dev);
}
static const struct pci_device_id ipmi_pci_devices[] = {
{ PCI_VDEVICE(HP, PCI_DEVICE_ID_HP_MMC) },
{ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_IPMI_SMIC, ~0) },
{ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_IPMI_KCS, ~0) },
{ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_IPMI_BT, ~0) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, ipmi_pci_devices);
static struct pci_driver ipmi_pci_driver = {
.name = SI_DEVICE_NAME,
.id_table = ipmi_pci_devices,
.probe = ipmi_pci_probe,
.remove = ipmi_pci_remove,
};
void ipmi_si_pci_init(void)
{
if (si_trypci) {
int rv = pci_register_driver(&ipmi_pci_driver);
if (rv)
pr_err("Unable to register PCI driver: %d\n", rv);
else
pci_registered = true;
}
}
void ipmi_si_pci_shutdown(void)
{
if (pci_registered)
pci_unregister_driver(&ipmi_pci_driver);
}
@@ -0,0 +1,469 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* ipmi_si_platform.c
*
* Handling for platform devices in IPMI (ACPI, OF, and things
* coming from the platform.
*/
#define pr_fmt(fmt) "ipmi_platform: " fmt
#define dev_fmt pr_fmt
#include <linux/types.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include "ipmi_si.h"
#include "ipmi_dmi.h"
static bool platform_registered;
static bool si_tryplatform = true;
#ifdef CONFIG_ACPI
static bool si_tryacpi = true;
#endif
#ifdef CONFIG_OF
static bool si_tryopenfirmware = true;
#endif
#ifdef CONFIG_DMI
static bool si_trydmi = true;
#else
static bool si_trydmi = false;
#endif
module_param_named(tryplatform, si_tryplatform, bool, 0);
MODULE_PARM_DESC(tryplatform,
"Setting this to zero will disable the default scan of the interfaces identified via platform interfaces besides ACPI, OpenFirmware, and DMI");
#ifdef CONFIG_ACPI
module_param_named(tryacpi, si_tryacpi, bool, 0);
MODULE_PARM_DESC(tryacpi,
"Setting this to zero will disable the default scan of the interfaces identified via ACPI");
#endif
#ifdef CONFIG_OF
module_param_named(tryopenfirmware, si_tryopenfirmware, bool, 0);
MODULE_PARM_DESC(tryopenfirmware,
"Setting this to zero will disable the default scan of the interfaces identified via OpenFirmware");
#endif
#ifdef CONFIG_DMI
module_param_named(trydmi, si_trydmi, bool, 0);
MODULE_PARM_DESC(trydmi,
"Setting this to zero will disable the default scan of the interfaces identified via DMI");
#endif
#ifdef CONFIG_ACPI
/* For GPE-type interrupts. */
static u32 ipmi_acpi_gpe(acpi_handle gpe_device,
u32 gpe_number, void *context)
{
struct si_sm_io *io = context;
ipmi_si_irq_handler(io->irq, io->irq_handler_data);
return ACPI_INTERRUPT_HANDLED;
}
static void acpi_gpe_irq_cleanup(struct si_sm_io *io)
{
if (!io->irq)
return;
ipmi_irq_start_cleanup(io);
acpi_remove_gpe_handler(NULL, io->irq, &ipmi_acpi_gpe);
}
static int acpi_gpe_irq_setup(struct si_sm_io *io)
{
acpi_status status;
if (!io->irq)
return 0;
status = acpi_install_gpe_handler(NULL,
io->irq,
ACPI_GPE_LEVEL_TRIGGERED,
&ipmi_acpi_gpe,
io);
if (ACPI_FAILURE(status)) {
dev_warn(io->dev,
"Unable to claim ACPI GPE %d, running polled\n",
io->irq);
io->irq = 0;
return -EINVAL;
}
io->irq_cleanup = acpi_gpe_irq_cleanup;
ipmi_irq_finish_setup(io);
dev_info(io->dev, "Using ACPI GPE %d\n", io->irq);
return 0;
}
#endif
static void ipmi_set_addr_data_and_space(struct resource *r, struct si_sm_io *io)
{
if (resource_type(r) == IORESOURCE_IO)
io->addr_space = IPMI_IO_ADDR_SPACE;
else
io->addr_space = IPMI_MEM_ADDR_SPACE;
io->addr_data = r->start;
}
static struct resource *
ipmi_get_info_from_resources(struct platform_device *pdev,
struct si_sm_io *io)
{
struct resource *res, *res_second;
res = platform_get_mem_or_io(pdev, 0);
if (!res) {
dev_err(&pdev->dev, "no I/O or memory address\n");
return NULL;
}
ipmi_set_addr_data_and_space(res, io);
io->regspacing = DEFAULT_REGSPACING;
res_second = platform_get_mem_or_io(pdev, 1);
if (res_second && resource_type(res_second) == resource_type(res)) {
if (res_second->start > io->addr_data)
io->regspacing = res_second->start - io->addr_data;
}
return res;
}
static int platform_ipmi_probe(struct platform_device *pdev)
{
struct si_sm_io io;
u8 type, slave_addr, addr_source, regsize, regshift;
int rv;
rv = device_property_read_u8(&pdev->dev, "addr-source", &addr_source);
if (rv)
addr_source = SI_PLATFORM;
if (addr_source >= SI_LAST)
return -EINVAL;
if (addr_source == SI_SMBIOS) {
if (!si_trydmi)
return -ENODEV;
} else if (addr_source != SI_HARDCODED) {
if (!si_tryplatform)
return -ENODEV;
}
rv = device_property_read_u8(&pdev->dev, "ipmi-type", &type);
if (rv)
return -ENODEV;
memset(&io, 0, sizeof(io));
io.addr_source = addr_source;
dev_info(&pdev->dev, "probing via %s\n",
ipmi_addr_src_to_str(addr_source));
switch (type) {
case SI_KCS:
io.si_info = &ipmi_kcs_si_info;
break;
case SI_SMIC:
io.si_info = &ipmi_smic_si_info;
break;
case SI_BT:
io.si_info = &ipmi_bt_si_info;
break;
case SI_TYPE_INVALID: /* User disabled this in hardcode. */
return -ENODEV;
default:
dev_err(&pdev->dev, "ipmi-type property is invalid\n");
return -EINVAL;
}
io.regsize = DEFAULT_REGSIZE;
rv = device_property_read_u8(&pdev->dev, "reg-size", &regsize);
if (!rv)
io.regsize = regsize;
io.regshift = 0;
rv = device_property_read_u8(&pdev->dev, "reg-shift", &regshift);
if (!rv)
io.regshift = regshift;
if (!ipmi_get_info_from_resources(pdev, &io))
return -EINVAL;
rv = device_property_read_u8(&pdev->dev, "slave-addr", &slave_addr);
if (rv)
io.slave_addr = 0x20;
else
io.slave_addr = slave_addr;
io.irq = platform_get_irq_optional(pdev, 0);
if (io.irq > 0)
io.irq_setup = ipmi_std_irq_setup;
else
io.irq = 0;
io.dev = &pdev->dev;
pr_info("ipmi_si: %s: %s %#lx regsize %d spacing %d irq %d\n",
ipmi_addr_src_to_str(addr_source),
(io.addr_space == IPMI_IO_ADDR_SPACE) ? "io" : "mem",
io.addr_data, io.regsize, io.regspacing, io.irq);
ipmi_si_add_smi(&io);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id of_ipmi_match[] = {
{ .type = "ipmi", .compatible = "ipmi-kcs", .data = &ipmi_kcs_si_info },
{ .type = "ipmi", .compatible = "ipmi-smic", .data = &ipmi_smic_si_info },
{ .type = "ipmi", .compatible = "ipmi-bt", .data = &ipmi_bt_si_info },
{}
};
MODULE_DEVICE_TABLE(of, of_ipmi_match);
static int of_ipmi_probe(struct platform_device *pdev)
{
struct si_sm_io io;
struct resource resource;
const __be32 *regsize, *regspacing, *regshift;
struct device_node *np = pdev->dev.of_node;
int ret;
int proplen;
if (!si_tryopenfirmware)
return -ENODEV;
dev_info(&pdev->dev, "probing via device tree\n");
if (!of_device_is_available(np))
return -EINVAL;
ret = of_address_to_resource(np, 0, &resource);
if (ret) {
dev_warn(&pdev->dev, "invalid address from OF\n");
return ret;
}
regsize = of_get_property(np, "reg-size", &proplen);
if (regsize && proplen != 4) {
dev_warn(&pdev->dev, "invalid regsize from OF\n");
return -EINVAL;
}
regspacing = of_get_property(np, "reg-spacing", &proplen);
if (regspacing && proplen != 4) {
dev_warn(&pdev->dev, "invalid regspacing from OF\n");
return -EINVAL;
}
regshift = of_get_property(np, "reg-shift", &proplen);
if (regshift && proplen != 4) {
dev_warn(&pdev->dev, "invalid regshift from OF\n");
return -EINVAL;
}
memset(&io, 0, sizeof(io));
io.si_info = device_get_match_data(&pdev->dev);
io.addr_source = SI_DEVICETREE;
io.irq_setup = ipmi_std_irq_setup;
ipmi_set_addr_data_and_space(&resource, &io);
io.regsize = regsize ? be32_to_cpup(regsize) : DEFAULT_REGSIZE;
io.regspacing = regspacing ? be32_to_cpup(regspacing) : DEFAULT_REGSPACING;
io.regshift = regshift ? be32_to_cpup(regshift) : 0;
io.irq = platform_get_irq_optional(pdev, 0);
if (io.irq < 0)
io.irq = 0;
io.dev = &pdev->dev;
dev_dbg(&pdev->dev, "addr 0x%lx regsize %d spacing %d irq %d\n",
io.addr_data, io.regsize, io.regspacing, io.irq);
return ipmi_si_add_smi(&io);
}
#else
#define of_ipmi_match NULL
static int of_ipmi_probe(struct platform_device *dev)
{
return -ENODEV;
}
#endif
#ifdef CONFIG_ACPI
static int find_slave_address(struct si_sm_io *io, int slave_addr)
{
#ifdef CONFIG_IPMI_DMI_DECODE
if (!slave_addr)
slave_addr = ipmi_dmi_get_slave_addr(io->si_info->type,
io->addr_space,
io->addr_data);
#endif
return slave_addr;
}
static int acpi_ipmi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct si_sm_io io;
acpi_handle handle;
acpi_status status;
unsigned long long tmp;
struct resource *res;
if (!si_tryacpi)
return -ENODEV;
handle = ACPI_HANDLE(dev);
if (!handle)
return -ENODEV;
memset(&io, 0, sizeof(io));
io.addr_source = SI_ACPI;
dev_info(dev, "probing via ACPI\n");
io.addr_info.acpi_info.acpi_handle = handle;
/* _IFT tells us the interface type: KCS, BT, etc */
status = acpi_evaluate_integer(handle, "_IFT", NULL, &tmp);
if (ACPI_FAILURE(status)) {
dev_err(dev, "Could not find ACPI IPMI interface type\n");
return -EINVAL;
}
switch (tmp) {
case 1:
io.si_info = &ipmi_kcs_si_info;
break;
case 2:
io.si_info = &ipmi_smic_si_info;
break;
case 3:
io.si_info = &ipmi_bt_si_info;
break;
case 4: /* SSIF, just ignore */
return -ENODEV;
default:
dev_info(dev, "unknown IPMI type %lld\n", tmp);
return -EINVAL;
}
io.dev = dev;
io.regsize = DEFAULT_REGSIZE;
io.regshift = 0;
res = ipmi_get_info_from_resources(pdev, &io);
if (!res)
return -EINVAL;
/* If _GPE exists, use it; otherwise use standard interrupts */
status = acpi_evaluate_integer(handle, "_GPE", NULL, &tmp);
if (ACPI_SUCCESS(status)) {
io.irq = tmp;
io.irq_setup = acpi_gpe_irq_setup;
} else {
int irq = platform_get_irq_optional(pdev, 0);
if (irq > 0) {
io.irq = irq;
io.irq_setup = ipmi_std_irq_setup;
}
}
io.slave_addr = find_slave_address(&io, io.slave_addr);
dev_info(dev, "%pR regsize %d spacing %d irq %d\n",
res, io.regsize, io.regspacing, io.irq);
request_module_nowait("acpi_ipmi");
return ipmi_si_add_smi(&io);
}
static const struct acpi_device_id acpi_ipmi_match[] = {
{ "IPI0001", 0 },
{ },
};
MODULE_DEVICE_TABLE(acpi, acpi_ipmi_match);
#else
static int acpi_ipmi_probe(struct platform_device *dev)
{
return -ENODEV;
}
#endif
static int ipmi_probe(struct platform_device *pdev)
{
if (pdev->dev.of_node && of_ipmi_probe(pdev) == 0)
return 0;
if (acpi_ipmi_probe(pdev) == 0)
return 0;
return platform_ipmi_probe(pdev);
}
static void ipmi_remove(struct platform_device *pdev)
{
ipmi_si_remove_by_dev(&pdev->dev);
}
static int pdev_match_name(struct device *dev, const void *data)
{
struct platform_device *pdev = to_platform_device(dev);
const char *name = data;
return strcmp(pdev->name, name) == 0;
}
void ipmi_remove_platform_device_by_name(char *name)
{
struct device *dev;
while ((dev = bus_find_device(&platform_bus_type, NULL, name,
pdev_match_name))) {
struct platform_device *pdev = to_platform_device(dev);
platform_device_unregister(pdev);
put_device(dev);
}
}
static const struct platform_device_id si_plat_ids[] = {
{ .name = "dmi-ipmi-si" },
{ .name = "hardcode-ipmi-si" },
{ .name = "hotmod-ipmi-si" },
{ }
};
struct platform_driver ipmi_platform_driver = {
.driver = {
.name = SI_DEVICE_NAME,
.of_match_table = of_ipmi_match,
.acpi_match_table = ACPI_PTR(acpi_ipmi_match),
},
.probe = ipmi_probe,
.remove = ipmi_remove,
.id_table = si_plat_ids
};
void ipmi_si_platform_init(void)
{
int rv = platform_driver_register(&ipmi_platform_driver);
if (rv)
pr_err("Unable to register driver: %d\n", rv);
else
platform_registered = true;
}
void ipmi_si_platform_shutdown(void)
{
if (platform_registered)
platform_driver_unregister(&ipmi_platform_driver);
}
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: GPL-2.0+
#include <linux/io.h>
#include "ipmi_si.h"
static unsigned char port_inb(const struct si_sm_io *io, unsigned int offset)
{
unsigned int addr = io->addr_data;
return inb(addr + (offset * io->regspacing));
}
static void port_outb(const struct si_sm_io *io, unsigned int offset,
unsigned char b)
{
unsigned int addr = io->addr_data;
outb(b, addr + (offset * io->regspacing));
}
static unsigned char port_inw(const struct si_sm_io *io, unsigned int offset)
{
unsigned int addr = io->addr_data;
return (inw(addr + (offset * io->regspacing)) >> io->regshift) & 0xff;
}
static void port_outw(const struct si_sm_io *io, unsigned int offset,
unsigned char b)
{
unsigned int addr = io->addr_data;
outw(b << io->regshift, addr + (offset * io->regspacing));
}
static unsigned char port_inl(const struct si_sm_io *io, unsigned int offset)
{
unsigned int addr = io->addr_data;
return (inl(addr + (offset * io->regspacing)) >> io->regshift) & 0xff;
}
static void port_outl(const struct si_sm_io *io, unsigned int offset,
unsigned char b)
{
unsigned int addr = io->addr_data;
outl(b << io->regshift, addr+(offset * io->regspacing));
}
static void port_cleanup(struct si_sm_io *io)
{
unsigned int addr = io->addr_data;
int idx;
if (addr) {
for (idx = 0; idx < io->io_size; idx++)
release_region(addr + idx * io->regspacing,
io->regsize);
}
}
int ipmi_si_port_setup(struct si_sm_io *io)
{
unsigned int addr = io->addr_data;
int idx;
if (!addr)
return -ENODEV;
/*
* Figure out the actual inb/inw/inl/etc routine to use based
* upon the register size.
*/
switch (io->regsize) {
case 1:
io->inputb = port_inb;
io->outputb = port_outb;
break;
case 2:
io->inputb = port_inw;
io->outputb = port_outw;
break;
case 4:
io->inputb = port_inl;
io->outputb = port_outl;
break;
default:
dev_warn(io->dev, "Invalid register size: %d\n",
io->regsize);
return -EINVAL;
}
/*
* Some BIOSes reserve disjoint I/O regions in their ACPI
* tables. This causes problems when trying to register the
* entire I/O region. Therefore we must register each I/O
* port separately.
*/
for (idx = 0; idx < io->io_size; idx++) {
if (request_region(addr + idx * io->regspacing,
io->regsize, SI_DEVICE_NAME) == NULL) {
/* Undo allocations */
while (idx--)
release_region(addr + idx * io->regspacing,
io->regsize);
return -EIO;
}
}
io->io_cleanup = port_cleanup;
return 0;
}
+104
View File
@@ -0,0 +1,104 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* ipmi_si_sm.h
*
* State machine interface for low-level IPMI system management
* interface state machines. This code is the interface between
* the ipmi_smi code (that handles the policy of a KCS, SMIC, or
* BT interface) and the actual low-level state machine.
*
* Author: MontaVista Software, Inc.
* Corey Minyard <minyard@mvista.com>
* source@mvista.com
*
* Copyright 2002 MontaVista Software Inc.
*/
#ifndef __IPMI_SI_SM_H__
#define __IPMI_SI_SM_H__
#include "ipmi_si.h"
/*
* This is defined by the state machines themselves, it is an opaque
* data type for them to use.
*/
struct si_sm_data;
/* Results of SMI events. */
enum si_sm_result {
SI_SM_CALL_WITHOUT_DELAY, /* Call the driver again immediately */
SI_SM_CALL_WITH_DELAY, /* Delay some before calling again. */
SI_SM_CALL_WITH_TICK_DELAY,/* Delay >=1 tick before calling again. */
SI_SM_TRANSACTION_COMPLETE, /* A transaction is finished. */
SI_SM_IDLE, /* The SM is in idle state. */
SI_SM_HOSED, /* The hardware violated the state machine. */
/*
* The hardware is asserting attn and the state machine is
* idle.
*/
SI_SM_ATTN
};
/* Handlers for the SMI state machine. */
struct si_sm_handlers {
/*
* Put the version number of the state machine here so the
* upper layer can print it.
*/
char *version;
/*
* Initialize the data and return the amount of I/O space to
* reserve for the space.
*/
unsigned int (*init_data)(struct si_sm_data *smi,
struct si_sm_io *io);
/*
* Start a new transaction in the state machine. This will
* return -2 if the state machine is not idle, -1 if the size
* is invalid (to large or too small), or 0 if the transaction
* is successfully completed.
*/
int (*start_transaction)(struct si_sm_data *smi,
unsigned char *data, unsigned int size);
/*
* Return the results after the transaction. This will return
* -1 if the buffer is too small, zero if no transaction is
* present, or the actual length of the result data.
*/
int (*get_result)(struct si_sm_data *smi,
unsigned char *data, unsigned int length);
/*
* Call this periodically (for a polled interface) or upon
* receiving an interrupt (for a interrupt-driven interface).
* If interrupt driven, you should probably poll this
* periodically when not in idle state. This should be called
* with the time that passed since the last call, if it is
* significant. Time is in microseconds.
*/
enum si_sm_result (*event)(struct si_sm_data *smi, long time);
/*
* Attempt to detect an SMI. Returns 0 on success or nonzero
* on failure.
*/
int (*detect)(struct si_sm_data *smi);
/* The interface is shutting down, so clean it up. */
void (*cleanup)(struct si_sm_data *smi);
/* Return the size of the SMI structure in bytes. */
int (*size)(void);
};
/* Current state machines that we can use. */
extern const struct si_sm_handlers kcs_smi_handlers;
extern const struct si_sm_handlers smic_smi_handlers;
extern const struct si_sm_handlers bt_smi_handlers;
#endif /* __IPMI_SI_SM_H__ */
@@ -0,0 +1,585 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* ipmi_smic_sm.c
*
* The state-machine driver for an IPMI SMIC driver
*
* It started as a copy of Corey Minyard's driver for the KSC interface
* and the kernel patch "mmcdev-patch-245" by HP
*
* modified by: Hannes Schulz <schulz@schwaar.com>
* ipmi@schwaar.com
*
*
* Corey Minyard's driver for the KSC interface has the following
* copyright notice:
* Copyright 2002 MontaVista Software Inc.
*
* the kernel patch "mmcdev-patch-245" by HP has the following
* copyright notice:
* (c) Copyright 2001 Grant Grundler (c) Copyright
* 2001 Hewlett-Packard Company
*/
#define DEBUG /* So dev_dbg() is always available. */
#include <linux/kernel.h> /* For printk. */
#include <linux/string.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/ipmi_msgdefs.h> /* for completion codes */
#include "ipmi_si_sm.h"
/* smic_debug is a bit-field
* SMIC_DEBUG_ENABLE - turned on for now
* SMIC_DEBUG_MSG - commands and their responses
* SMIC_DEBUG_STATES - state machine
*/
#define SMIC_DEBUG_STATES 4
#define SMIC_DEBUG_MSG 2
#define SMIC_DEBUG_ENABLE 1
static int smic_debug = 1;
module_param(smic_debug, int, 0644);
MODULE_PARM_DESC(smic_debug, "debug bitmask, 1=enable, 2=messages, 4=states");
enum smic_states {
SMIC_IDLE,
SMIC_START_OP,
SMIC_OP_OK,
SMIC_WRITE_START,
SMIC_WRITE_NEXT,
SMIC_WRITE_END,
SMIC_WRITE2READ,
SMIC_READ_START,
SMIC_READ_NEXT,
SMIC_READ_END,
SMIC_HOSED
};
#define MAX_SMIC_READ_SIZE 80
#define MAX_SMIC_WRITE_SIZE 80
#define SMIC_MAX_ERROR_RETRIES 3
/* Timeouts in microseconds. */
#define SMIC_RETRY_TIMEOUT (2*USEC_PER_SEC)
/* SMIC Flags Register Bits */
#define SMIC_RX_DATA_READY 0x80
#define SMIC_TX_DATA_READY 0x40
/*
* SMIC_SMI and SMIC_EVM_DATA_AVAIL are only used by
* a few systems, and then only by Systems Management
* Interrupts, not by the OS. Always ignore these bits.
*
*/
#define SMIC_SMI 0x10
#define SMIC_EVM_DATA_AVAIL 0x08
#define SMIC_SMS_DATA_AVAIL 0x04
#define SMIC_FLAG_BSY 0x01
/* SMIC Error Codes */
#define EC_NO_ERROR 0x00
#define EC_ABORTED 0x01
#define EC_ILLEGAL_CONTROL 0x02
#define EC_NO_RESPONSE 0x03
#define EC_ILLEGAL_COMMAND 0x04
#define EC_BUFFER_FULL 0x05
struct si_sm_data {
enum smic_states state;
struct si_sm_io *io;
unsigned char write_data[MAX_SMIC_WRITE_SIZE];
int write_pos;
int write_count;
int orig_write_count;
unsigned char read_data[MAX_SMIC_READ_SIZE];
int read_pos;
int truncated;
unsigned int error_retries;
long smic_timeout;
};
static unsigned int init_smic_data(struct si_sm_data *smic,
struct si_sm_io *io)
{
smic->state = SMIC_IDLE;
smic->io = io;
smic->write_pos = 0;
smic->write_count = 0;
smic->orig_write_count = 0;
smic->read_pos = 0;
smic->error_retries = 0;
smic->truncated = 0;
smic->smic_timeout = SMIC_RETRY_TIMEOUT;
/* We use 3 bytes of I/O. */
return 3;
}
static int start_smic_transaction(struct si_sm_data *smic,
unsigned char *data, unsigned int size)
{
unsigned int i;
if (size < 2)
return IPMI_REQ_LEN_INVALID_ERR;
if (size > MAX_SMIC_WRITE_SIZE)
return IPMI_REQ_LEN_EXCEEDED_ERR;
if ((smic->state != SMIC_IDLE) && (smic->state != SMIC_HOSED)) {
dev_warn(smic->io->dev,
"SMIC in invalid state %d\n", smic->state);
return IPMI_NOT_IN_MY_STATE_ERR;
}
if (smic_debug & SMIC_DEBUG_MSG) {
dev_dbg(smic->io->dev, "%s -", __func__);
for (i = 0; i < size; i++)
pr_cont(" %02x", data[i]);
pr_cont("\n");
}
smic->error_retries = 0;
memcpy(smic->write_data, data, size);
smic->write_count = size;
smic->orig_write_count = size;
smic->write_pos = 0;
smic->read_pos = 0;
smic->state = SMIC_START_OP;
smic->smic_timeout = SMIC_RETRY_TIMEOUT;
return 0;
}
static int smic_get_result(struct si_sm_data *smic,
unsigned char *data, unsigned int length)
{
int i;
if (smic_debug & SMIC_DEBUG_MSG) {
dev_dbg(smic->io->dev, "smic_get result -");
for (i = 0; i < smic->read_pos; i++)
pr_cont(" %02x", smic->read_data[i]);
pr_cont("\n");
}
if (length < smic->read_pos) {
smic->read_pos = length;
smic->truncated = 1;
}
memcpy(data, smic->read_data, smic->read_pos);
if ((length >= 3) && (smic->read_pos < 3)) {
data[2] = IPMI_ERR_UNSPECIFIED;
smic->read_pos = 3;
}
if (smic->truncated) {
data[2] = IPMI_ERR_MSG_TRUNCATED;
smic->truncated = 0;
}
return smic->read_pos;
}
static inline unsigned char read_smic_flags(struct si_sm_data *smic)
{
return smic->io->inputb(smic->io, 2);
}
static inline unsigned char read_smic_status(struct si_sm_data *smic)
{
return smic->io->inputb(smic->io, 1);
}
static inline unsigned char read_smic_data(struct si_sm_data *smic)
{
return smic->io->inputb(smic->io, 0);
}
static inline void write_smic_flags(struct si_sm_data *smic,
unsigned char flags)
{
smic->io->outputb(smic->io, 2, flags);
}
static inline void write_smic_control(struct si_sm_data *smic,
unsigned char control)
{
smic->io->outputb(smic->io, 1, control);
}
static inline void write_si_sm_data(struct si_sm_data *smic,
unsigned char data)
{
smic->io->outputb(smic->io, 0, data);
}
static inline void start_error_recovery(struct si_sm_data *smic, char *reason)
{
(smic->error_retries)++;
if (smic->error_retries > SMIC_MAX_ERROR_RETRIES) {
if (smic_debug & SMIC_DEBUG_ENABLE)
pr_warn("ipmi_smic_drv: smic hosed: %s\n", reason);
smic->state = SMIC_HOSED;
} else {
smic->write_count = smic->orig_write_count;
smic->write_pos = 0;
smic->read_pos = 0;
smic->state = SMIC_START_OP;
smic->smic_timeout = SMIC_RETRY_TIMEOUT;
}
}
static inline void write_next_byte(struct si_sm_data *smic)
{
write_si_sm_data(smic, smic->write_data[smic->write_pos]);
(smic->write_pos)++;
(smic->write_count)--;
}
static inline void read_next_byte(struct si_sm_data *smic)
{
if (smic->read_pos >= MAX_SMIC_READ_SIZE) {
read_smic_data(smic);
smic->truncated = 1;
} else {
smic->read_data[smic->read_pos] = read_smic_data(smic);
smic->read_pos++;
}
}
/* SMIC Control/Status Code Components */
#define SMIC_GET_STATUS 0x00 /* Control form's name */
#define SMIC_READY 0x00 /* Status form's name */
#define SMIC_WR_START 0x01 /* Unified Control/Status names... */
#define SMIC_WR_NEXT 0x02
#define SMIC_WR_END 0x03
#define SMIC_RD_START 0x04
#define SMIC_RD_NEXT 0x05
#define SMIC_RD_END 0x06
#define SMIC_CODE_MASK 0x0f
#define SMIC_CONTROL 0x00
#define SMIC_STATUS 0x80
#define SMIC_CS_MASK 0x80
#define SMIC_SMS 0x40
#define SMIC_SMM 0x60
#define SMIC_STREAM_MASK 0x60
/* SMIC Control Codes */
#define SMIC_CC_SMS_GET_STATUS (SMIC_CONTROL|SMIC_SMS|SMIC_GET_STATUS)
#define SMIC_CC_SMS_WR_START (SMIC_CONTROL|SMIC_SMS|SMIC_WR_START)
#define SMIC_CC_SMS_WR_NEXT (SMIC_CONTROL|SMIC_SMS|SMIC_WR_NEXT)
#define SMIC_CC_SMS_WR_END (SMIC_CONTROL|SMIC_SMS|SMIC_WR_END)
#define SMIC_CC_SMS_RD_START (SMIC_CONTROL|SMIC_SMS|SMIC_RD_START)
#define SMIC_CC_SMS_RD_NEXT (SMIC_CONTROL|SMIC_SMS|SMIC_RD_NEXT)
#define SMIC_CC_SMS_RD_END (SMIC_CONTROL|SMIC_SMS|SMIC_RD_END)
#define SMIC_CC_SMM_GET_STATUS (SMIC_CONTROL|SMIC_SMM|SMIC_GET_STATUS)
#define SMIC_CC_SMM_WR_START (SMIC_CONTROL|SMIC_SMM|SMIC_WR_START)
#define SMIC_CC_SMM_WR_NEXT (SMIC_CONTROL|SMIC_SMM|SMIC_WR_NEXT)
#define SMIC_CC_SMM_WR_END (SMIC_CONTROL|SMIC_SMM|SMIC_WR_END)
#define SMIC_CC_SMM_RD_START (SMIC_CONTROL|SMIC_SMM|SMIC_RD_START)
#define SMIC_CC_SMM_RD_NEXT (SMIC_CONTROL|SMIC_SMM|SMIC_RD_NEXT)
#define SMIC_CC_SMM_RD_END (SMIC_CONTROL|SMIC_SMM|SMIC_RD_END)
/* SMIC Status Codes */
#define SMIC_SC_SMS_READY (SMIC_STATUS|SMIC_SMS|SMIC_READY)
#define SMIC_SC_SMS_WR_START (SMIC_STATUS|SMIC_SMS|SMIC_WR_START)
#define SMIC_SC_SMS_WR_NEXT (SMIC_STATUS|SMIC_SMS|SMIC_WR_NEXT)
#define SMIC_SC_SMS_WR_END (SMIC_STATUS|SMIC_SMS|SMIC_WR_END)
#define SMIC_SC_SMS_RD_START (SMIC_STATUS|SMIC_SMS|SMIC_RD_START)
#define SMIC_SC_SMS_RD_NEXT (SMIC_STATUS|SMIC_SMS|SMIC_RD_NEXT)
#define SMIC_SC_SMS_RD_END (SMIC_STATUS|SMIC_SMS|SMIC_RD_END)
#define SMIC_SC_SMM_READY (SMIC_STATUS|SMIC_SMM|SMIC_READY)
#define SMIC_SC_SMM_WR_START (SMIC_STATUS|SMIC_SMM|SMIC_WR_START)
#define SMIC_SC_SMM_WR_NEXT (SMIC_STATUS|SMIC_SMM|SMIC_WR_NEXT)
#define SMIC_SC_SMM_WR_END (SMIC_STATUS|SMIC_SMM|SMIC_WR_END)
#define SMIC_SC_SMM_RD_START (SMIC_STATUS|SMIC_SMM|SMIC_RD_START)
#define SMIC_SC_SMM_RD_NEXT (SMIC_STATUS|SMIC_SMM|SMIC_RD_NEXT)
#define SMIC_SC_SMM_RD_END (SMIC_STATUS|SMIC_SMM|SMIC_RD_END)
/* these are the control/status codes we actually use
SMIC_CC_SMS_GET_STATUS 0x40
SMIC_CC_SMS_WR_START 0x41
SMIC_CC_SMS_WR_NEXT 0x42
SMIC_CC_SMS_WR_END 0x43
SMIC_CC_SMS_RD_START 0x44
SMIC_CC_SMS_RD_NEXT 0x45
SMIC_CC_SMS_RD_END 0x46
SMIC_SC_SMS_READY 0xC0
SMIC_SC_SMS_WR_START 0xC1
SMIC_SC_SMS_WR_NEXT 0xC2
SMIC_SC_SMS_WR_END 0xC3
SMIC_SC_SMS_RD_START 0xC4
SMIC_SC_SMS_RD_NEXT 0xC5
SMIC_SC_SMS_RD_END 0xC6
*/
static enum si_sm_result smic_event(struct si_sm_data *smic, long time)
{
unsigned char status;
unsigned char flags;
unsigned char data;
if (smic->state == SMIC_HOSED) {
init_smic_data(smic, smic->io);
return SI_SM_HOSED;
}
if (smic->state != SMIC_IDLE) {
if (smic_debug & SMIC_DEBUG_STATES)
dev_dbg(smic->io->dev,
"%s - smic->smic_timeout = %ld, time = %ld\n",
__func__, smic->smic_timeout, time);
/*
* FIXME: smic_event is sometimes called with time >
* SMIC_RETRY_TIMEOUT
*/
if (time < SMIC_RETRY_TIMEOUT) {
smic->smic_timeout -= time;
if (smic->smic_timeout < 0) {
start_error_recovery(smic, "smic timed out.");
return SI_SM_CALL_WITH_DELAY;
}
}
}
flags = read_smic_flags(smic);
if (flags & SMIC_FLAG_BSY)
return SI_SM_CALL_WITH_DELAY;
status = read_smic_status(smic);
if (smic_debug & SMIC_DEBUG_STATES)
dev_dbg(smic->io->dev,
"%s - state = %d, flags = 0x%02x, status = 0x%02x\n",
__func__, smic->state, flags, status);
switch (smic->state) {
case SMIC_IDLE:
/* in IDLE we check for available messages */
if (flags & SMIC_SMS_DATA_AVAIL)
return SI_SM_ATTN;
return SI_SM_IDLE;
case SMIC_START_OP:
/* sanity check whether smic is really idle */
write_smic_control(smic, SMIC_CC_SMS_GET_STATUS);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_OP_OK;
break;
case SMIC_OP_OK:
if (status != SMIC_SC_SMS_READY) {
/* this should not happen */
start_error_recovery(smic,
"state = SMIC_OP_OK,"
" status != SMIC_SC_SMS_READY");
return SI_SM_CALL_WITH_DELAY;
}
/* OK so far; smic is idle let us start ... */
write_smic_control(smic, SMIC_CC_SMS_WR_START);
write_next_byte(smic);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_WRITE_START;
break;
case SMIC_WRITE_START:
if (status != SMIC_SC_SMS_WR_START) {
start_error_recovery(smic,
"state = SMIC_WRITE_START, "
"status != SMIC_SC_SMS_WR_START");
return SI_SM_CALL_WITH_DELAY;
}
/*
* we must not issue WR_(NEXT|END) unless
* TX_DATA_READY is set
* */
if (flags & SMIC_TX_DATA_READY) {
if (smic->write_count == 1) {
/* last byte */
write_smic_control(smic, SMIC_CC_SMS_WR_END);
smic->state = SMIC_WRITE_END;
} else {
write_smic_control(smic, SMIC_CC_SMS_WR_NEXT);
smic->state = SMIC_WRITE_NEXT;
}
write_next_byte(smic);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
} else
return SI_SM_CALL_WITH_DELAY;
break;
case SMIC_WRITE_NEXT:
if (status != SMIC_SC_SMS_WR_NEXT) {
start_error_recovery(smic,
"state = SMIC_WRITE_NEXT, "
"status != SMIC_SC_SMS_WR_NEXT");
return SI_SM_CALL_WITH_DELAY;
}
/* this is the same code as in SMIC_WRITE_START */
if (flags & SMIC_TX_DATA_READY) {
if (smic->write_count == 1) {
write_smic_control(smic, SMIC_CC_SMS_WR_END);
smic->state = SMIC_WRITE_END;
} else {
write_smic_control(smic, SMIC_CC_SMS_WR_NEXT);
smic->state = SMIC_WRITE_NEXT;
}
write_next_byte(smic);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
} else
return SI_SM_CALL_WITH_DELAY;
break;
case SMIC_WRITE_END:
if (status != SMIC_SC_SMS_WR_END) {
start_error_recovery(smic,
"state = SMIC_WRITE_END, "
"status != SMIC_SC_SMS_WR_END");
return SI_SM_CALL_WITH_DELAY;
}
/* data register holds an error code */
data = read_smic_data(smic);
if (data != 0) {
if (smic_debug & SMIC_DEBUG_ENABLE)
dev_dbg(smic->io->dev,
"SMIC_WRITE_END: data = %02x\n",
data);
start_error_recovery(smic,
"state = SMIC_WRITE_END, "
"data != SUCCESS");
return SI_SM_CALL_WITH_DELAY;
} else
smic->state = SMIC_WRITE2READ;
break;
case SMIC_WRITE2READ:
/*
* we must wait for RX_DATA_READY to be set before we
* can continue
*/
if (flags & SMIC_RX_DATA_READY) {
write_smic_control(smic, SMIC_CC_SMS_RD_START);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_READ_START;
} else
return SI_SM_CALL_WITH_DELAY;
break;
case SMIC_READ_START:
if (status != SMIC_SC_SMS_RD_START) {
start_error_recovery(smic,
"state = SMIC_READ_START, "
"status != SMIC_SC_SMS_RD_START");
return SI_SM_CALL_WITH_DELAY;
}
if (flags & SMIC_RX_DATA_READY) {
read_next_byte(smic);
write_smic_control(smic, SMIC_CC_SMS_RD_NEXT);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_READ_NEXT;
} else
return SI_SM_CALL_WITH_DELAY;
break;
case SMIC_READ_NEXT:
switch (status) {
/*
* smic tells us that this is the last byte to be read
* --> clean up
*/
case SMIC_SC_SMS_RD_END:
read_next_byte(smic);
write_smic_control(smic, SMIC_CC_SMS_RD_END);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_READ_END;
break;
case SMIC_SC_SMS_RD_NEXT:
if (flags & SMIC_RX_DATA_READY) {
read_next_byte(smic);
write_smic_control(smic, SMIC_CC_SMS_RD_NEXT);
write_smic_flags(smic, flags | SMIC_FLAG_BSY);
smic->state = SMIC_READ_NEXT;
} else
return SI_SM_CALL_WITH_DELAY;
break;
default:
start_error_recovery(
smic,
"state = SMIC_READ_NEXT, "
"status != SMIC_SC_SMS_RD_(NEXT|END)");
return SI_SM_CALL_WITH_DELAY;
}
break;
case SMIC_READ_END:
if (status != SMIC_SC_SMS_READY) {
start_error_recovery(smic,
"state = SMIC_READ_END, "
"status != SMIC_SC_SMS_READY");
return SI_SM_CALL_WITH_DELAY;
}
data = read_smic_data(smic);
/* data register holds an error code */
if (data != 0) {
if (smic_debug & SMIC_DEBUG_ENABLE)
dev_dbg(smic->io->dev,
"SMIC_READ_END: data = %02x\n",
data);
start_error_recovery(smic,
"state = SMIC_READ_END, "
"data != SUCCESS");
return SI_SM_CALL_WITH_DELAY;
} else {
smic->state = SMIC_IDLE;
return SI_SM_TRANSACTION_COMPLETE;
}
case SMIC_HOSED:
init_smic_data(smic, smic->io);
return SI_SM_HOSED;
default:
if (smic_debug & SMIC_DEBUG_ENABLE) {
dev_dbg(smic->io->dev,
"smic->state = %d\n", smic->state);
start_error_recovery(smic, "state = UNKNOWN");
return SI_SM_CALL_WITH_DELAY;
}
}
smic->smic_timeout = SMIC_RETRY_TIMEOUT;
return SI_SM_CALL_WITHOUT_DELAY;
}
static int smic_detect(struct si_sm_data *smic)
{
/*
* It's impossible for the SMIC fnags register to be all 1's,
* (assuming a properly functioning, self-initialized BMC)
* but that's what you get from reading a bogus address, so we
* test that first.
*/
if (read_smic_flags(smic) == 0xff)
return 1;
return 0;
}
static void smic_cleanup(struct si_sm_data *kcs)
{
}
static int smic_size(void)
{
return sizeof(struct si_sm_data);
}
const struct si_sm_handlers smic_smi_handlers = {
.init_data = init_smic_data,
.start_transaction = start_smic_transaction,
.get_result = smic_get_result,
.event = smic_event,
.detect = smic_detect,
.cleanup = smic_cleanup,
.size = smic_size,
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+190
View File
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015-2018, Intel Corporation.
* Copyright (c) 2021, IBM Corp.
*/
#include <linux/device.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include "kcs_bmc.h"
/* Implement both the device and client interfaces here */
#include "kcs_bmc_device.h"
#include "kcs_bmc_client.h"
/* Record registered devices and drivers */
static DEFINE_MUTEX(kcs_bmc_lock);
static LIST_HEAD(kcs_bmc_devices);
static LIST_HEAD(kcs_bmc_drivers);
/* Consumer data access */
u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc)
{
return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
}
EXPORT_SYMBOL(kcs_bmc_read_data);
void kcs_bmc_write_data(struct kcs_bmc_device *kcs_bmc, u8 data)
{
kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
}
EXPORT_SYMBOL(kcs_bmc_write_data);
u8 kcs_bmc_read_status(struct kcs_bmc_device *kcs_bmc)
{
return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
}
EXPORT_SYMBOL(kcs_bmc_read_status);
void kcs_bmc_write_status(struct kcs_bmc_device *kcs_bmc, u8 data)
{
kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
}
EXPORT_SYMBOL(kcs_bmc_write_status);
void kcs_bmc_update_status(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 val)
{
kcs_bmc->ops->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
}
EXPORT_SYMBOL(kcs_bmc_update_status);
irqreturn_t kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_client *client;
irqreturn_t rc = IRQ_NONE;
unsigned long flags;
spin_lock_irqsave(&kcs_bmc->lock, flags);
client = kcs_bmc->client;
if (client)
rc = client->ops->event(client);
spin_unlock_irqrestore(&kcs_bmc->lock, flags);
return rc;
}
EXPORT_SYMBOL(kcs_bmc_handle_event);
int kcs_bmc_enable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client)
{
int rc;
spin_lock_irq(&kcs_bmc->lock);
if (kcs_bmc->client) {
rc = -EBUSY;
} else {
u8 mask = KCS_BMC_EVENT_TYPE_IBF;
kcs_bmc->client = client;
kcs_bmc_update_event_mask(kcs_bmc, mask, mask);
rc = 0;
}
spin_unlock_irq(&kcs_bmc->lock);
return rc;
}
EXPORT_SYMBOL(kcs_bmc_enable_device);
void kcs_bmc_disable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client)
{
spin_lock_irq(&kcs_bmc->lock);
if (client == kcs_bmc->client) {
u8 mask = KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE;
kcs_bmc_update_event_mask(kcs_bmc, mask, 0);
kcs_bmc->client = NULL;
}
spin_unlock_irq(&kcs_bmc->lock);
}
EXPORT_SYMBOL(kcs_bmc_disable_device);
int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_driver *drv;
int error = 0;
int rc;
spin_lock_init(&kcs_bmc->lock);
kcs_bmc->client = NULL;
mutex_lock(&kcs_bmc_lock);
list_add(&kcs_bmc->entry, &kcs_bmc_devices);
list_for_each_entry(drv, &kcs_bmc_drivers, entry) {
rc = drv->ops->add_device(kcs_bmc);
if (!rc)
continue;
dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
kcs_bmc->channel, rc);
error = rc;
}
mutex_unlock(&kcs_bmc_lock);
return error;
}
EXPORT_SYMBOL(kcs_bmc_add_device);
void kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_driver *drv;
int rc;
mutex_lock(&kcs_bmc_lock);
list_del(&kcs_bmc->entry);
list_for_each_entry(drv, &kcs_bmc_drivers, entry) {
rc = drv->ops->remove_device(kcs_bmc);
if (rc)
dev_err(kcs_bmc->dev, "Failed to remove chardev for KCS channel %d: %d",
kcs_bmc->channel, rc);
}
mutex_unlock(&kcs_bmc_lock);
}
EXPORT_SYMBOL(kcs_bmc_remove_device);
void kcs_bmc_register_driver(struct kcs_bmc_driver *drv)
{
struct kcs_bmc_device *kcs_bmc;
int rc;
mutex_lock(&kcs_bmc_lock);
list_add(&drv->entry, &kcs_bmc_drivers);
list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
rc = drv->ops->add_device(kcs_bmc);
if (rc)
dev_err(kcs_bmc->dev, "Failed to add driver for KCS channel %d: %d",
kcs_bmc->channel, rc);
}
mutex_unlock(&kcs_bmc_lock);
}
EXPORT_SYMBOL(kcs_bmc_register_driver);
void kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv)
{
struct kcs_bmc_device *kcs_bmc;
int rc;
mutex_lock(&kcs_bmc_lock);
list_del(&drv->entry);
list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
rc = drv->ops->remove_device(kcs_bmc);
if (rc)
dev_err(kcs_bmc->dev, "Failed to remove driver for KCS channel %d: %d",
kcs_bmc->channel, rc);
}
mutex_unlock(&kcs_bmc_lock);
}
EXPORT_SYMBOL(kcs_bmc_unregister_driver);
void kcs_bmc_update_event_mask(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 events)
{
kcs_bmc->ops->irq_mask_update(kcs_bmc, mask, events);
}
EXPORT_SYMBOL(kcs_bmc_update_event_mask);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
+46
View File
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2015-2018, Intel Corporation.
*/
#ifndef __KCS_BMC_H__
#define __KCS_BMC_H__
#include <linux/list.h>
#define KCS_BMC_EVENT_TYPE_OBE BIT(0)
#define KCS_BMC_EVENT_TYPE_IBF BIT(1)
#define KCS_BMC_STR_OBF BIT(0)
#define KCS_BMC_STR_IBF BIT(1)
#define KCS_BMC_STR_CMD_DAT BIT(3)
/* IPMI 2.0 - 9.5, KCS Interface Registers
* @idr: Input Data Register
* @odr: Output Data Register
* @str: Status Register
*/
struct kcs_ioreg {
u32 idr;
u32 odr;
u32 str;
};
struct kcs_bmc_device_ops;
struct kcs_bmc_client;
struct kcs_bmc_device {
struct list_head entry;
struct device *dev;
u32 channel;
struct kcs_ioreg ioreg;
const struct kcs_bmc_device_ops *ops;
spinlock_t lock;
struct kcs_bmc_client *client;
};
#endif /* __KCS_BMC_H__ */
@@ -0,0 +1,682 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015-2018, Intel Corporation.
*/
#define pr_fmt(fmt) "aspeed-kcs-bmc: " fmt
#include <linux/atomic.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/regmap.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include "kcs_bmc_device.h"
#define DEVICE_NAME "ast-kcs-bmc"
#define KCS_CHANNEL_MAX 4
/*
* Field class descriptions
*
* LPCyE Enable LPC channel y
* IBFIEy Input Buffer Full IRQ Enable for LPC channel y
* IRQxEy Assert SerIRQ x for LPC channel y (Deprecated, use IDyIRQX, IRQXEy)
* IDyIRQX Use the specified 4-bit SerIRQ for LPC channel y
* SELyIRQX SerIRQ polarity for LPC channel y (low: 0, high: 1)
* IRQXEy Assert the SerIRQ specified in IDyIRQX for LPC channel y
*/
#define LPC_TYIRQX_LOW 0b00
#define LPC_TYIRQX_HIGH 0b01
#define LPC_TYIRQX_RSVD 0b10
#define LPC_TYIRQX_RISING 0b11
#define LPC_HICR0 0x000
#define LPC_HICR0_LPC3E BIT(7)
#define LPC_HICR0_LPC2E BIT(6)
#define LPC_HICR0_LPC1E BIT(5)
#define LPC_HICR2 0x008
#define LPC_HICR2_IBFIE3 BIT(3)
#define LPC_HICR2_IBFIE2 BIT(2)
#define LPC_HICR2_IBFIE1 BIT(1)
#define LPC_HICR4 0x010
#define LPC_HICR4_LADR12AS BIT(7)
#define LPC_HICR4_KCSENBL BIT(2)
#define LPC_SIRQCR0 0x070
/* IRQ{12,1}E1 are deprecated as of AST2600 A3 but necessary for prior chips */
#define LPC_SIRQCR0_IRQ12E1 BIT(1)
#define LPC_SIRQCR0_IRQ1E1 BIT(0)
#define LPC_HICR5 0x080
#define LPC_HICR5_ID3IRQX_MASK GENMASK(23, 20)
#define LPC_HICR5_ID3IRQX_SHIFT 20
#define LPC_HICR5_ID2IRQX_MASK GENMASK(19, 16)
#define LPC_HICR5_ID2IRQX_SHIFT 16
#define LPC_HICR5_SEL3IRQX BIT(15)
#define LPC_HICR5_IRQXE3 BIT(14)
#define LPC_HICR5_SEL2IRQX BIT(13)
#define LPC_HICR5_IRQXE2 BIT(12)
#define LPC_LADR3H 0x014
#define LPC_LADR3L 0x018
#define LPC_LADR12H 0x01C
#define LPC_LADR12L 0x020
#define LPC_IDR1 0x024
#define LPC_IDR2 0x028
#define LPC_IDR3 0x02C
#define LPC_ODR1 0x030
#define LPC_ODR2 0x034
#define LPC_ODR3 0x038
#define LPC_STR1 0x03C
#define LPC_STR2 0x040
#define LPC_STR3 0x044
#define LPC_HICRB 0x100
#define LPC_HICRB_EN16LADR2 BIT(5)
#define LPC_HICRB_EN16LADR1 BIT(4)
#define LPC_HICRB_IBFIE4 BIT(1)
#define LPC_HICRB_LPC4E BIT(0)
#define LPC_HICRC 0x104
#define LPC_HICRC_ID4IRQX_MASK GENMASK(7, 4)
#define LPC_HICRC_ID4IRQX_SHIFT 4
#define LPC_HICRC_TY4IRQX_MASK GENMASK(3, 2)
#define LPC_HICRC_TY4IRQX_SHIFT 2
#define LPC_HICRC_OBF4_AUTO_CLR BIT(1)
#define LPC_HICRC_IRQXE4 BIT(0)
#define LPC_LADR4 0x110
#define LPC_IDR4 0x114
#define LPC_ODR4 0x118
#define LPC_STR4 0x11C
#define LPC_LSADR12 0x120
#define LPC_LSADR12_LSADR2_MASK GENMASK(31, 16)
#define LPC_LSADR12_LSADR2_SHIFT 16
#define LPC_LSADR12_LSADR1_MASK GENMASK(15, 0)
#define LPC_LSADR12_LSADR1_SHIFT 0
#define OBE_POLL_PERIOD (HZ / 2)
enum aspeed_kcs_irq_mode {
aspeed_kcs_irq_none,
aspeed_kcs_irq_serirq,
};
struct aspeed_kcs_bmc {
struct kcs_bmc_device kcs_bmc;
struct regmap *map;
struct {
enum aspeed_kcs_irq_mode mode;
int id;
} upstream_irq;
struct {
spinlock_t lock;
bool remove;
struct timer_list timer;
} obe;
};
static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc_device *kcs_bmc)
{
return container_of(kcs_bmc, struct aspeed_kcs_bmc, kcs_bmc);
}
static u8 aspeed_kcs_inb(struct kcs_bmc_device *kcs_bmc, u32 reg)
{
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
u32 val = 0;
int rc;
rc = regmap_read(priv->map, reg, &val);
WARN(rc != 0, "regmap_read() failed: %d\n", rc);
return rc == 0 ? (u8) val : 0;
}
static void aspeed_kcs_outb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 data)
{
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
int rc;
rc = regmap_write(priv->map, reg, data);
WARN(rc != 0, "regmap_write() failed: %d\n", rc);
/* Trigger the upstream IRQ on ODR writes, if enabled */
switch (reg) {
case LPC_ODR1:
case LPC_ODR2:
case LPC_ODR3:
case LPC_ODR4:
break;
default:
return;
}
if (priv->upstream_irq.mode != aspeed_kcs_irq_serirq)
return;
switch (kcs_bmc->channel) {
case 1:
switch (priv->upstream_irq.id) {
case 12:
regmap_update_bits(priv->map, LPC_SIRQCR0, LPC_SIRQCR0_IRQ12E1,
LPC_SIRQCR0_IRQ12E1);
break;
case 1:
regmap_update_bits(priv->map, LPC_SIRQCR0, LPC_SIRQCR0_IRQ1E1,
LPC_SIRQCR0_IRQ1E1);
break;
default:
break;
}
break;
case 2:
regmap_update_bits(priv->map, LPC_HICR5, LPC_HICR5_IRQXE2, LPC_HICR5_IRQXE2);
break;
case 3:
regmap_update_bits(priv->map, LPC_HICR5, LPC_HICR5_IRQXE3, LPC_HICR5_IRQXE3);
break;
case 4:
regmap_update_bits(priv->map, LPC_HICRC, LPC_HICRC_IRQXE4, LPC_HICRC_IRQXE4);
break;
default:
break;
}
}
static void aspeed_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask, u8 val)
{
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
int rc;
rc = regmap_update_bits(priv->map, reg, mask, val);
WARN(rc != 0, "regmap_update_bits() failed: %d\n", rc);
}
/*
* We note D for Data, and C for Cmd/Status, default rules are
*
* 1. Only the D address is given:
* A. KCS1/KCS2 (D/C: X/X+4)
* D/C: CA0h/CA4h
* D/C: CA8h/CACh
* B. KCS3 (D/C: XX2/XX3h)
* D/C: CA2h/CA3h
* C. KCS4 (D/C: X/X+1)
* D/C: CA4h/CA5h
*
* 2. Both the D/C addresses are given:
* A. KCS1/KCS2/KCS4 (D/C: X/Y)
* D/C: CA0h/CA1h
* D/C: CA8h/CA9h
* D/C: CA4h/CA5h
* B. KCS3 (D/C: XX2/XX3h)
* D/C: CA2h/CA3h
*/
static int aspeed_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u32 addrs[2], int nr_addrs)
{
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
if (WARN_ON(nr_addrs < 1 || nr_addrs > 2))
return -EINVAL;
switch (priv->kcs_bmc.channel) {
case 1:
regmap_update_bits(priv->map, LPC_HICR4, LPC_HICR4_LADR12AS, 0);
regmap_write(priv->map, LPC_LADR12H, addrs[0] >> 8);
regmap_write(priv->map, LPC_LADR12L, addrs[0] & 0xFF);
if (nr_addrs == 2) {
regmap_update_bits(priv->map, LPC_LSADR12, LPC_LSADR12_LSADR1_MASK,
addrs[1] << LPC_LSADR12_LSADR1_SHIFT);
regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_EN16LADR1,
LPC_HICRB_EN16LADR1);
}
break;
case 2:
regmap_update_bits(priv->map, LPC_HICR4, LPC_HICR4_LADR12AS, LPC_HICR4_LADR12AS);
regmap_write(priv->map, LPC_LADR12H, addrs[0] >> 8);
regmap_write(priv->map, LPC_LADR12L, addrs[0] & 0xFF);
if (nr_addrs == 2) {
regmap_update_bits(priv->map, LPC_LSADR12, LPC_LSADR12_LSADR2_MASK,
addrs[1] << LPC_LSADR12_LSADR2_SHIFT);
regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_EN16LADR2,
LPC_HICRB_EN16LADR2);
}
break;
case 3:
if (nr_addrs == 2) {
dev_err(priv->kcs_bmc.dev,
"Channel 3 only supports inferred status IO address\n");
return -EINVAL;
}
regmap_write(priv->map, LPC_LADR3H, addrs[0] >> 8);
regmap_write(priv->map, LPC_LADR3L, addrs[0] & 0xFF);
break;
case 4:
if (nr_addrs == 1)
regmap_write(priv->map, LPC_LADR4, ((addrs[0] + 1) << 16) | addrs[0]);
else
regmap_write(priv->map, LPC_LADR4, (addrs[1] << 16) | addrs[0]);
break;
default:
return -EINVAL;
}
return 0;
}
static inline int aspeed_kcs_map_serirq_type(u32 dt_type)
{
switch (dt_type) {
case IRQ_TYPE_EDGE_RISING:
return LPC_TYIRQX_RISING;
case IRQ_TYPE_LEVEL_HIGH:
return LPC_TYIRQX_HIGH;
case IRQ_TYPE_LEVEL_LOW:
return LPC_TYIRQX_LOW;
default:
return -EINVAL;
}
}
static int aspeed_kcs_config_upstream_irq(struct aspeed_kcs_bmc *priv, u32 id, u32 dt_type)
{
unsigned int mask, val, hw_type;
int ret;
if (id > 15)
return -EINVAL;
ret = aspeed_kcs_map_serirq_type(dt_type);
if (ret < 0)
return ret;
hw_type = ret;
priv->upstream_irq.mode = aspeed_kcs_irq_serirq;
priv->upstream_irq.id = id;
switch (priv->kcs_bmc.channel) {
case 1:
/* Needs IRQxE1 rather than (ID1IRQX, SEL1IRQX, IRQXE1) before AST2600 A3 */
break;
case 2:
if (!(hw_type == LPC_TYIRQX_LOW || hw_type == LPC_TYIRQX_HIGH))
return -EINVAL;
mask = LPC_HICR5_SEL2IRQX | LPC_HICR5_ID2IRQX_MASK;
val = (id << LPC_HICR5_ID2IRQX_SHIFT);
val |= (hw_type == LPC_TYIRQX_HIGH) ? LPC_HICR5_SEL2IRQX : 0;
regmap_update_bits(priv->map, LPC_HICR5, mask, val);
break;
case 3:
if (!(hw_type == LPC_TYIRQX_LOW || hw_type == LPC_TYIRQX_HIGH))
return -EINVAL;
mask = LPC_HICR5_SEL3IRQX | LPC_HICR5_ID3IRQX_MASK;
val = (id << LPC_HICR5_ID3IRQX_SHIFT);
val |= (hw_type == LPC_TYIRQX_HIGH) ? LPC_HICR5_SEL3IRQX : 0;
regmap_update_bits(priv->map, LPC_HICR5, mask, val);
break;
case 4:
mask = LPC_HICRC_ID4IRQX_MASK | LPC_HICRC_TY4IRQX_MASK | LPC_HICRC_OBF4_AUTO_CLR;
val = (id << LPC_HICRC_ID4IRQX_SHIFT) | (hw_type << LPC_HICRC_TY4IRQX_SHIFT);
regmap_update_bits(priv->map, LPC_HICRC, mask, val);
break;
default:
dev_warn(priv->kcs_bmc.dev,
"SerIRQ configuration not supported on KCS channel %d\n",
priv->kcs_bmc.channel);
return -EINVAL;
}
return 0;
}
static void aspeed_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enable)
{
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
switch (kcs_bmc->channel) {
case 1:
regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC1E, enable * LPC_HICR0_LPC1E);
return;
case 2:
regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC2E, enable * LPC_HICR0_LPC2E);
return;
case 3:
regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC3E, enable * LPC_HICR0_LPC3E);
regmap_update_bits(priv->map, LPC_HICR4,
LPC_HICR4_KCSENBL, enable * LPC_HICR4_KCSENBL);
return;
case 4:
regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_LPC4E, enable * LPC_HICRB_LPC4E);
return;
default:
pr_warn("%s: Unsupported channel: %d", __func__, kcs_bmc->channel);
return;
}
}
static void aspeed_kcs_check_obe(struct timer_list *timer)
{
struct aspeed_kcs_bmc *priv = container_of(timer, struct aspeed_kcs_bmc, obe.timer);
unsigned long flags;
u8 str;
spin_lock_irqsave(&priv->obe.lock, flags);
if (priv->obe.remove) {
spin_unlock_irqrestore(&priv->obe.lock, flags);
return;
}
str = aspeed_kcs_inb(&priv->kcs_bmc, priv->kcs_bmc.ioreg.str);
if (str & KCS_BMC_STR_OBF) {
mod_timer(timer, jiffies + OBE_POLL_PERIOD);
spin_unlock_irqrestore(&priv->obe.lock, flags);
return;
}
spin_unlock_irqrestore(&priv->obe.lock, flags);
kcs_bmc_handle_event(&priv->kcs_bmc);
}
static void aspeed_kcs_irq_mask_update(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 state)
{
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
int rc;
u8 str;
/* We don't have an OBE IRQ, emulate it */
if (mask & KCS_BMC_EVENT_TYPE_OBE) {
if (KCS_BMC_EVENT_TYPE_OBE & state) {
/*
* Given we don't have an OBE IRQ, delay by polling briefly to see if we can
* observe such an event before returning to the caller. This is not
* incorrect because OBF may have already become clear before enabling the
* IRQ if we had one, under which circumstance no event will be propagated
* anyway.
*
* The onus is on the client to perform a race-free check that it hasn't
* missed the event.
*/
rc = read_poll_timeout_atomic(aspeed_kcs_inb, str,
!(str & KCS_BMC_STR_OBF), 1, 100, false,
&priv->kcs_bmc, priv->kcs_bmc.ioreg.str);
/* Time for the slow path? */
if (rc == -ETIMEDOUT)
mod_timer(&priv->obe.timer, jiffies + OBE_POLL_PERIOD);
} else {
timer_delete(&priv->obe.timer);
}
}
if (mask & KCS_BMC_EVENT_TYPE_IBF) {
const bool enable = !!(state & KCS_BMC_EVENT_TYPE_IBF);
switch (kcs_bmc->channel) {
case 1:
regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIE1,
enable * LPC_HICR2_IBFIE1);
return;
case 2:
regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIE2,
enable * LPC_HICR2_IBFIE2);
return;
case 3:
regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIE3,
enable * LPC_HICR2_IBFIE3);
return;
case 4:
regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_IBFIE4,
enable * LPC_HICRB_IBFIE4);
return;
default:
pr_warn("%s: Unsupported channel: %d", __func__, kcs_bmc->channel);
return;
}
}
}
static const struct kcs_bmc_device_ops aspeed_kcs_ops = {
.irq_mask_update = aspeed_kcs_irq_mask_update,
.io_inputb = aspeed_kcs_inb,
.io_outputb = aspeed_kcs_outb,
.io_updateb = aspeed_kcs_updateb,
};
static irqreturn_t aspeed_kcs_irq(int irq, void *arg)
{
struct kcs_bmc_device *kcs_bmc = arg;
return kcs_bmc_handle_event(kcs_bmc);
}
static int aspeed_kcs_config_downstream_irq(struct kcs_bmc_device *kcs_bmc,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int irq;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
return devm_request_irq(dev, irq, aspeed_kcs_irq, IRQF_SHARED,
dev_name(dev), kcs_bmc);
}
static const struct kcs_ioreg ast_kcs_bmc_ioregs[KCS_CHANNEL_MAX] = {
{ .idr = LPC_IDR1, .odr = LPC_ODR1, .str = LPC_STR1 },
{ .idr = LPC_IDR2, .odr = LPC_ODR2, .str = LPC_STR2 },
{ .idr = LPC_IDR3, .odr = LPC_ODR3, .str = LPC_STR3 },
{ .idr = LPC_IDR4, .odr = LPC_ODR4, .str = LPC_STR4 },
};
static int aspeed_kcs_of_get_channel(struct platform_device *pdev)
{
struct device_node *np;
struct kcs_ioreg ioreg;
const __be32 *reg;
int i;
np = pdev->dev.of_node;
/* Don't translate addresses, we want offsets for the regmaps */
reg = of_get_address(np, 0, NULL, NULL);
if (!reg)
return -EINVAL;
ioreg.idr = be32_to_cpup(reg);
reg = of_get_address(np, 1, NULL, NULL);
if (!reg)
return -EINVAL;
ioreg.odr = be32_to_cpup(reg);
reg = of_get_address(np, 2, NULL, NULL);
if (!reg)
return -EINVAL;
ioreg.str = be32_to_cpup(reg);
for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) {
if (!memcmp(&ast_kcs_bmc_ioregs[i], &ioreg, sizeof(ioreg)))
return i + 1;
}
return -EINVAL;
}
static int
aspeed_kcs_of_get_io_address(struct platform_device *pdev, u32 addrs[2])
{
int rc;
rc = of_property_read_variable_u32_array(pdev->dev.of_node,
"aspeed,lpc-io-reg",
addrs, 1, 2);
if (rc < 0) {
dev_err(&pdev->dev, "No valid 'aspeed,lpc-io-reg' configured\n");
return rc;
}
if (addrs[0] > 0xffff) {
dev_err(&pdev->dev, "Invalid data address in 'aspeed,lpc-io-reg'\n");
return -EINVAL;
}
if (rc == 2 && addrs[1] > 0xffff) {
dev_err(&pdev->dev, "Invalid status address in 'aspeed,lpc-io-reg'\n");
return -EINVAL;
}
return rc;
}
static int aspeed_kcs_probe(struct platform_device *pdev)
{
struct kcs_bmc_device *kcs_bmc;
struct aspeed_kcs_bmc *priv;
struct device_node *np;
bool have_upstream_irq;
u32 upstream_irq[2];
int rc, channel;
int nr_addrs;
u32 addrs[2];
np = pdev->dev.of_node->parent;
if (!of_device_is_compatible(np, "aspeed,ast2400-lpc-v2") &&
!of_device_is_compatible(np, "aspeed,ast2500-lpc-v2") &&
!of_device_is_compatible(np, "aspeed,ast2600-lpc-v2")) {
dev_err(&pdev->dev, "unsupported LPC device binding\n");
return -ENODEV;
}
channel = aspeed_kcs_of_get_channel(pdev);
if (channel < 0)
return channel;
nr_addrs = aspeed_kcs_of_get_io_address(pdev, addrs);
if (nr_addrs < 0)
return nr_addrs;
np = pdev->dev.of_node;
rc = of_property_read_u32_array(np, "aspeed,lpc-interrupts", upstream_irq, 2);
if (rc && rc != -EINVAL)
return -EINVAL;
have_upstream_irq = !rc;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
kcs_bmc = &priv->kcs_bmc;
kcs_bmc->dev = &pdev->dev;
kcs_bmc->channel = channel;
kcs_bmc->ioreg = ast_kcs_bmc_ioregs[channel - 1];
kcs_bmc->ops = &aspeed_kcs_ops;
priv->map = syscon_node_to_regmap(pdev->dev.parent->of_node);
if (IS_ERR(priv->map)) {
dev_err(&pdev->dev, "Couldn't get regmap\n");
return -ENODEV;
}
spin_lock_init(&priv->obe.lock);
priv->obe.remove = false;
timer_setup(&priv->obe.timer, aspeed_kcs_check_obe, 0);
rc = aspeed_kcs_set_address(kcs_bmc, addrs, nr_addrs);
if (rc)
return rc;
/* Host to BMC IRQ */
rc = aspeed_kcs_config_downstream_irq(kcs_bmc, pdev);
if (rc)
return rc;
/* BMC to Host IRQ */
if (have_upstream_irq) {
rc = aspeed_kcs_config_upstream_irq(priv, upstream_irq[0], upstream_irq[1]);
if (rc < 0)
return rc;
} else {
priv->upstream_irq.mode = aspeed_kcs_irq_none;
}
platform_set_drvdata(pdev, priv);
aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
aspeed_kcs_enable_channel(kcs_bmc, true);
rc = kcs_bmc_add_device(&priv->kcs_bmc);
if (rc) {
dev_warn(&pdev->dev, "Failed to register channel %d: %d\n", kcs_bmc->channel, rc);
return rc;
}
dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n",
kcs_bmc->channel, addrs[0]);
return 0;
}
static void aspeed_kcs_remove(struct platform_device *pdev)
{
struct aspeed_kcs_bmc *priv = platform_get_drvdata(pdev);
struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc;
kcs_bmc_remove_device(kcs_bmc);
aspeed_kcs_enable_channel(kcs_bmc, false);
aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
/* Make sure it's proper dead */
spin_lock_irq(&priv->obe.lock);
priv->obe.remove = true;
spin_unlock_irq(&priv->obe.lock);
timer_delete_sync(&priv->obe.timer);
}
static const struct of_device_id ast_kcs_bmc_match[] = {
{ .compatible = "aspeed,ast2400-kcs-bmc-v2" },
{ .compatible = "aspeed,ast2500-kcs-bmc-v2" },
{ .compatible = "aspeed,ast2600-kcs-bmc" },
{ }
};
MODULE_DEVICE_TABLE(of, ast_kcs_bmc_match);
static struct platform_driver ast_kcs_bmc_driver = {
.driver = {
.name = DEVICE_NAME,
.of_match_table = ast_kcs_bmc_match,
},
.probe = aspeed_kcs_probe,
.remove = aspeed_kcs_remove,
};
module_platform_driver(ast_kcs_bmc_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
MODULE_DESCRIPTION("Aspeed device interface to the KCS BMC device");
@@ -0,0 +1,568 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015-2018, Intel Corporation.
*/
#define pr_fmt(fmt) "kcs-bmc: " fmt
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/ipmi_bmc.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "kcs_bmc_client.h"
/* Different phases of the KCS BMC module.
* KCS_PHASE_IDLE:
* BMC should not be expecting nor sending any data.
* KCS_PHASE_WRITE_START:
* BMC is receiving a WRITE_START command from system software.
* KCS_PHASE_WRITE_DATA:
* BMC is receiving a data byte from system software.
* KCS_PHASE_WRITE_END_CMD:
* BMC is waiting a last data byte from system software.
* KCS_PHASE_WRITE_DONE:
* BMC has received the whole request from system software.
* KCS_PHASE_WAIT_READ:
* BMC is waiting the response from the upper IPMI service.
* KCS_PHASE_READ:
* BMC is transferring the response to system software.
* KCS_PHASE_ABORT_ERROR1:
* BMC is waiting error status request from system software.
* KCS_PHASE_ABORT_ERROR2:
* BMC is waiting for idle status afer error from system software.
* KCS_PHASE_ERROR:
* BMC has detected a protocol violation at the interface level.
*/
enum kcs_ipmi_phases {
KCS_PHASE_IDLE,
KCS_PHASE_WRITE_START,
KCS_PHASE_WRITE_DATA,
KCS_PHASE_WRITE_END_CMD,
KCS_PHASE_WRITE_DONE,
KCS_PHASE_WAIT_READ,
KCS_PHASE_READ,
KCS_PHASE_ABORT_ERROR1,
KCS_PHASE_ABORT_ERROR2,
KCS_PHASE_ERROR
};
/* IPMI 2.0 - Table 9-4, KCS Interface Status Codes */
enum kcs_ipmi_errors {
KCS_NO_ERROR = 0x00,
KCS_ABORTED_BY_COMMAND = 0x01,
KCS_ILLEGAL_CONTROL_CODE = 0x02,
KCS_LENGTH_ERROR = 0x06,
KCS_UNSPECIFIED_ERROR = 0xFF
};
struct kcs_bmc_ipmi {
struct list_head entry;
struct kcs_bmc_client client;
spinlock_t lock;
enum kcs_ipmi_phases phase;
enum kcs_ipmi_errors error;
wait_queue_head_t queue;
bool data_in_avail;
int data_in_idx;
u8 *data_in;
int data_out_idx;
int data_out_len;
u8 *data_out;
struct mutex mutex;
u8 *kbuffer;
struct miscdevice miscdev;
};
#define DEVICE_NAME "ipmi-kcs"
#define KCS_MSG_BUFSIZ 1000
#define KCS_ZERO_DATA 0
/* IPMI 2.0 - Table 9-1, KCS Interface Status Register Bits */
#define KCS_STATUS_STATE(state) (state << 6)
#define KCS_STATUS_STATE_MASK GENMASK(7, 6)
#define KCS_STATUS_CMD_DAT BIT(3)
#define KCS_STATUS_SMS_ATN BIT(2)
#define KCS_STATUS_IBF BIT(1)
#define KCS_STATUS_OBF BIT(0)
/* IPMI 2.0 - Table 9-2, KCS Interface State Bits */
enum kcs_states {
IDLE_STATE = 0,
READ_STATE = 1,
WRITE_STATE = 2,
ERROR_STATE = 3,
};
/* IPMI 2.0 - Table 9-3, KCS Interface Control Codes */
#define KCS_CMD_GET_STATUS_ABORT 0x60
#define KCS_CMD_WRITE_START 0x61
#define KCS_CMD_WRITE_END 0x62
#define KCS_CMD_READ_BYTE 0x68
static inline void set_state(struct kcs_bmc_ipmi *priv, u8 state)
{
kcs_bmc_update_status(priv->client.dev, KCS_STATUS_STATE_MASK, KCS_STATUS_STATE(state));
}
static void kcs_bmc_ipmi_force_abort(struct kcs_bmc_ipmi *priv)
{
set_state(priv, ERROR_STATE);
kcs_bmc_read_data(priv->client.dev);
kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
priv->phase = KCS_PHASE_ERROR;
priv->data_in_avail = false;
priv->data_in_idx = 0;
}
static void kcs_bmc_ipmi_handle_data(struct kcs_bmc_ipmi *priv)
{
struct kcs_bmc_device *dev;
u8 data;
dev = priv->client.dev;
switch (priv->phase) {
case KCS_PHASE_WRITE_START:
priv->phase = KCS_PHASE_WRITE_DATA;
fallthrough;
case KCS_PHASE_WRITE_DATA:
if (priv->data_in_idx < KCS_MSG_BUFSIZ) {
set_state(priv, WRITE_STATE);
kcs_bmc_write_data(dev, KCS_ZERO_DATA);
priv->data_in[priv->data_in_idx++] = kcs_bmc_read_data(dev);
} else {
kcs_bmc_ipmi_force_abort(priv);
priv->error = KCS_LENGTH_ERROR;
}
break;
case KCS_PHASE_WRITE_END_CMD:
if (priv->data_in_idx < KCS_MSG_BUFSIZ) {
set_state(priv, READ_STATE);
priv->data_in[priv->data_in_idx++] = kcs_bmc_read_data(dev);
priv->phase = KCS_PHASE_WRITE_DONE;
priv->data_in_avail = true;
wake_up_interruptible(&priv->queue);
} else {
kcs_bmc_ipmi_force_abort(priv);
priv->error = KCS_LENGTH_ERROR;
}
break;
case KCS_PHASE_READ:
if (priv->data_out_idx == priv->data_out_len)
set_state(priv, IDLE_STATE);
data = kcs_bmc_read_data(dev);
if (data != KCS_CMD_READ_BYTE) {
set_state(priv, ERROR_STATE);
kcs_bmc_write_data(dev, KCS_ZERO_DATA);
break;
}
if (priv->data_out_idx == priv->data_out_len) {
kcs_bmc_write_data(dev, KCS_ZERO_DATA);
priv->phase = KCS_PHASE_IDLE;
break;
}
kcs_bmc_write_data(dev, priv->data_out[priv->data_out_idx++]);
break;
case KCS_PHASE_ABORT_ERROR1:
set_state(priv, READ_STATE);
kcs_bmc_read_data(dev);
kcs_bmc_write_data(dev, priv->error);
priv->phase = KCS_PHASE_ABORT_ERROR2;
break;
case KCS_PHASE_ABORT_ERROR2:
set_state(priv, IDLE_STATE);
kcs_bmc_read_data(dev);
kcs_bmc_write_data(dev, KCS_ZERO_DATA);
priv->phase = KCS_PHASE_IDLE;
break;
default:
kcs_bmc_ipmi_force_abort(priv);
break;
}
}
static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc_ipmi *priv)
{
u8 cmd;
set_state(priv, WRITE_STATE);
kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
cmd = kcs_bmc_read_data(priv->client.dev);
switch (cmd) {
case KCS_CMD_WRITE_START:
priv->phase = KCS_PHASE_WRITE_START;
priv->error = KCS_NO_ERROR;
priv->data_in_avail = false;
priv->data_in_idx = 0;
break;
case KCS_CMD_WRITE_END:
if (priv->phase != KCS_PHASE_WRITE_DATA) {
kcs_bmc_ipmi_force_abort(priv);
break;
}
priv->phase = KCS_PHASE_WRITE_END_CMD;
break;
case KCS_CMD_GET_STATUS_ABORT:
if (priv->error == KCS_NO_ERROR)
priv->error = KCS_ABORTED_BY_COMMAND;
priv->phase = KCS_PHASE_ABORT_ERROR1;
priv->data_in_avail = false;
priv->data_in_idx = 0;
break;
default:
kcs_bmc_ipmi_force_abort(priv);
priv->error = KCS_ILLEGAL_CONTROL_CODE;
break;
}
}
static inline struct kcs_bmc_ipmi *client_to_kcs_bmc_ipmi(struct kcs_bmc_client *client)
{
return container_of(client, struct kcs_bmc_ipmi, client);
}
static irqreturn_t kcs_bmc_ipmi_event(struct kcs_bmc_client *client)
{
struct kcs_bmc_ipmi *priv;
u8 status;
int ret;
priv = client_to_kcs_bmc_ipmi(client);
if (!priv)
return IRQ_NONE;
spin_lock(&priv->lock);
status = kcs_bmc_read_status(client->dev);
if (status & KCS_STATUS_IBF) {
if (status & KCS_STATUS_CMD_DAT)
kcs_bmc_ipmi_handle_cmd(priv);
else
kcs_bmc_ipmi_handle_data(priv);
ret = IRQ_HANDLED;
} else {
ret = IRQ_NONE;
}
spin_unlock(&priv->lock);
return ret;
}
static const struct kcs_bmc_client_ops kcs_bmc_ipmi_client_ops = {
.event = kcs_bmc_ipmi_event,
};
static inline struct kcs_bmc_ipmi *to_kcs_bmc(struct file *filp)
{
return container_of(filp->private_data, struct kcs_bmc_ipmi, miscdev);
}
static int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
return kcs_bmc_enable_device(priv->client.dev, &priv->client);
}
static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
__poll_t mask = 0;
poll_wait(filp, &priv->queue, wait);
spin_lock_irq(&priv->lock);
if (priv->data_in_avail)
mask |= EPOLLIN;
spin_unlock_irq(&priv->lock);
return mask;
}
static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
bool data_avail;
size_t data_len;
ssize_t ret;
if (!(filp->f_flags & O_NONBLOCK))
wait_event_interruptible(priv->queue,
priv->data_in_avail);
mutex_lock(&priv->mutex);
spin_lock_irq(&priv->lock);
data_avail = priv->data_in_avail;
if (data_avail) {
data_len = priv->data_in_idx;
memcpy(priv->kbuffer, priv->data_in, data_len);
}
spin_unlock_irq(&priv->lock);
if (!data_avail) {
ret = -EAGAIN;
goto out_unlock;
}
if (count < data_len) {
pr_err("channel=%u with too large data : %zu\n",
priv->client.dev->channel, data_len);
spin_lock_irq(&priv->lock);
kcs_bmc_ipmi_force_abort(priv);
spin_unlock_irq(&priv->lock);
ret = -EOVERFLOW;
goto out_unlock;
}
if (copy_to_user(buf, priv->kbuffer, data_len)) {
ret = -EFAULT;
goto out_unlock;
}
ret = data_len;
spin_lock_irq(&priv->lock);
if (priv->phase == KCS_PHASE_WRITE_DONE) {
priv->phase = KCS_PHASE_WAIT_READ;
priv->data_in_avail = false;
priv->data_in_idx = 0;
} else {
ret = -EAGAIN;
}
spin_unlock_irq(&priv->lock);
out_unlock:
mutex_unlock(&priv->mutex);
return ret;
}
static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
ssize_t ret;
/* a minimum response size '3' : netfn + cmd + ccode */
if (count < 3 || count > KCS_MSG_BUFSIZ)
return -EINVAL;
mutex_lock(&priv->mutex);
if (copy_from_user(priv->kbuffer, buf, count)) {
ret = -EFAULT;
goto out_unlock;
}
spin_lock_irq(&priv->lock);
if (priv->phase == KCS_PHASE_WAIT_READ) {
priv->phase = KCS_PHASE_READ;
priv->data_out_idx = 1;
priv->data_out_len = count;
memcpy(priv->data_out, priv->kbuffer, count);
kcs_bmc_write_data(priv->client.dev, priv->data_out[0]);
ret = count;
} else {
ret = -EINVAL;
}
spin_unlock_irq(&priv->lock);
out_unlock:
mutex_unlock(&priv->mutex);
return ret;
}
static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
long ret = 0;
spin_lock_irq(&priv->lock);
switch (cmd) {
case IPMI_BMC_IOCTL_SET_SMS_ATN:
kcs_bmc_update_status(priv->client.dev, KCS_STATUS_SMS_ATN, KCS_STATUS_SMS_ATN);
break;
case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
kcs_bmc_update_status(priv->client.dev, KCS_STATUS_SMS_ATN, 0);
break;
case IPMI_BMC_IOCTL_FORCE_ABORT:
kcs_bmc_ipmi_force_abort(priv);
break;
default:
ret = -EINVAL;
break;
}
spin_unlock_irq(&priv->lock);
return ret;
}
static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
{
struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
kcs_bmc_ipmi_force_abort(priv);
kcs_bmc_disable_device(priv->client.dev, &priv->client);
return 0;
}
static const struct file_operations kcs_bmc_ipmi_fops = {
.owner = THIS_MODULE,
.open = kcs_bmc_ipmi_open,
.read = kcs_bmc_ipmi_read,
.write = kcs_bmc_ipmi_write,
.release = kcs_bmc_ipmi_release,
.poll = kcs_bmc_ipmi_poll,
.unlocked_ioctl = kcs_bmc_ipmi_ioctl,
};
static DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock);
static LIST_HEAD(kcs_bmc_ipmi_instances);
static int kcs_bmc_ipmi_add_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_ipmi *priv;
int rc;
priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
spin_lock_init(&priv->lock);
mutex_init(&priv->mutex);
init_waitqueue_head(&priv->queue);
priv->client.dev = kcs_bmc;
priv->client.ops = &kcs_bmc_ipmi_client_ops;
priv->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
priv->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
priv->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
priv->miscdev.minor = MISC_DYNAMIC_MINOR;
priv->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u", DEVICE_NAME,
kcs_bmc->channel);
if (!priv->data_in || !priv->data_out || !priv->kbuffer || !priv->miscdev.name)
return -EINVAL;
priv->miscdev.fops = &kcs_bmc_ipmi_fops;
rc = misc_register(&priv->miscdev);
if (rc) {
dev_err(kcs_bmc->dev, "Unable to register device: %d\n", rc);
return rc;
}
spin_lock_irq(&kcs_bmc_ipmi_instances_lock);
list_add(&priv->entry, &kcs_bmc_ipmi_instances);
spin_unlock_irq(&kcs_bmc_ipmi_instances_lock);
dev_info(kcs_bmc->dev, "Initialised IPMI client for channel %d", kcs_bmc->channel);
return 0;
}
static int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_ipmi *priv = NULL, *pos;
spin_lock_irq(&kcs_bmc_ipmi_instances_lock);
list_for_each_entry(pos, &kcs_bmc_ipmi_instances, entry) {
if (pos->client.dev == kcs_bmc) {
priv = pos;
list_del(&pos->entry);
break;
}
}
spin_unlock_irq(&kcs_bmc_ipmi_instances_lock);
if (!priv)
return -ENODEV;
misc_deregister(&priv->miscdev);
kcs_bmc_disable_device(priv->client.dev, &priv->client);
devm_kfree(kcs_bmc->dev, priv->kbuffer);
devm_kfree(kcs_bmc->dev, priv->data_out);
devm_kfree(kcs_bmc->dev, priv->data_in);
devm_kfree(kcs_bmc->dev, priv);
return 0;
}
static const struct kcs_bmc_driver_ops kcs_bmc_ipmi_driver_ops = {
.add_device = kcs_bmc_ipmi_add_device,
.remove_device = kcs_bmc_ipmi_remove_device,
};
static struct kcs_bmc_driver kcs_bmc_ipmi_driver = {
.ops = &kcs_bmc_ipmi_driver_ops,
};
static int __init kcs_bmc_ipmi_init(void)
{
kcs_bmc_register_driver(&kcs_bmc_ipmi_driver);
return 0;
}
module_init(kcs_bmc_ipmi_init);
static void __exit kcs_bmc_ipmi_exit(void)
{
kcs_bmc_unregister_driver(&kcs_bmc_ipmi_driver);
}
module_exit(kcs_bmc_ipmi_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2021, IBM Corp. */
#ifndef __KCS_BMC_CONSUMER_H__
#define __KCS_BMC_CONSUMER_H__
#include <linux/irqreturn.h>
#include "kcs_bmc.h"
struct kcs_bmc_driver_ops {
int (*add_device)(struct kcs_bmc_device *kcs_bmc);
int (*remove_device)(struct kcs_bmc_device *kcs_bmc);
};
struct kcs_bmc_driver {
struct list_head entry;
const struct kcs_bmc_driver_ops *ops;
};
struct kcs_bmc_client_ops {
irqreturn_t (*event)(struct kcs_bmc_client *client);
};
struct kcs_bmc_client {
const struct kcs_bmc_client_ops *ops;
struct kcs_bmc_device *dev;
};
void kcs_bmc_register_driver(struct kcs_bmc_driver *drv);
void kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv);
int kcs_bmc_enable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client);
void kcs_bmc_disable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client);
void kcs_bmc_update_event_mask(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 events);
u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc);
void kcs_bmc_write_data(struct kcs_bmc_device *kcs_bmc, u8 data);
u8 kcs_bmc_read_status(struct kcs_bmc_device *kcs_bmc);
void kcs_bmc_write_status(struct kcs_bmc_device *kcs_bmc, u8 data);
void kcs_bmc_update_status(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 val);
#endif
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2021, IBM Corp. */
#ifndef __KCS_BMC_DEVICE_H__
#define __KCS_BMC_DEVICE_H__
#include <linux/irqreturn.h>
#include "kcs_bmc.h"
struct kcs_bmc_device_ops {
void (*irq_mask_update)(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 enable);
u8 (*io_inputb)(struct kcs_bmc_device *kcs_bmc, u32 reg);
void (*io_outputb)(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 b);
void (*io_updateb)(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask, u8 b);
};
irqreturn_t kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc);
int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc);
void kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc);
#endif
@@ -0,0 +1,251 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018, Nuvoton Corporation.
* Copyright (c) 2018, Intel Corporation.
*/
#define pr_fmt(fmt) "nuvoton-kcs-bmc: " fmt
#include <linux/atomic.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include "kcs_bmc_device.h"
#define DEVICE_NAME "npcm-kcs-bmc"
#define KCS_CHANNEL_MAX 3
#define KCS1ST 0x0C
#define KCS2ST 0x1E
#define KCS3ST 0x30
#define KCS1DO 0x0E
#define KCS2DO 0x20
#define KCS3DO 0x32
#define KCS1DI 0x10
#define KCS2DI 0x22
#define KCS3DI 0x34
#define KCS1CTL 0x18
#define KCS2CTL 0x2A
#define KCS3CTL 0x3C
#define KCS_CTL_IBFIE BIT(0)
#define KCS_CTL_OBEIE BIT(1)
#define KCS1IE 0x1C
#define KCS2IE 0x2E
#define KCS3IE 0x40
#define KCS_IE_IRQE BIT(0)
#define KCS_IE_HIRQE BIT(3)
/*
* 7.2.4 Core KCS Registers
* Registers in this module are 8 bits. An 8-bit register must be accessed
* by an 8-bit read or write.
*
* sts: KCS Channel n Status Register (KCSnST).
* dob: KCS Channel n Data Out Buffer Register (KCSnDO).
* dib: KCS Channel n Data In Buffer Register (KCSnDI).
* ctl: KCS Channel n Control Register (KCSnCTL).
* ie : KCS Channel n Interrupt Enable Register (KCSnIE).
*/
struct npcm7xx_kcs_reg {
u32 sts;
u32 dob;
u32 dib;
u32 ctl;
u32 ie;
};
struct npcm7xx_kcs_bmc {
struct kcs_bmc_device kcs_bmc;
struct regmap *map;
const struct npcm7xx_kcs_reg *reg;
};
static const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = {
{ .sts = KCS1ST, .dob = KCS1DO, .dib = KCS1DI, .ctl = KCS1CTL, .ie = KCS1IE },
{ .sts = KCS2ST, .dob = KCS2DO, .dib = KCS2DI, .ctl = KCS2CTL, .ie = KCS2IE },
{ .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE },
};
static inline struct npcm7xx_kcs_bmc *to_npcm7xx_kcs_bmc(struct kcs_bmc_device *kcs_bmc)
{
return container_of(kcs_bmc, struct npcm7xx_kcs_bmc, kcs_bmc);
}
static u8 npcm7xx_kcs_inb(struct kcs_bmc_device *kcs_bmc, u32 reg)
{
struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
u32 val = 0;
int rc;
rc = regmap_read(priv->map, reg, &val);
WARN(rc != 0, "regmap_read() failed: %d\n", rc);
return rc == 0 ? (u8)val : 0;
}
static void npcm7xx_kcs_outb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 data)
{
struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
int rc;
rc = regmap_write(priv->map, reg, data);
WARN(rc != 0, "regmap_write() failed: %d\n", rc);
}
static void npcm7xx_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask, u8 data)
{
struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
int rc;
rc = regmap_update_bits(priv->map, reg, mask, data);
WARN(rc != 0, "regmap_update_bits() failed: %d\n", rc);
}
static void npcm7xx_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enable)
{
struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
regmap_update_bits(priv->map, priv->reg->ie, KCS_IE_IRQE | KCS_IE_HIRQE,
enable ? KCS_IE_IRQE | KCS_IE_HIRQE : 0);
}
static void npcm7xx_kcs_irq_mask_update(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 state)
{
struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
if (mask & KCS_BMC_EVENT_TYPE_OBE)
regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_OBEIE,
!!(state & KCS_BMC_EVENT_TYPE_OBE) * KCS_CTL_OBEIE);
if (mask & KCS_BMC_EVENT_TYPE_IBF)
regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
!!(state & KCS_BMC_EVENT_TYPE_IBF) * KCS_CTL_IBFIE);
}
static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
{
struct kcs_bmc_device *kcs_bmc = arg;
return kcs_bmc_handle_event(kcs_bmc);
}
static int npcm7xx_kcs_config_irq(struct kcs_bmc_device *kcs_bmc,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int irq;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
return devm_request_irq(dev, irq, npcm7xx_kcs_irq, IRQF_SHARED,
dev_name(dev), kcs_bmc);
}
static const struct kcs_bmc_device_ops npcm7xx_kcs_ops = {
.irq_mask_update = npcm7xx_kcs_irq_mask_update,
.io_inputb = npcm7xx_kcs_inb,
.io_outputb = npcm7xx_kcs_outb,
.io_updateb = npcm7xx_kcs_updateb,
};
static int npcm7xx_kcs_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct npcm7xx_kcs_bmc *priv;
struct kcs_bmc_device *kcs_bmc;
u32 chan;
int rc;
rc = of_property_read_u32(dev->of_node, "kcs_chan", &chan);
if (rc != 0 || chan == 0 || chan > KCS_CHANNEL_MAX) {
dev_err(dev, "no valid 'kcs_chan' configured\n");
return -ENODEV;
}
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->map = syscon_node_to_regmap(dev->parent->of_node);
if (IS_ERR(priv->map)) {
dev_err(dev, "Couldn't get regmap\n");
return -ENODEV;
}
priv->reg = &npcm7xx_kcs_reg_tbl[chan - 1];
kcs_bmc = &priv->kcs_bmc;
kcs_bmc->dev = &pdev->dev;
kcs_bmc->channel = chan;
kcs_bmc->ioreg.idr = priv->reg->dib;
kcs_bmc->ioreg.odr = priv->reg->dob;
kcs_bmc->ioreg.str = priv->reg->sts;
kcs_bmc->ops = &npcm7xx_kcs_ops;
platform_set_drvdata(pdev, priv);
rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev);
if (rc)
return rc;
npcm7xx_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
npcm7xx_kcs_enable_channel(kcs_bmc, true);
rc = kcs_bmc_add_device(kcs_bmc);
if (rc) {
dev_warn(&pdev->dev, "Failed to register channel %d: %d\n", kcs_bmc->channel, rc);
return rc;
}
pr_info("channel=%u idr=0x%x odr=0x%x str=0x%x\n",
chan,
kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr, kcs_bmc->ioreg.str);
return 0;
}
static void npcm7xx_kcs_remove(struct platform_device *pdev)
{
struct npcm7xx_kcs_bmc *priv = platform_get_drvdata(pdev);
struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc;
kcs_bmc_remove_device(kcs_bmc);
npcm7xx_kcs_enable_channel(kcs_bmc, false);
npcm7xx_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0);
}
static const struct of_device_id npcm_kcs_bmc_match[] = {
{ .compatible = "nuvoton,npcm750-kcs-bmc" },
{ }
};
MODULE_DEVICE_TABLE(of, npcm_kcs_bmc_match);
static struct platform_driver npcm_kcs_bmc_driver = {
.driver = {
.name = DEVICE_NAME,
.of_match_table = npcm_kcs_bmc_match,
},
.probe = npcm7xx_kcs_probe,
.remove = npcm7xx_kcs_remove,
};
module_platform_driver(npcm_kcs_bmc_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Avi Fishman <avifishman70@gmail.com>");
MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
MODULE_DESCRIPTION("NPCM7xx device interface to the KCS BMC device");
@@ -0,0 +1,159 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* Copyright (c) 2021 IBM Corp. */
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/sched/signal.h>
#include <linux/serio.h>
#include <linux/slab.h>
#include "kcs_bmc_client.h"
struct kcs_bmc_serio {
struct list_head entry;
struct kcs_bmc_client client;
struct serio *port;
spinlock_t lock;
};
static inline struct kcs_bmc_serio *client_to_kcs_bmc_serio(struct kcs_bmc_client *client)
{
return container_of(client, struct kcs_bmc_serio, client);
}
static irqreturn_t kcs_bmc_serio_event(struct kcs_bmc_client *client)
{
struct kcs_bmc_serio *priv;
u8 handled = IRQ_NONE;
u8 status;
priv = client_to_kcs_bmc_serio(client);
spin_lock(&priv->lock);
status = kcs_bmc_read_status(client->dev);
if (status & KCS_BMC_STR_IBF)
handled = serio_interrupt(priv->port, kcs_bmc_read_data(client->dev), 0);
spin_unlock(&priv->lock);
return handled;
}
static const struct kcs_bmc_client_ops kcs_bmc_serio_client_ops = {
.event = kcs_bmc_serio_event,
};
static int kcs_bmc_serio_open(struct serio *port)
{
struct kcs_bmc_serio *priv = port->port_data;
return kcs_bmc_enable_device(priv->client.dev, &priv->client);
}
static void kcs_bmc_serio_close(struct serio *port)
{
struct kcs_bmc_serio *priv = port->port_data;
kcs_bmc_disable_device(priv->client.dev, &priv->client);
}
static DEFINE_SPINLOCK(kcs_bmc_serio_instances_lock);
static LIST_HEAD(kcs_bmc_serio_instances);
static int kcs_bmc_serio_add_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_serio *priv;
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_obj(*port);
if (!port)
return -ENOMEM;
port->id.type = SERIO_8042;
port->open = kcs_bmc_serio_open;
port->close = kcs_bmc_serio_close;
port->port_data = priv;
port->dev.parent = kcs_bmc->dev;
spin_lock_init(&priv->lock);
priv->port = port;
priv->client.dev = kcs_bmc;
priv->client.ops = &kcs_bmc_serio_client_ops;
spin_lock_irq(&kcs_bmc_serio_instances_lock);
list_add(&priv->entry, &kcs_bmc_serio_instances);
spin_unlock_irq(&kcs_bmc_serio_instances_lock);
serio_register_port(port);
dev_info(kcs_bmc->dev, "Initialised serio client for channel %d", kcs_bmc->channel);
return 0;
}
static int kcs_bmc_serio_remove_device(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_serio *priv = NULL, *pos;
spin_lock_irq(&kcs_bmc_serio_instances_lock);
list_for_each_entry(pos, &kcs_bmc_serio_instances, entry) {
if (pos->client.dev == kcs_bmc) {
priv = pos;
list_del(&pos->entry);
break;
}
}
spin_unlock_irq(&kcs_bmc_serio_instances_lock);
if (!priv)
return -ENODEV;
/* kfree()s priv->port via put_device() */
serio_unregister_port(priv->port);
/* Ensure the IBF IRQ is disabled if we were the active client */
kcs_bmc_disable_device(kcs_bmc, &priv->client);
devm_kfree(priv->client.dev->dev, priv);
return 0;
}
static const struct kcs_bmc_driver_ops kcs_bmc_serio_driver_ops = {
.add_device = kcs_bmc_serio_add_device,
.remove_device = kcs_bmc_serio_remove_device,
};
static struct kcs_bmc_driver kcs_bmc_serio_driver = {
.ops = &kcs_bmc_serio_driver_ops,
};
static int __init kcs_bmc_serio_init(void)
{
kcs_bmc_register_driver(&kcs_bmc_serio_driver);
return 0;
}
module_init(kcs_bmc_serio_init);
static void __exit kcs_bmc_serio_exit(void)
{
kcs_bmc_unregister_driver(&kcs_bmc_serio_driver);
}
module_exit(kcs_bmc_serio_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
MODULE_DESCRIPTION("Adapter driver for serio access to BMC KCS devices");
File diff suppressed because it is too large Load Diff