2021-03-19 06:31:01

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 01/21] dt-bindings: aspeed-lpc: Remove LPC partitioning

From: "Chia-Wei, Wang" <[email protected]>

The LPC controller has no concept of the BMC and the Host partitions.
This patch fixes the documentation by removing the description on LPC
partitions. The register offsets illustrated in the DTS node examples
are also fixed to adapt to the LPC DTS change.

Signed-off-by: Chia-Wei Wang <[email protected]>
Reviewed-by: Andrew Jeffery <[email protected]>
Acked-by: Rob Herring <[email protected]>
Acked-by: Lee Jones <[email protected]>
---
.../devicetree/bindings/mfd/aspeed-lpc.txt | 100 +++++-------------
1 file changed, 25 insertions(+), 75 deletions(-)

diff --git a/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt b/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt
index d0a38ba8b9ce..936aa108eab4 100644
--- a/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt
+++ b/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt
@@ -9,13 +9,7 @@ primary use case of the Aspeed LPC controller is as a slave on the bus
conditions it can also take the role of bus master.

The LPC controller is represented as a multi-function device to account for the
-mix of functionality it provides. The principle split is between the register
-layout at the start of the I/O space which is, to quote the Aspeed datasheet,
-"basically compatible with the [LPC registers from the] popular BMC controller
-H8S/2168[1]", and everything else, where everything else is an eclectic
-collection of functions with a esoteric register layout. "Everything else",
-here labeled the "host" portion of the controller, includes, but is not limited
-to:
+mix of functionality, which includes, but is not limited to:

* An IPMI Block Transfer[2] Controller

@@ -44,80 +38,36 @@ Required properties
===================

- compatible: One of:
- "aspeed,ast2400-lpc", "simple-mfd"
- "aspeed,ast2500-lpc", "simple-mfd"
- "aspeed,ast2600-lpc", "simple-mfd"
+ "aspeed,ast2400-lpc-v2", "simple-mfd", "syscon"
+ "aspeed,ast2500-lpc-v2", "simple-mfd", "syscon"
+ "aspeed,ast2600-lpc-v2", "simple-mfd", "syscon"

- reg: contains the physical address and length values of the Aspeed
LPC memory region.

- #address-cells: <1>
- #size-cells: <1>
-- ranges: Maps 0 to the physical address and length of the LPC memory
- region
-
-Required LPC Child nodes
-========================
-
-BMC Node
---------
-
-- compatible: One of:
- "aspeed,ast2400-lpc-bmc"
- "aspeed,ast2500-lpc-bmc"
- "aspeed,ast2600-lpc-bmc"
-
-- reg: contains the physical address and length values of the
- H8S/2168-compatible LPC controller memory region
-
-Host Node
----------
-
-- compatible: One of:
- "aspeed,ast2400-lpc-host", "simple-mfd", "syscon"
- "aspeed,ast2500-lpc-host", "simple-mfd", "syscon"
- "aspeed,ast2600-lpc-host", "simple-mfd", "syscon"
-
-- reg: contains the address and length values of the host-related
- register space for the Aspeed LPC controller
-
-- #address-cells: <1>
-- #size-cells: <1>
-- ranges: Maps 0 to the address and length of the host-related LPC memory
+- ranges: Maps 0 to the physical address and length of the LPC memory
region

Example:

lpc: lpc@1e789000 {
- compatible = "aspeed,ast2500-lpc", "simple-mfd";
+ compatible = "aspeed,ast2500-lpc-v2", "simple-mfd", "syscon";
reg = <0x1e789000 0x1000>;

#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0x1e789000 0x1000>;

- lpc_bmc: lpc-bmc@0 {
- compatible = "aspeed,ast2500-lpc-bmc";
+ lpc_snoop: lpc-snoop@0 {
+ compatible = "aspeed,ast2600-lpc-snoop";
reg = <0x0 0x80>;
- };
-
- lpc_host: lpc-host@80 {
- compatible = "aspeed,ast2500-lpc-host", "simple-mfd", "syscon";
- reg = <0x80 0x1e0>;
- reg-io-width = <4>;
-
- #address-cells = <1>;
- #size-cells = <1>;
- ranges = <0x0 0x80 0x1e0>;
+ interrupts = <GIC_SPI 144 IRQ_TYPE_LEVEL_HIGH>;
+ snoop-ports = <0x80>;
};
};

-BMC Node Children
-==================
-
-
-Host Node Children
-==================

LPC Host Interface Controller
-------------------
@@ -149,14 +99,12 @@ Optional properties:

Example:

-lpc-host@80 {
- lpc_ctrl: lpc-ctrl@0 {
- compatible = "aspeed,ast2500-lpc-ctrl";
- reg = <0x0 0x80>;
- clocks = <&syscon ASPEED_CLK_GATE_LCLK>;
- memory-region = <&flash_memory>;
- flash = <&spi>;
- };
+lpc_ctrl: lpc-ctrl@80 {
+ compatible = "aspeed,ast2500-lpc-ctrl";
+ reg = <0x80 0x80>;
+ clocks = <&syscon ASPEED_CLK_GATE_LCLK>;
+ memory-region = <&flash_memory>;
+ flash = <&spi>;
};

LPC Host Controller
@@ -179,9 +127,9 @@ Required properties:

Example:

-lhc: lhc@20 {
+lhc: lhc@a0 {
compatible = "aspeed,ast2500-lhc";
- reg = <0x20 0x24 0x48 0x8>;
+ reg = <0xa0 0x24 0xc8 0x8>;
};

LPC reset control
@@ -192,16 +140,18 @@ state of the LPC bus. Some systems may chose to modify this configuration.

Required properties:

- - compatible: "aspeed,ast2600-lpc-reset" or
- "aspeed,ast2500-lpc-reset"
- "aspeed,ast2400-lpc-reset"
+ - compatible: One of:
+ "aspeed,ast2600-lpc-reset";
+ "aspeed,ast2500-lpc-reset";
+ "aspeed,ast2400-lpc-reset";
+
- reg: offset and length of the IP in the LHC memory region
- #reset-controller indicates the number of reset cells expected

Example:

-lpc_reset: reset-controller@18 {
+lpc_reset: reset-controller@98 {
compatible = "aspeed,ast2500-lpc-reset";
- reg = <0x18 0x4>;
+ reg = <0x98 0x4>;
#reset-cells = <1>;
};
--
2.27.0


2021-03-19 06:31:01

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 04/21] pinctrl: aspeed-g5: Adapt to new LPC device tree layout

From: "Chia-Wei, Wang" <[email protected]>

Add check against LPC device v2 compatible string to
ensure that the fixed device tree layout is adopted.
The LPC register offsets are also fixed accordingly.

Signed-off-by: Chia-Wei Wang <[email protected]>
Reviewed-by: Andrew Jeffery <[email protected]>
Acked-by: Linus Walleij <[email protected]>
---
drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c b/drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c
index 0cab4c2576e2..996ebcba4d38 100644
--- a/drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c
+++ b/drivers/pinctrl/aspeed/pinctrl-aspeed-g5.c
@@ -60,7 +60,7 @@
#define COND2 { ASPEED_IP_SCU, SCU94, GENMASK(1, 0), 0, 0 }

/* LHCR0 is offset from the end of the H8S/2168-compatible registers */
-#define LHCR0 0x20
+#define LHCR0 0xa0
#define GFX064 0x64

#define B14 0
@@ -2648,14 +2648,19 @@ static struct regmap *aspeed_g5_acquire_regmap(struct aspeed_pinmux_data *ctx,
}

if (ip == ASPEED_IP_LPC) {
- struct device_node *node;
+ struct device_node *np;
struct regmap *map;

- node = of_parse_phandle(ctx->dev->of_node,
+ np = of_parse_phandle(ctx->dev->of_node,
"aspeed,external-nodes", 1);
- if (node) {
- map = syscon_node_to_regmap(node->parent);
- of_node_put(node);
+ if (np) {
+ if (!of_device_is_compatible(np->parent, "aspeed,ast2400-lpc-v2") &&
+ !of_device_is_compatible(np->parent, "aspeed,ast2500-lpc-v2") &&
+ !of_device_is_compatible(np->parent, "aspeed,ast2600-lpc-v2"))
+ return ERR_PTR(-ENODEV);
+
+ map = syscon_node_to_regmap(np->parent);
+ of_node_put(np);
if (IS_ERR(map))
return map;
} else
--
2.27.0

2021-03-19 06:31:11

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 06/21] ipmi: kcs_bmc_aspeed: Use of match data to extract KCS properties

Unpack and remove the aspeed_kcs_probe_of_v[12]() functions to aid
rearranging how the private device-driver memory is allocated.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/kcs_bmc_aspeed.c | 146 ++++++++++++++---------------
1 file changed, 68 insertions(+), 78 deletions(-)

diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
index eefe362f65f0..061f53676206 100644
--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
@@ -13,6 +13,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/regmap.h>
@@ -63,6 +64,10 @@ struct aspeed_kcs_bmc {
struct regmap *map;
};

+struct aspeed_kcs_of_ops {
+ int (*get_channel)(struct platform_device *pdev);
+ int (*get_io_address)(struct platform_device *pdev);
+};

static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
{
@@ -231,13 +236,10 @@ static const struct kcs_ioreg ast_kcs_bmc_ioregs[KCS_CHANNEL_MAX] = {
{ .idr = LPC_IDR4, .odr = LPC_ODR4, .str = LPC_STR4 },
};

-static struct kcs_bmc *aspeed_kcs_probe_of_v1(struct platform_device *pdev)
+static int aspeed_kcs_of_v1_get_channel(struct platform_device *pdev)
{
- struct aspeed_kcs_bmc *priv;
struct device_node *np;
- struct kcs_bmc *kcs;
u32 channel;
- u32 slave;
int rc;

np = pdev->dev.of_node;
@@ -245,105 +247,78 @@ static struct kcs_bmc *aspeed_kcs_probe_of_v1(struct platform_device *pdev)
rc = of_property_read_u32(np, "kcs_chan", &channel);
if ((rc != 0) || (channel == 0 || channel > KCS_CHANNEL_MAX)) {
dev_err(&pdev->dev, "no valid 'kcs_chan' configured\n");
- return ERR_PTR(-EINVAL);
+ return -EINVAL;
}

- kcs = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
- if (!kcs)
- return ERR_PTR(-ENOMEM);
+ return channel;
+}

- priv = kcs_bmc_priv(kcs);
- 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 ERR_PTR(-ENODEV);
- }
+static int aspeed_kcs_of_v1_get_io_address(struct platform_device *pdev)
+{
+ u32 slave;
+ int rc;

- rc = of_property_read_u32(np, "kcs_addr", &slave);
- if (rc) {
+ rc = of_property_read_u32(pdev->dev.of_node, "kcs_addr", &slave);
+ if (rc || slave > 0xffff) {
dev_err(&pdev->dev, "no valid 'kcs_addr' configured\n");
- return ERR_PTR(-EINVAL);
+ return -EINVAL;
}

- kcs->ioreg = ast_kcs_bmc_ioregs[channel - 1];
- aspeed_kcs_set_address(kcs, slave);
-
- return kcs;
-}
-
-static int aspeed_kcs_calculate_channel(const struct kcs_ioreg *regs)
-{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) {
- if (!memcmp(&ast_kcs_bmc_ioregs[i], regs, sizeof(*regs)))
- return i + 1;
- }
-
- return -EINVAL;
+ return slave;
}

-static struct kcs_bmc *aspeed_kcs_probe_of_v2(struct platform_device *pdev)
+static int aspeed_kcs_of_v2_get_channel(struct platform_device *pdev)
{
- struct aspeed_kcs_bmc *priv;
struct device_node *np;
struct kcs_ioreg ioreg;
- struct kcs_bmc *kcs;
const __be32 *reg;
- int channel;
- u32 slave;
- int rc;
+ 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 ERR_PTR(-EINVAL);
+ return -EINVAL;
ioreg.idr = be32_to_cpup(reg);

reg = of_get_address(np, 1, NULL, NULL);
if (!reg)
- return ERR_PTR(-EINVAL);
+ return -EINVAL;
ioreg.odr = be32_to_cpup(reg);

reg = of_get_address(np, 2, NULL, NULL);
if (!reg)
- return ERR_PTR(-EINVAL);
+ return -EINVAL;
ioreg.str = be32_to_cpup(reg);

- channel = aspeed_kcs_calculate_channel(&ioreg);
- if (channel < 0)
- return ERR_PTR(channel);
-
- kcs = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
- if (!kcs)
- return ERR_PTR(-ENOMEM);
-
- kcs->ioreg = ioreg;
-
- priv = kcs_bmc_priv(kcs);
- 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 ERR_PTR(-ENODEV);
+ for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) {
+ if (!memcmp(&ast_kcs_bmc_ioregs[i], &ioreg, sizeof(ioreg)))
+ return i + 1;
}

- rc = of_property_read_u32(np, "aspeed,lpc-io-reg", &slave);
- if (rc)
- return ERR_PTR(rc);
+ return -EINVAL;
+}

- aspeed_kcs_set_address(kcs, slave);
+static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
+{
+ uint32_t slave;
+ int rc;

- return kcs;
+ rc = of_property_read_u32(pdev->dev.of_node, "aspeed,lpc-io-reg", &slave);
+ if (rc || slave > 0xffff)
+ return -EINVAL;
+
+ return slave;
}

static int aspeed_kcs_probe(struct platform_device *pdev)
{
+ const struct aspeed_kcs_of_ops *ops;
struct device *dev = &pdev->dev;
struct kcs_bmc *kcs_bmc;
struct device_node *np;
- int rc;
+ int rc, channel, addr;

np = dev->of_node->parent;
if (!of_device_is_compatible(np, "aspeed,ast2400-lpc-v2") &&
@@ -352,23 +327,28 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
dev_err(dev, "unsupported LPC device binding\n");
return -ENODEV;
}
-
- np = dev->of_node;
- if (of_device_is_compatible(np, "aspeed,ast2400-kcs-bmc") ||
- of_device_is_compatible(np, "aspeed,ast2500-kcs-bmc"))
- kcs_bmc = aspeed_kcs_probe_of_v1(pdev);
- else if (of_device_is_compatible(np, "aspeed,ast2400-kcs-bmc-v2") ||
- of_device_is_compatible(np, "aspeed,ast2500-kcs-bmc-v2"))
- kcs_bmc = aspeed_kcs_probe_of_v2(pdev);
- else
+ ops = of_device_get_match_data(&pdev->dev);
+ if (!ops)
return -EINVAL;

- if (IS_ERR(kcs_bmc))
- return PTR_ERR(kcs_bmc);
+ channel = ops->get_channel(pdev);
+ if (channel < 0)
+ return channel;

+ kcs_bmc = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
+ if (!kcs_bmc)
+ return -ENOMEM;
+
+ kcs_bmc->ioreg = ast_kcs_bmc_ioregs[channel - 1];
kcs_bmc->io_inputb = aspeed_kcs_inb;
kcs_bmc->io_outputb = aspeed_kcs_outb;

+ addr = ops->get_io_address(pdev);
+ if (addr < 0)
+ return addr;
+
+ aspeed_kcs_set_address(kcs_bmc, addr);
+
rc = aspeed_kcs_config_irq(kcs_bmc, pdev);
if (rc)
return rc;
@@ -400,11 +380,21 @@ static int aspeed_kcs_remove(struct platform_device *pdev)
return 0;
}

+static const struct aspeed_kcs_of_ops of_v1_ops = {
+ .get_channel = aspeed_kcs_of_v1_get_channel,
+ .get_io_address = aspeed_kcs_of_v1_get_io_address,
+};
+
+static const struct aspeed_kcs_of_ops of_v2_ops = {
+ .get_channel = aspeed_kcs_of_v2_get_channel,
+ .get_io_address = aspeed_kcs_of_v2_get_io_address,
+};
+
static const struct of_device_id ast_kcs_bmc_match[] = {
- { .compatible = "aspeed,ast2400-kcs-bmc" },
- { .compatible = "aspeed,ast2500-kcs-bmc" },
- { .compatible = "aspeed,ast2400-kcs-bmc-v2" },
- { .compatible = "aspeed,ast2500-kcs-bmc-v2" },
+ { .compatible = "aspeed,ast2400-kcs-bmc", .data = &of_v1_ops },
+ { .compatible = "aspeed,ast2500-kcs-bmc", .data = &of_v1_ops },
+ { .compatible = "aspeed,ast2400-kcs-bmc-v2", .data = &of_v2_ops },
+ { .compatible = "aspeed,ast2500-kcs-bmc-v2", .data = &of_v2_ops },
{ }
};
MODULE_DEVICE_TABLE(of, ast_kcs_bmc_match);
--
2.27.0

2021-03-19 06:31:31

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 11/21] ipmi: kcs_bmc: Split headers into device and client

Strengthen the distinction between code that abstracts the
implementation of the KCS behaviours (device drivers) and code that
exploits KCS behaviours (clients). Neither needs to know about the APIs
required by the other, so provide separate headers.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/kcs_bmc.c | 21 ++++++++++-----
drivers/char/ipmi/kcs_bmc.h | 30 ++++++++++-----------
drivers/char/ipmi/kcs_bmc_aspeed.c | 20 +++++++++-----
drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 39 ++++++++++++++++++---------
drivers/char/ipmi/kcs_bmc_client.h | 29 ++++++++++++++++++++
drivers/char/ipmi/kcs_bmc_device.h | 19 +++++++++++++
drivers/char/ipmi/kcs_bmc_npcm7xx.c | 20 +++++++++-----
7 files changed, 129 insertions(+), 49 deletions(-)
create mode 100644 drivers/char/ipmi/kcs_bmc_client.h
create mode 100644 drivers/char/ipmi/kcs_bmc_device.h

diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
index 709b6bdec165..1046ce2bbefc 100644
--- a/drivers/char/ipmi/kcs_bmc.c
+++ b/drivers/char/ipmi/kcs_bmc.c
@@ -1,46 +1,52 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015-2018, Intel Corporation.
+ * Copyright (c) 2021, IBM Corp.
*/

#include <linux/module.h>

#include "kcs_bmc.h"

+/* Implement both the device and client interfaces here */
+#include "kcs_bmc_device.h"
+#include "kcs_bmc_client.h"
+
+/* Consumer data access */
+
u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
{
- return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
+ 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 *kcs_bmc, u8 data)
{
- kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, 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 *kcs_bmc)
{
- return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
+ 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 *kcs_bmc, u8 data)
{
- kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, 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 *kcs_bmc, u8 mask, u8 val)
{
- kcs_bmc->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
+ kcs_bmc->ops->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
}
EXPORT_SYMBOL(kcs_bmc_update_status);

-int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);
int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
{
- return kcs_bmc_ipmi_event(kcs_bmc);
+ return kcs_bmc->client.ops->event(&kcs_bmc->client);
}
EXPORT_SYMBOL(kcs_bmc_handle_event);

@@ -60,4 +66,5 @@ EXPORT_SYMBOL(kcs_bmc_remove_device);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <[email protected]>");
+MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
index bf0ae327997f..a1350e567723 100644
--- a/drivers/char/ipmi/kcs_bmc.h
+++ b/drivers/char/ipmi/kcs_bmc.h
@@ -8,6 +8,15 @@

#include <linux/miscdevice.h>

+#include "kcs_bmc_client.h"
+
+#define KCS_BMC_EVENT_NONE 0
+#define KCS_BMC_EVENT_HANDLED 1
+
+#define KCS_BMC_STR_OBF BIT(0)
+#define KCS_BMC_STR_IBF BIT(1)
+#define KCS_BMC_STR_CMD_DAT BIT(3)
+
/* Different phases of the KCS BMC module.
* KCS_PHASE_IDLE:
* BMC should not be expecting nor sending any data.
@@ -66,19 +75,21 @@ struct kcs_ioreg {
u32 str;
};

+struct kcs_bmc_device_ops;
+
struct kcs_bmc {
struct device *dev;

+ const struct kcs_bmc_device_ops *ops;
+
+ struct kcs_bmc_client client;
+
spinlock_t lock;

u32 channel;
int running;

- /* Setup by BMC KCS controller driver */
struct kcs_ioreg ioreg;
- u8 (*io_inputb)(struct kcs_bmc *kcs_bmc, u32 reg);
- void (*io_outputb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 b);
- void (*io_updateb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val);

enum kcs_phases phase;
enum kcs_errors error;
@@ -97,15 +108,4 @@ struct kcs_bmc {

struct miscdevice miscdev;
};
-
-int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
-int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc);
-int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc);
-
-u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
-void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
-u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc);
-void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data);
-void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val);
-
#endif /* __KCS_BMC_H__ */
diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
index 0416ac78ce68..1b313355b1c8 100644
--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
@@ -21,7 +21,7 @@
#include <linux/slab.h>
#include <linux/timer.h>

-#include "kcs_bmc.h"
+#include "kcs_bmc_device.h"


#define DEVICE_NAME "ast-kcs-bmc"
@@ -220,14 +220,22 @@ static void aspeed_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
}
}

+static const struct kcs_bmc_device_ops aspeed_kcs_ops = {
+ .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 *kcs_bmc = arg;
+ int rc;

- if (!kcs_bmc_handle_event(kcs_bmc))
- return IRQ_HANDLED;
+ rc = kcs_bmc_handle_event(kcs_bmc);
+ if (rc < 0)
+ dev_warn(kcs_bmc->dev, "Failed to service irq: %d\n", rc);

- return IRQ_NONE;
+ return rc == KCS_BMC_EVENT_HANDLED ? IRQ_HANDLED : IRQ_NONE;
}

static int aspeed_kcs_config_irq(struct kcs_bmc *kcs_bmc,
@@ -362,9 +370,7 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
kcs_bmc->dev = &pdev->dev;
kcs_bmc->channel = channel;
kcs_bmc->ioreg = ast_kcs_bmc_ioregs[channel - 1];
- kcs_bmc->io_inputb = aspeed_kcs_inb;
- kcs_bmc->io_outputb = aspeed_kcs_outb;
- kcs_bmc->io_updateb = aspeed_kcs_updateb;
+ kcs_bmc->ops = &aspeed_kcs_ops;

priv->map = syscon_node_to_regmap(pdev->dev.parent->of_node);
if (IS_ERR(priv->map)) {
diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
index 0ca71c135a1a..fd852d8abe48 100644
--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
@@ -22,7 +22,6 @@

#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)
@@ -179,12 +178,19 @@ static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc *kcs_bmc)
}
}

-int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);
-int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc)
+static inline struct kcs_bmc *client_to_kcs_bmc(struct kcs_bmc_client *client)
{
+ return container_of(client, struct kcs_bmc, client);
+}
+
+static int kcs_bmc_ipmi_event(struct kcs_bmc_client *client)
+{
+ struct kcs_bmc *kcs_bmc;
unsigned long flags;
- int ret = -ENODATA;
u8 status;
+ int ret;
+
+ kcs_bmc = client_to_kcs_bmc(client);

spin_lock_irqsave(&kcs_bmc->lock, flags);

@@ -197,23 +203,28 @@ int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc)
else
kcs_bmc_ipmi_handle_data(kcs_bmc);

- ret = 0;
+ ret = KCS_BMC_EVENT_HANDLED;
+ } else {
+ ret = KCS_BMC_EVENT_NONE;
}

spin_unlock_irqrestore(&kcs_bmc->lock, flags);

return ret;
}
-EXPORT_SYMBOL(kcs_bmc_ipmi_event);

-static inline struct kcs_bmc *to_kcs_bmc(struct file *filp)
+static const struct kcs_bmc_client_ops kcs_bmc_ipmi_client_ops = {
+ .event = kcs_bmc_ipmi_event,
+};
+
+static inline struct kcs_bmc *file_to_kcs_bmc(struct file *filp)
{
return container_of(filp->private_data, struct kcs_bmc, miscdev);
}

static int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp)
{
- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
int ret = 0;

spin_lock_irq(&kcs_bmc->lock);
@@ -228,7 +239,7 @@ static int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp)

static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
{
- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
__poll_t mask = 0;

poll_wait(filp, &kcs_bmc->queue, wait);
@@ -244,7 +255,7 @@ static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
bool data_avail;
size_t data_len;
ssize_t ret;
@@ -306,7 +317,7 @@ static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
ssize_t ret;

/* a minimum response size '3' : netfn + cmd + ccode */
@@ -342,7 +353,7 @@ static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
long ret = 0;

spin_lock_irq(&kcs_bmc->lock);
@@ -372,7 +383,7 @@ static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,

static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
{
- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);

spin_lock_irq(&kcs_bmc->lock);
kcs_bmc->running = 0;
@@ -401,6 +412,8 @@ int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc)
mutex_init(&kcs_bmc->mutex);
init_waitqueue_head(&kcs_bmc->queue);

+ kcs_bmc->client.dev = kcs_bmc;
+ kcs_bmc->client.ops = &kcs_bmc_ipmi_client_ops;
kcs_bmc->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
kcs_bmc->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
kcs_bmc->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
diff --git a/drivers/char/ipmi/kcs_bmc_client.h b/drivers/char/ipmi/kcs_bmc_client.h
new file mode 100644
index 000000000000..140631d157d8
--- /dev/null
+++ b/drivers/char/ipmi/kcs_bmc_client.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2021, IBM Corp. */
+
+#ifndef __KCS_BMC_CONSUMER_H__
+#define __KCS_BMC_CONSUMER_H__
+
+#include <linux/list.h>
+#include <linux/notifier.h>
+#include <stdbool.h>
+
+struct kcs_bmc;
+struct kcs_bmc_client_ops;
+
+struct kcs_bmc_client {
+ const struct kcs_bmc_client_ops *ops;
+
+ struct kcs_bmc *dev;
+};
+
+struct kcs_bmc_client_ops {
+ int (*event)(struct kcs_bmc_client *client);
+};
+
+u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
+void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
+u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc);
+void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data);
+void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val);
+#endif
diff --git a/drivers/char/ipmi/kcs_bmc_device.h b/drivers/char/ipmi/kcs_bmc_device.h
new file mode 100644
index 000000000000..33462174516d
--- /dev/null
+++ b/drivers/char/ipmi/kcs_bmc_device.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2021, IBM Corp. */
+
+#ifndef __KCS_BMC_DEVICE_H__
+#define __KCS_BMC_DEVICE_H__
+
+#include "kcs_bmc.h"
+
+struct kcs_bmc_device_ops {
+ u8 (*io_inputb)(struct kcs_bmc *kcs_bmc, u32 reg);
+ void (*io_outputb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 b);
+ void (*io_updateb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 b);
+};
+
+int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc);
+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc);
+
+#endif
diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
index 5d017498dc69..1d21697fc585 100644
--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
@@ -17,7 +17,7 @@
#include <linux/regmap.h>
#include <linux/slab.h>

-#include "kcs_bmc.h"
+#include "kcs_bmc_device.h"

#define DEVICE_NAME "npcm-kcs-bmc"
#define KCS_CHANNEL_MAX 3
@@ -127,11 +127,13 @@ static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
{
struct kcs_bmc *kcs_bmc = arg;
+ int rc;

- if (!kcs_bmc_handle_event(kcs_bmc))
- return IRQ_HANDLED;
+ rc = kcs_bmc_handle_event(kcs_bmc);
+ if (rc < 0)
+ dev_warn(kcs_bmc->dev, "Failed to service irq: %d\n", rc);

- return IRQ_NONE;
+ return rc == KCS_BMC_EVENT_HANDLED ? IRQ_HANDLED : IRQ_NONE;
}

static int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc,
@@ -148,6 +150,12 @@ static int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc,
dev_name(dev), kcs_bmc);
}

+static const struct kcs_bmc_device_ops npcm7xx_kcs_ops = {
+ .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;
@@ -179,9 +187,7 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
kcs_bmc->ioreg.idr = priv->reg->dib;
kcs_bmc->ioreg.odr = priv->reg->dob;
kcs_bmc->ioreg.str = priv->reg->sts;
- kcs_bmc->io_inputb = npcm7xx_kcs_inb;
- kcs_bmc->io_outputb = npcm7xx_kcs_outb;
- kcs_bmc->io_updateb = npcm7xx_kcs_updateb;
+ kcs_bmc->ops = &npcm7xx_kcs_ops;

platform_set_drvdata(pdev, priv);

--
2.27.0

2021-03-19 06:31:32

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 10/21] ipmi: kcs_bmc: Turn the driver data-structures inside-out

Make the KCS device drivers responsible for allocating their own memory.

Until now the private data for the device driver was allocated internal
to the private data for the chardev interface. This coupling required
the slightly awkward API of passing through the struct size for the
driver private data to the chardev constructor, and then retrieving a
pointer to the driver private data from the allocated chardev memory.

In addition to being awkward, the arrangement prevents the
implementation of alternative userspace interfaces as the device driver
private data is not independent.

Peel a layer off the onion and turn the data-structures inside out by
exploiting container_of() and embedding `struct kcs_device` in the
driver private data.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/kcs_bmc.c | 15 +++++--
drivers/char/ipmi/kcs_bmc.h | 12 ++----
drivers/char/ipmi/kcs_bmc_aspeed.c | 60 ++++++++++++++++-----------
drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 60 ++++++++++++++++++---------
drivers/char/ipmi/kcs_bmc_npcm7xx.c | 37 ++++++++++-------
5 files changed, 113 insertions(+), 71 deletions(-)

diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
index ef5c48ffe74a..709b6bdec165 100644
--- a/drivers/char/ipmi/kcs_bmc.c
+++ b/drivers/char/ipmi/kcs_bmc.c
@@ -44,12 +44,19 @@ int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
}
EXPORT_SYMBOL(kcs_bmc_handle_event);

-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel)
+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc)
{
- return kcs_bmc_ipmi_alloc(dev, sizeof_priv, channel);
+ return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
}
-EXPORT_SYMBOL(kcs_bmc_alloc);
+EXPORT_SYMBOL(kcs_bmc_add_device);
+
+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc)
+{
+ return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
+}
+EXPORT_SYMBOL(kcs_bmc_remove_device);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <[email protected]>");
diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
index febea0c8deb4..bf0ae327997f 100644
--- a/drivers/char/ipmi/kcs_bmc.h
+++ b/drivers/char/ipmi/kcs_bmc.h
@@ -67,6 +67,8 @@ struct kcs_ioreg {
};

struct kcs_bmc {
+ struct device *dev;
+
spinlock_t lock;

u32 channel;
@@ -94,17 +96,11 @@ struct kcs_bmc {
u8 *kbuffer;

struct miscdevice miscdev;
-
- unsigned long priv[];
};

-static inline void *kcs_bmc_priv(struct kcs_bmc *kcs_bmc)
-{
- return kcs_bmc->priv;
-}
-
int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel);
+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc);
+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc);

u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
index 630cf095560e..0416ac78ce68 100644
--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
@@ -61,6 +61,8 @@
#define LPC_STR4 0x11C

struct aspeed_kcs_bmc {
+ struct kcs_bmc kcs_bmc;
+
struct regmap *map;
};

@@ -69,9 +71,14 @@ struct aspeed_kcs_of_ops {
int (*get_io_address)(struct platform_device *pdev);
};

+static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc *kcs_bmc)
+{
+ return container_of(kcs_bmc, struct aspeed_kcs_bmc, kcs_bmc);
+}
+
static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
{
- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
u32 val = 0;
int rc;

@@ -83,7 +90,7 @@ static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)

static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
{
- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
int rc;

rc = regmap_write(priv->map, reg, data);
@@ -92,7 +99,7 @@ static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)

static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val)
{
- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
int rc;

rc = regmap_update_bits(priv->map, reg, mask, val);
@@ -114,7 +121,7 @@ static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val
*/
static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
{
- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);

switch (kcs_bmc->channel) {
case 1:
@@ -148,7 +155,7 @@ static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)

static void aspeed_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
{
- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);

switch (kcs_bmc->channel) {
case 1:
@@ -323,16 +330,16 @@ static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
static int aspeed_kcs_probe(struct platform_device *pdev)
{
const struct aspeed_kcs_of_ops *ops;
- struct device *dev = &pdev->dev;
+ struct aspeed_kcs_bmc *priv;
struct kcs_bmc *kcs_bmc;
struct device_node *np;
int rc, channel, addr;

- np = dev->of_node->parent;
+ 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(dev, "unsupported LPC device binding\n");
+ dev_err(&pdev->dev, "unsupported LPC device binding\n");
return -ENODEV;
}
ops = of_device_get_match_data(&pdev->dev);
@@ -343,18 +350,27 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
if (channel < 0)
return channel;

- kcs_bmc = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
- if (!kcs_bmc)
+ addr = ops->get_io_address(pdev);
+ if (addr < 0)
+ return addr;
+
+ 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->io_inputb = aspeed_kcs_inb;
kcs_bmc->io_outputb = aspeed_kcs_outb;
kcs_bmc->io_updateb = aspeed_kcs_updateb;

- addr = ops->get_io_address(pdev);
- if (addr < 0)
- return addr;
+ 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;
+ }

aspeed_kcs_set_address(kcs_bmc, addr);

@@ -362,29 +378,25 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
if (rc)
return rc;

- dev_set_drvdata(dev, kcs_bmc);
+ platform_set_drvdata(pdev, priv);

aspeed_kcs_enable_channel(kcs_bmc, true);

- rc = misc_register(&kcs_bmc->miscdev);
- if (rc) {
- dev_err(dev, "Unable to register device\n");
+ rc = kcs_bmc_add_device(&priv->kcs_bmc);
+ if (rc < 0)
return rc;
- }

- dev_dbg(&pdev->dev,
- "Probed KCS device %d (IDR=0x%x, ODR=0x%x, STR=0x%x)\n",
- kcs_bmc->channel, kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr,
- kcs_bmc->ioreg.str);
+ dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n", kcs_bmc->channel, addr);

return 0;
}

static int aspeed_kcs_remove(struct platform_device *pdev)
{
- struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
+ struct aspeed_kcs_bmc *priv = platform_get_drvdata(pdev);
+ struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;

- misc_deregister(&kcs_bmc->miscdev);
+ kcs_bmc_remove_device(kcs_bmc);

return 0;
}
diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
index 82c77994e481..0ca71c135a1a 100644
--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
@@ -382,7 +382,7 @@ static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
return 0;
}

-static const struct file_operations kcs_bmc_fops = {
+static const struct file_operations kcs_bmc_ipmi_fops = {
.owner = THIS_MODULE,
.open = kcs_bmc_ipmi_open,
.read = kcs_bmc_ipmi_read,
@@ -392,36 +392,58 @@ static const struct file_operations kcs_bmc_fops = {
.unlocked_ioctl = kcs_bmc_ipmi_ioctl,
};

-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel)
+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc)
{
- struct kcs_bmc *kcs_bmc;
-
- kcs_bmc = devm_kzalloc(dev, sizeof(*kcs_bmc) + sizeof_priv, GFP_KERNEL);
- if (!kcs_bmc)
- return NULL;
+ int rc;

spin_lock_init(&kcs_bmc->lock);
- kcs_bmc->channel = channel;
-
mutex_init(&kcs_bmc->mutex);
init_waitqueue_head(&kcs_bmc->queue);

- kcs_bmc->data_in = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
- kcs_bmc->data_out = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
- kcs_bmc->kbuffer = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
+ kcs_bmc->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
+ kcs_bmc->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
+ kcs_bmc->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);

kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
- kcs_bmc->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%u",
- DEVICE_NAME, channel);
+ kcs_bmc->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u",
+ DEVICE_NAME, kcs_bmc->channel);
if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
!kcs_bmc->miscdev.name)
- return NULL;
- kcs_bmc->miscdev.fops = &kcs_bmc_fops;
+ return -ENOMEM;

- return kcs_bmc;
+ kcs_bmc->miscdev.fops = &kcs_bmc_ipmi_fops;
+
+ rc = misc_register(&kcs_bmc->miscdev);
+ if (rc) {
+ dev_err(kcs_bmc->dev, "Unable to register device: %d\n", rc);
+ return rc;
+ }
+
+ dev_info(kcs_bmc->dev, "Initialised IPMI client for channel %d", kcs_bmc->channel);
+
+ return 0;
+}
+EXPORT_SYMBOL(kcs_bmc_ipmi_attach_cdev);
+
+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc)
+{
+ misc_deregister(&kcs_bmc->miscdev);
+
+ spin_lock_irq(&kcs_bmc->lock);
+ kcs_bmc->running = 0;
+ kcs_bmc_ipmi_force_abort(kcs_bmc);
+ spin_unlock_irq(&kcs_bmc->lock);
+
+ devm_kfree(kcs_bmc->dev, kcs_bmc->kbuffer);
+ devm_kfree(kcs_bmc->dev, kcs_bmc->data_out);
+ devm_kfree(kcs_bmc->dev, kcs_bmc->data_in);
+ devm_kfree(kcs_bmc->dev, kcs_bmc);
+
+ return 0;
}
-EXPORT_SYMBOL(kcs_bmc_ipmi_alloc);
+EXPORT_SYMBOL(kcs_bmc_ipmi_detach_cdev);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <[email protected]>");
diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
index 1f44aadec9e8..5d017498dc69 100644
--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
@@ -65,6 +65,8 @@ struct npcm7xx_kcs_reg {
};

struct npcm7xx_kcs_bmc {
+ struct kcs_bmc kcs_bmc;
+
struct regmap *map;

const struct npcm7xx_kcs_reg *reg;
@@ -76,9 +78,14 @@ static const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = {
{ .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE },
};

+static inline struct npcm7xx_kcs_bmc *to_npcm7xx_kcs_bmc(struct kcs_bmc *kcs_bmc)
+{
+ return container_of(kcs_bmc, struct npcm7xx_kcs_bmc, kcs_bmc);
+}
+
static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
{
- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
u32 val = 0;
int rc;

@@ -90,7 +97,7 @@ static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)

static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
{
- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
int rc;

rc = regmap_write(priv->map, reg, data);
@@ -99,7 +106,7 @@ static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)

static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 data)
{
- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
int rc;

rc = regmap_update_bits(priv->map, reg, mask, data);
@@ -108,7 +115,7 @@ static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 da

static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
{
- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);

regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
enable ? KCS_CTL_IBFIE : 0);
@@ -155,11 +162,10 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
return -ENODEV;
}

- kcs_bmc = kcs_bmc_alloc(dev, sizeof(*priv), chan);
- if (!kcs_bmc)
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
return -ENOMEM;

- priv = kcs_bmc_priv(kcs_bmc);
priv->map = syscon_node_to_regmap(dev->parent->of_node);
if (IS_ERR(priv->map)) {
dev_err(dev, "Couldn't get regmap\n");
@@ -167,6 +173,9 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
}
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;
@@ -174,31 +183,27 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
kcs_bmc->io_outputb = npcm7xx_kcs_outb;
kcs_bmc->io_updateb = npcm7xx_kcs_updateb;

- dev_set_drvdata(dev, kcs_bmc);
+ platform_set_drvdata(pdev, priv);

npcm7xx_kcs_enable_channel(kcs_bmc, true);
rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev);
if (rc)
return rc;

- rc = misc_register(&kcs_bmc->miscdev);
- if (rc) {
- dev_err(dev, "Unable to register device\n");
- 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;
+ return kcs_bmc_add_device(kcs_bmc);
}

static int npcm7xx_kcs_remove(struct platform_device *pdev)
{
- struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
+ struct npcm7xx_kcs_bmc *priv = platform_get_drvdata(pdev);
+ struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;

- misc_deregister(&kcs_bmc->miscdev);
+ kcs_bmc_remove_device(kcs_bmc);

return 0;
}
--
2.27.0

2021-03-19 06:31:33

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 13/21] ipmi: kcs_bmc: Decouple the IPMI chardev from the core

Now that we have untangled the data-structures, split the userspace
interface out into its own module. Userspace interfaces and drivers are
registered to the KCS BMC core to support arbitrary binding of either.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/Kconfig | 13 +++++
drivers/char/ipmi/Makefile | 3 +-
drivers/char/ipmi/kcs_bmc.c | 78 ++++++++++++++++++++++++++-
drivers/char/ipmi/kcs_bmc.h | 4 --
drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 33 +++++++++---
drivers/char/ipmi/kcs_bmc_client.h | 14 +++++
6 files changed, 132 insertions(+), 13 deletions(-)

diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
index 07847d9a459a..bc5f81899b62 100644
--- a/drivers/char/ipmi/Kconfig
+++ b/drivers/char/ipmi/Kconfig
@@ -124,6 +124,19 @@ config NPCM7XX_KCS_IPMI_BMC
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 ASPEED_BT_IPMI_BMC
depends on ARCH_ASPEED || COMPILE_TEST
depends on REGMAP && REGMAP_MMIO && MFD_SYSCON
diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
index a302bc865370..fcfa676afddb 100644
--- a/drivers/char/ipmi/Makefile
+++ b/drivers/char/ipmi/Makefile
@@ -22,7 +22,8 @@ obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o
obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o
obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
-obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o kcs_bmc_cdev_ipmi.o
+obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.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
diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
index 266ebec71d6f..694db6ee2a92 100644
--- a/drivers/char/ipmi/kcs_bmc.c
+++ b/drivers/char/ipmi/kcs_bmc.c
@@ -5,7 +5,9 @@
*/

#include <linux/device.h>
+#include <linux/list.h>
#include <linux/module.h>
+#include <linux/mutex.h>

#include "kcs_bmc.h"

@@ -13,6 +15,11 @@
#include "kcs_bmc_device.h"
#include "kcs_bmc_client.h"

+/* Record probed devices and cdevs */
+static DEFINE_MUTEX(kcs_bmc_lock);
+static LIST_HEAD(kcs_bmc_devices);
+static LIST_HEAD(kcs_bmc_cdevs);
+
/* Consumer data access */

u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc)
@@ -100,16 +107,83 @@ EXPORT_SYMBOL(kcs_bmc_disable_device);

int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc)
{
- return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
+ struct kcs_bmc_cdev *cdev;
+ 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(cdev, &kcs_bmc_cdevs, entry) {
+ rc = cdev->ops->add_device(kcs_bmc);
+ if (rc)
+ dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
+ kcs_bmc->channel, rc);
+ }
+ mutex_unlock(&kcs_bmc_lock);
+
+ return 0;
}
EXPORT_SYMBOL(kcs_bmc_add_device);

int kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc)
{
- return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
+ struct kcs_bmc_cdev *cdev;
+ int rc;
+
+ mutex_lock(&kcs_bmc_lock);
+ list_del(&kcs_bmc->entry);
+ list_for_each_entry(cdev, &kcs_bmc_cdevs, entry) {
+ rc = cdev->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);
+
+ return 0;
}
EXPORT_SYMBOL(kcs_bmc_remove_device);

+int kcs_bmc_register_cdev(struct kcs_bmc_cdev *cdev)
+{
+ struct kcs_bmc_device *kcs_bmc;
+ int rc;
+
+ mutex_lock(&kcs_bmc_lock);
+ list_add(&cdev->entry, &kcs_bmc_cdevs);
+ list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
+ rc = cdev->ops->add_device(kcs_bmc);
+ if (rc)
+ dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
+ kcs_bmc->channel, rc);
+ }
+ mutex_unlock(&kcs_bmc_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(kcs_bmc_register_cdev);
+
+int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev)
+{
+ struct kcs_bmc_device *kcs_bmc;
+ int rc;
+
+ mutex_lock(&kcs_bmc_lock);
+ list_del(&cdev->entry);
+ list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
+ rc = cdev->ops->remove_device(kcs_bmc);
+ if (rc)
+ dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
+ kcs_bmc->channel, rc);
+ }
+ mutex_unlock(&kcs_bmc_lock);
+
+ return rc;
+}
+EXPORT_SYMBOL(kcs_bmc_unregister_cdev);
+
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <[email protected]>");
MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
index 3f266740c759..5deb9a0b8e60 100644
--- a/drivers/char/ipmi/kcs_bmc.h
+++ b/drivers/char/ipmi/kcs_bmc.h
@@ -42,8 +42,4 @@ struct kcs_bmc_device {
spinlock_t lock;
struct kcs_bmc_client *client;
};
-
-/* Temporary exports while refactoring */
-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc);
-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc);
#endif /* __KCS_BMC_H__ */
diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
index 58c42e76483d..df83d67851ac 100644
--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
@@ -469,8 +469,7 @@ static const struct file_operations kcs_bmc_ipmi_fops = {
static DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock);
static LIST_HEAD(kcs_bmc_ipmi_instances);

-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc);
-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc)
+static int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_ipmi *priv;
int rc;
@@ -512,10 +511,8 @@ int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc)

return 0;
}
-EXPORT_SYMBOL(kcs_bmc_ipmi_attach_cdev);

-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc);
-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc)
+static int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_ipmi *priv = NULL, *pos;

@@ -541,7 +538,31 @@ int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc)

return 0;
}
-EXPORT_SYMBOL(kcs_bmc_ipmi_detach_cdev);
+
+static const struct kcs_bmc_cdev_ops kcs_bmc_ipmi_cdev_ops = {
+ .add_device = kcs_bmc_ipmi_attach_cdev,
+ .remove_device = kcs_bmc_ipmi_detach_cdev,
+};
+
+static struct kcs_bmc_cdev kcs_bmc_ipmi_cdev = {
+ .ops = &kcs_bmc_ipmi_cdev_ops,
+};
+
+static int kcs_bmc_ipmi_init(void)
+{
+ return kcs_bmc_register_cdev(&kcs_bmc_ipmi_cdev);
+}
+module_init(kcs_bmc_ipmi_init);
+
+static void kcs_bmc_ipmi_exit(void)
+{
+ int rc;
+
+ rc = kcs_bmc_unregister_cdev(&kcs_bmc_ipmi_cdev);
+ if (rc)
+ pr_warn("Failed to remove KCS BMC client: %d", rc);
+}
+module_exit(kcs_bmc_ipmi_exit);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <[email protected]>");
diff --git a/drivers/char/ipmi/kcs_bmc_client.h b/drivers/char/ipmi/kcs_bmc_client.h
index 2dd710f4b4aa..d0a7404ff584 100644
--- a/drivers/char/ipmi/kcs_bmc_client.h
+++ b/drivers/char/ipmi/kcs_bmc_client.h
@@ -10,6 +10,17 @@

#include "kcs_bmc.h"

+struct kcs_bmc_cdev_ops {
+ int (*add_device)(struct kcs_bmc_device *kcs_bmc);
+ int (*remove_device)(struct kcs_bmc_device *kcs_bmc);
+};
+
+struct kcs_bmc_cdev {
+ struct list_head entry;
+
+ const struct kcs_bmc_cdev_ops *ops;
+};
+
struct kcs_bmc_client_ops {
int (*event)(struct kcs_bmc_client *client);
};
@@ -20,6 +31,9 @@ struct kcs_bmc_client {
struct kcs_bmc_device *dev;
};

+int kcs_bmc_register_cdev(struct kcs_bmc_cdev *cdev);
+int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev);
+
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);

--
2.27.0

2021-03-19 06:31:51

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 15/21] ipmi: kcs_bmc: Don't enforce single-open policy in the kernel

Soon it will be possible for one KCS device to have multiple associated
chardevs exposed to userspace (for IPMI and raw-style access). However,
don't prevent userspace from:

1. Opening more than one chardev at a time, or
2. Opening the same chardev more than once.

System behaviour is undefined for both classes of multiple access, so
userspace must manage itself accordingly.

The implementation delivers IBF and OBF events to the first chardev
client to associate with the KCS device. An open on a related chardev
cannot associate its client with the KCS device and so will not
receive notification of events. However, any fd on any chardev may race
their accesses to the data and status registers.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/kcs_bmc.c | 34 ++++++++++-------------------
drivers/char/ipmi/kcs_bmc_aspeed.c | 3 +--
drivers/char/ipmi/kcs_bmc_npcm7xx.c | 3 +--
3 files changed, 14 insertions(+), 26 deletions(-)

diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
index 05bbb72418b2..2fafa9541934 100644
--- a/drivers/char/ipmi/kcs_bmc.c
+++ b/drivers/char/ipmi/kcs_bmc.c
@@ -55,24 +55,12 @@ EXPORT_SYMBOL(kcs_bmc_update_status);
int kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc)
{
struct kcs_bmc_client *client;
- int rc;
+ int rc = KCS_BMC_EVENT_NONE;

spin_lock(&kcs_bmc->lock);
client = kcs_bmc->client;
- if (client) {
+ if (!WARN_ON_ONCE(!client))
rc = client->ops->event(client);
- } else {
- u8 status;
-
- status = kcs_bmc_read_status(kcs_bmc);
- if (status & KCS_BMC_STR_IBF) {
- /* Ack the event by reading the data */
- kcs_bmc_read_data(kcs_bmc);
- rc = KCS_BMC_EVENT_HANDLED;
- } else {
- rc = KCS_BMC_EVENT_NONE;
- }
- }
spin_unlock(&kcs_bmc->lock);

return rc;
@@ -81,26 +69,28 @@ 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 {
+ if (!kcs_bmc->client) {
+ u8 mask = KCS_BMC_EVENT_TYPE_IBF;
+
kcs_bmc->client = client;
- rc = 0;
+ kcs_bmc_update_event_mask(kcs_bmc, mask, mask);
}
spin_unlock_irq(&kcs_bmc->lock);

- return rc;
+ return 0;
}
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)
+ 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);
diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
index 5f26471c038c..271845eb2e26 100644
--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
@@ -419,8 +419,7 @@ static int aspeed_kcs_probe(struct platform_device *pdev)

platform_set_drvdata(pdev, priv);

- aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE),
- KCS_BMC_EVENT_TYPE_IBF);
+ 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);
diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
index c2032728a03d..fdf35cad2eba 100644
--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
@@ -207,8 +207,7 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
if (rc)
return rc;

- npcm7xx_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE),
- KCS_BMC_EVENT_TYPE_IBF);
+ 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);

pr_info("channel=%u idr=0x%x odr=0x%x str=0x%x\n",
--
2.27.0

2021-03-19 06:31:54

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 16/21] ipmi: kcs_bmc: Add a "raw" character device interface

The existing IPMI chardev encodes IPMI behaviours as the name suggests.
However, KCS devices are useful beyond IPMI (or keyboards), as they
provide a means to generate IRQs and exchange arbitrary data between a
BMC and its host system.

Implement a "raw" KCS character device that exposes the IDR, ODR and STR
registers to userspace via read() and write() implemented on a character
device:

+--------+--------+---------+
| Offset | read() | write() |
+--------+--------+---------+
| 0 | IDR | ODR |
+--------+--------+---------+
| 1 | STR | STR |
+--------+--------+---------+

This interface allows userspace to implement arbitrary (though somewhat
inefficient) protocols for exchanging information between a BMC and host
firmware. Conceptually the KCS interface can be used as an out-of-band
machanism for interrupt-signaled control messages while bulk data
transfers occur over more appropriate interfaces between the BMC and the
host (which may lack their own interrupt mechanism, e.g. LPC FW cycles).

poll() is provided, which will wait for IBF or OBE conditions for data
reads and writes respectively. Reads of STR on its own never blocks,
though accessing both offsets in the one system call may block if the
data registers are not ready.

Signed-off-by: Andrew Jeffery <[email protected]>
---
Documentation/ABI/testing/dev-raw-kcs | 25 ++
drivers/char/ipmi/Kconfig | 17 +
drivers/char/ipmi/Makefile | 1 +
drivers/char/ipmi/kcs_bmc_cdev_raw.c | 443 ++++++++++++++++++++++++++
4 files changed, 486 insertions(+)
create mode 100644 Documentation/ABI/testing/dev-raw-kcs
create mode 100644 drivers/char/ipmi/kcs_bmc_cdev_raw.c

diff --git a/Documentation/ABI/testing/dev-raw-kcs b/Documentation/ABI/testing/dev-raw-kcs
new file mode 100644
index 000000000000..06e7e2071562
--- /dev/null
+++ b/Documentation/ABI/testing/dev-raw-kcs
@@ -0,0 +1,25 @@
+What: /dev/raw-kcs*
+Date: 2021-02-15
+KernelVersion: 5.13
+Contact: [email protected]
+Contact: [email protected]
+Contact: Andrew Jeffery <[email protected]>
+Description: ``/dev/raw-kcs*`` exposes to userspace the data and
+ status registers of Keyboard-Controller-Style (KCS) IPMI
+ interfaces via read() and write() syscalls. Direct
+ exposure of the data and status registers enables
+ inefficient but arbitrary protocols to be implemented
+ over the device. A typical approach is to use KCS
+ devices for out-of-band signalling for bulk data
+ transfers over other interfaces between a Baseboard
+ Management Controller and its host.
+
+ +--------+--------+---------+
+ | Offset | read() | write() |
+ +--------+--------+---------+
+ | 0 | IDR | ODR |
+ +--------+--------+---------+
+ | 1 | STR | STR |
+ +--------+--------+---------+
+
+Users: libmctp: https://github.com/openbmc/libmctp
diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
index bc5f81899b62..273ac1a1f870 100644
--- a/drivers/char/ipmi/Kconfig
+++ b/drivers/char/ipmi/Kconfig
@@ -137,6 +137,23 @@ config IPMI_KCS_BMC_CDEV_IPMI
This support is also available as a module. The module will be
called kcs_bmc_cdev_ipmi.

+config IPMI_KCS_BMC_CDEV_RAW
+ depends on IPMI_KCS_BMC
+ tristate "Raw character device interface for BMC KCS devices"
+ help
+ Provides a BMC-side character device directly exposing the
+ data and status registers of a KCS device to userspace. While
+ KCS devices are commonly used to implement IPMI message
+ passing, they provide a general interface for exchange of
+ interrupts, data and status information between the BMC and
+ its host.
+
+ Say YES if you wish to use the KCS devices to implement
+ protocols that are not IPMI.
+
+ This support is also available as a module. The module will be
+ called kcs_bmc_cdev_raw.
+
config ASPEED_BT_IPMI_BMC
depends on ARCH_ASPEED || COMPILE_TEST
depends on REGMAP && REGMAP_MMIO && MFD_SYSCON
diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
index fcfa676afddb..c8cc248ddd90 100644
--- a/drivers/char/ipmi/Makefile
+++ b/drivers/char/ipmi/Makefile
@@ -24,6 +24,7 @@ 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_CDEV_IPMI) += kcs_bmc_cdev_ipmi.o
+obj-$(CONFIG_IPMI_KCS_BMC_CDEV_RAW) += kcs_bmc_cdev_raw.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
diff --git a/drivers/char/ipmi/kcs_bmc_cdev_raw.c b/drivers/char/ipmi/kcs_bmc_cdev_raw.c
new file mode 100644
index 000000000000..bdd258648c8e
--- /dev/null
+++ b/drivers/char/ipmi/kcs_bmc_cdev_raw.c
@@ -0,0 +1,443 @@
+// 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/fs.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+
+#include "kcs_bmc_client.h"
+
+#define DEVICE_NAME "raw-kcs"
+
+struct kcs_bmc_raw {
+ struct list_head entry;
+
+ struct kcs_bmc_client client;
+
+ wait_queue_head_t queue;
+ u8 events;
+ bool writable;
+ bool readable;
+ u8 idr;
+
+ struct miscdevice miscdev;
+};
+
+static inline struct kcs_bmc_raw *client_to_kcs_bmc_raw(struct kcs_bmc_client *client)
+{
+ return container_of(client, struct kcs_bmc_raw, client);
+}
+
+/* Call under priv->queue.lock */
+static void kcs_bmc_raw_update_event_mask(struct kcs_bmc_raw *priv, u8 mask, u8 state)
+{
+ kcs_bmc_update_event_mask(priv->client.dev, mask, state);
+ priv->events &= ~mask;
+ priv->events |= state & mask;
+}
+
+static int kcs_bmc_raw_event(struct kcs_bmc_client *client)
+{
+ struct kcs_bmc_raw *priv;
+ struct device *dev;
+ u8 status, handled;
+
+ priv = client_to_kcs_bmc_raw(client);
+ dev = priv->miscdev.this_device;
+
+ spin_lock(&priv->queue.lock);
+
+ status = kcs_bmc_read_status(client->dev);
+ handled = 0;
+
+ if ((priv->events & KCS_BMC_EVENT_TYPE_IBF) && (status & KCS_BMC_STR_IBF)) {
+ if (priv->readable)
+ dev_err(dev, "Storm brewing!");
+
+ dev_dbg(dev, "Disabling IDR events for back-pressure\n");
+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_IBF, 0);
+ priv->idr = kcs_bmc_read_data(client->dev);
+ priv->readable = true;
+
+ dev_dbg(dev, "IDR read, waking waiters\n");
+ wake_up_locked(&priv->queue);
+
+ handled |= KCS_BMC_EVENT_TYPE_IBF;
+ }
+
+ if ((priv->events & KCS_BMC_EVENT_TYPE_OBE) && !(status & KCS_BMC_STR_OBF)) {
+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, 0);
+ priv->writable = true;
+
+ dev_dbg(dev, "ODR writable, waking waiters\n");
+ wake_up_locked(&priv->queue);
+
+ handled |= KCS_BMC_EVENT_TYPE_OBE;
+ }
+
+ spin_unlock(&priv->queue.lock);
+
+ return handled ? KCS_BMC_EVENT_HANDLED : KCS_BMC_EVENT_NONE;
+}
+
+static const struct kcs_bmc_client_ops kcs_bmc_raw_client_ops = {
+ .event = kcs_bmc_raw_event,
+};
+
+static inline struct kcs_bmc_raw *file_to_kcs_bmc_raw(struct file *filp)
+{
+ return container_of(filp->private_data, struct kcs_bmc_raw, miscdev);
+}
+
+static int kcs_bmc_raw_open(struct inode *inode, struct file *filp)
+{
+ struct kcs_bmc_raw *priv = file_to_kcs_bmc_raw(filp);
+
+ return kcs_bmc_enable_device(priv->client.dev, &priv->client);
+}
+
+static bool kcs_bmc_raw_prepare_obe(struct kcs_bmc_raw *priv)
+{
+ bool writable;
+
+ /* Enable the OBE event so we can catch the host clearing OBF */
+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, KCS_BMC_EVENT_TYPE_OBE);
+
+ /* Now that we'll catch an OBE event, check if it's already occurred */
+ writable = !(kcs_bmc_read_status(priv->client.dev) & KCS_BMC_STR_OBF);
+
+ /* If OBF is clear we've missed the OBE event, so disable it */
+ if (writable)
+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, 0);
+
+ return writable;
+}
+
+static __poll_t kcs_bmc_raw_poll(struct file *filp, poll_table *wait)
+{
+ struct kcs_bmc_raw *priv;
+ __poll_t events = 0;
+
+ priv = file_to_kcs_bmc_raw(filp);
+
+ poll_wait(filp, &priv->queue, wait);
+
+ spin_lock_irq(&priv->queue.lock);
+ if (kcs_bmc_raw_prepare_obe(priv))
+ events |= (EPOLLOUT | EPOLLWRNORM);
+
+ if (priv->readable || (kcs_bmc_read_status(priv->client.dev) & KCS_BMC_STR_IBF))
+ events |= (EPOLLIN | EPOLLRDNORM);
+ spin_unlock_irq(&priv->queue.lock);
+
+ return events;
+}
+
+static ssize_t kcs_bmc_raw_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct kcs_bmc_device *kcs_bmc;
+ struct kcs_bmc_raw *priv;
+ bool read_idr, read_str;
+ struct device *dev;
+ u8 idr, str;
+ ssize_t rc;
+
+ priv = file_to_kcs_bmc_raw(filp);
+ kcs_bmc = priv->client.dev;
+ dev = priv->miscdev.this_device;
+
+ if (!count)
+ return 0;
+
+ if (count > 2 || *ppos > 1)
+ return -EINVAL;
+
+ if (*ppos + count > 2)
+ return -EINVAL;
+
+ read_idr = (*ppos == 0);
+ read_str = (*ppos == 1) || (count == 2);
+
+ spin_lock_irq(&priv->queue.lock);
+ if (read_idr) {
+ dev_dbg(dev, "Waiting for IBF\n");
+ str = kcs_bmc_read_status(kcs_bmc);
+ if ((filp->f_flags & O_NONBLOCK) && (str & KCS_BMC_STR_IBF)) {
+ rc = -EWOULDBLOCK;
+ goto out;
+ }
+
+ rc = wait_event_interruptible_locked(priv->queue,
+ priv->readable || (str & KCS_BMC_STR_IBF));
+ if (rc < 0)
+ goto out;
+
+ if (signal_pending(current)) {
+ dev_dbg(dev, "Interrupted waiting for IBF\n");
+ rc = -EINTR;
+ goto out;
+ }
+
+ /*
+ * Re-enable events prior to possible read of IDR (which clears
+ * IBF) to ensure we receive interrupts for subsequent writes
+ * to IDR. Writes to IDR by the host should not occur while IBF
+ * is set.
+ */
+ dev_dbg(dev, "Woken by IBF, enabling IRQ\n");
+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_IBF,
+ KCS_BMC_EVENT_TYPE_IBF);
+
+ /* Read data out of IDR into internal storage if necessary */
+ if (!priv->readable) {
+ WARN(!(str & KCS_BMC_STR_IBF), "Unknown reason for wakeup!");
+
+ priv->idr = kcs_bmc_read_data(kcs_bmc);
+ }
+
+ /* Copy data from internal storage to userspace */
+ idr = priv->idr;
+
+ /* We're done consuming the internally stored value */
+ priv->readable = false;
+ }
+
+ if (read_str) {
+ str = kcs_bmc_read_status(kcs_bmc);
+ if (*ppos == 0 || priv->readable)
+ /*
+ * If we got this far with `*ppos == 0` then we've read
+ * data out of IDR, so set IBF when reporting back to
+ * userspace so userspace knows the IDR value is valid.
+ */
+ str |= KCS_BMC_STR_IBF;
+
+ dev_dbg(dev, "Read status 0x%x\n", str);
+
+ }
+
+ rc = count;
+out:
+ spin_unlock_irq(&priv->queue.lock);
+
+ if (rc < 0)
+ return rc;
+
+ /* Now copy the data in to the userspace buffer */
+
+ if (read_idr)
+ if (copy_to_user(buf++, &idr, sizeof(idr)))
+ return -EFAULT;
+
+ if (read_str)
+ if (copy_to_user(buf, &str, sizeof(str)))
+ return -EFAULT;
+
+ return count;
+}
+
+static ssize_t kcs_bmc_raw_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct kcs_bmc_device *kcs_bmc;
+ bool write_odr, write_str;
+ struct kcs_bmc_raw *priv;
+ struct device *dev;
+ uint8_t data[2];
+ ssize_t result;
+ u8 str;
+
+ priv = file_to_kcs_bmc_raw(filp);
+ kcs_bmc = priv->client.dev;
+ dev = priv->miscdev.this_device;
+
+ if (!count)
+ return count;
+
+ if (count > 2)
+ return -EINVAL;
+
+ if (*ppos >= 2)
+ return -EINVAL;
+
+ if (*ppos + count > 2)
+ return -EINVAL;
+
+ if (copy_from_user(data, buf, count))
+ return -EFAULT;
+
+ write_odr = (*ppos == 0);
+ write_str = (*ppos == 1) || (count == 2);
+
+ spin_lock_irq(&priv->queue.lock);
+
+ /* Always write status before data, we generate the SerIRQ by writing ODR */
+ if (write_str) {
+ /* The index of STR in the userspace buffer depends on whether ODR is written */
+ str = data[*ppos == 0];
+ if (!(str & KCS_BMC_STR_OBF))
+ dev_warn(dev, "Clearing OBF with status write: 0x%x\n", str);
+ dev_dbg(dev, "Writing status 0x%x\n", str);
+ kcs_bmc_write_status(kcs_bmc, str);
+ }
+
+ if (write_odr) {
+ /* If we're writing ODR it's always the first byte in the buffer */
+ u8 odr = data[0];
+
+ str = kcs_bmc_read_status(kcs_bmc);
+ if (str & KCS_BMC_STR_OBF) {
+ if (filp->f_flags & O_NONBLOCK) {
+ result = -EWOULDBLOCK;
+ goto out;
+ }
+
+ priv->writable = kcs_bmc_raw_prepare_obe(priv);
+
+ /* Now either OBF is already clear, or we'll get an OBE event to wake us */
+ dev_dbg(dev, "Waiting for OBF to clear\n");
+ wait_event_interruptible_locked(priv->queue, priv->writable);
+
+ if (signal_pending(current)) {
+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, 0);
+ result = -EINTR;
+ goto out;
+ }
+
+ WARN_ON(kcs_bmc_read_status(kcs_bmc) & KCS_BMC_STR_OBF);
+ }
+
+ dev_dbg(dev, "Writing 0x%x to ODR\n", odr);
+ kcs_bmc_write_data(kcs_bmc, odr);
+ }
+
+ result = count;
+out:
+ spin_unlock_irq(&priv->queue.lock);
+
+ return result;
+}
+
+static int kcs_bmc_raw_release(struct inode *inode, struct file *filp)
+{
+ struct kcs_bmc_raw *priv = file_to_kcs_bmc_raw(filp);
+
+ kcs_bmc_disable_device(priv->client.dev, &priv->client);
+
+ return 0;
+}
+
+static const struct file_operations kcs_bmc_raw_fops = {
+ .owner = THIS_MODULE,
+ .open = kcs_bmc_raw_open,
+ .llseek = no_seek_end_llseek,
+ .read = kcs_bmc_raw_read,
+ .write = kcs_bmc_raw_write,
+ .poll = kcs_bmc_raw_poll,
+ .release = kcs_bmc_raw_release,
+};
+
+static DEFINE_SPINLOCK(kcs_bmc_raw_instances_lock);
+static LIST_HEAD(kcs_bmc_raw_instances);
+
+static int kcs_bmc_raw_attach_cdev(struct kcs_bmc_device *kcs_bmc)
+{
+ struct kcs_bmc_raw *priv;
+ int rc;
+
+ priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->client.dev = kcs_bmc;
+ priv->client.ops = &kcs_bmc_raw_client_ops;
+
+ init_waitqueue_head(&priv->queue);
+ priv->writable = false;
+ priv->readable = false;
+
+ 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->miscdev.name)
+ return -EINVAL;
+
+ priv->miscdev.fops = &kcs_bmc_raw_fops;
+
+ /* Initialise our expected events. Listen for IBF but ignore OBE until necessary */
+ kcs_bmc_raw_update_event_mask(priv, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE),
+ KCS_BMC_EVENT_TYPE_IBF);
+
+ rc = misc_register(&priv->miscdev);
+ if (rc) {
+ dev_err(kcs_bmc->dev, "Unable to register device\n");
+ return rc;
+ }
+
+ spin_lock_irq(&kcs_bmc_raw_instances_lock);
+ list_add(&priv->entry, &kcs_bmc_raw_instances);
+ spin_unlock_irq(&kcs_bmc_raw_instances_lock);
+
+ dev_info(kcs_bmc->dev, "Initialised raw client for channel %d", kcs_bmc->channel);
+
+ return 0;
+}
+
+static int kcs_bmc_raw_detach_cdev(struct kcs_bmc_device *kcs_bmc)
+{
+ struct kcs_bmc_raw *priv = NULL, *pos;
+
+ spin_lock_irq(&kcs_bmc_raw_instances_lock);
+ list_for_each_entry(pos, &kcs_bmc_raw_instances, entry) {
+ if (pos->client.dev == kcs_bmc) {
+ priv = pos;
+ list_del(&pos->entry);
+ break;
+ }
+ }
+ spin_unlock_irq(&kcs_bmc_raw_instances_lock);
+
+ if (!priv)
+ return 0;
+
+ misc_deregister(&priv->miscdev);
+ kcs_bmc_disable_device(kcs_bmc, &priv->client);
+ devm_kfree(priv->client.dev->dev, priv);
+
+ return 0;
+}
+
+static const struct kcs_bmc_cdev_ops kcs_bmc_raw_cdev_ops = {
+ .add_device = kcs_bmc_raw_attach_cdev,
+ .remove_device = kcs_bmc_raw_detach_cdev,
+};
+
+static struct kcs_bmc_cdev kcs_bmc_raw_cdev = {
+ .ops = &kcs_bmc_raw_cdev_ops,
+};
+
+static int kcs_bmc_raw_init(void)
+{
+ return kcs_bmc_register_cdev(&kcs_bmc_raw_cdev);
+}
+module_init(kcs_bmc_raw_init);
+
+static void kcs_bmc_raw_exit(void)
+{
+ int rc;
+
+ rc = kcs_bmc_unregister_cdev(&kcs_bmc_raw_cdev);
+ if (rc)
+ pr_warn("Failed to remove KCS BMC client: %d", rc);
+}
+module_exit(kcs_bmc_raw_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
+MODULE_DESCRIPTION("Character device for raw access to a KCS device");
--
2.27.0

2021-03-19 06:31:58

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 09/21] ipmi: kcs_bmc: Split out kcs_bmc_cdev_ipmi

Take steps towards defining a coherent API to separate the KCS device
drivers from the userspace interface. Decreasing the coupling will
improve the separation of concerns and enable the introduction of
alternative userspace interfaces.

For now, simply split the chardev logic out to a separate file. The code
continues to build into the same module.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/Makefile | 2 +-
drivers/char/ipmi/kcs_bmc.c | 423 +------------------------
drivers/char/ipmi/kcs_bmc.h | 10 +-
drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 428 ++++++++++++++++++++++++++
4 files changed, 451 insertions(+), 412 deletions(-)
create mode 100644 drivers/char/ipmi/kcs_bmc_cdev_ipmi.c

diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
index 0822adc2ec41..a302bc865370 100644
--- a/drivers/char/ipmi/Makefile
+++ b/drivers/char/ipmi/Makefile
@@ -22,7 +22,7 @@ obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o
obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o
obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
-obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o
+obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o 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
diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
index c4336c1f2d6d..ef5c48ffe74a 100644
--- a/drivers/char/ipmi/kcs_bmc.c
+++ b/drivers/char/ipmi/kcs_bmc.c
@@ -3,446 +3,51 @@
* 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/module.h>
-#include <linux/platform_device.h>
-#include <linux/poll.h>
-#include <linux/sched.h>
-#include <linux/slab.h>

#include "kcs_bmc.h"

-#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 u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
+u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
{
return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
}
+EXPORT_SYMBOL(kcs_bmc_read_data);

-static inline void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data)
+void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data)
{
kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
}
+EXPORT_SYMBOL(kcs_bmc_write_data);

-static inline u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc)
+u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc)
{
return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
}
+EXPORT_SYMBOL(kcs_bmc_read_status);

-static inline void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data)
+void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data)
{
kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
}
+EXPORT_SYMBOL(kcs_bmc_write_status);

-static void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
+void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
{
kcs_bmc->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
}
+EXPORT_SYMBOL(kcs_bmc_update_status);

-static inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
-{
- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_STATE_MASK,
- KCS_STATUS_STATE(state));
-}
-
-static void kcs_force_abort(struct kcs_bmc *kcs_bmc)
-{
- set_state(kcs_bmc, ERROR_STATE);
- kcs_bmc_read_data(kcs_bmc);
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
-
- kcs_bmc->phase = KCS_PHASE_ERROR;
- kcs_bmc->data_in_avail = false;
- kcs_bmc->data_in_idx = 0;
-}
-
-static void kcs_bmc_handle_data(struct kcs_bmc *kcs_bmc)
-{
- u8 data;
-
- switch (kcs_bmc->phase) {
- case KCS_PHASE_WRITE_START:
- kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
- fallthrough;
-
- case KCS_PHASE_WRITE_DATA:
- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
- set_state(kcs_bmc, WRITE_STATE);
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
- kcs_bmc_read_data(kcs_bmc);
- } else {
- kcs_force_abort(kcs_bmc);
- kcs_bmc->error = KCS_LENGTH_ERROR;
- }
- break;
-
- case KCS_PHASE_WRITE_END_CMD:
- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
- set_state(kcs_bmc, READ_STATE);
- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
- kcs_bmc_read_data(kcs_bmc);
- kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
- kcs_bmc->data_in_avail = true;
- wake_up_interruptible(&kcs_bmc->queue);
- } else {
- kcs_force_abort(kcs_bmc);
- kcs_bmc->error = KCS_LENGTH_ERROR;
- }
- break;
-
- case KCS_PHASE_READ:
- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
- set_state(kcs_bmc, IDLE_STATE);
-
- data = kcs_bmc_read_data(kcs_bmc);
- if (data != KCS_CMD_READ_BYTE) {
- set_state(kcs_bmc, ERROR_STATE);
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
- break;
- }
-
- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
- kcs_bmc->phase = KCS_PHASE_IDLE;
- break;
- }
-
- kcs_bmc_write_data(kcs_bmc,
- kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
- break;
-
- case KCS_PHASE_ABORT_ERROR1:
- set_state(kcs_bmc, READ_STATE);
- kcs_bmc_read_data(kcs_bmc);
- kcs_bmc_write_data(kcs_bmc, kcs_bmc->error);
- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
- break;
-
- case KCS_PHASE_ABORT_ERROR2:
- set_state(kcs_bmc, IDLE_STATE);
- kcs_bmc_read_data(kcs_bmc);
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
- kcs_bmc->phase = KCS_PHASE_IDLE;
- break;
-
- default:
- kcs_force_abort(kcs_bmc);
- break;
- }
-}
-
-static void kcs_bmc_handle_cmd(struct kcs_bmc *kcs_bmc)
-{
- u8 cmd;
-
- set_state(kcs_bmc, WRITE_STATE);
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
-
- cmd = kcs_bmc_read_data(kcs_bmc);
- switch (cmd) {
- case KCS_CMD_WRITE_START:
- kcs_bmc->phase = KCS_PHASE_WRITE_START;
- kcs_bmc->error = KCS_NO_ERROR;
- kcs_bmc->data_in_avail = false;
- kcs_bmc->data_in_idx = 0;
- break;
-
- case KCS_CMD_WRITE_END:
- if (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
- kcs_force_abort(kcs_bmc);
- break;
- }
-
- kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
- break;
-
- case KCS_CMD_GET_STATUS_ABORT:
- if (kcs_bmc->error == KCS_NO_ERROR)
- kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
-
- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
- kcs_bmc->data_in_avail = false;
- kcs_bmc->data_in_idx = 0;
- break;
-
- default:
- kcs_force_abort(kcs_bmc);
- kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
- break;
- }
-}
-
+int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);
int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
{
- unsigned long flags;
- int ret = -ENODATA;
- u8 status;
-
- spin_lock_irqsave(&kcs_bmc->lock, flags);
-
- status = kcs_bmc_read_status(kcs_bmc);
- if (status & KCS_STATUS_IBF) {
- if (!kcs_bmc->running)
- kcs_force_abort(kcs_bmc);
- else if (status & KCS_STATUS_CMD_DAT)
- kcs_bmc_handle_cmd(kcs_bmc);
- else
- kcs_bmc_handle_data(kcs_bmc);
-
- ret = 0;
- }
-
- spin_unlock_irqrestore(&kcs_bmc->lock, flags);
-
- return ret;
+ return kcs_bmc_ipmi_event(kcs_bmc);
}
EXPORT_SYMBOL(kcs_bmc_handle_event);

-static inline struct kcs_bmc *to_kcs_bmc(struct file *filp)
-{
- return container_of(filp->private_data, struct kcs_bmc, miscdev);
-}
-
-static int kcs_bmc_open(struct inode *inode, struct file *filp)
-{
- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
- int ret = 0;
-
- spin_lock_irq(&kcs_bmc->lock);
- if (!kcs_bmc->running)
- kcs_bmc->running = 1;
- else
- ret = -EBUSY;
- spin_unlock_irq(&kcs_bmc->lock);
-
- return ret;
-}
-
-static __poll_t kcs_bmc_poll(struct file *filp, poll_table *wait)
-{
- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
- __poll_t mask = 0;
-
- poll_wait(filp, &kcs_bmc->queue, wait);
-
- spin_lock_irq(&kcs_bmc->lock);
- if (kcs_bmc->data_in_avail)
- mask |= EPOLLIN;
- spin_unlock_irq(&kcs_bmc->lock);
-
- return mask;
-}
-
-static ssize_t kcs_bmc_read(struct file *filp, char __user *buf,
- size_t count, loff_t *ppos)
-{
- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
- bool data_avail;
- size_t data_len;
- ssize_t ret;
-
- if (!(filp->f_flags & O_NONBLOCK))
- wait_event_interruptible(kcs_bmc->queue,
- kcs_bmc->data_in_avail);
-
- mutex_lock(&kcs_bmc->mutex);
-
- spin_lock_irq(&kcs_bmc->lock);
- data_avail = kcs_bmc->data_in_avail;
- if (data_avail) {
- data_len = kcs_bmc->data_in_idx;
- memcpy(kcs_bmc->kbuffer, kcs_bmc->data_in, data_len);
- }
- spin_unlock_irq(&kcs_bmc->lock);
-
- if (!data_avail) {
- ret = -EAGAIN;
- goto out_unlock;
- }
-
- if (count < data_len) {
- pr_err("channel=%u with too large data : %zu\n",
- kcs_bmc->channel, data_len);
-
- spin_lock_irq(&kcs_bmc->lock);
- kcs_force_abort(kcs_bmc);
- spin_unlock_irq(&kcs_bmc->lock);
-
- ret = -EOVERFLOW;
- goto out_unlock;
- }
-
- if (copy_to_user(buf, kcs_bmc->kbuffer, data_len)) {
- ret = -EFAULT;
- goto out_unlock;
- }
-
- ret = data_len;
-
- spin_lock_irq(&kcs_bmc->lock);
- if (kcs_bmc->phase == KCS_PHASE_WRITE_DONE) {
- kcs_bmc->phase = KCS_PHASE_WAIT_READ;
- kcs_bmc->data_in_avail = false;
- kcs_bmc->data_in_idx = 0;
- } else {
- ret = -EAGAIN;
- }
- spin_unlock_irq(&kcs_bmc->lock);
-
-out_unlock:
- mutex_unlock(&kcs_bmc->mutex);
-
- return ret;
-}
-
-static ssize_t kcs_bmc_write(struct file *filp, const char __user *buf,
- size_t count, loff_t *ppos)
-{
- struct kcs_bmc *kcs_bmc = 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(&kcs_bmc->mutex);
-
- if (copy_from_user(kcs_bmc->kbuffer, buf, count)) {
- ret = -EFAULT;
- goto out_unlock;
- }
-
- spin_lock_irq(&kcs_bmc->lock);
- if (kcs_bmc->phase == KCS_PHASE_WAIT_READ) {
- kcs_bmc->phase = KCS_PHASE_READ;
- kcs_bmc->data_out_idx = 1;
- kcs_bmc->data_out_len = count;
- memcpy(kcs_bmc->data_out, kcs_bmc->kbuffer, count);
- kcs_bmc_write_data(kcs_bmc, kcs_bmc->data_out[0]);
- ret = count;
- } else {
- ret = -EINVAL;
- }
- spin_unlock_irq(&kcs_bmc->lock);
-
-out_unlock:
- mutex_unlock(&kcs_bmc->mutex);
-
- return ret;
-}
-
-static long kcs_bmc_ioctl(struct file *filp, unsigned int cmd,
- unsigned long arg)
-{
- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
- long ret = 0;
-
- spin_lock_irq(&kcs_bmc->lock);
-
- switch (cmd) {
- case IPMI_BMC_IOCTL_SET_SMS_ATN:
- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, KCS_STATUS_SMS_ATN);
- break;
-
- case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, 0);
- break;
-
- case IPMI_BMC_IOCTL_FORCE_ABORT:
- kcs_force_abort(kcs_bmc);
- break;
-
- default:
- ret = -EINVAL;
- break;
- }
-
- spin_unlock_irq(&kcs_bmc->lock);
-
- return ret;
-}
-
-static int kcs_bmc_release(struct inode *inode, struct file *filp)
-{
- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
-
- spin_lock_irq(&kcs_bmc->lock);
- kcs_bmc->running = 0;
- kcs_force_abort(kcs_bmc);
- spin_unlock_irq(&kcs_bmc->lock);
-
- return 0;
-}
-
-static const struct file_operations kcs_bmc_fops = {
- .owner = THIS_MODULE,
- .open = kcs_bmc_open,
- .read = kcs_bmc_read,
- .write = kcs_bmc_write,
- .release = kcs_bmc_release,
- .poll = kcs_bmc_poll,
- .unlocked_ioctl = kcs_bmc_ioctl,
-};
-
+struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel)
{
- struct kcs_bmc *kcs_bmc;
-
- kcs_bmc = devm_kzalloc(dev, sizeof(*kcs_bmc) + sizeof_priv, GFP_KERNEL);
- if (!kcs_bmc)
- return NULL;
-
- spin_lock_init(&kcs_bmc->lock);
- kcs_bmc->channel = channel;
-
- mutex_init(&kcs_bmc->mutex);
- init_waitqueue_head(&kcs_bmc->queue);
-
- kcs_bmc->data_in = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
- kcs_bmc->data_out = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
- kcs_bmc->kbuffer = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
-
- kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
- kcs_bmc->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%u",
- DEVICE_NAME, channel);
- if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
- !kcs_bmc->miscdev.name)
- return NULL;
- kcs_bmc->miscdev.fops = &kcs_bmc_fops;
-
- return kcs_bmc;
+ return kcs_bmc_ipmi_alloc(dev, sizeof_priv, channel);
}
EXPORT_SYMBOL(kcs_bmc_alloc);

diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
index 970f53892f2d..febea0c8deb4 100644
--- a/drivers/char/ipmi/kcs_bmc.h
+++ b/drivers/char/ipmi/kcs_bmc.h
@@ -104,6 +104,12 @@ static inline void *kcs_bmc_priv(struct kcs_bmc *kcs_bmc)
}

int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv,
- u32 channel);
+struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel);
+
+u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
+void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
+u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc);
+void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data);
+void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val);
+
#endif /* __KCS_BMC_H__ */
diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
new file mode 100644
index 000000000000..82c77994e481
--- /dev/null
+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
@@ -0,0 +1,428 @@
+// 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include "kcs_bmc.h"
+
+#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 *kcs_bmc, u8 state)
+{
+ kcs_bmc_update_status(kcs_bmc, KCS_STATUS_STATE_MASK,
+ KCS_STATUS_STATE(state));
+}
+
+static void kcs_bmc_ipmi_force_abort(struct kcs_bmc *kcs_bmc)
+{
+ set_state(kcs_bmc, ERROR_STATE);
+ kcs_bmc_read_data(kcs_bmc);
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
+
+ kcs_bmc->phase = KCS_PHASE_ERROR;
+ kcs_bmc->data_in_avail = false;
+ kcs_bmc->data_in_idx = 0;
+}
+
+static void kcs_bmc_ipmi_handle_data(struct kcs_bmc *kcs_bmc)
+{
+ u8 data;
+
+ switch (kcs_bmc->phase) {
+ case KCS_PHASE_WRITE_START:
+ kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
+ fallthrough;
+
+ case KCS_PHASE_WRITE_DATA:
+ if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
+ set_state(kcs_bmc, WRITE_STATE);
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
+ kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
+ kcs_bmc_read_data(kcs_bmc);
+ } else {
+ kcs_bmc_ipmi_force_abort(kcs_bmc);
+ kcs_bmc->error = KCS_LENGTH_ERROR;
+ }
+ break;
+
+ case KCS_PHASE_WRITE_END_CMD:
+ if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
+ set_state(kcs_bmc, READ_STATE);
+ kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
+ kcs_bmc_read_data(kcs_bmc);
+ kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
+ kcs_bmc->data_in_avail = true;
+ wake_up_interruptible(&kcs_bmc->queue);
+ } else {
+ kcs_bmc_ipmi_force_abort(kcs_bmc);
+ kcs_bmc->error = KCS_LENGTH_ERROR;
+ }
+ break;
+
+ case KCS_PHASE_READ:
+ if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
+ set_state(kcs_bmc, IDLE_STATE);
+
+ data = kcs_bmc_read_data(kcs_bmc);
+ if (data != KCS_CMD_READ_BYTE) {
+ set_state(kcs_bmc, ERROR_STATE);
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
+ break;
+ }
+
+ if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
+ kcs_bmc->phase = KCS_PHASE_IDLE;
+ break;
+ }
+
+ kcs_bmc_write_data(kcs_bmc,
+ kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
+ break;
+
+ case KCS_PHASE_ABORT_ERROR1:
+ set_state(kcs_bmc, READ_STATE);
+ kcs_bmc_read_data(kcs_bmc);
+ kcs_bmc_write_data(kcs_bmc, kcs_bmc->error);
+ kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
+ break;
+
+ case KCS_PHASE_ABORT_ERROR2:
+ set_state(kcs_bmc, IDLE_STATE);
+ kcs_bmc_read_data(kcs_bmc);
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
+ kcs_bmc->phase = KCS_PHASE_IDLE;
+ break;
+
+ default:
+ kcs_bmc_ipmi_force_abort(kcs_bmc);
+ break;
+ }
+}
+
+static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc *kcs_bmc)
+{
+ u8 cmd;
+
+ set_state(kcs_bmc, WRITE_STATE);
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
+
+ cmd = kcs_bmc_read_data(kcs_bmc);
+ switch (cmd) {
+ case KCS_CMD_WRITE_START:
+ kcs_bmc->phase = KCS_PHASE_WRITE_START;
+ kcs_bmc->error = KCS_NO_ERROR;
+ kcs_bmc->data_in_avail = false;
+ kcs_bmc->data_in_idx = 0;
+ break;
+
+ case KCS_CMD_WRITE_END:
+ if (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
+ kcs_bmc_ipmi_force_abort(kcs_bmc);
+ break;
+ }
+
+ kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
+ break;
+
+ case KCS_CMD_GET_STATUS_ABORT:
+ if (kcs_bmc->error == KCS_NO_ERROR)
+ kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
+
+ kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
+ kcs_bmc->data_in_avail = false;
+ kcs_bmc->data_in_idx = 0;
+ break;
+
+ default:
+ kcs_bmc_ipmi_force_abort(kcs_bmc);
+ kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
+ break;
+ }
+}
+
+int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);
+int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc)
+{
+ unsigned long flags;
+ int ret = -ENODATA;
+ u8 status;
+
+ spin_lock_irqsave(&kcs_bmc->lock, flags);
+
+ status = kcs_bmc_read_status(kcs_bmc);
+ if (status & KCS_STATUS_IBF) {
+ if (!kcs_bmc->running)
+ kcs_bmc_ipmi_force_abort(kcs_bmc);
+ else if (status & KCS_STATUS_CMD_DAT)
+ kcs_bmc_ipmi_handle_cmd(kcs_bmc);
+ else
+ kcs_bmc_ipmi_handle_data(kcs_bmc);
+
+ ret = 0;
+ }
+
+ spin_unlock_irqrestore(&kcs_bmc->lock, flags);
+
+ return ret;
+}
+EXPORT_SYMBOL(kcs_bmc_ipmi_event);
+
+static inline struct kcs_bmc *to_kcs_bmc(struct file *filp)
+{
+ return container_of(filp->private_data, struct kcs_bmc, miscdev);
+}
+
+static int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp)
+{
+ struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
+ int ret = 0;
+
+ spin_lock_irq(&kcs_bmc->lock);
+ if (!kcs_bmc->running)
+ kcs_bmc->running = 1;
+ else
+ ret = -EBUSY;
+ spin_unlock_irq(&kcs_bmc->lock);
+
+ return ret;
+}
+
+static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
+{
+ struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
+ __poll_t mask = 0;
+
+ poll_wait(filp, &kcs_bmc->queue, wait);
+
+ spin_lock_irq(&kcs_bmc->lock);
+ if (kcs_bmc->data_in_avail)
+ mask |= EPOLLIN;
+ spin_unlock_irq(&kcs_bmc->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 *kcs_bmc = to_kcs_bmc(filp);
+ bool data_avail;
+ size_t data_len;
+ ssize_t ret;
+
+ if (!(filp->f_flags & O_NONBLOCK))
+ wait_event_interruptible(kcs_bmc->queue,
+ kcs_bmc->data_in_avail);
+
+ mutex_lock(&kcs_bmc->mutex);
+
+ spin_lock_irq(&kcs_bmc->lock);
+ data_avail = kcs_bmc->data_in_avail;
+ if (data_avail) {
+ data_len = kcs_bmc->data_in_idx;
+ memcpy(kcs_bmc->kbuffer, kcs_bmc->data_in, data_len);
+ }
+ spin_unlock_irq(&kcs_bmc->lock);
+
+ if (!data_avail) {
+ ret = -EAGAIN;
+ goto out_unlock;
+ }
+
+ if (count < data_len) {
+ pr_err("channel=%u with too large data : %zu\n",
+ kcs_bmc->channel, data_len);
+
+ spin_lock_irq(&kcs_bmc->lock);
+ kcs_bmc_ipmi_force_abort(kcs_bmc);
+ spin_unlock_irq(&kcs_bmc->lock);
+
+ ret = -EOVERFLOW;
+ goto out_unlock;
+ }
+
+ if (copy_to_user(buf, kcs_bmc->kbuffer, data_len)) {
+ ret = -EFAULT;
+ goto out_unlock;
+ }
+
+ ret = data_len;
+
+ spin_lock_irq(&kcs_bmc->lock);
+ if (kcs_bmc->phase == KCS_PHASE_WRITE_DONE) {
+ kcs_bmc->phase = KCS_PHASE_WAIT_READ;
+ kcs_bmc->data_in_avail = false;
+ kcs_bmc->data_in_idx = 0;
+ } else {
+ ret = -EAGAIN;
+ }
+ spin_unlock_irq(&kcs_bmc->lock);
+
+out_unlock:
+ mutex_unlock(&kcs_bmc->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 *kcs_bmc = 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(&kcs_bmc->mutex);
+
+ if (copy_from_user(kcs_bmc->kbuffer, buf, count)) {
+ ret = -EFAULT;
+ goto out_unlock;
+ }
+
+ spin_lock_irq(&kcs_bmc->lock);
+ if (kcs_bmc->phase == KCS_PHASE_WAIT_READ) {
+ kcs_bmc->phase = KCS_PHASE_READ;
+ kcs_bmc->data_out_idx = 1;
+ kcs_bmc->data_out_len = count;
+ memcpy(kcs_bmc->data_out, kcs_bmc->kbuffer, count);
+ kcs_bmc_write_data(kcs_bmc, kcs_bmc->data_out[0]);
+ ret = count;
+ } else {
+ ret = -EINVAL;
+ }
+ spin_unlock_irq(&kcs_bmc->lock);
+
+out_unlock:
+ mutex_unlock(&kcs_bmc->mutex);
+
+ return ret;
+}
+
+static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
+ long ret = 0;
+
+ spin_lock_irq(&kcs_bmc->lock);
+
+ switch (cmd) {
+ case IPMI_BMC_IOCTL_SET_SMS_ATN:
+ kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, KCS_STATUS_SMS_ATN);
+ break;
+
+ case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
+ kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, 0);
+ break;
+
+ case IPMI_BMC_IOCTL_FORCE_ABORT:
+ kcs_bmc_ipmi_force_abort(kcs_bmc);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock_irq(&kcs_bmc->lock);
+
+ return ret;
+}
+
+static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
+{
+ struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
+
+ spin_lock_irq(&kcs_bmc->lock);
+ kcs_bmc->running = 0;
+ kcs_bmc_ipmi_force_abort(kcs_bmc);
+ spin_unlock_irq(&kcs_bmc->lock);
+
+ return 0;
+}
+
+static const struct file_operations kcs_bmc_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,
+};
+
+struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
+struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel)
+{
+ struct kcs_bmc *kcs_bmc;
+
+ kcs_bmc = devm_kzalloc(dev, sizeof(*kcs_bmc) + sizeof_priv, GFP_KERNEL);
+ if (!kcs_bmc)
+ return NULL;
+
+ spin_lock_init(&kcs_bmc->lock);
+ kcs_bmc->channel = channel;
+
+ mutex_init(&kcs_bmc->mutex);
+ init_waitqueue_head(&kcs_bmc->queue);
+
+ kcs_bmc->data_in = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
+ kcs_bmc->data_out = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
+ kcs_bmc->kbuffer = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
+
+ kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
+ kcs_bmc->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%u",
+ DEVICE_NAME, channel);
+ if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
+ !kcs_bmc->miscdev.name)
+ return NULL;
+ kcs_bmc->miscdev.fops = &kcs_bmc_fops;
+
+ return kcs_bmc;
+}
+EXPORT_SYMBOL(kcs_bmc_ipmi_alloc);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Haiyue Wang <[email protected]>");
+MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
--
2.27.0

2021-03-19 06:32:11

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 14/21] ipmi: kcs_bmc: Allow clients to control KCS IRQ state

Add a mechanism for controlling whether the client associated with a
KCS device will receive Input Buffer Full (IBF) and Output Buffer Empty
(OBE) events. This enables an abstract implementation of poll() for KCS
devices.

A wart in the implementation is that the ASPEED KCS devices don't
support an OBE interrupt for the BMC. Instead we pretend it has one by
polling the status register waiting for the Output Buffer Full (OBF) bit
to clear, and generating an event when OBE is observed.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/kcs_bmc.c | 6 ++
drivers/char/ipmi/kcs_bmc.h | 3 +
drivers/char/ipmi/kcs_bmc_aspeed.c | 150 ++++++++++++++++++----------
drivers/char/ipmi/kcs_bmc_client.h | 2 +
drivers/char/ipmi/kcs_bmc_device.h | 1 +
drivers/char/ipmi/kcs_bmc_npcm7xx.c | 25 ++++-
6 files changed, 130 insertions(+), 57 deletions(-)

diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
index 694db6ee2a92..05bbb72418b2 100644
--- a/drivers/char/ipmi/kcs_bmc.c
+++ b/drivers/char/ipmi/kcs_bmc.c
@@ -184,6 +184,12 @@ int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev)
}
EXPORT_SYMBOL(kcs_bmc_unregister_cdev);

+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 <[email protected]>");
MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
index 5deb9a0b8e60..11fff935218c 100644
--- a/drivers/char/ipmi/kcs_bmc.h
+++ b/drivers/char/ipmi/kcs_bmc.h
@@ -11,6 +11,9 @@
#define KCS_BMC_EVENT_NONE 0
#define KCS_BMC_EVENT_HANDLED 1

+#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)
diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
index 6f26e7366c0b..5f26471c038c 100644
--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
@@ -60,10 +60,18 @@
#define LPC_ODR4 0x118
#define LPC_STR4 0x11C

+#define OBE_POLL_PERIOD (HZ / 2)
+
struct aspeed_kcs_bmc {
struct kcs_bmc_device kcs_bmc;

struct regmap *map;
+
+ struct {
+ spinlock_t lock;
+ bool remove;
+ struct timer_list timer;
+ } obe;
};

struct aspeed_kcs_of_ops {
@@ -159,68 +167,89 @@ static void aspeed_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enabl

switch (kcs_bmc->channel) {
case 1:
- if (enable) {
- regmap_update_bits(priv->map, LPC_HICR2,
- LPC_HICR2_IBFIF1, LPC_HICR2_IBFIF1);
- regmap_update_bits(priv->map, LPC_HICR0,
- LPC_HICR0_LPC1E, LPC_HICR0_LPC1E);
- } else {
- regmap_update_bits(priv->map, LPC_HICR0,
- LPC_HICR0_LPC1E, 0);
- regmap_update_bits(priv->map, LPC_HICR2,
- LPC_HICR2_IBFIF1, 0);
- }
- break;
-
+ regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC1E, enable * LPC_HICR0_LPC1E);
+ return;
case 2:
- if (enable) {
- regmap_update_bits(priv->map, LPC_HICR2,
- LPC_HICR2_IBFIF2, LPC_HICR2_IBFIF2);
- regmap_update_bits(priv->map, LPC_HICR0,
- LPC_HICR0_LPC2E, LPC_HICR0_LPC2E);
- } else {
- regmap_update_bits(priv->map, LPC_HICR0,
- LPC_HICR0_LPC2E, 0);
- regmap_update_bits(priv->map, LPC_HICR2,
- LPC_HICR2_IBFIF2, 0);
- }
- break;
-
+ regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC2E, enable * LPC_HICR0_LPC2E);
+ return;
case 3:
- if (enable) {
- regmap_update_bits(priv->map, LPC_HICR2,
- LPC_HICR2_IBFIF3, LPC_HICR2_IBFIF3);
- regmap_update_bits(priv->map, LPC_HICR0,
- LPC_HICR0_LPC3E, LPC_HICR0_LPC3E);
- regmap_update_bits(priv->map, LPC_HICR4,
- LPC_HICR4_KCSENBL, LPC_HICR4_KCSENBL);
- } else {
- regmap_update_bits(priv->map, LPC_HICR0,
- LPC_HICR0_LPC3E, 0);
- regmap_update_bits(priv->map, LPC_HICR4,
- LPC_HICR4_KCSENBL, 0);
- regmap_update_bits(priv->map, LPC_HICR2,
- LPC_HICR2_IBFIF3, 0);
- }
- break;
-
+ 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:
- if (enable)
- regmap_update_bits(priv->map, LPC_HICRB,
- LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E,
- LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E);
+ 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);
+
+ /* We don't have an OBE IRQ, emulate it */
+ if (KCS_BMC_EVENT_TYPE_OBE & mask) {
+ if (KCS_BMC_EVENT_TYPE_OBE & state)
+ mod_timer(&priv->obe.timer, jiffies + OBE_POLL_PERIOD);
else
- regmap_update_bits(priv->map, LPC_HICRB,
- LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E,
- 0);
- break;
+ del_timer(&priv->obe.timer);
+ }

- default:
- break;
+ if (KCS_BMC_EVENT_TYPE_IBF & mask) {
+ const bool enable = !!(KCS_BMC_EVENT_TYPE_IBF & state);
+
+ switch (kcs_bmc->channel) {
+ case 1:
+ regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF1,
+ enable * LPC_HICR2_IBFIF1);
+ return;
+ case 2:
+ regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF2,
+ enable * LPC_HICR2_IBFIF2);
+ return;
+ case 3:
+ regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF3,
+ enable * LPC_HICR2_IBFIF3);
+ return;
+ case 4:
+ regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_IBFIF4,
+ enable * LPC_HICRB_IBFIF4);
+ 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,
@@ -378,6 +407,10 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
return -ENODEV;
}

+ spin_lock_init(&priv->obe.lock);
+ priv->obe.remove = false;
+ timer_setup(&priv->obe.timer, aspeed_kcs_check_obe, 0);
+
aspeed_kcs_set_address(kcs_bmc, addr);

rc = aspeed_kcs_config_irq(kcs_bmc, pdev);
@@ -386,6 +419,8 @@ static int aspeed_kcs_probe(struct platform_device *pdev)

platform_set_drvdata(pdev, priv);

+ aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE),
+ KCS_BMC_EVENT_TYPE_IBF);
aspeed_kcs_enable_channel(kcs_bmc, true);

rc = kcs_bmc_add_device(&priv->kcs_bmc);
@@ -404,6 +439,15 @@ static int aspeed_kcs_remove(struct platform_device *pdev)

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);
+ del_timer_sync(&priv->obe.timer);
+
return 0;
}

diff --git a/drivers/char/ipmi/kcs_bmc_client.h b/drivers/char/ipmi/kcs_bmc_client.h
index d0a7404ff584..456796da33de 100644
--- a/drivers/char/ipmi/kcs_bmc_client.h
+++ b/drivers/char/ipmi/kcs_bmc_client.h
@@ -37,6 +37,8 @@ int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev);
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);
diff --git a/drivers/char/ipmi/kcs_bmc_device.h b/drivers/char/ipmi/kcs_bmc_device.h
index 57b7174b2bac..f1ca8912496a 100644
--- a/drivers/char/ipmi/kcs_bmc_device.h
+++ b/drivers/char/ipmi/kcs_bmc_device.h
@@ -7,6 +7,7 @@
#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);
diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
index dce93ec895fc..c2032728a03d 100644
--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
@@ -38,6 +38,7 @@
#define KCS2CTL 0x2A
#define KCS3CTL 0x3C
#define KCS_CTL_IBFIE BIT(0)
+#define KCS_CTL_OBEIE BIT(0)

#define KCS1IE 0x1C
#define KCS2IE 0x2E
@@ -117,13 +118,23 @@ static void npcm7xx_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enab
{
struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);

- regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
- enable ? KCS_CTL_IBFIE : 0);
-
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 (KCS_BMC_EVENT_TYPE_OBE & mask)
+ regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_OBEIE,
+ !!(KCS_BMC_EVENT_TYPE_OBE & state) * KCS_CTL_OBEIE);
+
+ if (KCS_BMC_EVENT_TYPE_IBF & mask)
+ regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
+ !!(KCS_BMC_EVENT_TYPE_IBF & state) * KCS_CTL_IBFIE);
+}
+
static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
{
struct kcs_bmc_device *kcs_bmc = arg;
@@ -151,6 +162,7 @@ static int npcm7xx_kcs_config_irq(struct kcs_bmc_device *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,
@@ -191,11 +203,13 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)

platform_set_drvdata(pdev, priv);

- npcm7xx_kcs_enable_channel(kcs_bmc, true);
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),
+ KCS_BMC_EVENT_TYPE_IBF);
+ npcm7xx_kcs_enable_channel(kcs_bmc, true);

pr_info("channel=%u idr=0x%x odr=0x%x str=0x%x\n",
chan,
@@ -211,6 +225,9 @@ static int npcm7xx_kcs_remove(struct platform_device *pdev)

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);
+
return 0;
}

--
2.27.0

2021-03-19 06:32:17

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 18/21] dt-bindings: ipmi: Add optional SerIRQ property to ASPEED KCS devices

Allocating IO and IRQ resources to LPC devices is in-theory an operation
for the host, however ASPEED don't appear to expose this capability
outside the BMC (e.g. SuperIO). Instead, we are left with BMC-internal
registers for managing these resources, so introduce a devicetree
property for KCS devices to describe SerIRQ properties.

Signed-off-by: Andrew Jeffery <[email protected]>
---
.../bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml | 14 ++++++++++++++
1 file changed, 14 insertions(+)

diff --git a/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
index 697ca575454f..4ff6fabfcb30 100644
--- a/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
+++ b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
@@ -49,6 +49,18 @@ properties:
channels the status address is derived from the data address, but the
status address may be optionally provided.

+ aspeed,lpc-interrupts:
+ $ref: "/schemas/types.yaml#/definitions/uint32-array"
+ minItems: 2
+ maxItems: 2
+ description: |
+ A 2-cell property expressing the LPC SerIRQ number and the interrupt
+ level/sense encoding (specified in the standard fashion).
+
+ Note that the generated interrupt is issued from the BMC to the host, and
+ thus the target interrupt controller is not captured by the BMC's
+ devicetree.
+
kcs_chan:
deprecated: true
$ref: '/schemas/types.yaml#/definitions/uint32'
@@ -84,9 +96,11 @@ allOf:

examples:
- |
+ #include <dt-bindings/interrupt-controller/irq.h>
kcs3: kcs@24 {
compatible = "aspeed,ast2600-kcs-bmc";
reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>;
aspeed,lpc-io-reg = <0xca2>;
+ aspeed,lpc-interrupts = <11 IRQ_TYPE_LEVEL_LOW>;
interrupts = <8>;
};
--
2.27.0

2021-03-19 06:32:22

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 19/21] ipmi: kcs_bmc_aspeed: Implement KCS SerIRQ configuration

Apply the SerIRQ ID and level/sense behaviours from the devicetree if
provided.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/kcs_bmc_aspeed.c | 179 ++++++++++++++++++++++++++++-
1 file changed, 177 insertions(+), 2 deletions(-)

diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
index 271845eb2e26..3782aef4eb73 100644
--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
@@ -9,6 +9,7 @@
#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>
@@ -28,6 +29,22 @@

#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)
@@ -39,6 +56,19 @@
#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
@@ -55,6 +85,13 @@
#define LPC_HICRB 0x100
#define LPC_HICRB_IBFIF4 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
@@ -62,11 +99,21 @@

#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;
@@ -103,6 +150,49 @@ static void aspeed_kcs_outb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 data)

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)
@@ -161,6 +251,73 @@ static void aspeed_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u16 addr)
}
}

+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;
+
+ if (id > 15)
+ return -EINVAL;
+
+ hw_type = aspeed_kcs_map_serirq_type(dt_type);
+ if (hw_type < 0)
+ return hw_type;
+
+ 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);
@@ -267,7 +424,7 @@ static irqreturn_t aspeed_kcs_irq(int irq, void *arg)
return rc == KCS_BMC_EVENT_HANDLED ? IRQ_HANDLED : IRQ_NONE;
}

-static int aspeed_kcs_config_irq(struct kcs_bmc_device *kcs_bmc,
+static int aspeed_kcs_config_downstream_irq(struct kcs_bmc_device *kcs_bmc,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -371,6 +528,8 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
struct aspeed_kcs_bmc *priv;
struct device_node *np;
int rc, channel, addr;
+ bool have_upstream_irq;
+ u32 upstream_irq[2];

np = pdev->dev.of_node->parent;
if (!of_device_is_compatible(np, "aspeed,ast2400-lpc-v2") &&
@@ -379,6 +538,7 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "unsupported LPC device binding\n");
return -ENODEV;
}
+
ops = of_device_get_match_data(&pdev->dev);
if (!ops)
return -EINVAL;
@@ -391,6 +551,13 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
if (addr < 0)
return addr;

+ 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;
@@ -413,10 +580,17 @@ static int aspeed_kcs_probe(struct platform_device *pdev)

aspeed_kcs_set_address(kcs_bmc, addr);

- rc = aspeed_kcs_config_irq(kcs_bmc, pdev);
+ /* 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)
+ aspeed_kcs_config_upstream_irq(priv, upstream_irq[0], upstream_irq[1]);
+ 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);
@@ -481,4 +655,5 @@ module_platform_driver(ast_kcs_bmc_driver);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <[email protected]>");
+MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
MODULE_DESCRIPTION("Aspeed device interface to the KCS BMC device");
--
2.27.0

2021-03-19 06:33:05

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 05/21] soc: aspeed: Adapt to new LPC device tree layout

From: "Chia-Wei, Wang" <[email protected]>

Add check against LPC device v2 compatible string to
ensure that the fixed device tree layout is adopted.
The LPC register offsets are also fixed accordingly.

Signed-off-by: Chia-Wei Wang <[email protected]>
Reviewed-by: Andrew Jeffery <[email protected]>
---
drivers/soc/aspeed/aspeed-lpc-ctrl.c | 20 ++++++++++++++------
drivers/soc/aspeed/aspeed-lpc-snoop.c | 23 +++++++++++++++--------
2 files changed, 29 insertions(+), 14 deletions(-)

diff --git a/drivers/soc/aspeed/aspeed-lpc-ctrl.c b/drivers/soc/aspeed/aspeed-lpc-ctrl.c
index 439bcd6b8c4a..c557ffd0992c 100644
--- a/drivers/soc/aspeed/aspeed-lpc-ctrl.c
+++ b/drivers/soc/aspeed/aspeed-lpc-ctrl.c
@@ -18,15 +18,15 @@

#define DEVICE_NAME "aspeed-lpc-ctrl"

-#define HICR5 0x0
+#define HICR5 0x80
#define HICR5_ENL2H BIT(8)
#define HICR5_ENFWH BIT(10)

-#define HICR6 0x4
+#define HICR6 0x84
#define SW_FWH2AHB BIT(17)

-#define HICR7 0x8
-#define HICR8 0xc
+#define HICR7 0x88
+#define HICR8 0x8c

struct aspeed_lpc_ctrl {
struct miscdevice miscdev;
@@ -215,6 +215,7 @@ static int aspeed_lpc_ctrl_probe(struct platform_device *pdev)
struct device_node *node;
struct resource resm;
struct device *dev;
+ struct device_node *np;
int rc;

dev = &pdev->dev;
@@ -270,8 +271,15 @@ static int aspeed_lpc_ctrl_probe(struct platform_device *pdev)
}
}

- lpc_ctrl->regmap = syscon_node_to_regmap(
- pdev->dev.parent->of_node);
+ np = pdev->dev.parent->of_node;
+ 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(dev, "unsupported LPC device binding\n");
+ return -ENODEV;
+ }
+
+ lpc_ctrl->regmap = syscon_node_to_regmap(np);
if (IS_ERR(lpc_ctrl->regmap)) {
dev_err(dev, "Couldn't get regmap\n");
return -ENODEV;
diff --git a/drivers/soc/aspeed/aspeed-lpc-snoop.c b/drivers/soc/aspeed/aspeed-lpc-snoop.c
index 20acac6342ef..210455efb321 100644
--- a/drivers/soc/aspeed/aspeed-lpc-snoop.c
+++ b/drivers/soc/aspeed/aspeed-lpc-snoop.c
@@ -29,26 +29,25 @@
#define NUM_SNOOP_CHANNELS 2
#define SNOOP_FIFO_SIZE 2048

-#define HICR5 0x0
+#define HICR5 0x80
#define HICR5_EN_SNP0W BIT(0)
#define HICR5_ENINT_SNP0W BIT(1)
#define HICR5_EN_SNP1W BIT(2)
#define HICR5_ENINT_SNP1W BIT(3)
-
-#define HICR6 0x4
+#define HICR6 0x84
#define HICR6_STR_SNP0W BIT(0)
#define HICR6_STR_SNP1W BIT(1)
-#define SNPWADR 0x10
+#define SNPWADR 0x90
#define SNPWADR_CH0_MASK GENMASK(15, 0)
#define SNPWADR_CH0_SHIFT 0
#define SNPWADR_CH1_MASK GENMASK(31, 16)
#define SNPWADR_CH1_SHIFT 16
-#define SNPWDR 0x14
+#define SNPWDR 0x94
#define SNPWDR_CH0_MASK GENMASK(7, 0)
#define SNPWDR_CH0_SHIFT 0
#define SNPWDR_CH1_MASK GENMASK(15, 8)
#define SNPWDR_CH1_SHIFT 8
-#define HICRB 0x80
+#define HICRB 0x100
#define HICRB_ENSNP0D BIT(14)
#define HICRB_ENSNP1D BIT(15)

@@ -260,6 +259,7 @@ static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
{
struct aspeed_lpc_snoop *lpc_snoop;
struct device *dev;
+ struct device_node *np;
u32 port;
int rc;

@@ -269,8 +269,15 @@ static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
if (!lpc_snoop)
return -ENOMEM;

- lpc_snoop->regmap = syscon_node_to_regmap(
- pdev->dev.parent->of_node);
+ np = pdev->dev.parent->of_node;
+ 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(dev, "unsupported LPC device binding\n");
+ return -ENODEV;
+ }
+
+ lpc_snoop->regmap = syscon_node_to_regmap(np);
if (IS_ERR(lpc_snoop->regmap)) {
dev_err(dev, "Couldn't get regmap\n");
return -ENODEV;
--
2.27.0

2021-03-19 06:33:17

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 07/21] ipmi: kcs_bmc: Make status update atomic

Enable more efficient implementation of read-modify-write sequences.
Both device drivers for the KCS BMC stack use regmaps. The new callback
allows us to exploit regmap_update_bits().

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/kcs_bmc.c | 7 +------
drivers/char/ipmi/kcs_bmc.h | 1 +
drivers/char/ipmi/kcs_bmc_aspeed.c | 9 +++++++++
drivers/char/ipmi/kcs_bmc_npcm7xx.c | 10 ++++++++++
4 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
index f292e74bd4a5..58fb1a7bd50d 100644
--- a/drivers/char/ipmi/kcs_bmc.c
+++ b/drivers/char/ipmi/kcs_bmc.c
@@ -67,12 +67,7 @@ static inline void write_status(struct kcs_bmc *kcs_bmc, u8 data)

static void update_status_bits(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
{
- u8 tmp = read_status(kcs_bmc);
-
- tmp &= ~mask;
- tmp |= val & mask;
-
- write_status(kcs_bmc, tmp);
+ kcs_bmc->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
}

static inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
index eb9ea4ce78b8..970f53892f2d 100644
--- a/drivers/char/ipmi/kcs_bmc.h
+++ b/drivers/char/ipmi/kcs_bmc.h
@@ -76,6 +76,7 @@ struct kcs_bmc {
struct kcs_ioreg ioreg;
u8 (*io_inputb)(struct kcs_bmc *kcs_bmc, u32 reg);
void (*io_outputb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 b);
+ void (*io_updateb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val);

enum kcs_phases phase;
enum kcs_errors error;
diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
index 061f53676206..630cf095560e 100644
--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
@@ -90,6 +90,14 @@ static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
WARN(rc != 0, "regmap_write() failed: %d\n", rc);
}

+static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val)
+{
+ struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
+ int rc;
+
+ rc = regmap_update_bits(priv->map, reg, mask, val);
+ WARN(rc != 0, "regmap_update_bits() failed: %d\n", rc);
+}

/*
* AST_usrGuide_KCS.pdf
@@ -342,6 +350,7 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
kcs_bmc->ioreg = ast_kcs_bmc_ioregs[channel - 1];
kcs_bmc->io_inputb = aspeed_kcs_inb;
kcs_bmc->io_outputb = aspeed_kcs_outb;
+ kcs_bmc->io_updateb = aspeed_kcs_updateb;

addr = ops->get_io_address(pdev);
if (addr < 0)
diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
index 722f7391fe1f..1f44aadec9e8 100644
--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
@@ -97,6 +97,15 @@ static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
WARN(rc != 0, "regmap_write() failed: %d\n", rc);
}

+static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 data)
+{
+ struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(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 *kcs_bmc, bool enable)
{
struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
@@ -163,6 +172,7 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
kcs_bmc->ioreg.str = priv->reg->sts;
kcs_bmc->io_inputb = npcm7xx_kcs_inb;
kcs_bmc->io_outputb = npcm7xx_kcs_outb;
+ kcs_bmc->io_updateb = npcm7xx_kcs_updateb;

dev_set_drvdata(dev, kcs_bmc);

--
2.27.0

2021-03-19 06:33:17

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 08/21] ipmi: kcs_bmc: Rename {read,write}_{status,data}() functions

Rename the functions in preparation for separating the IPMI chardev out
from the KCS BMC core.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/kcs_bmc.c | 52 ++++++++++++++++++-------------------
1 file changed, 25 insertions(+), 27 deletions(-)

diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
index 58fb1a7bd50d..c4336c1f2d6d 100644
--- a/drivers/char/ipmi/kcs_bmc.c
+++ b/drivers/char/ipmi/kcs_bmc.c
@@ -45,42 +45,42 @@ enum kcs_states {
#define KCS_CMD_WRITE_END 0x62
#define KCS_CMD_READ_BYTE 0x68

-static inline u8 read_data(struct kcs_bmc *kcs_bmc)
+static inline u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
{
return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
}

-static inline void write_data(struct kcs_bmc *kcs_bmc, u8 data)
+static inline void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data)
{
kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
}

-static inline u8 read_status(struct kcs_bmc *kcs_bmc)
+static inline u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc)
{
return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
}

-static inline void write_status(struct kcs_bmc *kcs_bmc, u8 data)
+static inline void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data)
{
kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
}

-static void update_status_bits(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
+static void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
{
kcs_bmc->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
}

static inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
{
- update_status_bits(kcs_bmc, KCS_STATUS_STATE_MASK,
+ kcs_bmc_update_status(kcs_bmc, KCS_STATUS_STATE_MASK,
KCS_STATUS_STATE(state));
}

static void kcs_force_abort(struct kcs_bmc *kcs_bmc)
{
set_state(kcs_bmc, ERROR_STATE);
- read_data(kcs_bmc);
- write_data(kcs_bmc, KCS_ZERO_DATA);
+ kcs_bmc_read_data(kcs_bmc);
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);

kcs_bmc->phase = KCS_PHASE_ERROR;
kcs_bmc->data_in_avail = false;
@@ -99,9 +99,9 @@ static void kcs_bmc_handle_data(struct kcs_bmc *kcs_bmc)
case KCS_PHASE_WRITE_DATA:
if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
set_state(kcs_bmc, WRITE_STATE);
- write_data(kcs_bmc, KCS_ZERO_DATA);
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
- read_data(kcs_bmc);
+ kcs_bmc_read_data(kcs_bmc);
} else {
kcs_force_abort(kcs_bmc);
kcs_bmc->error = KCS_LENGTH_ERROR;
@@ -112,7 +112,7 @@ static void kcs_bmc_handle_data(struct kcs_bmc *kcs_bmc)
if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
set_state(kcs_bmc, READ_STATE);
kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
- read_data(kcs_bmc);
+ kcs_bmc_read_data(kcs_bmc);
kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
kcs_bmc->data_in_avail = true;
wake_up_interruptible(&kcs_bmc->queue);
@@ -126,34 +126,34 @@ static void kcs_bmc_handle_data(struct kcs_bmc *kcs_bmc)
if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
set_state(kcs_bmc, IDLE_STATE);

- data = read_data(kcs_bmc);
+ data = kcs_bmc_read_data(kcs_bmc);
if (data != KCS_CMD_READ_BYTE) {
set_state(kcs_bmc, ERROR_STATE);
- write_data(kcs_bmc, KCS_ZERO_DATA);
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
break;
}

if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
- write_data(kcs_bmc, KCS_ZERO_DATA);
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
kcs_bmc->phase = KCS_PHASE_IDLE;
break;
}

- write_data(kcs_bmc,
+ kcs_bmc_write_data(kcs_bmc,
kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
break;

case KCS_PHASE_ABORT_ERROR1:
set_state(kcs_bmc, READ_STATE);
- read_data(kcs_bmc);
- write_data(kcs_bmc, kcs_bmc->error);
+ kcs_bmc_read_data(kcs_bmc);
+ kcs_bmc_write_data(kcs_bmc, kcs_bmc->error);
kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
break;

case KCS_PHASE_ABORT_ERROR2:
set_state(kcs_bmc, IDLE_STATE);
- read_data(kcs_bmc);
- write_data(kcs_bmc, KCS_ZERO_DATA);
+ kcs_bmc_read_data(kcs_bmc);
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
kcs_bmc->phase = KCS_PHASE_IDLE;
break;

@@ -168,9 +168,9 @@ static void kcs_bmc_handle_cmd(struct kcs_bmc *kcs_bmc)
u8 cmd;

set_state(kcs_bmc, WRITE_STATE);
- write_data(kcs_bmc, KCS_ZERO_DATA);
+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);

- cmd = read_data(kcs_bmc);
+ cmd = kcs_bmc_read_data(kcs_bmc);
switch (cmd) {
case KCS_CMD_WRITE_START:
kcs_bmc->phase = KCS_PHASE_WRITE_START;
@@ -212,7 +212,7 @@ int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)

spin_lock_irqsave(&kcs_bmc->lock, flags);

- status = read_status(kcs_bmc);
+ status = kcs_bmc_read_status(kcs_bmc);
if (status & KCS_STATUS_IBF) {
if (!kcs_bmc->running)
kcs_force_abort(kcs_bmc);
@@ -350,7 +350,7 @@ static ssize_t kcs_bmc_write(struct file *filp, const char __user *buf,
kcs_bmc->data_out_idx = 1;
kcs_bmc->data_out_len = count;
memcpy(kcs_bmc->data_out, kcs_bmc->kbuffer, count);
- write_data(kcs_bmc, kcs_bmc->data_out[0]);
+ kcs_bmc_write_data(kcs_bmc, kcs_bmc->data_out[0]);
ret = count;
} else {
ret = -EINVAL;
@@ -373,13 +373,11 @@ static long kcs_bmc_ioctl(struct file *filp, unsigned int cmd,

switch (cmd) {
case IPMI_BMC_IOCTL_SET_SMS_ATN:
- update_status_bits(kcs_bmc, KCS_STATUS_SMS_ATN,
- KCS_STATUS_SMS_ATN);
+ kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, KCS_STATUS_SMS_ATN);
break;

case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
- update_status_bits(kcs_bmc, KCS_STATUS_SMS_ATN,
- 0);
+ kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, 0);
break;

case IPMI_BMC_IOCTL_FORCE_ABORT:
--
2.27.0

2021-03-19 06:33:27

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 12/21] ipmi: kcs_bmc: Strip private client data from struct kcs_bmc

Move all client-private data out of `struct kcs_bmc` into the KCS client
implementation.

With this change the KCS BMC core code now only concerns itself with
abstract `struct kcs_bmc` and `struct kcs_bmc_client` types, achieving
expected separation of concerns. Further, the change clears the path for
implementation of alternative userspace interfaces.

The chardev data-structures are rearranged in the same manner applied to
the KCS device driver data-structures in an earlier patch - `struct
kcs_bmc_client` is embedded in the client's private data and we exploit
container_of() to translate as required.

Finally, now that it is free of client data, `struct kcs_bmc` is renamed
to `struct kcs_bmc_device` to contrast `struct kcs_bmc_client`.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/kcs_bmc.c | 68 +++-
drivers/char/ipmi/kcs_bmc.h | 86 +-----
drivers/char/ipmi/kcs_bmc_aspeed.c | 22 +-
drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 428 ++++++++++++++++----------
drivers/char/ipmi/kcs_bmc_client.h | 28 +-
drivers/char/ipmi/kcs_bmc_device.h | 12 +-
drivers/char/ipmi/kcs_bmc_npcm7xx.c | 20 +-
7 files changed, 368 insertions(+), 296 deletions(-)

diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
index 1046ce2bbefc..266ebec71d6f 100644
--- a/drivers/char/ipmi/kcs_bmc.c
+++ b/drivers/char/ipmi/kcs_bmc.c
@@ -4,6 +4,7 @@
* Copyright (c) 2021, IBM Corp.
*/

+#include <linux/device.h>
#include <linux/module.h>

#include "kcs_bmc.h"
@@ -14,51 +15,96 @@

/* Consumer data access */

-u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
+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 *kcs_bmc, u8 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 *kcs_bmc)
+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 *kcs_bmc, u8 data)
+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 *kcs_bmc, u8 mask, u8 val)
+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);

-int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
+int kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc)
{
- return kcs_bmc->client.ops->event(&kcs_bmc->client);
+ struct kcs_bmc_client *client;
+ int rc;
+
+ spin_lock(&kcs_bmc->lock);
+ client = kcs_bmc->client;
+ if (client) {
+ rc = client->ops->event(client);
+ } else {
+ u8 status;
+
+ status = kcs_bmc_read_status(kcs_bmc);
+ if (status & KCS_BMC_STR_IBF) {
+ /* Ack the event by reading the data */
+ kcs_bmc_read_data(kcs_bmc);
+ rc = KCS_BMC_EVENT_HANDLED;
+ } else {
+ rc = KCS_BMC_EVENT_NONE;
+ }
+ }
+ spin_unlock(&kcs_bmc->lock);
+
+ return rc;
}
EXPORT_SYMBOL(kcs_bmc_handle_event);

-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
-int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc)
+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 {
+ kcs_bmc->client = client;
+ 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)
+ 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)
{
return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
}
EXPORT_SYMBOL(kcs_bmc_add_device);

-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
-int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc)
+int kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc)
{
return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
}
diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
index a1350e567723..3f266740c759 100644
--- a/drivers/char/ipmi/kcs_bmc.h
+++ b/drivers/char/ipmi/kcs_bmc.h
@@ -6,9 +6,7 @@
#ifndef __KCS_BMC_H__
#define __KCS_BMC_H__

-#include <linux/miscdevice.h>
-
-#include "kcs_bmc_client.h"
+#include <linux/list.h>

#define KCS_BMC_EVENT_NONE 0
#define KCS_BMC_EVENT_HANDLED 1
@@ -17,53 +15,6 @@
#define KCS_BMC_STR_IBF BIT(1)
#define KCS_BMC_STR_CMD_DAT BIT(3)

-/* 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_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_errors {
- KCS_NO_ERROR = 0x00,
- KCS_ABORTED_BY_COMMAND = 0x01,
- KCS_ILLEGAL_CONTROL_CODE = 0x02,
- KCS_LENGTH_ERROR = 0x06,
- KCS_UNSPECIFIED_ERROR = 0xFF
-};
-
/* IPMI 2.0 - 9.5, KCS Interface Registers
* @idr: Input Data Register
* @odr: Output Data Register
@@ -76,36 +27,23 @@ struct kcs_ioreg {
};

struct kcs_bmc_device_ops;
+struct kcs_bmc_client;
+
+struct kcs_bmc_device {
+ struct list_head entry;

-struct kcs_bmc {
struct device *dev;
-
- const struct kcs_bmc_device_ops *ops;
-
- struct kcs_bmc_client client;
-
- spinlock_t lock;
-
u32 channel;
- int running;

struct kcs_ioreg ioreg;

- enum kcs_phases phase;
- enum kcs_errors error;
+ const struct kcs_bmc_device_ops *ops;

- 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;
+ spinlock_t lock;
+ struct kcs_bmc_client *client;
};
+
+/* Temporary exports while refactoring */
+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc);
+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc);
#endif /* __KCS_BMC_H__ */
diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
index 1b313355b1c8..6f26e7366c0b 100644
--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
@@ -61,7 +61,7 @@
#define LPC_STR4 0x11C

struct aspeed_kcs_bmc {
- struct kcs_bmc kcs_bmc;
+ struct kcs_bmc_device kcs_bmc;

struct regmap *map;
};
@@ -71,12 +71,12 @@ struct aspeed_kcs_of_ops {
int (*get_io_address)(struct platform_device *pdev);
};

-static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc *kcs_bmc)
+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 *kcs_bmc, u32 reg)
+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;
@@ -88,7 +88,7 @@ static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
return rc == 0 ? (u8) val : 0;
}

-static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
+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;
@@ -97,7 +97,7 @@ static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
WARN(rc != 0, "regmap_write() failed: %d\n", rc);
}

-static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val)
+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;
@@ -119,7 +119,7 @@ static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val
* C. KCS4
* D / C : CA4h / CA5h
*/
-static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
+static void aspeed_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u16 addr)
{
struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);

@@ -153,7 +153,7 @@ static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
}
}

-static void aspeed_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
+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);

@@ -228,7 +228,7 @@ static const struct kcs_bmc_device_ops aspeed_kcs_ops = {

static irqreturn_t aspeed_kcs_irq(int irq, void *arg)
{
- struct kcs_bmc *kcs_bmc = arg;
+ struct kcs_bmc_device *kcs_bmc = arg;
int rc;

rc = kcs_bmc_handle_event(kcs_bmc);
@@ -238,7 +238,7 @@ static irqreturn_t aspeed_kcs_irq(int irq, void *arg)
return rc == KCS_BMC_EVENT_HANDLED ? IRQ_HANDLED : IRQ_NONE;
}

-static int aspeed_kcs_config_irq(struct kcs_bmc *kcs_bmc,
+static int aspeed_kcs_config_irq(struct kcs_bmc_device *kcs_bmc,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -338,8 +338,8 @@ static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
static int aspeed_kcs_probe(struct platform_device *pdev)
{
const struct aspeed_kcs_of_ops *ops;
+ struct kcs_bmc_device *kcs_bmc;
struct aspeed_kcs_bmc *priv;
- struct kcs_bmc *kcs_bmc;
struct device_node *np;
int rc, channel, addr;

@@ -400,7 +400,7 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
static int aspeed_kcs_remove(struct platform_device *pdev)
{
struct aspeed_kcs_bmc *priv = platform_get_drvdata(pdev);
- struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
+ struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc;

kcs_bmc_remove_device(kcs_bmc);

diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
index fd852d8abe48..58c42e76483d 100644
--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
@@ -8,13 +8,88 @@
#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.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"

@@ -44,171 +119,169 @@ enum kcs_states {
#define KCS_CMD_WRITE_END 0x62
#define KCS_CMD_READ_BYTE 0x68

-static inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
+static inline void set_state(struct kcs_bmc_ipmi *priv, u8 state)
{
- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_STATE_MASK,
- KCS_STATUS_STATE(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 *kcs_bmc)
+static void kcs_bmc_ipmi_force_abort(struct kcs_bmc_ipmi *priv)
{
- set_state(kcs_bmc, ERROR_STATE);
- kcs_bmc_read_data(kcs_bmc);
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
+ set_state(priv, ERROR_STATE);
+ kcs_bmc_read_data(priv->client.dev);
+ kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);

- kcs_bmc->phase = KCS_PHASE_ERROR;
- kcs_bmc->data_in_avail = false;
- kcs_bmc->data_in_idx = 0;
+ 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 *kcs_bmc)
+static void kcs_bmc_ipmi_handle_data(struct kcs_bmc_ipmi *priv)
{
+ struct kcs_bmc_device *dev;
u8 data;

- switch (kcs_bmc->phase) {
+ dev = priv->client.dev;
+
+ switch (priv->phase) {
case KCS_PHASE_WRITE_START:
- kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
+ priv->phase = KCS_PHASE_WRITE_DATA;
fallthrough;

case KCS_PHASE_WRITE_DATA:
- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
- set_state(kcs_bmc, WRITE_STATE);
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
- kcs_bmc_read_data(kcs_bmc);
+ 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(kcs_bmc);
- kcs_bmc->error = KCS_LENGTH_ERROR;
+ kcs_bmc_ipmi_force_abort(priv);
+ priv->error = KCS_LENGTH_ERROR;
}
break;

case KCS_PHASE_WRITE_END_CMD:
- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
- set_state(kcs_bmc, READ_STATE);
- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
- kcs_bmc_read_data(kcs_bmc);
- kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
- kcs_bmc->data_in_avail = true;
- wake_up_interruptible(&kcs_bmc->queue);
+ 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(kcs_bmc);
- kcs_bmc->error = KCS_LENGTH_ERROR;
+ kcs_bmc_ipmi_force_abort(priv);
+ priv->error = KCS_LENGTH_ERROR;
}
break;

case KCS_PHASE_READ:
- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
- set_state(kcs_bmc, IDLE_STATE);
+ if (priv->data_out_idx == priv->data_out_len)
+ set_state(priv, IDLE_STATE);

- data = kcs_bmc_read_data(kcs_bmc);
+ data = kcs_bmc_read_data(dev);
if (data != KCS_CMD_READ_BYTE) {
- set_state(kcs_bmc, ERROR_STATE);
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
+ set_state(priv, ERROR_STATE);
+ kcs_bmc_write_data(dev, KCS_ZERO_DATA);
break;
}

- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
- kcs_bmc->phase = KCS_PHASE_IDLE;
+ 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(kcs_bmc,
- kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
+ kcs_bmc_write_data(dev, priv->data_out[priv->data_out_idx++]);
break;

case KCS_PHASE_ABORT_ERROR1:
- set_state(kcs_bmc, READ_STATE);
- kcs_bmc_read_data(kcs_bmc);
- kcs_bmc_write_data(kcs_bmc, kcs_bmc->error);
- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
+ 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(kcs_bmc, IDLE_STATE);
- kcs_bmc_read_data(kcs_bmc);
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
- kcs_bmc->phase = KCS_PHASE_IDLE;
+ 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(kcs_bmc);
+ kcs_bmc_ipmi_force_abort(priv);
break;
}
}

-static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc *kcs_bmc)
+static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc_ipmi *priv)
{
u8 cmd;

- set_state(kcs_bmc, WRITE_STATE);
- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
+ set_state(priv, WRITE_STATE);
+ kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);

- cmd = kcs_bmc_read_data(kcs_bmc);
+ cmd = kcs_bmc_read_data(priv->client.dev);
switch (cmd) {
case KCS_CMD_WRITE_START:
- kcs_bmc->phase = KCS_PHASE_WRITE_START;
- kcs_bmc->error = KCS_NO_ERROR;
- kcs_bmc->data_in_avail = false;
- kcs_bmc->data_in_idx = 0;
+ 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 (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
- kcs_bmc_ipmi_force_abort(kcs_bmc);
+ if (priv->phase != KCS_PHASE_WRITE_DATA) {
+ kcs_bmc_ipmi_force_abort(priv);
break;
}

- kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
+ priv->phase = KCS_PHASE_WRITE_END_CMD;
break;

case KCS_CMD_GET_STATUS_ABORT:
- if (kcs_bmc->error == KCS_NO_ERROR)
- kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
+ if (priv->error == KCS_NO_ERROR)
+ priv->error = KCS_ABORTED_BY_COMMAND;

- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
- kcs_bmc->data_in_avail = false;
- kcs_bmc->data_in_idx = 0;
+ priv->phase = KCS_PHASE_ABORT_ERROR1;
+ priv->data_in_avail = false;
+ priv->data_in_idx = 0;
break;

default:
- kcs_bmc_ipmi_force_abort(kcs_bmc);
- kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
+ kcs_bmc_ipmi_force_abort(priv);
+ priv->error = KCS_ILLEGAL_CONTROL_CODE;
break;
}
}

-static inline struct kcs_bmc *client_to_kcs_bmc(struct kcs_bmc_client *client)
+static inline struct kcs_bmc_ipmi *client_to_kcs_bmc_ipmi(struct kcs_bmc_client *client)
{
- return container_of(client, struct kcs_bmc, client);
+ return container_of(client, struct kcs_bmc_ipmi, client);
}

static int kcs_bmc_ipmi_event(struct kcs_bmc_client *client)
{
- struct kcs_bmc *kcs_bmc;
- unsigned long flags;
+ struct kcs_bmc_ipmi *priv;
u8 status;
int ret;

- kcs_bmc = client_to_kcs_bmc(client);
+ priv = client_to_kcs_bmc_ipmi(client);
+ if (!priv)
+ return KCS_BMC_EVENT_NONE;

- spin_lock_irqsave(&kcs_bmc->lock, flags);
+ spin_lock(&priv->lock);

- status = kcs_bmc_read_status(kcs_bmc);
+ status = kcs_bmc_read_status(client->dev);
if (status & KCS_STATUS_IBF) {
- if (!kcs_bmc->running)
- kcs_bmc_ipmi_force_abort(kcs_bmc);
- else if (status & KCS_STATUS_CMD_DAT)
- kcs_bmc_ipmi_handle_cmd(kcs_bmc);
+ if (status & KCS_STATUS_CMD_DAT)
+ kcs_bmc_ipmi_handle_cmd(priv);
else
- kcs_bmc_ipmi_handle_data(kcs_bmc);
+ kcs_bmc_ipmi_handle_data(priv);

ret = KCS_BMC_EVENT_HANDLED;
} else {
ret = KCS_BMC_EVENT_NONE;
}

- spin_unlock_irqrestore(&kcs_bmc->lock, flags);
+ spin_unlock(&priv->lock);

return ret;
}
@@ -217,37 +290,29 @@ static const struct kcs_bmc_client_ops kcs_bmc_ipmi_client_ops = {
.event = kcs_bmc_ipmi_event,
};

-static inline struct kcs_bmc *file_to_kcs_bmc(struct file *filp)
+static inline struct kcs_bmc_ipmi *to_kcs_bmc(struct file *filp)
{
- return container_of(filp->private_data, struct kcs_bmc, miscdev);
+ 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 *kcs_bmc = file_to_kcs_bmc(filp);
- int ret = 0;
+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);

- spin_lock_irq(&kcs_bmc->lock);
- if (!kcs_bmc->running)
- kcs_bmc->running = 1;
- else
- ret = -EBUSY;
- spin_unlock_irq(&kcs_bmc->lock);
-
- return ret;
+ 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 *kcs_bmc = file_to_kcs_bmc(filp);
+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
__poll_t mask = 0;

- poll_wait(filp, &kcs_bmc->queue, wait);
+ poll_wait(filp, &priv->queue, wait);

- spin_lock_irq(&kcs_bmc->lock);
- if (kcs_bmc->data_in_avail)
+ spin_lock_irq(&priv->lock);
+ if (priv->data_in_avail)
mask |= EPOLLIN;
- spin_unlock_irq(&kcs_bmc->lock);
+ spin_unlock_irq(&priv->lock);

return mask;
}
@@ -255,24 +320,24 @@ static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
+ 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(kcs_bmc->queue,
- kcs_bmc->data_in_avail);
+ wait_event_interruptible(priv->queue,
+ priv->data_in_avail);

- mutex_lock(&kcs_bmc->mutex);
+ mutex_lock(&priv->mutex);

- spin_lock_irq(&kcs_bmc->lock);
- data_avail = kcs_bmc->data_in_avail;
+ spin_lock_irq(&priv->lock);
+ data_avail = priv->data_in_avail;
if (data_avail) {
- data_len = kcs_bmc->data_in_idx;
- memcpy(kcs_bmc->kbuffer, kcs_bmc->data_in, data_len);
+ data_len = priv->data_in_idx;
+ memcpy(priv->kbuffer, priv->data_in, data_len);
}
- spin_unlock_irq(&kcs_bmc->lock);
+ spin_unlock_irq(&priv->lock);

if (!data_avail) {
ret = -EAGAIN;
@@ -281,35 +346,35 @@ static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,

if (count < data_len) {
pr_err("channel=%u with too large data : %zu\n",
- kcs_bmc->channel, data_len);
+ priv->client.dev->channel, data_len);

- spin_lock_irq(&kcs_bmc->lock);
- kcs_bmc_ipmi_force_abort(kcs_bmc);
- spin_unlock_irq(&kcs_bmc->lock);
+ 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, kcs_bmc->kbuffer, data_len)) {
+ if (copy_to_user(buf, priv->kbuffer, data_len)) {
ret = -EFAULT;
goto out_unlock;
}

ret = data_len;

- spin_lock_irq(&kcs_bmc->lock);
- if (kcs_bmc->phase == KCS_PHASE_WRITE_DONE) {
- kcs_bmc->phase = KCS_PHASE_WAIT_READ;
- kcs_bmc->data_in_avail = false;
- kcs_bmc->data_in_idx = 0;
+ 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(&kcs_bmc->lock);
+ spin_unlock_irq(&priv->lock);

out_unlock:
- mutex_unlock(&kcs_bmc->mutex);
+ mutex_unlock(&priv->mutex);

return ret;
}
@@ -317,35 +382,35 @@ static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
+ 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(&kcs_bmc->mutex);
+ mutex_lock(&priv->mutex);

- if (copy_from_user(kcs_bmc->kbuffer, buf, count)) {
+ if (copy_from_user(priv->kbuffer, buf, count)) {
ret = -EFAULT;
goto out_unlock;
}

- spin_lock_irq(&kcs_bmc->lock);
- if (kcs_bmc->phase == KCS_PHASE_WAIT_READ) {
- kcs_bmc->phase = KCS_PHASE_READ;
- kcs_bmc->data_out_idx = 1;
- kcs_bmc->data_out_len = count;
- memcpy(kcs_bmc->data_out, kcs_bmc->kbuffer, count);
- kcs_bmc_write_data(kcs_bmc, kcs_bmc->data_out[0]);
+ 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(&kcs_bmc->lock);
+ spin_unlock_irq(&priv->lock);

out_unlock:
- mutex_unlock(&kcs_bmc->mutex);
+ mutex_unlock(&priv->mutex);

return ret;
}
@@ -353,22 +418,22 @@ static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
long ret = 0;

- spin_lock_irq(&kcs_bmc->lock);
+ spin_lock_irq(&priv->lock);

switch (cmd) {
case IPMI_BMC_IOCTL_SET_SMS_ATN:
- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, KCS_STATUS_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(kcs_bmc, KCS_STATUS_SMS_ATN, 0);
+ kcs_bmc_update_status(priv->client.dev, KCS_STATUS_SMS_ATN, 0);
break;

case IPMI_BMC_IOCTL_FORCE_ABORT:
- kcs_bmc_ipmi_force_abort(kcs_bmc);
+ kcs_bmc_ipmi_force_abort(priv);
break;

default:
@@ -376,19 +441,17 @@ static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
break;
}

- spin_unlock_irq(&kcs_bmc->lock);
+ spin_unlock_irq(&priv->lock);

return ret;
}

static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
{
- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);

- spin_lock_irq(&kcs_bmc->lock);
- kcs_bmc->running = 0;
- kcs_bmc_ipmi_force_abort(kcs_bmc);
- spin_unlock_irq(&kcs_bmc->lock);
+ kcs_bmc_ipmi_force_abort(priv);
+ kcs_bmc_disable_device(priv->client.dev, &priv->client);

return 0;
}
@@ -403,56 +466,78 @@ static const struct file_operations kcs_bmc_ipmi_fops = {
.unlocked_ioctl = kcs_bmc_ipmi_ioctl,
};

-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc)
+static DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock);
+static LIST_HEAD(kcs_bmc_ipmi_instances);
+
+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc);
+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc)
{
+ struct kcs_bmc_ipmi *priv;
int rc;

- spin_lock_init(&kcs_bmc->lock);
- mutex_init(&kcs_bmc->mutex);
- init_waitqueue_head(&kcs_bmc->queue);
-
- kcs_bmc->client.dev = kcs_bmc;
- kcs_bmc->client.ops = &kcs_bmc_ipmi_client_ops;
- kcs_bmc->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
- kcs_bmc->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
- kcs_bmc->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
-
- kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
- kcs_bmc->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u",
- DEVICE_NAME, kcs_bmc->channel);
- if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
- !kcs_bmc->miscdev.name)
+ priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
return -ENOMEM;

- kcs_bmc->miscdev.fops = &kcs_bmc_ipmi_fops;
+ spin_lock_init(&priv->lock);
+ mutex_init(&priv->mutex);

- rc = misc_register(&kcs_bmc->miscdev);
+ 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;
}
EXPORT_SYMBOL(kcs_bmc_ipmi_attach_cdev);

-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc)
+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc);
+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc)
{
- misc_deregister(&kcs_bmc->miscdev);
+ struct kcs_bmc_ipmi *priv = NULL, *pos;

- spin_lock_irq(&kcs_bmc->lock);
- kcs_bmc->running = 0;
- kcs_bmc_ipmi_force_abort(kcs_bmc);
- spin_unlock_irq(&kcs_bmc->lock);
+ 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);

- devm_kfree(kcs_bmc->dev, kcs_bmc->kbuffer);
- devm_kfree(kcs_bmc->dev, kcs_bmc->data_out);
- devm_kfree(kcs_bmc->dev, kcs_bmc->data_in);
- devm_kfree(kcs_bmc->dev, kcs_bmc);
+ if (!priv)
+ return 0;
+
+ 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;
}
@@ -460,4 +545,5 @@ EXPORT_SYMBOL(kcs_bmc_ipmi_detach_cdev);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Haiyue Wang <[email protected]>");
+MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
diff --git a/drivers/char/ipmi/kcs_bmc_client.h b/drivers/char/ipmi/kcs_bmc_client.h
index 140631d157d8..2dd710f4b4aa 100644
--- a/drivers/char/ipmi/kcs_bmc_client.h
+++ b/drivers/char/ipmi/kcs_bmc_client.h
@@ -8,22 +8,24 @@
#include <linux/notifier.h>
#include <stdbool.h>

-struct kcs_bmc;
-struct kcs_bmc_client_ops;
-
-struct kcs_bmc_client {
- const struct kcs_bmc_client_ops *ops;
-
- struct kcs_bmc *dev;
-};
+#include "kcs_bmc.h"

struct kcs_bmc_client_ops {
int (*event)(struct kcs_bmc_client *client);
};

-u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
-void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
-u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc);
-void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data);
-void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val);
+struct kcs_bmc_client {
+ const struct kcs_bmc_client_ops *ops;
+
+ struct kcs_bmc_device *dev;
+};
+
+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);
+
+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
diff --git a/drivers/char/ipmi/kcs_bmc_device.h b/drivers/char/ipmi/kcs_bmc_device.h
index 33462174516d..57b7174b2bac 100644
--- a/drivers/char/ipmi/kcs_bmc_device.h
+++ b/drivers/char/ipmi/kcs_bmc_device.h
@@ -7,13 +7,13 @@
#include "kcs_bmc.h"

struct kcs_bmc_device_ops {
- u8 (*io_inputb)(struct kcs_bmc *kcs_bmc, u32 reg);
- void (*io_outputb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 b);
- void (*io_updateb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 b);
+ 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);
};

-int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
-int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc);
-int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc);
+int kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc);
+int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc);
+int kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc);

#endif
diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
index 1d21697fc585..dce93ec895fc 100644
--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
@@ -65,7 +65,7 @@ struct npcm7xx_kcs_reg {
};

struct npcm7xx_kcs_bmc {
- struct kcs_bmc kcs_bmc;
+ struct kcs_bmc_device kcs_bmc;

struct regmap *map;

@@ -78,12 +78,12 @@ static const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = {
{ .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE },
};

-static inline struct npcm7xx_kcs_bmc *to_npcm7xx_kcs_bmc(struct kcs_bmc *kcs_bmc)
+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 *kcs_bmc, u32 reg)
+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;
@@ -95,7 +95,7 @@ static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
return rc == 0 ? (u8)val : 0;
}

-static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
+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;
@@ -104,7 +104,7 @@ static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
WARN(rc != 0, "regmap_write() failed: %d\n", rc);
}

-static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 data)
+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;
@@ -113,7 +113,7 @@ static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 da
WARN(rc != 0, "regmap_update_bits() failed: %d\n", rc);
}

-static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
+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);

@@ -126,7 +126,7 @@ static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)

static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
{
- struct kcs_bmc *kcs_bmc = arg;
+ struct kcs_bmc_device *kcs_bmc = arg;
int rc;

rc = kcs_bmc_handle_event(kcs_bmc);
@@ -136,7 +136,7 @@ static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
return rc == KCS_BMC_EVENT_HANDLED ? IRQ_HANDLED : IRQ_NONE;
}

-static int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc,
+static int npcm7xx_kcs_config_irq(struct kcs_bmc_device *kcs_bmc,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -160,7 +160,7 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct npcm7xx_kcs_bmc *priv;
- struct kcs_bmc *kcs_bmc;
+ struct kcs_bmc_device *kcs_bmc;
u32 chan;
int rc;

@@ -207,7 +207,7 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
static int npcm7xx_kcs_remove(struct platform_device *pdev)
{
struct npcm7xx_kcs_bmc *priv = platform_get_drvdata(pdev);
- struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
+ struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc;

kcs_bmc_remove_device(kcs_bmc);

--
2.27.0

2021-03-19 06:34:07

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 17/21] dt-bindings: ipmi: Convert ASPEED KCS binding to schema

Given the deprecated binding, improve the ability to detect issues in
the platform devicetrees. Further, a subsequent patch will introduce a
new interrupts property for specifying SerIRQ behaviour, so convert
before we do any further additions.

Signed-off-by: Andrew Jeffery <[email protected]>
---
.../bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml | 92 +++++++++++++++++++
.../bindings/ipmi/aspeed-kcs-bmc.txt | 33 -------
2 files changed, 92 insertions(+), 33 deletions(-)
create mode 100644 Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
delete mode 100644 Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt

diff --git a/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
new file mode 100644
index 000000000000..697ca575454f
--- /dev/null
+++ b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
@@ -0,0 +1,92 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/ipmi/aspeed,ast2400-kcs-bmc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ASPEED BMC KCS Devices
+
+maintainers:
+ - Andrew Jeffery <[email protected]>
+
+description: |
+ The Aspeed BMC SoCs typically use the Keyboard-Controller-Style (KCS)
+ interfaces on the LPC bus for in-band IPMI communication with their host.
+
+properties:
+ compatible:
+ oneOf:
+ - description: Channel ID derived from reg
+ items:
+ enum:
+ - aspeed,ast2400-kcs-bmc-v2
+ - aspeed,ast2500-kcs-bmc-v2
+ - aspeed,ast2600-kcs-bmc
+
+ - description: Old-style with explicit channel ID, no reg
+ deprecated: true
+ items:
+ enum:
+ - aspeed,ast2400-kcs-bmc
+ - aspeed,ast2500-kcs-bmc
+
+ interrupts:
+ maxItems: 1
+
+ reg:
+ # maxItems: 3
+ items:
+ - description: IDR register
+ - description: ODR register
+ - description: STR register
+
+ aspeed,lpc-io-reg:
+ $ref: '/schemas/types.yaml#/definitions/uint32-array'
+ minItems: 1
+ maxItems: 2
+ description: |
+ The host CPU LPC IO data and status addresses for the device. For most
+ channels the status address is derived from the data address, but the
+ status address may be optionally provided.
+
+ kcs_chan:
+ deprecated: true
+ $ref: '/schemas/types.yaml#/definitions/uint32'
+ description: The LPC channel number in the controller
+
+ kcs_addr:
+ deprecated: true
+ $ref: '/schemas/types.yaml#/definitions/uint32'
+ description: The host CPU IO map address
+
+required:
+ - compatible
+ - interrupts
+
+additionalProperties: false
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - aspeed,ast2400-kcs-bmc
+ - aspeed,ast2500-kcs-bmc
+ then:
+ required:
+ - kcs_chan
+ - kcs_addr
+ else:
+ required:
+ - reg
+ - aspeed,lpc-io-reg
+
+examples:
+ - |
+ kcs3: kcs@24 {
+ compatible = "aspeed,ast2600-kcs-bmc";
+ reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>;
+ aspeed,lpc-io-reg = <0xca2>;
+ interrupts = <8>;
+ };
diff --git a/Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt b/Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt
deleted file mode 100644
index 193e71ca96b0..000000000000
--- a/Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-# Aspeed KCS (Keyboard Controller Style) IPMI interface
-
-The Aspeed SOCs (AST2400 and AST2500) are commonly used as BMCs
-(Baseboard Management Controllers) and the KCS interface can be
-used to perform in-band IPMI communication with their host.
-
-## v1
-Required properties:
-- compatible : should be one of
- "aspeed,ast2400-kcs-bmc"
- "aspeed,ast2500-kcs-bmc"
-- interrupts : interrupt generated by the controller
-- kcs_chan : The LPC channel number in the controller
-- kcs_addr : The host CPU IO map address
-
-## v2
-Required properties:
-- compatible : should be one of
- "aspeed,ast2400-kcs-bmc-v2"
- "aspeed,ast2500-kcs-bmc-v2"
-- reg : The address and size of the IDR, ODR and STR registers
-- interrupts : interrupt generated by the controller
-- aspeed,lpc-io-reg : The host CPU LPC IO address for the device
-
-Example:
-
- kcs3: kcs@24 {
- compatible = "aspeed,ast2500-kcs-bmc-v2";
- reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>;
- aspeed,lpc-reg = <0xca2>;
- interrupts = <8>;
- status = "okay";
- };
--
2.27.0

2021-03-19 06:34:20

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 20/21] ipmi: kcs_bmc_aspeed: Fix IBFIE typo from datasheet

Input Buffer Full Interrupt Enable (IBFIE) is typoed as IBFIF for some
registers in the datasheet. Fix the driver to use the sensible acronym.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/kcs_bmc_aspeed.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
index 3782aef4eb73..7334b1f51dcc 100644
--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
@@ -50,9 +50,9 @@
#define LPC_HICR0_LPC2E BIT(6)
#define LPC_HICR0_LPC1E BIT(5)
#define LPC_HICR2 0x008
-#define LPC_HICR2_IBFIF3 BIT(3)
-#define LPC_HICR2_IBFIF2 BIT(2)
-#define LPC_HICR2_IBFIF1 BIT(1)
+#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)
@@ -83,7 +83,7 @@
#define LPC_STR2 0x040
#define LPC_STR3 0x044
#define LPC_HICRB 0x100
-#define LPC_HICRB_IBFIF4 BIT(1)
+#define LPC_HICRB_IBFIE4 BIT(1)
#define LPC_HICRB_LPC4E BIT(0)
#define LPC_HICRC 0x104
#define LPC_HICRC_ID4IRQX_MASK GENMASK(7, 4)
@@ -383,20 +383,20 @@ static void aspeed_kcs_irq_mask_update(struct kcs_bmc_device *kcs_bmc, u8 mask,

switch (kcs_bmc->channel) {
case 1:
- regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF1,
- enable * LPC_HICR2_IBFIF1);
+ 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_IBFIF2,
- enable * LPC_HICR2_IBFIF2);
+ 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_IBFIF3,
- enable * LPC_HICR2_IBFIF3);
+ 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_IBFIF4,
- enable * LPC_HICRB_IBFIF4);
+ 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);
--
2.27.0

2021-03-19 06:34:22

by Andrew Jeffery

[permalink] [raw]
Subject: [PATCH v2 21/21] ipmi: kcs_bmc_aspeed: Optionally apply status address

Some Aspeed KCS devices can derive the status register address from the
address of the data register. As such, the address of the status
register can be implicit in the configuration if desired. On the other
hand, sometimes address schemes might be requested that are incompatible
with the default addressing scheme. Allow these requests where possible
if the devicetree specifies the status register address.

Signed-off-by: Andrew Jeffery <[email protected]>
---
drivers/char/ipmi/kcs_bmc_aspeed.c | 113 +++++++++++++++++++++--------
1 file changed, 81 insertions(+), 32 deletions(-)

diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
index 7334b1f51dcc..98789b837690 100644
--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
@@ -83,6 +83,8 @@
#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
@@ -96,6 +98,11 @@
#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)

@@ -123,7 +130,7 @@ struct aspeed_kcs_bmc {

struct aspeed_kcs_of_ops {
int (*get_channel)(struct platform_device *pdev);
- int (*get_io_address)(struct platform_device *pdev);
+ int (*get_io_address)(struct platform_device *pdev, u32 addrs[2]);
};

static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc_device *kcs_bmc)
@@ -217,38 +224,64 @@ static void aspeed_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask,
* C. KCS4
* D / C : CA4h / CA5h
*/
-static void aspeed_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u16 addr)
+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);

- switch (kcs_bmc->channel) {
+ 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, addr >> 8);
- regmap_write(priv->map, LPC_LADR12L, addr & 0xFF);
+ 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, addr >> 8);
- regmap_write(priv->map, LPC_LADR12L, addr & 0xFF);
+ 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:
- regmap_write(priv->map, LPC_LADR3H, addr >> 8);
- regmap_write(priv->map, LPC_LADR3L, addr & 0xFF);
+ 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:
- regmap_write(priv->map, LPC_LADR4, ((addr + 1) << 16) |
- addr);
+ 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:
- break;
+ return -EINVAL;
}
+
+ return 0;
}

static inline int aspeed_kcs_map_serirq_type(u32 dt_type)
@@ -462,18 +495,18 @@ static int aspeed_kcs_of_v1_get_channel(struct platform_device *pdev)
return channel;
}

-static int aspeed_kcs_of_v1_get_io_address(struct platform_device *pdev)
+static int
+aspeed_kcs_of_v1_get_io_address(struct platform_device *pdev, u32 addrs[2])
{
- u32 slave;
int rc;

- rc = of_property_read_u32(pdev->dev.of_node, "kcs_addr", &slave);
- if (rc || slave > 0xffff) {
+ rc = of_property_read_u32(pdev->dev.of_node, "kcs_addr", addrs);
+ if (rc || addrs[0] > 0xffff) {
dev_err(&pdev->dev, "no valid 'kcs_addr' configured\n");
return -EINVAL;
}

- return slave;
+ return 1;
}

static int aspeed_kcs_of_v2_get_channel(struct platform_device *pdev)
@@ -509,16 +542,27 @@ static int aspeed_kcs_of_v2_get_channel(struct platform_device *pdev)
return -EINVAL;
}

-static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
+static int
+aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev, u32 addrs[2])
{
- uint32_t slave;
int rc;

- rc = of_property_read_u32(pdev->dev.of_node, "aspeed,lpc-io-reg", &slave);
- if (rc || slave > 0xffff)
+ rc = of_property_read_variable_u32_array(pdev->dev.of_node,
+ "aspeed,lpc-io-reg",
+ addrs, 1, 2);
+ if (rc < 0)
+ return rc;
+
+ if (WARN_ON(rc == 0))
+ return -EINVAL;
+
+ if (addrs[0] > 0xffff)
+ return -EINVAL;
+
+ if (rc == 2 && addrs[1] > 0xffff)
return -EINVAL;

- return slave;
+ return rc;
}

static int aspeed_kcs_probe(struct platform_device *pdev)
@@ -527,9 +571,11 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
struct kcs_bmc_device *kcs_bmc;
struct aspeed_kcs_bmc *priv;
struct device_node *np;
- int rc, channel, addr;
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") &&
@@ -547,9 +593,9 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
if (channel < 0)
return channel;

- addr = ops->get_io_address(pdev);
- if (addr < 0)
- return addr;
+ nr_addrs = ops->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);
@@ -578,7 +624,9 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
priv->obe.remove = false;
timer_setup(&priv->obe.timer, aspeed_kcs_check_obe, 0);

- aspeed_kcs_set_address(kcs_bmc, addr);
+ 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);
@@ -600,7 +648,8 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
if (rc < 0)
return rc;

- dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n", kcs_bmc->channel, addr);
+ dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n",
+ kcs_bmc->channel, addrs[0]);

return 0;
}
--
2.27.0

2021-03-26 01:52:21

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH v2 17/21] dt-bindings: ipmi: Convert ASPEED KCS binding to schema

On Fri, 19 Mar 2021 16:57:48 +1030, Andrew Jeffery wrote:
> Given the deprecated binding, improve the ability to detect issues in
> the platform devicetrees. Further, a subsequent patch will introduce a
> new interrupts property for specifying SerIRQ behaviour, so convert
> before we do any further additions.
>
> Signed-off-by: Andrew Jeffery <[email protected]>
> ---
> .../bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml | 92 +++++++++++++++++++
> .../bindings/ipmi/aspeed-kcs-bmc.txt | 33 -------
> 2 files changed, 92 insertions(+), 33 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
> delete mode 100644 Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt
>

Reviewed-by: Rob Herring <[email protected]>

2021-03-26 01:53:21

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH v2 18/21] dt-bindings: ipmi: Add optional SerIRQ property to ASPEED KCS devices

On Fri, 19 Mar 2021 16:57:49 +1030, Andrew Jeffery wrote:
> Allocating IO and IRQ resources to LPC devices is in-theory an operation
> for the host, however ASPEED don't appear to expose this capability
> outside the BMC (e.g. SuperIO). Instead, we are left with BMC-internal
> registers for managing these resources, so introduce a devicetree
> property for KCS devices to describe SerIRQ properties.
>
> Signed-off-by: Andrew Jeffery <[email protected]>
> ---
> .../bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
>

Reviewed-by: Rob Herring <[email protected]>

2021-04-01 10:32:56

by Zev Weiss

[permalink] [raw]
Subject: Re: [EXTERNAL] [PATCH v2 19/21] ipmi: kcs_bmc_aspeed: Implement KCS SerIRQ configuration

On Fri, Mar 19, 2021 at 01:27:50AM CDT, Andrew Jeffery wrote:
>Apply the SerIRQ ID and level/sense behaviours from the devicetree if
>provided.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> drivers/char/ipmi/kcs_bmc_aspeed.c | 179 ++++++++++++++++++++++++++++-
> 1 file changed, 177 insertions(+), 2 deletions(-)
>
>diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
>index 271845eb2e26..3782aef4eb73 100644
>--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
>+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
>@@ -9,6 +9,7 @@
> #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>
>@@ -28,6 +29,22 @@
>
> #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)
>@@ -39,6 +56,19 @@
> #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
>@@ -55,6 +85,13 @@
> #define LPC_HICRB 0x100
> #define LPC_HICRB_IBFIF4 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
>@@ -62,11 +99,21 @@
>
> #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;
>@@ -103,6 +150,49 @@ static void aspeed_kcs_outb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 data)
>
> 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)
>@@ -161,6 +251,73 @@ static void aspeed_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u16 addr)
> }
> }
>
>+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;
>+
>+ if (id > 15)
>+ return -EINVAL;
>+
>+ hw_type = aspeed_kcs_map_serirq_type(dt_type);
>+ if (hw_type < 0)
>+ return hw_type;
>+
>+ 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);
>@@ -267,7 +424,7 @@ static irqreturn_t aspeed_kcs_irq(int irq, void *arg)
> return rc == KCS_BMC_EVENT_HANDLED ? IRQ_HANDLED : IRQ_NONE;
> }
>
>-static int aspeed_kcs_config_irq(struct kcs_bmc_device *kcs_bmc,
>+static int aspeed_kcs_config_downstream_irq(struct kcs_bmc_device *kcs_bmc,
> struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
>@@ -371,6 +528,8 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> struct aspeed_kcs_bmc *priv;
> struct device_node *np;
> int rc, channel, addr;
>+ bool have_upstream_irq;
>+ u32 upstream_irq[2];
>
> np = pdev->dev.of_node->parent;
> if (!of_device_is_compatible(np, "aspeed,ast2400-lpc-v2") &&
>@@ -379,6 +538,7 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> dev_err(&pdev->dev, "unsupported LPC device binding\n");
> return -ENODEV;
> }
>+
> ops = of_device_get_match_data(&pdev->dev);
> if (!ops)
> return -EINVAL;
>@@ -391,6 +551,13 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> if (addr < 0)
> return addr;
>
>+ 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;
>@@ -413,10 +580,17 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
>
> aspeed_kcs_set_address(kcs_bmc, addr);
>
>- rc = aspeed_kcs_config_irq(kcs_bmc, pdev);
>+ /* 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)
>+ aspeed_kcs_config_upstream_irq(priv, upstream_irq[0], upstream_irq[1]);

This has a return value that could indicate an error if the DT
properties are misconfigured; do we want to check for that here?

>+ 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);
>@@ -481,4 +655,5 @@ module_platform_driver(ast_kcs_bmc_driver);
>
> MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Haiyue Wang <[email protected]>");
>+MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
> MODULE_DESCRIPTION("Aspeed device interface to the KCS BMC device");
>--
>2.27.0
>

2021-04-01 18:44:21

by Zev Weiss

[permalink] [raw]
Subject: Re [PATCH v2 21/21] ipmi: kcs_bmc_aspeed: Optionally apply status address

On Fri, Mar 19, 2021 at 01:27:52AM CDT, Andrew Jeffery wrote:
>Some Aspeed KCS devices can derive the status register address from the
>address of the data register. As such, the address of the status
>register can be implicit in the configuration if desired. On the other
>hand, sometimes address schemes might be requested that are incompatible
>with the default addressing scheme. Allow these requests where possible
>if the devicetree specifies the status register address.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> drivers/char/ipmi/kcs_bmc_aspeed.c | 113 +++++++++++++++++++++--------
> 1 file changed, 81 insertions(+), 32 deletions(-)
>
>diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
>index 7334b1f51dcc..98789b837690 100644
>--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
>+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
>@@ -83,6 +83,8 @@
> #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
>@@ -96,6 +98,11 @@
> #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)
>
>@@ -123,7 +130,7 @@ struct aspeed_kcs_bmc {
>
> struct aspeed_kcs_of_ops {
> int (*get_channel)(struct platform_device *pdev);
>- int (*get_io_address)(struct platform_device *pdev);
>+ int (*get_io_address)(struct platform_device *pdev, u32 addrs[2]);
> };
>
> static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc_device *kcs_bmc)
>@@ -217,38 +224,64 @@ static void aspeed_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask,
> * C. KCS4
> * D / C : CA4h / CA5h
> */
>-static void aspeed_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u16 addr)
>+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);
>
>- switch (kcs_bmc->channel) {
>+ 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, addr >> 8);
>- regmap_write(priv->map, LPC_LADR12L, addr & 0xFF);
>+ 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, addr >> 8);
>- regmap_write(priv->map, LPC_LADR12L, addr & 0xFF);
>+ 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:
>- regmap_write(priv->map, LPC_LADR3H, addr >> 8);
>- regmap_write(priv->map, LPC_LADR3L, addr & 0xFF);
>+ 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:
>- regmap_write(priv->map, LPC_LADR4, ((addr + 1) << 16) |
>- addr);
>+ 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:
>- break;
>+ return -EINVAL;
> }
>+
>+ return 0;
> }
>
> static inline int aspeed_kcs_map_serirq_type(u32 dt_type)
>@@ -462,18 +495,18 @@ static int aspeed_kcs_of_v1_get_channel(struct platform_device *pdev)
> return channel;
> }
>
>-static int aspeed_kcs_of_v1_get_io_address(struct platform_device *pdev)
>+static int
>+aspeed_kcs_of_v1_get_io_address(struct platform_device *pdev, u32 addrs[2])
> {
>- u32 slave;
> int rc;
>
>- rc = of_property_read_u32(pdev->dev.of_node, "kcs_addr", &slave);
>- if (rc || slave > 0xffff) {
>+ rc = of_property_read_u32(pdev->dev.of_node, "kcs_addr", addrs);
>+ if (rc || addrs[0] > 0xffff) {
> dev_err(&pdev->dev, "no valid 'kcs_addr' configured\n");
> return -EINVAL;
> }
>
>- return slave;
>+ return 1;
> }
>
> static int aspeed_kcs_of_v2_get_channel(struct platform_device *pdev)
>@@ -509,16 +542,27 @@ static int aspeed_kcs_of_v2_get_channel(struct platform_device *pdev)
> return -EINVAL;
> }
>
>-static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
>+static int
>+aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev, u32 addrs[2])
> {
>- uint32_t slave;
> int rc;
>
>- rc = of_property_read_u32(pdev->dev.of_node, "aspeed,lpc-io-reg", &slave);
>- if (rc || slave > 0xffff)
>+ rc = of_property_read_variable_u32_array(pdev->dev.of_node,
>+ "aspeed,lpc-io-reg",
>+ addrs, 1, 2);
>+ if (rc < 0)
>+ return rc;
>+
>+ if (WARN_ON(rc == 0))
>+ return -EINVAL;

Is this check necessary? It looks like
of_property_read_variable_u32_array() should fail in that case given
sz_min==1, so this seems like it should be impossible to trigger.

>+
>+ if (addrs[0] > 0xffff)
>+ return -EINVAL;
>+
>+ if (rc == 2 && addrs[1] > 0xffff)
> return -EINVAL;
>
>- return slave;
>+ return rc;
> }
>
> static int aspeed_kcs_probe(struct platform_device *pdev)
>@@ -527,9 +571,11 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> struct kcs_bmc_device *kcs_bmc;
> struct aspeed_kcs_bmc *priv;
> struct device_node *np;
>- int rc, channel, addr;
> 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") &&
>@@ -547,9 +593,9 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> if (channel < 0)
> return channel;
>
>- addr = ops->get_io_address(pdev);
>- if (addr < 0)
>- return addr;
>+ nr_addrs = ops->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);
>@@ -578,7 +624,9 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> priv->obe.remove = false;
> timer_setup(&priv->obe.timer, aspeed_kcs_check_obe, 0);
>
>- aspeed_kcs_set_address(kcs_bmc, addr);
>+ 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);
>@@ -600,7 +648,8 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> if (rc < 0)
> return rc;
>
>- dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n", kcs_bmc->channel, addr);
>+ dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n",
>+ kcs_bmc->channel, addrs[0]);
>
> return 0;
> }
>--
>2.27.0
>

2021-04-06 15:35:45

by ChiaWei Wang

[permalink] [raw]
Subject: RE: [PATCH v2 13/21] ipmi: kcs_bmc: Decouple the IPMI chardev from the core

I have tried this patch on Intel EGS CRB with AST2600 A1 as the BMC.
Chiawei

Tested-by: Chia-Wei Wang <[email protected]>

> -----Original Message-----
> From: Andrew Jeffery <[email protected]>
> Sent: Friday, March 19, 2021 2:28 PM
> To: [email protected]; [email protected];
> [email protected]
> Subject: [PATCH v2 13/21] ipmi: kcs_bmc: Decouple the IPMI chardev from the
> core
>
> Now that we have untangled the data-structures, split the userspace interface
> out into its own module. Userspace interfaces and drivers are registered to the
> KCS BMC core to support arbitrary binding of either.
>
> Signed-off-by: Andrew Jeffery <[email protected]>
> ---
> drivers/char/ipmi/Kconfig | 13 +++++
> drivers/char/ipmi/Makefile | 3 +-
> drivers/char/ipmi/kcs_bmc.c | 78
> ++++++++++++++++++++++++++-
> drivers/char/ipmi/kcs_bmc.h | 4 --
> drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 33 +++++++++---
> drivers/char/ipmi/kcs_bmc_client.h | 14 +++++
> 6 files changed, 132 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index
> 07847d9a459a..bc5f81899b62 100644
> --- a/drivers/char/ipmi/Kconfig
> +++ b/drivers/char/ipmi/Kconfig
> @@ -124,6 +124,19 @@ config NPCM7XX_KCS_IPMI_BMC
> 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 ASPEED_BT_IPMI_BMC
> depends on ARCH_ASPEED || COMPILE_TEST
> depends on REGMAP && REGMAP_MMIO && MFD_SYSCON diff --git
> a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile index
> a302bc865370..fcfa676afddb 100644
> --- a/drivers/char/ipmi/Makefile
> +++ b/drivers/char/ipmi/Makefile
> @@ -22,7 +22,8 @@ obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o
> obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o
> obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
> obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
> -obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o kcs_bmc_cdev_ipmi.o
> +obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.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 diff --git
> a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c index
> 266ebec71d6f..694db6ee2a92 100644
> --- a/drivers/char/ipmi/kcs_bmc.c
> +++ b/drivers/char/ipmi/kcs_bmc.c
> @@ -5,7 +5,9 @@
> */
>
> #include <linux/device.h>
> +#include <linux/list.h>
> #include <linux/module.h>
> +#include <linux/mutex.h>
>
> #include "kcs_bmc.h"
>
> @@ -13,6 +15,11 @@
> #include "kcs_bmc_device.h"
> #include "kcs_bmc_client.h"
>
> +/* Record probed devices and cdevs */
> +static DEFINE_MUTEX(kcs_bmc_lock);
> +static LIST_HEAD(kcs_bmc_devices);
> +static LIST_HEAD(kcs_bmc_cdevs);
> +
> /* Consumer data access */
>
> u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc) @@ -100,16
> +107,83 @@ EXPORT_SYMBOL(kcs_bmc_disable_device);
>
> int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc) {
> - return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
> + struct kcs_bmc_cdev *cdev;
> + 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(cdev, &kcs_bmc_cdevs, entry) {
> + rc = cdev->ops->add_device(kcs_bmc);
> + if (rc)
> + dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel
> %d: %d",
> + kcs_bmc->channel, rc);
> + }
> + mutex_unlock(&kcs_bmc_lock);
> +
> + return 0;
> }
> EXPORT_SYMBOL(kcs_bmc_add_device);
>
> int kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc) {
> - return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
> + struct kcs_bmc_cdev *cdev;
> + int rc;
> +
> + mutex_lock(&kcs_bmc_lock);
> + list_del(&kcs_bmc->entry);
> + list_for_each_entry(cdev, &kcs_bmc_cdevs, entry) {
> + rc = cdev->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);
> +
> + return 0;
> }
> EXPORT_SYMBOL(kcs_bmc_remove_device);
>
> +int kcs_bmc_register_cdev(struct kcs_bmc_cdev *cdev) {
> + struct kcs_bmc_device *kcs_bmc;
> + int rc;
> +
> + mutex_lock(&kcs_bmc_lock);
> + list_add(&cdev->entry, &kcs_bmc_cdevs);
> + list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
> + rc = cdev->ops->add_device(kcs_bmc);
> + if (rc)
> + dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel
> %d: %d",
> + kcs_bmc->channel, rc);
> + }
> + mutex_unlock(&kcs_bmc_lock);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL(kcs_bmc_register_cdev);
> +
> +int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev) {
> + struct kcs_bmc_device *kcs_bmc;
> + int rc;
> +
> + mutex_lock(&kcs_bmc_lock);
> + list_del(&cdev->entry);
> + list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
> + rc = cdev->ops->remove_device(kcs_bmc);
> + if (rc)
> + dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel
> %d: %d",
> + kcs_bmc->channel, rc);
> + }
> + mutex_unlock(&kcs_bmc_lock);
> +
> + return rc;
> +}
> +EXPORT_SYMBOL(kcs_bmc_unregister_cdev);
> +
> MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Haiyue Wang <[email protected]>");
> MODULE_AUTHOR("Andrew Jeffery <[email protected]>"); diff --git
> a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h index
> 3f266740c759..5deb9a0b8e60 100644
> --- a/drivers/char/ipmi/kcs_bmc.h
> +++ b/drivers/char/ipmi/kcs_bmc.h
> @@ -42,8 +42,4 @@ struct kcs_bmc_device {
> spinlock_t lock;
> struct kcs_bmc_client *client;
> };
> -
> -/* Temporary exports while refactoring */ -int
> kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc); -int
> kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc); #endif /*
> __KCS_BMC_H__ */ diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
> b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
> index 58c42e76483d..df83d67851ac 100644
> --- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
> +++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
> @@ -469,8 +469,7 @@ static const struct file_operations kcs_bmc_ipmi_fops
> = { static DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock);
> static LIST_HEAD(kcs_bmc_ipmi_instances);
>
> -int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc); -int
> kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc)
> +static int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc)
> {
> struct kcs_bmc_ipmi *priv;
> int rc;
> @@ -512,10 +511,8 @@ int kcs_bmc_ipmi_attach_cdev(struct
> kcs_bmc_device *kcs_bmc)
>
> return 0;
> }
> -EXPORT_SYMBOL(kcs_bmc_ipmi_attach_cdev);
>
> -int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc); -int
> kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc)
> +static int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc)
> {
> struct kcs_bmc_ipmi *priv = NULL, *pos;
>
> @@ -541,7 +538,31 @@ int kcs_bmc_ipmi_detach_cdev(struct
> kcs_bmc_device *kcs_bmc)
>
> return 0;
> }
> -EXPORT_SYMBOL(kcs_bmc_ipmi_detach_cdev);
> +
> +static const struct kcs_bmc_cdev_ops kcs_bmc_ipmi_cdev_ops = {
> + .add_device = kcs_bmc_ipmi_attach_cdev,
> + .remove_device = kcs_bmc_ipmi_detach_cdev, };
> +
> +static struct kcs_bmc_cdev kcs_bmc_ipmi_cdev = {
> + .ops = &kcs_bmc_ipmi_cdev_ops,
> +};
> +
> +static int kcs_bmc_ipmi_init(void)
> +{
> + return kcs_bmc_register_cdev(&kcs_bmc_ipmi_cdev);
> +}
> +module_init(kcs_bmc_ipmi_init);
> +
> +static void kcs_bmc_ipmi_exit(void)
> +{
> + int rc;
> +
> + rc = kcs_bmc_unregister_cdev(&kcs_bmc_ipmi_cdev);
> + if (rc)
> + pr_warn("Failed to remove KCS BMC client: %d", rc); }
> +module_exit(kcs_bmc_ipmi_exit);
>
> MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Haiyue Wang <[email protected]>"); diff
> --git a/drivers/char/ipmi/kcs_bmc_client.h
> b/drivers/char/ipmi/kcs_bmc_client.h
> index 2dd710f4b4aa..d0a7404ff584 100644
> --- a/drivers/char/ipmi/kcs_bmc_client.h
> +++ b/drivers/char/ipmi/kcs_bmc_client.h
> @@ -10,6 +10,17 @@
>
> #include "kcs_bmc.h"
>
> +struct kcs_bmc_cdev_ops {
> + int (*add_device)(struct kcs_bmc_device *kcs_bmc);
> + int (*remove_device)(struct kcs_bmc_device *kcs_bmc); };
> +
> +struct kcs_bmc_cdev {
> + struct list_head entry;
> +
> + const struct kcs_bmc_cdev_ops *ops;
> +};
> +
> struct kcs_bmc_client_ops {
> int (*event)(struct kcs_bmc_client *client); }; @@ -20,6 +31,9 @@
> struct kcs_bmc_client {
> struct kcs_bmc_device *dev;
> };
>
> +int kcs_bmc_register_cdev(struct kcs_bmc_cdev *cdev); int
> +kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev);
> +
> 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);
>
> --
> 2.27.0

2021-04-06 15:36:32

by ChiaWei Wang

[permalink] [raw]
Subject: RE: [PATCH v2 06/21] ipmi: kcs_bmc_aspeed: Use of match data to extract KCS properties

I have tried this patch on Intel EGS CRB with AST2600 A1 as the BMC.
Chiawei

Tested-by: Chia-Wei Wang <[email protected]>

> -----Original Message-----
> From: Andrew Jeffery <[email protected]>
> Sent: Friday, March 19, 2021 2:28 PM
> To: [email protected]; [email protected];
> [email protected]
>
> Unpack and remove the aspeed_kcs_probe_of_v[12]() functions to aid
> rearranging how the private device-driver memory is allocated.
>
> Signed-off-by: Andrew Jeffery <[email protected]>
> ---
> drivers/char/ipmi/kcs_bmc_aspeed.c | 146 ++++++++++++++---------------
> 1 file changed, 68 insertions(+), 78 deletions(-)
>
> diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c
> b/drivers/char/ipmi/kcs_bmc_aspeed.c
> index eefe362f65f0..061f53676206 100644
> --- a/drivers/char/ipmi/kcs_bmc_aspeed.c
> +++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
> @@ -13,6 +13,7 @@
> #include <linux/module.h>
> #include <linux/of.h>
> #include <linux/of_address.h>
> +#include <linux/of_device.h>
> #include <linux/platform_device.h>
> #include <linux/poll.h>
> #include <linux/regmap.h>
> @@ -63,6 +64,10 @@ struct aspeed_kcs_bmc {
> struct regmap *map;
> };
>
> +struct aspeed_kcs_of_ops {
> + int (*get_channel)(struct platform_device *pdev);
> + int (*get_io_address)(struct platform_device *pdev); };
>
> static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg) { @@ -231,13
> +236,10 @@ static const struct kcs_ioreg
> ast_kcs_bmc_ioregs[KCS_CHANNEL_MAX] = {
> { .idr = LPC_IDR4, .odr = LPC_ODR4, .str = LPC_STR4 }, };
>
> -static struct kcs_bmc *aspeed_kcs_probe_of_v1(struct platform_device
> *pdev)
> +static int aspeed_kcs_of_v1_get_channel(struct platform_device *pdev)
> {
> - struct aspeed_kcs_bmc *priv;
> struct device_node *np;
> - struct kcs_bmc *kcs;
> u32 channel;
> - u32 slave;
> int rc;
>
> np = pdev->dev.of_node;
> @@ -245,105 +247,78 @@ static struct kcs_bmc
> *aspeed_kcs_probe_of_v1(struct platform_device *pdev)
> rc = of_property_read_u32(np, "kcs_chan", &channel);
> if ((rc != 0) || (channel == 0 || channel > KCS_CHANNEL_MAX)) {
> dev_err(&pdev->dev, "no valid 'kcs_chan' configured\n");
> - return ERR_PTR(-EINVAL);
> + return -EINVAL;
> }
>
> - kcs = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc),
> channel);
> - if (!kcs)
> - return ERR_PTR(-ENOMEM);
> + return channel;
> +}
>
> - priv = kcs_bmc_priv(kcs);
> - 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 ERR_PTR(-ENODEV);
> - }
> +static int aspeed_kcs_of_v1_get_io_address(struct platform_device
> +*pdev) {
> + u32 slave;
> + int rc;
>
> - rc = of_property_read_u32(np, "kcs_addr", &slave);
> - if (rc) {
> + rc = of_property_read_u32(pdev->dev.of_node, "kcs_addr", &slave);
> + if (rc || slave > 0xffff) {
> dev_err(&pdev->dev, "no valid 'kcs_addr' configured\n");
> - return ERR_PTR(-EINVAL);
> + return -EINVAL;
> }
>
> - kcs->ioreg = ast_kcs_bmc_ioregs[channel - 1];
> - aspeed_kcs_set_address(kcs, slave);
> -
> - return kcs;
> -}
> -
> -static int aspeed_kcs_calculate_channel(const struct kcs_ioreg *regs) -{
> - int i;
> -
> - for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) {
> - if (!memcmp(&ast_kcs_bmc_ioregs[i], regs, sizeof(*regs)))
> - return i + 1;
> - }
> -
> - return -EINVAL;
> + return slave;
> }
>
> -static struct kcs_bmc *aspeed_kcs_probe_of_v2(struct platform_device
> *pdev)
> +static int aspeed_kcs_of_v2_get_channel(struct platform_device *pdev)
> {
> - struct aspeed_kcs_bmc *priv;
> struct device_node *np;
> struct kcs_ioreg ioreg;
> - struct kcs_bmc *kcs;
> const __be32 *reg;
> - int channel;
> - u32 slave;
> - int rc;
> + 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 ERR_PTR(-EINVAL);
> + return -EINVAL;
> ioreg.idr = be32_to_cpup(reg);
>
> reg = of_get_address(np, 1, NULL, NULL);
> if (!reg)
> - return ERR_PTR(-EINVAL);
> + return -EINVAL;
> ioreg.odr = be32_to_cpup(reg);
>
> reg = of_get_address(np, 2, NULL, NULL);
> if (!reg)
> - return ERR_PTR(-EINVAL);
> + return -EINVAL;
> ioreg.str = be32_to_cpup(reg);
>
> - channel = aspeed_kcs_calculate_channel(&ioreg);
> - if (channel < 0)
> - return ERR_PTR(channel);
> -
> - kcs = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc),
> channel);
> - if (!kcs)
> - return ERR_PTR(-ENOMEM);
> -
> - kcs->ioreg = ioreg;
> -
> - priv = kcs_bmc_priv(kcs);
> - 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 ERR_PTR(-ENODEV);
> + for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) {
> + if (!memcmp(&ast_kcs_bmc_ioregs[i], &ioreg, sizeof(ioreg)))
> + return i + 1;
> }
>
> - rc = of_property_read_u32(np, "aspeed,lpc-io-reg", &slave);
> - if (rc)
> - return ERR_PTR(rc);
> + return -EINVAL;
> +}
>
> - aspeed_kcs_set_address(kcs, slave);
> +static int aspeed_kcs_of_v2_get_io_address(struct platform_device
> +*pdev) {
> + uint32_t slave;
> + int rc;
>
> - return kcs;
> + rc = of_property_read_u32(pdev->dev.of_node, "aspeed,lpc-io-reg",
> &slave);
> + if (rc || slave > 0xffff)
> + return -EINVAL;
> +
> + return slave;
> }
>
> static int aspeed_kcs_probe(struct platform_device *pdev) {
> + const struct aspeed_kcs_of_ops *ops;
> struct device *dev = &pdev->dev;
> struct kcs_bmc *kcs_bmc;
> struct device_node *np;
> - int rc;
> + int rc, channel, addr;
>
> np = dev->of_node->parent;
> if (!of_device_is_compatible(np, "aspeed,ast2400-lpc-v2") && @@
> -352,23 +327,28 @@ static int aspeed_kcs_probe(struct platform_device
> *pdev)
> dev_err(dev, "unsupported LPC device binding\n");
> return -ENODEV;
> }
> -
> - np = dev->of_node;
> - if (of_device_is_compatible(np, "aspeed,ast2400-kcs-bmc") ||
> - of_device_is_compatible(np, "aspeed,ast2500-kcs-bmc"))
> - kcs_bmc = aspeed_kcs_probe_of_v1(pdev);
> - else if (of_device_is_compatible(np, "aspeed,ast2400-kcs-bmc-v2") ||
> - of_device_is_compatible(np, "aspeed,ast2500-kcs-bmc-v2"))
> - kcs_bmc = aspeed_kcs_probe_of_v2(pdev);
> - else
> + ops = of_device_get_match_data(&pdev->dev);
> + if (!ops)
> return -EINVAL;
>
> - if (IS_ERR(kcs_bmc))
> - return PTR_ERR(kcs_bmc);
> + channel = ops->get_channel(pdev);
> + if (channel < 0)
> + return channel;
>
> + kcs_bmc = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc),
> channel);
> + if (!kcs_bmc)
> + return -ENOMEM;
> +
> + kcs_bmc->ioreg = ast_kcs_bmc_ioregs[channel - 1];
> kcs_bmc->io_inputb = aspeed_kcs_inb;
> kcs_bmc->io_outputb = aspeed_kcs_outb;
>
> + addr = ops->get_io_address(pdev);
> + if (addr < 0)
> + return addr;
> +
> + aspeed_kcs_set_address(kcs_bmc, addr);
> +
> rc = aspeed_kcs_config_irq(kcs_bmc, pdev);
> if (rc)
> return rc;
> @@ -400,11 +380,21 @@ static int aspeed_kcs_remove(struct
> platform_device *pdev)
> return 0;
> }
>
> +static const struct aspeed_kcs_of_ops of_v1_ops = {
> + .get_channel = aspeed_kcs_of_v1_get_channel,
> + .get_io_address = aspeed_kcs_of_v1_get_io_address, };
> +
> +static const struct aspeed_kcs_of_ops of_v2_ops = {
> + .get_channel = aspeed_kcs_of_v2_get_channel,
> + .get_io_address = aspeed_kcs_of_v2_get_io_address, };
> +
> static const struct of_device_id ast_kcs_bmc_match[] = {
> - { .compatible = "aspeed,ast2400-kcs-bmc" },
> - { .compatible = "aspeed,ast2500-kcs-bmc" },
> - { .compatible = "aspeed,ast2400-kcs-bmc-v2" },
> - { .compatible = "aspeed,ast2500-kcs-bmc-v2" },
> + { .compatible = "aspeed,ast2400-kcs-bmc", .data = &of_v1_ops },
> + { .compatible = "aspeed,ast2500-kcs-bmc", .data = &of_v1_ops },
> + { .compatible = "aspeed,ast2400-kcs-bmc-v2", .data = &of_v2_ops },
> + { .compatible = "aspeed,ast2500-kcs-bmc-v2", .data = &of_v2_ops },
> { }
> };
> MODULE_DEVICE_TABLE(of, ast_kcs_bmc_match);
> --
> 2.27.0

2021-04-06 15:36:33

by ChiaWei Wang

[permalink] [raw]
Subject: RE: [PATCH v2 21/21] ipmi: kcs_bmc_aspeed: Optionally apply status address

Reviewed-by: Chia-Wei Wang <[email protected]>

> -----Original Message-----
> From: Andrew Jeffery <[email protected]>
> Sent: Friday, March 19, 2021 2:28 PM
> To: [email protected]; [email protected];
> [email protected]
> Subject: [PATCH v2 21/21] ipmi: kcs_bmc_aspeed: Optionally apply status
> address
>
> Some Aspeed KCS devices can derive the status register address from the
> address of the data register. As such, the address of the status register can be
> implicit in the configuration if desired. On the other hand, sometimes address
> schemes might be requested that are incompatible with the default addressing
> scheme. Allow these requests where possible if the devicetree specifies the
> status register address.
>
> Signed-off-by: Andrew Jeffery <[email protected]>
> ---
> drivers/char/ipmi/kcs_bmc_aspeed.c | 113 +++++++++++++++++++++--------
> 1 file changed, 81 insertions(+), 32 deletions(-)
>
> diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c
> b/drivers/char/ipmi/kcs_bmc_aspeed.c
> index 7334b1f51dcc..98789b837690 100644
> --- a/drivers/char/ipmi/kcs_bmc_aspeed.c
> +++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
> @@ -83,6 +83,8 @@
> #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
> @@ -96,6 +98,11 @@
> #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)
>
> @@ -123,7 +130,7 @@ struct aspeed_kcs_bmc {
>
> struct aspeed_kcs_of_ops {
> int (*get_channel)(struct platform_device *pdev);
> - int (*get_io_address)(struct platform_device *pdev);
> + int (*get_io_address)(struct platform_device *pdev, u32 addrs[2]);
> };
>
> static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct
> kcs_bmc_device *kcs_bmc) @@ -217,38 +224,64 @@ static void
> aspeed_kcs_updateb(struct kcs_bmc_device *kcs_bmc, u32 reg, u8 mask,
> * C. KCS4
> * D / C : CA4h / CA5h
> */
> -static void aspeed_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u16
> addr)
> +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);
>
> - switch (kcs_bmc->channel) {
> + 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, addr >> 8);
> - regmap_write(priv->map, LPC_LADR12L, addr & 0xFF);
> + 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, addr >> 8);
> - regmap_write(priv->map, LPC_LADR12L, addr & 0xFF);
> + 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:
> - regmap_write(priv->map, LPC_LADR3H, addr >> 8);
> - regmap_write(priv->map, LPC_LADR3L, addr & 0xFF);
> + 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:
> - regmap_write(priv->map, LPC_LADR4, ((addr + 1) << 16) |
> - addr);
> + 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:
> - break;
> + return -EINVAL;
> }
> +
> + return 0;
> }
>
> static inline int aspeed_kcs_map_serirq_type(u32 dt_type) @@ -462,18
> +495,18 @@ static int aspeed_kcs_of_v1_get_channel(struct platform_device
> *pdev)
> return channel;
> }
>
> -static int aspeed_kcs_of_v1_get_io_address(struct platform_device *pdev)
> +static int
> +aspeed_kcs_of_v1_get_io_address(struct platform_device *pdev, u32
> +addrs[2])
> {
> - u32 slave;
> int rc;
>
> - rc = of_property_read_u32(pdev->dev.of_node, "kcs_addr", &slave);
> - if (rc || slave > 0xffff) {
> + rc = of_property_read_u32(pdev->dev.of_node, "kcs_addr", addrs);
> + if (rc || addrs[0] > 0xffff) {
> dev_err(&pdev->dev, "no valid 'kcs_addr' configured\n");
> return -EINVAL;
> }
>
> - return slave;
> + return 1;
> }
>
> static int aspeed_kcs_of_v2_get_channel(struct platform_device *pdev) @@
> -509,16 +542,27 @@ static int aspeed_kcs_of_v2_get_channel(struct
> platform_device *pdev)
> return -EINVAL;
> }
>
> -static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
> +static int
> +aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev, u32
> +addrs[2])
> {
> - uint32_t slave;
> int rc;
>
> - rc = of_property_read_u32(pdev->dev.of_node, "aspeed,lpc-io-reg",
> &slave);
> - if (rc || slave > 0xffff)
> + rc = of_property_read_variable_u32_array(pdev->dev.of_node,
> + "aspeed,lpc-io-reg",
> + addrs, 1, 2);
> + if (rc < 0)
> + return rc;
> +
> + if (WARN_ON(rc == 0))
> + return -EINVAL;
> +
> + if (addrs[0] > 0xffff)
> + return -EINVAL;
> +
> + if (rc == 2 && addrs[1] > 0xffff)
> return -EINVAL;
>
> - return slave;
> + return rc;
> }
>
> static int aspeed_kcs_probe(struct platform_device *pdev) @@ -527,9
> +571,11 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> struct kcs_bmc_device *kcs_bmc;
> struct aspeed_kcs_bmc *priv;
> struct device_node *np;
> - int rc, channel, addr;
> 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") && @@ -547,9
> +593,9 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> if (channel < 0)
> return channel;
>
> - addr = ops->get_io_address(pdev);
> - if (addr < 0)
> - return addr;
> + nr_addrs = ops->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); @@ -578,7 +624,9 @@ static int aspeed_kcs_probe(struct
> platform_device *pdev)
> priv->obe.remove = false;
> timer_setup(&priv->obe.timer, aspeed_kcs_check_obe, 0);
>
> - aspeed_kcs_set_address(kcs_bmc, addr);
> + 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); @@ -600,7
> +648,8 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> if (rc < 0)
> return rc;
>
> - dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n",
> kcs_bmc->channel, addr);
> + dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n",
> + kcs_bmc->channel, addrs[0]);
>
> return 0;
> }
> --
> 2.27.0

2021-04-09 03:22:23

by Joel Stanley

[permalink] [raw]
Subject: Re: [PATCH v2 01/21] dt-bindings: aspeed-lpc: Remove LPC partitioning

On Fri, 19 Mar 2021 at 06:28, Andrew Jeffery <[email protected]> wrote:
>
> From: "Chia-Wei, Wang" <[email protected]>
>
> The LPC controller has no concept of the BMC and the Host partitions.
> This patch fixes the documentation by removing the description on LPC
> partitions. The register offsets illustrated in the DTS node examples
> are also fixed to adapt to the LPC DTS change.

Is this accurate:

The node examples change their reg address to be an offset from the
LPC HC to be an offset from the base of the LPC region.

Reviewed-by: Joel Stanley <[email protected]>

>
> Signed-off-by: Chia-Wei Wang <[email protected]>
> Reviewed-by: Andrew Jeffery <[email protected]>
> Acked-by: Rob Herring <[email protected]>
> Acked-by: Lee Jones <[email protected]>
> ---
> .../devicetree/bindings/mfd/aspeed-lpc.txt | 100 +++++-------------
> 1 file changed, 25 insertions(+), 75 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt b/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt
> index d0a38ba8b9ce..936aa108eab4 100644
> --- a/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt
> +++ b/Documentation/devicetree/bindings/mfd/aspeed-lpc.txt
> @@ -9,13 +9,7 @@ primary use case of the Aspeed LPC controller is as a slave on the bus
> conditions it can also take the role of bus master.
>
> The LPC controller is represented as a multi-function device to account for the
> -mix of functionality it provides. The principle split is between the register
> -layout at the start of the I/O space which is, to quote the Aspeed datasheet,
> -"basically compatible with the [LPC registers from the] popular BMC controller
> -H8S/2168[1]", and everything else, where everything else is an eclectic
> -collection of functions with a esoteric register layout. "Everything else",
> -here labeled the "host" portion of the controller, includes, but is not limited
> -to:
> +mix of functionality, which includes, but is not limited to:
>
> * An IPMI Block Transfer[2] Controller
>
> @@ -44,80 +38,36 @@ Required properties
> ===================
>
> - compatible: One of:
> - "aspeed,ast2400-lpc", "simple-mfd"
> - "aspeed,ast2500-lpc", "simple-mfd"
> - "aspeed,ast2600-lpc", "simple-mfd"
> + "aspeed,ast2400-lpc-v2", "simple-mfd", "syscon"
> + "aspeed,ast2500-lpc-v2", "simple-mfd", "syscon"
> + "aspeed,ast2600-lpc-v2", "simple-mfd", "syscon"
>
> - reg: contains the physical address and length values of the Aspeed
> LPC memory region.
>
> - #address-cells: <1>
> - #size-cells: <1>
> -- ranges: Maps 0 to the physical address and length of the LPC memory
> - region
> -
> -Required LPC Child nodes
> -========================
> -
> -BMC Node
> ---------
> -
> -- compatible: One of:
> - "aspeed,ast2400-lpc-bmc"
> - "aspeed,ast2500-lpc-bmc"
> - "aspeed,ast2600-lpc-bmc"
> -
> -- reg: contains the physical address and length values of the
> - H8S/2168-compatible LPC controller memory region
> -
> -Host Node
> ----------
> -
> -- compatible: One of:
> - "aspeed,ast2400-lpc-host", "simple-mfd", "syscon"
> - "aspeed,ast2500-lpc-host", "simple-mfd", "syscon"
> - "aspeed,ast2600-lpc-host", "simple-mfd", "syscon"
> -
> -- reg: contains the address and length values of the host-related
> - register space for the Aspeed LPC controller
> -
> -- #address-cells: <1>
> -- #size-cells: <1>
> -- ranges: Maps 0 to the address and length of the host-related LPC memory
> +- ranges: Maps 0 to the physical address and length of the LPC memory
> region
>
> Example:
>
> lpc: lpc@1e789000 {
> - compatible = "aspeed,ast2500-lpc", "simple-mfd";
> + compatible = "aspeed,ast2500-lpc-v2", "simple-mfd", "syscon";
> reg = <0x1e789000 0x1000>;
>
> #address-cells = <1>;
> #size-cells = <1>;
> ranges = <0x0 0x1e789000 0x1000>;
>
> - lpc_bmc: lpc-bmc@0 {
> - compatible = "aspeed,ast2500-lpc-bmc";
> + lpc_snoop: lpc-snoop@0 {
> + compatible = "aspeed,ast2600-lpc-snoop";
> reg = <0x0 0x80>;
> - };
> -
> - lpc_host: lpc-host@80 {
> - compatible = "aspeed,ast2500-lpc-host", "simple-mfd", "syscon";
> - reg = <0x80 0x1e0>;
> - reg-io-width = <4>;
> -
> - #address-cells = <1>;
> - #size-cells = <1>;
> - ranges = <0x0 0x80 0x1e0>;
> + interrupts = <GIC_SPI 144 IRQ_TYPE_LEVEL_HIGH>;
> + snoop-ports = <0x80>;
> };
> };
>
> -BMC Node Children
> -==================
> -
> -
> -Host Node Children
> -==================
>
> LPC Host Interface Controller
> -------------------
> @@ -149,14 +99,12 @@ Optional properties:
>
> Example:
>
> -lpc-host@80 {
> - lpc_ctrl: lpc-ctrl@0 {
> - compatible = "aspeed,ast2500-lpc-ctrl";
> - reg = <0x0 0x80>;
> - clocks = <&syscon ASPEED_CLK_GATE_LCLK>;
> - memory-region = <&flash_memory>;
> - flash = <&spi>;
> - };
> +lpc_ctrl: lpc-ctrl@80 {
> + compatible = "aspeed,ast2500-lpc-ctrl";
> + reg = <0x80 0x80>;
> + clocks = <&syscon ASPEED_CLK_GATE_LCLK>;
> + memory-region = <&flash_memory>;
> + flash = <&spi>;
> };
>
> LPC Host Controller
> @@ -179,9 +127,9 @@ Required properties:
>
> Example:
>
> -lhc: lhc@20 {
> +lhc: lhc@a0 {
> compatible = "aspeed,ast2500-lhc";
> - reg = <0x20 0x24 0x48 0x8>;
> + reg = <0xa0 0x24 0xc8 0x8>;
> };
>
> LPC reset control
> @@ -192,16 +140,18 @@ state of the LPC bus. Some systems may chose to modify this configuration.
>
> Required properties:
>
> - - compatible: "aspeed,ast2600-lpc-reset" or
> - "aspeed,ast2500-lpc-reset"
> - "aspeed,ast2400-lpc-reset"
> + - compatible: One of:
> + "aspeed,ast2600-lpc-reset";
> + "aspeed,ast2500-lpc-reset";
> + "aspeed,ast2400-lpc-reset";
> +
> - reg: offset and length of the IP in the LHC memory region
> - #reset-controller indicates the number of reset cells expected
>
> Example:
>
> -lpc_reset: reset-controller@18 {
> +lpc_reset: reset-controller@98 {
> compatible = "aspeed,ast2500-lpc-reset";
> - reg = <0x18 0x4>;
> + reg = <0x98 0x4>;
> #reset-cells = <1>;
> };
> --
> 2.27.0
>

2021-04-09 03:38:54

by Joel Stanley

[permalink] [raw]
Subject: Re: [PATCH v2 04/21] pinctrl: aspeed-g5: Adapt to new LPC device tree layout

On Fri, 19 Mar 2021 at 06:28, Andrew Jeffery <[email protected]> wrote:
>
> From: "Chia-Wei, Wang" <[email protected]>
>
> Add check against LPC device v2 compatible string to
> ensure that the fixed device tree layout is adopted.
> The LPC register offsets are also fixed accordingly.
>
> Signed-off-by: Chia-Wei Wang <[email protected]>
> Reviewed-by: Andrew Jeffery <[email protected]>
> Acked-by: Linus Walleij <[email protected]>

Reviewed-by: Joel Stanley <[email protected]>

2021-04-09 03:40:00

by Joel Stanley

[permalink] [raw]
Subject: Re: [PATCH v2 05/21] soc: aspeed: Adapt to new LPC device tree layout

On Fri, 19 Mar 2021 at 06:28, Andrew Jeffery <[email protected]> wrote:
>
> From: "Chia-Wei, Wang" <[email protected]>
>
> Add check against LPC device v2 compatible string to
> ensure that the fixed device tree layout is adopted.
> The LPC register offsets are also fixed accordingly.
>
> Signed-off-by: Chia-Wei Wang <[email protected]>
> Reviewed-by: Andrew Jeffery <[email protected]>

Reviewed-by: Joel Stanley <[email protected]>

2021-04-09 03:57:42

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 09/21] ipmi: kcs_bmc: Split out kcs_bmc_cdev_ipmi

On Fri, Mar 19, 2021 at 01:27:40AM CDT, Andrew Jeffery wrote:
>Take steps towards defining a coherent API to separate the KCS device
>drivers from the userspace interface. Decreasing the coupling will
>improve the separation of concerns and enable the introduction of
>alternative userspace interfaces.
>
>For now, simply split the chardev logic out to a separate file. The code
>continues to build into the same module.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> drivers/char/ipmi/Makefile | 2 +-
> drivers/char/ipmi/kcs_bmc.c | 423 +------------------------
> drivers/char/ipmi/kcs_bmc.h | 10 +-
> drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 428 ++++++++++++++++++++++++++
> 4 files changed, 451 insertions(+), 412 deletions(-)
> create mode 100644 drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>
>diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
>index 0822adc2ec41..a302bc865370 100644
>--- a/drivers/char/ipmi/Makefile
>+++ b/drivers/char/ipmi/Makefile
>@@ -22,7 +22,7 @@ obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o
> obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o
> obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
> obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
>-obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o
>+obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o 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
>diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
>index c4336c1f2d6d..ef5c48ffe74a 100644
>--- a/drivers/char/ipmi/kcs_bmc.c
>+++ b/drivers/char/ipmi/kcs_bmc.c
>@@ -3,446 +3,51 @@
> * 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/module.h>
>-#include <linux/platform_device.h>
>-#include <linux/poll.h>
>-#include <linux/sched.h>
>-#include <linux/slab.h>
>
> #include "kcs_bmc.h"
>
>-#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 u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
>+u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
> {
> return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
> }
>+EXPORT_SYMBOL(kcs_bmc_read_data);
>
>-static inline void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data)
>+void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data)
> {
> kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
> }
>+EXPORT_SYMBOL(kcs_bmc_write_data);
>
>-static inline u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc)
>+u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc)
> {
> return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
> }
>+EXPORT_SYMBOL(kcs_bmc_read_status);
>
>-static inline void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data)
>+void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data)
> {
> kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
> }
>+EXPORT_SYMBOL(kcs_bmc_write_status);
>
>-static void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
>+void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
> {
> kcs_bmc->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
> }
>+EXPORT_SYMBOL(kcs_bmc_update_status);
>
>-static inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
>-{
>- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_STATE_MASK,
>- KCS_STATUS_STATE(state));
>-}
>-
>-static void kcs_force_abort(struct kcs_bmc *kcs_bmc)
>-{
>- set_state(kcs_bmc, ERROR_STATE);
>- kcs_bmc_read_data(kcs_bmc);
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>-
>- kcs_bmc->phase = KCS_PHASE_ERROR;
>- kcs_bmc->data_in_avail = false;
>- kcs_bmc->data_in_idx = 0;
>-}
>-
>-static void kcs_bmc_handle_data(struct kcs_bmc *kcs_bmc)
>-{
>- u8 data;
>-
>- switch (kcs_bmc->phase) {
>- case KCS_PHASE_WRITE_START:
>- kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
>- fallthrough;
>-
>- case KCS_PHASE_WRITE_DATA:
>- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
>- set_state(kcs_bmc, WRITE_STATE);
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
>- kcs_bmc_read_data(kcs_bmc);
>- } else {
>- kcs_force_abort(kcs_bmc);
>- kcs_bmc->error = KCS_LENGTH_ERROR;
>- }
>- break;
>-
>- case KCS_PHASE_WRITE_END_CMD:
>- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
>- set_state(kcs_bmc, READ_STATE);
>- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
>- kcs_bmc_read_data(kcs_bmc);
>- kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
>- kcs_bmc->data_in_avail = true;
>- wake_up_interruptible(&kcs_bmc->queue);
>- } else {
>- kcs_force_abort(kcs_bmc);
>- kcs_bmc->error = KCS_LENGTH_ERROR;
>- }
>- break;
>-
>- case KCS_PHASE_READ:
>- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
>- set_state(kcs_bmc, IDLE_STATE);
>-
>- data = kcs_bmc_read_data(kcs_bmc);
>- if (data != KCS_CMD_READ_BYTE) {
>- set_state(kcs_bmc, ERROR_STATE);
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>- break;
>- }
>-
>- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>- kcs_bmc->phase = KCS_PHASE_IDLE;
>- break;
>- }
>-
>- kcs_bmc_write_data(kcs_bmc,
>- kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
>- break;
>-
>- case KCS_PHASE_ABORT_ERROR1:
>- set_state(kcs_bmc, READ_STATE);
>- kcs_bmc_read_data(kcs_bmc);
>- kcs_bmc_write_data(kcs_bmc, kcs_bmc->error);
>- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
>- break;
>-
>- case KCS_PHASE_ABORT_ERROR2:
>- set_state(kcs_bmc, IDLE_STATE);
>- kcs_bmc_read_data(kcs_bmc);
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>- kcs_bmc->phase = KCS_PHASE_IDLE;
>- break;
>-
>- default:
>- kcs_force_abort(kcs_bmc);
>- break;
>- }
>-}
>-
>-static void kcs_bmc_handle_cmd(struct kcs_bmc *kcs_bmc)
>-{
>- u8 cmd;
>-
>- set_state(kcs_bmc, WRITE_STATE);
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>-
>- cmd = kcs_bmc_read_data(kcs_bmc);
>- switch (cmd) {
>- case KCS_CMD_WRITE_START:
>- kcs_bmc->phase = KCS_PHASE_WRITE_START;
>- kcs_bmc->error = KCS_NO_ERROR;
>- kcs_bmc->data_in_avail = false;
>- kcs_bmc->data_in_idx = 0;
>- break;
>-
>- case KCS_CMD_WRITE_END:
>- if (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
>- kcs_force_abort(kcs_bmc);
>- break;
>- }
>-
>- kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
>- break;
>-
>- case KCS_CMD_GET_STATUS_ABORT:
>- if (kcs_bmc->error == KCS_NO_ERROR)
>- kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
>-
>- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
>- kcs_bmc->data_in_avail = false;
>- kcs_bmc->data_in_idx = 0;
>- break;
>-
>- default:
>- kcs_force_abort(kcs_bmc);
>- kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
>- break;
>- }
>-}
>-
>+int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);

This declaration looks a bit out of place here; should it be in
kcs_bmc.h instead?

> int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
> {
>- unsigned long flags;
>- int ret = -ENODATA;
>- u8 status;
>-
>- spin_lock_irqsave(&kcs_bmc->lock, flags);
>-
>- status = kcs_bmc_read_status(kcs_bmc);
>- if (status & KCS_STATUS_IBF) {
>- if (!kcs_bmc->running)
>- kcs_force_abort(kcs_bmc);
>- else if (status & KCS_STATUS_CMD_DAT)
>- kcs_bmc_handle_cmd(kcs_bmc);
>- else
>- kcs_bmc_handle_data(kcs_bmc);
>-
>- ret = 0;
>- }
>-
>- spin_unlock_irqrestore(&kcs_bmc->lock, flags);
>-
>- return ret;
>+ return kcs_bmc_ipmi_event(kcs_bmc);
> }
> EXPORT_SYMBOL(kcs_bmc_handle_event);
>
>-static inline struct kcs_bmc *to_kcs_bmc(struct file *filp)
>-{
>- return container_of(filp->private_data, struct kcs_bmc, miscdev);
>-}
>-
>-static int kcs_bmc_open(struct inode *inode, struct file *filp)
>-{
>- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>- int ret = 0;
>-
>- spin_lock_irq(&kcs_bmc->lock);
>- if (!kcs_bmc->running)
>- kcs_bmc->running = 1;
>- else
>- ret = -EBUSY;
>- spin_unlock_irq(&kcs_bmc->lock);
>-
>- return ret;
>-}
>-
>-static __poll_t kcs_bmc_poll(struct file *filp, poll_table *wait)
>-{
>- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>- __poll_t mask = 0;
>-
>- poll_wait(filp, &kcs_bmc->queue, wait);
>-
>- spin_lock_irq(&kcs_bmc->lock);
>- if (kcs_bmc->data_in_avail)
>- mask |= EPOLLIN;
>- spin_unlock_irq(&kcs_bmc->lock);
>-
>- return mask;
>-}
>-
>-static ssize_t kcs_bmc_read(struct file *filp, char __user *buf,
>- size_t count, loff_t *ppos)
>-{
>- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>- bool data_avail;
>- size_t data_len;
>- ssize_t ret;
>-
>- if (!(filp->f_flags & O_NONBLOCK))
>- wait_event_interruptible(kcs_bmc->queue,
>- kcs_bmc->data_in_avail);
>-
>- mutex_lock(&kcs_bmc->mutex);
>-
>- spin_lock_irq(&kcs_bmc->lock);
>- data_avail = kcs_bmc->data_in_avail;
>- if (data_avail) {
>- data_len = kcs_bmc->data_in_idx;
>- memcpy(kcs_bmc->kbuffer, kcs_bmc->data_in, data_len);
>- }
>- spin_unlock_irq(&kcs_bmc->lock);
>-
>- if (!data_avail) {
>- ret = -EAGAIN;
>- goto out_unlock;
>- }
>-
>- if (count < data_len) {
>- pr_err("channel=%u with too large data : %zu\n",
>- kcs_bmc->channel, data_len);
>-
>- spin_lock_irq(&kcs_bmc->lock);
>- kcs_force_abort(kcs_bmc);
>- spin_unlock_irq(&kcs_bmc->lock);
>-
>- ret = -EOVERFLOW;
>- goto out_unlock;
>- }
>-
>- if (copy_to_user(buf, kcs_bmc->kbuffer, data_len)) {
>- ret = -EFAULT;
>- goto out_unlock;
>- }
>-
>- ret = data_len;
>-
>- spin_lock_irq(&kcs_bmc->lock);
>- if (kcs_bmc->phase == KCS_PHASE_WRITE_DONE) {
>- kcs_bmc->phase = KCS_PHASE_WAIT_READ;
>- kcs_bmc->data_in_avail = false;
>- kcs_bmc->data_in_idx = 0;
>- } else {
>- ret = -EAGAIN;
>- }
>- spin_unlock_irq(&kcs_bmc->lock);
>-
>-out_unlock:
>- mutex_unlock(&kcs_bmc->mutex);
>-
>- return ret;
>-}
>-
>-static ssize_t kcs_bmc_write(struct file *filp, const char __user *buf,
>- size_t count, loff_t *ppos)
>-{
>- struct kcs_bmc *kcs_bmc = 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(&kcs_bmc->mutex);
>-
>- if (copy_from_user(kcs_bmc->kbuffer, buf, count)) {
>- ret = -EFAULT;
>- goto out_unlock;
>- }
>-
>- spin_lock_irq(&kcs_bmc->lock);
>- if (kcs_bmc->phase == KCS_PHASE_WAIT_READ) {
>- kcs_bmc->phase = KCS_PHASE_READ;
>- kcs_bmc->data_out_idx = 1;
>- kcs_bmc->data_out_len = count;
>- memcpy(kcs_bmc->data_out, kcs_bmc->kbuffer, count);
>- kcs_bmc_write_data(kcs_bmc, kcs_bmc->data_out[0]);
>- ret = count;
>- } else {
>- ret = -EINVAL;
>- }
>- spin_unlock_irq(&kcs_bmc->lock);
>-
>-out_unlock:
>- mutex_unlock(&kcs_bmc->mutex);
>-
>- return ret;
>-}
>-
>-static long kcs_bmc_ioctl(struct file *filp, unsigned int cmd,
>- unsigned long arg)
>-{
>- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>- long ret = 0;
>-
>- spin_lock_irq(&kcs_bmc->lock);
>-
>- switch (cmd) {
>- case IPMI_BMC_IOCTL_SET_SMS_ATN:
>- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, KCS_STATUS_SMS_ATN);
>- break;
>-
>- case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
>- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, 0);
>- break;
>-
>- case IPMI_BMC_IOCTL_FORCE_ABORT:
>- kcs_force_abort(kcs_bmc);
>- break;
>-
>- default:
>- ret = -EINVAL;
>- break;
>- }
>-
>- spin_unlock_irq(&kcs_bmc->lock);
>-
>- return ret;
>-}
>-
>-static int kcs_bmc_release(struct inode *inode, struct file *filp)
>-{
>- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>-
>- spin_lock_irq(&kcs_bmc->lock);
>- kcs_bmc->running = 0;
>- kcs_force_abort(kcs_bmc);
>- spin_unlock_irq(&kcs_bmc->lock);
>-
>- return 0;
>-}
>-
>-static const struct file_operations kcs_bmc_fops = {
>- .owner = THIS_MODULE,
>- .open = kcs_bmc_open,
>- .read = kcs_bmc_read,
>- .write = kcs_bmc_write,
>- .release = kcs_bmc_release,
>- .poll = kcs_bmc_poll,
>- .unlocked_ioctl = kcs_bmc_ioctl,
>-};
>-
>+struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);

Similarly here.

> struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel)
> {
>- struct kcs_bmc *kcs_bmc;
>-
>- kcs_bmc = devm_kzalloc(dev, sizeof(*kcs_bmc) + sizeof_priv, GFP_KERNEL);
>- if (!kcs_bmc)
>- return NULL;
>-
>- spin_lock_init(&kcs_bmc->lock);
>- kcs_bmc->channel = channel;
>-
>- mutex_init(&kcs_bmc->mutex);
>- init_waitqueue_head(&kcs_bmc->queue);
>-
>- kcs_bmc->data_in = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>- kcs_bmc->data_out = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>- kcs_bmc->kbuffer = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>-
>- kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
>- kcs_bmc->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%u",
>- DEVICE_NAME, channel);
>- if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
>- !kcs_bmc->miscdev.name)
>- return NULL;
>- kcs_bmc->miscdev.fops = &kcs_bmc_fops;
>-
>- return kcs_bmc;
>+ return kcs_bmc_ipmi_alloc(dev, sizeof_priv, channel);
> }
> EXPORT_SYMBOL(kcs_bmc_alloc);
>
>diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
>index 970f53892f2d..febea0c8deb4 100644
>--- a/drivers/char/ipmi/kcs_bmc.h
>+++ b/drivers/char/ipmi/kcs_bmc.h
>@@ -104,6 +104,12 @@ static inline void *kcs_bmc_priv(struct kcs_bmc *kcs_bmc)
> }
>
> int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
>-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv,
>- u32 channel);
>+struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel);
>+
>+u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
>+void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
>+u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc);
>+void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data);
>+void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val);
>+
> #endif /* __KCS_BMC_H__ */
>diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>new file mode 100644
>index 000000000000..82c77994e481
>--- /dev/null
>+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>@@ -0,0 +1,428 @@
>+// 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/module.h>
>+#include <linux/platform_device.h>
>+#include <linux/poll.h>
>+#include <linux/sched.h>
>+#include <linux/slab.h>
>+
>+#include "kcs_bmc.h"
>+
>+#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 *kcs_bmc, u8 state)
>+{
>+ kcs_bmc_update_status(kcs_bmc, KCS_STATUS_STATE_MASK,
>+ KCS_STATUS_STATE(state));
>+}
>+
>+static void kcs_bmc_ipmi_force_abort(struct kcs_bmc *kcs_bmc)
>+{
>+ set_state(kcs_bmc, ERROR_STATE);
>+ kcs_bmc_read_data(kcs_bmc);
>+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>+
>+ kcs_bmc->phase = KCS_PHASE_ERROR;
>+ kcs_bmc->data_in_avail = false;
>+ kcs_bmc->data_in_idx = 0;
>+}
>+
>+static void kcs_bmc_ipmi_handle_data(struct kcs_bmc *kcs_bmc)
>+{
>+ u8 data;
>+
>+ switch (kcs_bmc->phase) {
>+ case KCS_PHASE_WRITE_START:
>+ kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
>+ fallthrough;
>+
>+ case KCS_PHASE_WRITE_DATA:
>+ if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
>+ set_state(kcs_bmc, WRITE_STATE);
>+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>+ kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
>+ kcs_bmc_read_data(kcs_bmc);
>+ } else {
>+ kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ kcs_bmc->error = KCS_LENGTH_ERROR;
>+ }
>+ break;
>+
>+ case KCS_PHASE_WRITE_END_CMD:
>+ if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
>+ set_state(kcs_bmc, READ_STATE);
>+ kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
>+ kcs_bmc_read_data(kcs_bmc);
>+ kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
>+ kcs_bmc->data_in_avail = true;
>+ wake_up_interruptible(&kcs_bmc->queue);
>+ } else {
>+ kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ kcs_bmc->error = KCS_LENGTH_ERROR;
>+ }
>+ break;
>+
>+ case KCS_PHASE_READ:
>+ if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
>+ set_state(kcs_bmc, IDLE_STATE);
>+
>+ data = kcs_bmc_read_data(kcs_bmc);
>+ if (data != KCS_CMD_READ_BYTE) {
>+ set_state(kcs_bmc, ERROR_STATE);
>+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>+ break;
>+ }
>+
>+ if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
>+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>+ kcs_bmc->phase = KCS_PHASE_IDLE;
>+ break;
>+ }
>+
>+ kcs_bmc_write_data(kcs_bmc,
>+ kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
>+ break;
>+
>+ case KCS_PHASE_ABORT_ERROR1:
>+ set_state(kcs_bmc, READ_STATE);
>+ kcs_bmc_read_data(kcs_bmc);
>+ kcs_bmc_write_data(kcs_bmc, kcs_bmc->error);
>+ kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
>+ break;
>+
>+ case KCS_PHASE_ABORT_ERROR2:
>+ set_state(kcs_bmc, IDLE_STATE);
>+ kcs_bmc_read_data(kcs_bmc);
>+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>+ kcs_bmc->phase = KCS_PHASE_IDLE;
>+ break;
>+
>+ default:
>+ kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ break;
>+ }
>+}
>+
>+static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc *kcs_bmc)
>+{
>+ u8 cmd;
>+
>+ set_state(kcs_bmc, WRITE_STATE);
>+ kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>+
>+ cmd = kcs_bmc_read_data(kcs_bmc);
>+ switch (cmd) {
>+ case KCS_CMD_WRITE_START:
>+ kcs_bmc->phase = KCS_PHASE_WRITE_START;
>+ kcs_bmc->error = KCS_NO_ERROR;
>+ kcs_bmc->data_in_avail = false;
>+ kcs_bmc->data_in_idx = 0;
>+ break;
>+
>+ case KCS_CMD_WRITE_END:
>+ if (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
>+ kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ break;
>+ }
>+
>+ kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
>+ break;
>+
>+ case KCS_CMD_GET_STATUS_ABORT:
>+ if (kcs_bmc->error == KCS_NO_ERROR)
>+ kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
>+
>+ kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
>+ kcs_bmc->data_in_avail = false;
>+ kcs_bmc->data_in_idx = 0;
>+ break;
>+
>+ default:
>+ kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
>+ break;
>+ }
>+}
>+
>+int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);

Another instance...

>+int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc)
>+{
>+ unsigned long flags;
>+ int ret = -ENODATA;
>+ u8 status;
>+
>+ spin_lock_irqsave(&kcs_bmc->lock, flags);
>+
>+ status = kcs_bmc_read_status(kcs_bmc);
>+ if (status & KCS_STATUS_IBF) {
>+ if (!kcs_bmc->running)
>+ kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ else if (status & KCS_STATUS_CMD_DAT)
>+ kcs_bmc_ipmi_handle_cmd(kcs_bmc);
>+ else
>+ kcs_bmc_ipmi_handle_data(kcs_bmc);
>+
>+ ret = 0;
>+ }
>+
>+ spin_unlock_irqrestore(&kcs_bmc->lock, flags);
>+
>+ return ret;
>+}
>+EXPORT_SYMBOL(kcs_bmc_ipmi_event);
>+
>+static inline struct kcs_bmc *to_kcs_bmc(struct file *filp)
>+{
>+ return container_of(filp->private_data, struct kcs_bmc, miscdev);
>+}
>+
>+static int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp)
>+{
>+ struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>+ int ret = 0;
>+
>+ spin_lock_irq(&kcs_bmc->lock);
>+ if (!kcs_bmc->running)
>+ kcs_bmc->running = 1;
>+ else
>+ ret = -EBUSY;
>+ spin_unlock_irq(&kcs_bmc->lock);
>+
>+ return ret;
>+}
>+
>+static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
>+{
>+ struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>+ __poll_t mask = 0;
>+
>+ poll_wait(filp, &kcs_bmc->queue, wait);
>+
>+ spin_lock_irq(&kcs_bmc->lock);
>+ if (kcs_bmc->data_in_avail)
>+ mask |= EPOLLIN;
>+ spin_unlock_irq(&kcs_bmc->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 *kcs_bmc = to_kcs_bmc(filp);
>+ bool data_avail;
>+ size_t data_len;
>+ ssize_t ret;
>+
>+ if (!(filp->f_flags & O_NONBLOCK))
>+ wait_event_interruptible(kcs_bmc->queue,
>+ kcs_bmc->data_in_avail);
>+
>+ mutex_lock(&kcs_bmc->mutex);
>+
>+ spin_lock_irq(&kcs_bmc->lock);
>+ data_avail = kcs_bmc->data_in_avail;
>+ if (data_avail) {
>+ data_len = kcs_bmc->data_in_idx;
>+ memcpy(kcs_bmc->kbuffer, kcs_bmc->data_in, data_len);
>+ }
>+ spin_unlock_irq(&kcs_bmc->lock);
>+
>+ if (!data_avail) {
>+ ret = -EAGAIN;
>+ goto out_unlock;
>+ }
>+
>+ if (count < data_len) {
>+ pr_err("channel=%u with too large data : %zu\n",
>+ kcs_bmc->channel, data_len);
>+
>+ spin_lock_irq(&kcs_bmc->lock);
>+ kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ spin_unlock_irq(&kcs_bmc->lock);
>+
>+ ret = -EOVERFLOW;
>+ goto out_unlock;
>+ }
>+
>+ if (copy_to_user(buf, kcs_bmc->kbuffer, data_len)) {
>+ ret = -EFAULT;
>+ goto out_unlock;
>+ }
>+
>+ ret = data_len;
>+
>+ spin_lock_irq(&kcs_bmc->lock);
>+ if (kcs_bmc->phase == KCS_PHASE_WRITE_DONE) {
>+ kcs_bmc->phase = KCS_PHASE_WAIT_READ;
>+ kcs_bmc->data_in_avail = false;
>+ kcs_bmc->data_in_idx = 0;
>+ } else {
>+ ret = -EAGAIN;
>+ }
>+ spin_unlock_irq(&kcs_bmc->lock);
>+
>+out_unlock:
>+ mutex_unlock(&kcs_bmc->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 *kcs_bmc = 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(&kcs_bmc->mutex);
>+
>+ if (copy_from_user(kcs_bmc->kbuffer, buf, count)) {
>+ ret = -EFAULT;
>+ goto out_unlock;
>+ }
>+
>+ spin_lock_irq(&kcs_bmc->lock);
>+ if (kcs_bmc->phase == KCS_PHASE_WAIT_READ) {
>+ kcs_bmc->phase = KCS_PHASE_READ;
>+ kcs_bmc->data_out_idx = 1;
>+ kcs_bmc->data_out_len = count;
>+ memcpy(kcs_bmc->data_out, kcs_bmc->kbuffer, count);
>+ kcs_bmc_write_data(kcs_bmc, kcs_bmc->data_out[0]);
>+ ret = count;
>+ } else {
>+ ret = -EINVAL;
>+ }
>+ spin_unlock_irq(&kcs_bmc->lock);
>+
>+out_unlock:
>+ mutex_unlock(&kcs_bmc->mutex);
>+
>+ return ret;
>+}
>+
>+static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
>+ unsigned long arg)
>+{
>+ struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>+ long ret = 0;
>+
>+ spin_lock_irq(&kcs_bmc->lock);
>+
>+ switch (cmd) {
>+ case IPMI_BMC_IOCTL_SET_SMS_ATN:
>+ kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, KCS_STATUS_SMS_ATN);
>+ break;
>+
>+ case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
>+ kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, 0);
>+ break;
>+
>+ case IPMI_BMC_IOCTL_FORCE_ABORT:
>+ kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ break;
>+
>+ default:
>+ ret = -EINVAL;
>+ break;
>+ }
>+
>+ spin_unlock_irq(&kcs_bmc->lock);
>+
>+ return ret;
>+}
>+
>+static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
>+{
>+ struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>+
>+ spin_lock_irq(&kcs_bmc->lock);
>+ kcs_bmc->running = 0;
>+ kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ spin_unlock_irq(&kcs_bmc->lock);
>+
>+ return 0;
>+}
>+
>+static const struct file_operations kcs_bmc_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,
>+};
>+
>+struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);

Here too.

>+struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel)
>+{
>+ struct kcs_bmc *kcs_bmc;
>+
>+ kcs_bmc = devm_kzalloc(dev, sizeof(*kcs_bmc) + sizeof_priv, GFP_KERNEL);
>+ if (!kcs_bmc)
>+ return NULL;
>+
>+ spin_lock_init(&kcs_bmc->lock);
>+ kcs_bmc->channel = channel;
>+
>+ mutex_init(&kcs_bmc->mutex);
>+ init_waitqueue_head(&kcs_bmc->queue);
>+
>+ kcs_bmc->data_in = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>+ kcs_bmc->data_out = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>+ kcs_bmc->kbuffer = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>+
>+ kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
>+ kcs_bmc->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%u",
>+ DEVICE_NAME, channel);
>+ if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
>+ !kcs_bmc->miscdev.name)
>+ return NULL;
>+ kcs_bmc->miscdev.fops = &kcs_bmc_fops;
>+
>+ return kcs_bmc;
>+}
>+EXPORT_SYMBOL(kcs_bmc_ipmi_alloc);
>+
>+MODULE_LICENSE("GPL v2");
>+MODULE_AUTHOR("Haiyue Wang <[email protected]>");
>+MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
>--
>2.27.0
>

2021-04-09 04:05:07

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 11/21] ipmi: kcs_bmc: Split headers into device and client

On Fri, Mar 19, 2021 at 01:27:42AM CDT, Andrew Jeffery wrote:
>Strengthen the distinction between code that abstracts the
>implementation of the KCS behaviours (device drivers) and code that
>exploits KCS behaviours (clients). Neither needs to know about the APIs
>required by the other, so provide separate headers.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> drivers/char/ipmi/kcs_bmc.c | 21 ++++++++++-----
> drivers/char/ipmi/kcs_bmc.h | 30 ++++++++++-----------
> drivers/char/ipmi/kcs_bmc_aspeed.c | 20 +++++++++-----
> drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 39 ++++++++++++++++++---------
> drivers/char/ipmi/kcs_bmc_client.h | 29 ++++++++++++++++++++
> drivers/char/ipmi/kcs_bmc_device.h | 19 +++++++++++++
> drivers/char/ipmi/kcs_bmc_npcm7xx.c | 20 +++++++++-----
> 7 files changed, 129 insertions(+), 49 deletions(-)
> create mode 100644 drivers/char/ipmi/kcs_bmc_client.h
> create mode 100644 drivers/char/ipmi/kcs_bmc_device.h
>
>diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
>index 709b6bdec165..1046ce2bbefc 100644
>--- a/drivers/char/ipmi/kcs_bmc.c
>+++ b/drivers/char/ipmi/kcs_bmc.c
>@@ -1,46 +1,52 @@
> // SPDX-License-Identifier: GPL-2.0
> /*
> * Copyright (c) 2015-2018, Intel Corporation.
>+ * Copyright (c) 2021, IBM Corp.
> */
>
> #include <linux/module.h>
>
> #include "kcs_bmc.h"
>
>+/* Implement both the device and client interfaces here */
>+#include "kcs_bmc_device.h"
>+#include "kcs_bmc_client.h"
>+
>+/* Consumer data access */
>+
> u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
> {
>- return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
>+ 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 *kcs_bmc, u8 data)
> {
>- kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, 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 *kcs_bmc)
> {
>- return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
>+ 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 *kcs_bmc, u8 data)
> {
>- kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, 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 *kcs_bmc, u8 mask, u8 val)
> {
>- kcs_bmc->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
>+ kcs_bmc->ops->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
> }
> EXPORT_SYMBOL(kcs_bmc_update_status);
>
>-int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);
> int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
> {
>- return kcs_bmc_ipmi_event(kcs_bmc);
>+ return kcs_bmc->client.ops->event(&kcs_bmc->client);
> }
> EXPORT_SYMBOL(kcs_bmc_handle_event);
>
>@@ -60,4 +66,5 @@ EXPORT_SYMBOL(kcs_bmc_remove_device);
>
> MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Haiyue Wang <[email protected]>");
>+MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
> MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
>diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
>index bf0ae327997f..a1350e567723 100644
>--- a/drivers/char/ipmi/kcs_bmc.h
>+++ b/drivers/char/ipmi/kcs_bmc.h
>@@ -8,6 +8,15 @@
>
> #include <linux/miscdevice.h>
>
>+#include "kcs_bmc_client.h"
>+
>+#define KCS_BMC_EVENT_NONE 0
>+#define KCS_BMC_EVENT_HANDLED 1

Is there a particular reason we're introducing these macros and using an
int return type for kcs_bmc_client_ops.event instead of just having it
be irqreturn_t? Other event types or outcomes we're anticipating needing
to handle maybe?

>+
>+#define KCS_BMC_STR_OBF BIT(0)
>+#define KCS_BMC_STR_IBF BIT(1)
>+#define KCS_BMC_STR_CMD_DAT BIT(3)

The first two of these macros are used later in the series, but the third
doesn't end up used at all I think?

>+
> /* Different phases of the KCS BMC module.
> * KCS_PHASE_IDLE:
> * BMC should not be expecting nor sending any data.
>@@ -66,19 +75,21 @@ struct kcs_ioreg {
> u32 str;
> };
>
>+struct kcs_bmc_device_ops;
>+
> struct kcs_bmc {
> struct device *dev;
>
>+ const struct kcs_bmc_device_ops *ops;
>+
>+ struct kcs_bmc_client client;
>+
> spinlock_t lock;
>
> u32 channel;
> int running;
>
>- /* Setup by BMC KCS controller driver */
> struct kcs_ioreg ioreg;
>- u8 (*io_inputb)(struct kcs_bmc *kcs_bmc, u32 reg);
>- void (*io_outputb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 b);
>- void (*io_updateb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val);
>
> enum kcs_phases phase;
> enum kcs_errors error;
>@@ -97,15 +108,4 @@ struct kcs_bmc {
>
> struct miscdevice miscdev;
> };
>-
>-int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
>-int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc);
>-int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc);
>-
>-u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
>-void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
>-u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc);
>-void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data);
>-void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val);
>-
> #endif /* __KCS_BMC_H__ */
>diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
>index 0416ac78ce68..1b313355b1c8 100644
>--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
>+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
>@@ -21,7 +21,7 @@
> #include <linux/slab.h>
> #include <linux/timer.h>
>
>-#include "kcs_bmc.h"
>+#include "kcs_bmc_device.h"
>
>
> #define DEVICE_NAME "ast-kcs-bmc"
>@@ -220,14 +220,22 @@ static void aspeed_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
> }
> }
>
>+static const struct kcs_bmc_device_ops aspeed_kcs_ops = {
>+ .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 *kcs_bmc = arg;
>+ int rc;
>
>- if (!kcs_bmc_handle_event(kcs_bmc))
>- return IRQ_HANDLED;
>+ rc = kcs_bmc_handle_event(kcs_bmc);
>+ if (rc < 0)
>+ dev_warn(kcs_bmc->dev, "Failed to service irq: %d\n", rc);
>
>- return IRQ_NONE;
>+ return rc == KCS_BMC_EVENT_HANDLED ? IRQ_HANDLED : IRQ_NONE;
> }
>
> static int aspeed_kcs_config_irq(struct kcs_bmc *kcs_bmc,
>@@ -362,9 +370,7 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> kcs_bmc->dev = &pdev->dev;
> kcs_bmc->channel = channel;
> kcs_bmc->ioreg = ast_kcs_bmc_ioregs[channel - 1];
>- kcs_bmc->io_inputb = aspeed_kcs_inb;
>- kcs_bmc->io_outputb = aspeed_kcs_outb;
>- kcs_bmc->io_updateb = aspeed_kcs_updateb;
>+ kcs_bmc->ops = &aspeed_kcs_ops;
>
> priv->map = syscon_node_to_regmap(pdev->dev.parent->of_node);
> if (IS_ERR(priv->map)) {
>diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>index 0ca71c135a1a..fd852d8abe48 100644
>--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>@@ -22,7 +22,6 @@
>
> #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)
>@@ -179,12 +178,19 @@ static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc *kcs_bmc)
> }
> }
>
>-int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);
>-int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc)
>+static inline struct kcs_bmc *client_to_kcs_bmc(struct kcs_bmc_client *client)
> {
>+ return container_of(client, struct kcs_bmc, client);
>+}
>+
>+static int kcs_bmc_ipmi_event(struct kcs_bmc_client *client)
>+{
>+ struct kcs_bmc *kcs_bmc;
> unsigned long flags;
>- int ret = -ENODATA;
> u8 status;
>+ int ret;
>+
>+ kcs_bmc = client_to_kcs_bmc(client);
>
> spin_lock_irqsave(&kcs_bmc->lock, flags);
>
>@@ -197,23 +203,28 @@ int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc)
> else
> kcs_bmc_ipmi_handle_data(kcs_bmc);
>
>- ret = 0;
>+ ret = KCS_BMC_EVENT_HANDLED;
>+ } else {
>+ ret = KCS_BMC_EVENT_NONE;
> }
>
> spin_unlock_irqrestore(&kcs_bmc->lock, flags);
>
> return ret;
> }
>-EXPORT_SYMBOL(kcs_bmc_ipmi_event);
>
>-static inline struct kcs_bmc *to_kcs_bmc(struct file *filp)
>+static const struct kcs_bmc_client_ops kcs_bmc_ipmi_client_ops = {
>+ .event = kcs_bmc_ipmi_event,
>+};
>+
>+static inline struct kcs_bmc *file_to_kcs_bmc(struct file *filp)
> {
> return container_of(filp->private_data, struct kcs_bmc, miscdev);
> }
>
> static int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp)
> {
>- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
> int ret = 0;
>
> spin_lock_irq(&kcs_bmc->lock);
>@@ -228,7 +239,7 @@ static int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp)
>
> static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
> {
>- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
> __poll_t mask = 0;
>
> poll_wait(filp, &kcs_bmc->queue, wait);
>@@ -244,7 +255,7 @@ static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
> static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
> size_t count, loff_t *ppos)
> {
>- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
> bool data_avail;
> size_t data_len;
> ssize_t ret;
>@@ -306,7 +317,7 @@ static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
> static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
> size_t count, loff_t *ppos)
> {
>- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
> ssize_t ret;
>
> /* a minimum response size '3' : netfn + cmd + ccode */
>@@ -342,7 +353,7 @@ static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
> static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
> unsigned long arg)
> {
>- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
> long ret = 0;
>
> spin_lock_irq(&kcs_bmc->lock);
>@@ -372,7 +383,7 @@ static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
>
> static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
> {
>- struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
>+ struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
>
> spin_lock_irq(&kcs_bmc->lock);
> kcs_bmc->running = 0;
>@@ -401,6 +412,8 @@ int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc)
> mutex_init(&kcs_bmc->mutex);
> init_waitqueue_head(&kcs_bmc->queue);
>
>+ kcs_bmc->client.dev = kcs_bmc;
>+ kcs_bmc->client.ops = &kcs_bmc_ipmi_client_ops;
> kcs_bmc->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
> kcs_bmc->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
> kcs_bmc->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>diff --git a/drivers/char/ipmi/kcs_bmc_client.h b/drivers/char/ipmi/kcs_bmc_client.h
>new file mode 100644
>index 000000000000..140631d157d8
>--- /dev/null
>+++ b/drivers/char/ipmi/kcs_bmc_client.h
>@@ -0,0 +1,29 @@
>+/* SPDX-License-Identifier: GPL-2.0 */
>+/* Copyright (c) 2021, IBM Corp. */
>+
>+#ifndef __KCS_BMC_CONSUMER_H__
>+#define __KCS_BMC_CONSUMER_H__
>+
>+#include <linux/list.h>
>+#include <linux/notifier.h>
>+#include <stdbool.h>
>+
>+struct kcs_bmc;
>+struct kcs_bmc_client_ops;
>+
>+struct kcs_bmc_client {
>+ const struct kcs_bmc_client_ops *ops;
>+
>+ struct kcs_bmc *dev;
>+};
>+
>+struct kcs_bmc_client_ops {
>+ int (*event)(struct kcs_bmc_client *client);
>+};
>+
>+u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
>+void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
>+u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc);
>+void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data);
>+void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val);
>+#endif
>diff --git a/drivers/char/ipmi/kcs_bmc_device.h b/drivers/char/ipmi/kcs_bmc_device.h
>new file mode 100644
>index 000000000000..33462174516d
>--- /dev/null
>+++ b/drivers/char/ipmi/kcs_bmc_device.h
>@@ -0,0 +1,19 @@
>+/* SPDX-License-Identifier: GPL-2.0 */
>+/* Copyright (c) 2021, IBM Corp. */
>+
>+#ifndef __KCS_BMC_DEVICE_H__
>+#define __KCS_BMC_DEVICE_H__
>+
>+#include "kcs_bmc.h"
>+
>+struct kcs_bmc_device_ops {
>+ u8 (*io_inputb)(struct kcs_bmc *kcs_bmc, u32 reg);
>+ void (*io_outputb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 b);
>+ void (*io_updateb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 b);
>+};
>+
>+int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
>+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc);
>+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc);
>+
>+#endif
>diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>index 5d017498dc69..1d21697fc585 100644
>--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>@@ -17,7 +17,7 @@
> #include <linux/regmap.h>
> #include <linux/slab.h>
>
>-#include "kcs_bmc.h"
>+#include "kcs_bmc_device.h"
>
> #define DEVICE_NAME "npcm-kcs-bmc"
> #define KCS_CHANNEL_MAX 3
>@@ -127,11 +127,13 @@ static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
> static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
> {
> struct kcs_bmc *kcs_bmc = arg;
>+ int rc;
>
>- if (!kcs_bmc_handle_event(kcs_bmc))
>- return IRQ_HANDLED;
>+ rc = kcs_bmc_handle_event(kcs_bmc);
>+ if (rc < 0)
>+ dev_warn(kcs_bmc->dev, "Failed to service irq: %d\n", rc);
>
>- return IRQ_NONE;
>+ return rc == KCS_BMC_EVENT_HANDLED ? IRQ_HANDLED : IRQ_NONE;
> }
>
> static int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc,
>@@ -148,6 +150,12 @@ static int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc,
> dev_name(dev), kcs_bmc);
> }
>
>+static const struct kcs_bmc_device_ops npcm7xx_kcs_ops = {
>+ .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;
>@@ -179,9 +187,7 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
> kcs_bmc->ioreg.idr = priv->reg->dib;
> kcs_bmc->ioreg.odr = priv->reg->dob;
> kcs_bmc->ioreg.str = priv->reg->sts;
>- kcs_bmc->io_inputb = npcm7xx_kcs_inb;
>- kcs_bmc->io_outputb = npcm7xx_kcs_outb;
>- kcs_bmc->io_updateb = npcm7xx_kcs_updateb;
>+ kcs_bmc->ops = &npcm7xx_kcs_ops;
>
> platform_set_drvdata(pdev, priv);
>
>--
>2.27.0
>

2021-04-09 04:10:57

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 12/21] ipmi: kcs_bmc: Strip private client data from struct kcs_bmc

On Fri, Mar 19, 2021 at 01:27:43AM CDT, Andrew Jeffery wrote:
>Move all client-private data out of `struct kcs_bmc` into the KCS client
>implementation.
>
>With this change the KCS BMC core code now only concerns itself with
>abstract `struct kcs_bmc` and `struct kcs_bmc_client` types, achieving
>expected separation of concerns. Further, the change clears the path for
>implementation of alternative userspace interfaces.
>
>The chardev data-structures are rearranged in the same manner applied to
>the KCS device driver data-structures in an earlier patch - `struct
>kcs_bmc_client` is embedded in the client's private data and we exploit
>container_of() to translate as required.
>
>Finally, now that it is free of client data, `struct kcs_bmc` is renamed
>to `struct kcs_bmc_device` to contrast `struct kcs_bmc_client`.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> drivers/char/ipmi/kcs_bmc.c | 68 +++-
> drivers/char/ipmi/kcs_bmc.h | 86 +-----
> drivers/char/ipmi/kcs_bmc_aspeed.c | 22 +-
> drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 428 ++++++++++++++++----------
> drivers/char/ipmi/kcs_bmc_client.h | 28 +-
> drivers/char/ipmi/kcs_bmc_device.h | 12 +-
> drivers/char/ipmi/kcs_bmc_npcm7xx.c | 20 +-
> 7 files changed, 368 insertions(+), 296 deletions(-)
>
>diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
>index 1046ce2bbefc..266ebec71d6f 100644
>--- a/drivers/char/ipmi/kcs_bmc.c
>+++ b/drivers/char/ipmi/kcs_bmc.c
>@@ -4,6 +4,7 @@
> * Copyright (c) 2021, IBM Corp.
> */
>
>+#include <linux/device.h>
> #include <linux/module.h>
>
> #include "kcs_bmc.h"
>@@ -14,51 +15,96 @@
>
> /* Consumer data access */
>
>-u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
>+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 *kcs_bmc, u8 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 *kcs_bmc)
>+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 *kcs_bmc, u8 data)
>+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 *kcs_bmc, u8 mask, u8 val)
>+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);
>
>-int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
>+int kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc)
> {
>- return kcs_bmc->client.ops->event(&kcs_bmc->client);
>+ struct kcs_bmc_client *client;
>+ int rc;
>+
>+ spin_lock(&kcs_bmc->lock);
>+ client = kcs_bmc->client;
>+ if (client) {
>+ rc = client->ops->event(client);
>+ } else {
>+ u8 status;
>+
>+ status = kcs_bmc_read_status(kcs_bmc);
>+ if (status & KCS_BMC_STR_IBF) {
>+ /* Ack the event by reading the data */
>+ kcs_bmc_read_data(kcs_bmc);
>+ rc = KCS_BMC_EVENT_HANDLED;
>+ } else {
>+ rc = KCS_BMC_EVENT_NONE;
>+ }
>+ }
>+ spin_unlock(&kcs_bmc->lock);
>+
>+ return rc;
> }
> EXPORT_SYMBOL(kcs_bmc_handle_event);
>
>-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
>-int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc)
>+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 {
>+ kcs_bmc->client = client;
>+ 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)
>+ kcs_bmc->client = NULL;

Is there any situation in which a non-matching client could be passed in
here? Might we consider issuing a warning of some sort or returning an
error to the caller if so?

>+ spin_unlock_irq(&kcs_bmc->lock);
>+}
>+EXPORT_SYMBOL(kcs_bmc_disable_device);
>+
>+int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc)
> {
> return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
> }
> EXPORT_SYMBOL(kcs_bmc_add_device);
>
>-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
>-int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc)
>+int kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc)
> {
> return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
> }
>diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
>index a1350e567723..3f266740c759 100644
>--- a/drivers/char/ipmi/kcs_bmc.h
>+++ b/drivers/char/ipmi/kcs_bmc.h
>@@ -6,9 +6,7 @@
> #ifndef __KCS_BMC_H__
> #define __KCS_BMC_H__
>
>-#include <linux/miscdevice.h>
>-
>-#include "kcs_bmc_client.h"
>+#include <linux/list.h>
>
> #define KCS_BMC_EVENT_NONE 0
> #define KCS_BMC_EVENT_HANDLED 1
>@@ -17,53 +15,6 @@
> #define KCS_BMC_STR_IBF BIT(1)
> #define KCS_BMC_STR_CMD_DAT BIT(3)
>
>-/* 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_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_errors {
>- KCS_NO_ERROR = 0x00,
>- KCS_ABORTED_BY_COMMAND = 0x01,
>- KCS_ILLEGAL_CONTROL_CODE = 0x02,
>- KCS_LENGTH_ERROR = 0x06,
>- KCS_UNSPECIFIED_ERROR = 0xFF
>-};
>-
> /* IPMI 2.0 - 9.5, KCS Interface Registers
> * @idr: Input Data Register
> * @odr: Output Data Register
>@@ -76,36 +27,23 @@ struct kcs_ioreg {
> };
>
> struct kcs_bmc_device_ops;
>+struct kcs_bmc_client;
>+
>+struct kcs_bmc_device {
>+ struct list_head entry;
>
>-struct kcs_bmc {
> struct device *dev;
>-
>- const struct kcs_bmc_device_ops *ops;
>-
>- struct kcs_bmc_client client;
>-
>- spinlock_t lock;
>-
> u32 channel;
>- int running;
>
> struct kcs_ioreg ioreg;
>
>- enum kcs_phases phase;
>- enum kcs_errors error;
>+ const struct kcs_bmc_device_ops *ops;
>
>- 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;
>+ spinlock_t lock;
>+ struct kcs_bmc_client *client;
> };
>+
>+/* Temporary exports while refactoring */
>+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc);
>+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc);
> #endif /* __KCS_BMC_H__ */
>diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
>index 1b313355b1c8..6f26e7366c0b 100644
>--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
>+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
>@@ -61,7 +61,7 @@
> #define LPC_STR4 0x11C
>
> struct aspeed_kcs_bmc {
>- struct kcs_bmc kcs_bmc;
>+ struct kcs_bmc_device kcs_bmc;
>
> struct regmap *map;
> };
>@@ -71,12 +71,12 @@ struct aspeed_kcs_of_ops {
> int (*get_io_address)(struct platform_device *pdev);
> };
>
>-static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc *kcs_bmc)
>+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 *kcs_bmc, u32 reg)
>+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;
>@@ -88,7 +88,7 @@ static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
> return rc == 0 ? (u8) val : 0;
> }
>
>-static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
>+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;
>@@ -97,7 +97,7 @@ static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
> WARN(rc != 0, "regmap_write() failed: %d\n", rc);
> }
>
>-static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val)
>+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;
>@@ -119,7 +119,7 @@ static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val
> * C. KCS4
> * D / C : CA4h / CA5h
> */
>-static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
>+static void aspeed_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u16 addr)
> {
> struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>
>@@ -153,7 +153,7 @@ static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
> }
> }
>
>-static void aspeed_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
>+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);
>
>@@ -228,7 +228,7 @@ static const struct kcs_bmc_device_ops aspeed_kcs_ops = {
>
> static irqreturn_t aspeed_kcs_irq(int irq, void *arg)
> {
>- struct kcs_bmc *kcs_bmc = arg;
>+ struct kcs_bmc_device *kcs_bmc = arg;
> int rc;
>
> rc = kcs_bmc_handle_event(kcs_bmc);
>@@ -238,7 +238,7 @@ static irqreturn_t aspeed_kcs_irq(int irq, void *arg)
> return rc == KCS_BMC_EVENT_HANDLED ? IRQ_HANDLED : IRQ_NONE;
> }
>
>-static int aspeed_kcs_config_irq(struct kcs_bmc *kcs_bmc,
>+static int aspeed_kcs_config_irq(struct kcs_bmc_device *kcs_bmc,
> struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
>@@ -338,8 +338,8 @@ static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
> static int aspeed_kcs_probe(struct platform_device *pdev)
> {
> const struct aspeed_kcs_of_ops *ops;
>+ struct kcs_bmc_device *kcs_bmc;
> struct aspeed_kcs_bmc *priv;
>- struct kcs_bmc *kcs_bmc;
> struct device_node *np;
> int rc, channel, addr;
>
>@@ -400,7 +400,7 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> static int aspeed_kcs_remove(struct platform_device *pdev)
> {
> struct aspeed_kcs_bmc *priv = platform_get_drvdata(pdev);
>- struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
>+ struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc;
>
> kcs_bmc_remove_device(kcs_bmc);
>
>diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>index fd852d8abe48..58c42e76483d 100644
>--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>@@ -8,13 +8,88 @@
> #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.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"
>
>@@ -44,171 +119,169 @@ enum kcs_states {
> #define KCS_CMD_WRITE_END 0x62
> #define KCS_CMD_READ_BYTE 0x68
>
>-static inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
>+static inline void set_state(struct kcs_bmc_ipmi *priv, u8 state)
> {
>- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_STATE_MASK,
>- KCS_STATUS_STATE(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 *kcs_bmc)
>+static void kcs_bmc_ipmi_force_abort(struct kcs_bmc_ipmi *priv)
> {
>- set_state(kcs_bmc, ERROR_STATE);
>- kcs_bmc_read_data(kcs_bmc);
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>+ set_state(priv, ERROR_STATE);
>+ kcs_bmc_read_data(priv->client.dev);
>+ kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
>
>- kcs_bmc->phase = KCS_PHASE_ERROR;
>- kcs_bmc->data_in_avail = false;
>- kcs_bmc->data_in_idx = 0;
>+ 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 *kcs_bmc)
>+static void kcs_bmc_ipmi_handle_data(struct kcs_bmc_ipmi *priv)
> {
>+ struct kcs_bmc_device *dev;
> u8 data;
>
>- switch (kcs_bmc->phase) {
>+ dev = priv->client.dev;
>+
>+ switch (priv->phase) {
> case KCS_PHASE_WRITE_START:
>- kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
>+ priv->phase = KCS_PHASE_WRITE_DATA;
> fallthrough;
>
> case KCS_PHASE_WRITE_DATA:
>- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
>- set_state(kcs_bmc, WRITE_STATE);
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
>- kcs_bmc_read_data(kcs_bmc);
>+ 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(kcs_bmc);
>- kcs_bmc->error = KCS_LENGTH_ERROR;
>+ kcs_bmc_ipmi_force_abort(priv);
>+ priv->error = KCS_LENGTH_ERROR;
> }
> break;
>
> case KCS_PHASE_WRITE_END_CMD:
>- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
>- set_state(kcs_bmc, READ_STATE);
>- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
>- kcs_bmc_read_data(kcs_bmc);
>- kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
>- kcs_bmc->data_in_avail = true;
>- wake_up_interruptible(&kcs_bmc->queue);
>+ 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(kcs_bmc);
>- kcs_bmc->error = KCS_LENGTH_ERROR;
>+ kcs_bmc_ipmi_force_abort(priv);
>+ priv->error = KCS_LENGTH_ERROR;
> }
> break;
>
> case KCS_PHASE_READ:
>- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
>- set_state(kcs_bmc, IDLE_STATE);
>+ if (priv->data_out_idx == priv->data_out_len)
>+ set_state(priv, IDLE_STATE);
>
>- data = kcs_bmc_read_data(kcs_bmc);
>+ data = kcs_bmc_read_data(dev);
> if (data != KCS_CMD_READ_BYTE) {
>- set_state(kcs_bmc, ERROR_STATE);
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>+ set_state(priv, ERROR_STATE);
>+ kcs_bmc_write_data(dev, KCS_ZERO_DATA);
> break;
> }
>
>- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>- kcs_bmc->phase = KCS_PHASE_IDLE;
>+ 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(kcs_bmc,
>- kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
>+ kcs_bmc_write_data(dev, priv->data_out[priv->data_out_idx++]);
> break;
>
> case KCS_PHASE_ABORT_ERROR1:
>- set_state(kcs_bmc, READ_STATE);
>- kcs_bmc_read_data(kcs_bmc);
>- kcs_bmc_write_data(kcs_bmc, kcs_bmc->error);
>- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
>+ 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(kcs_bmc, IDLE_STATE);
>- kcs_bmc_read_data(kcs_bmc);
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>- kcs_bmc->phase = KCS_PHASE_IDLE;
>+ 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(kcs_bmc);
>+ kcs_bmc_ipmi_force_abort(priv);
> break;
> }
> }
>
>-static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc *kcs_bmc)
>+static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc_ipmi *priv)
> {
> u8 cmd;
>
>- set_state(kcs_bmc, WRITE_STATE);
>- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>+ set_state(priv, WRITE_STATE);
>+ kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
>
>- cmd = kcs_bmc_read_data(kcs_bmc);
>+ cmd = kcs_bmc_read_data(priv->client.dev);
> switch (cmd) {
> case KCS_CMD_WRITE_START:
>- kcs_bmc->phase = KCS_PHASE_WRITE_START;
>- kcs_bmc->error = KCS_NO_ERROR;
>- kcs_bmc->data_in_avail = false;
>- kcs_bmc->data_in_idx = 0;
>+ 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 (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
>- kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ if (priv->phase != KCS_PHASE_WRITE_DATA) {
>+ kcs_bmc_ipmi_force_abort(priv);
> break;
> }
>
>- kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
>+ priv->phase = KCS_PHASE_WRITE_END_CMD;
> break;
>
> case KCS_CMD_GET_STATUS_ABORT:
>- if (kcs_bmc->error == KCS_NO_ERROR)
>- kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
>+ if (priv->error == KCS_NO_ERROR)
>+ priv->error = KCS_ABORTED_BY_COMMAND;
>
>- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
>- kcs_bmc->data_in_avail = false;
>- kcs_bmc->data_in_idx = 0;
>+ priv->phase = KCS_PHASE_ABORT_ERROR1;
>+ priv->data_in_avail = false;
>+ priv->data_in_idx = 0;
> break;
>
> default:
>- kcs_bmc_ipmi_force_abort(kcs_bmc);
>- kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
>+ kcs_bmc_ipmi_force_abort(priv);
>+ priv->error = KCS_ILLEGAL_CONTROL_CODE;
> break;
> }
> }
>
>-static inline struct kcs_bmc *client_to_kcs_bmc(struct kcs_bmc_client *client)
>+static inline struct kcs_bmc_ipmi *client_to_kcs_bmc_ipmi(struct kcs_bmc_client *client)
> {
>- return container_of(client, struct kcs_bmc, client);
>+ return container_of(client, struct kcs_bmc_ipmi, client);
> }
>
> static int kcs_bmc_ipmi_event(struct kcs_bmc_client *client)
> {
>- struct kcs_bmc *kcs_bmc;
>- unsigned long flags;
>+ struct kcs_bmc_ipmi *priv;
> u8 status;
> int ret;
>
>- kcs_bmc = client_to_kcs_bmc(client);
>+ priv = client_to_kcs_bmc_ipmi(client);
>+ if (!priv)
>+ return KCS_BMC_EVENT_NONE;
>
>- spin_lock_irqsave(&kcs_bmc->lock, flags);
>+ spin_lock(&priv->lock);
>
>- status = kcs_bmc_read_status(kcs_bmc);
>+ status = kcs_bmc_read_status(client->dev);
> if (status & KCS_STATUS_IBF) {
>- if (!kcs_bmc->running)
>- kcs_bmc_ipmi_force_abort(kcs_bmc);
>- else if (status & KCS_STATUS_CMD_DAT)
>- kcs_bmc_ipmi_handle_cmd(kcs_bmc);
>+ if (status & KCS_STATUS_CMD_DAT)
>+ kcs_bmc_ipmi_handle_cmd(priv);
> else
>- kcs_bmc_ipmi_handle_data(kcs_bmc);
>+ kcs_bmc_ipmi_handle_data(priv);
>
> ret = KCS_BMC_EVENT_HANDLED;
> } else {
> ret = KCS_BMC_EVENT_NONE;
> }
>
>- spin_unlock_irqrestore(&kcs_bmc->lock, flags);
>+ spin_unlock(&priv->lock);
>
> return ret;
> }
>@@ -217,37 +290,29 @@ static const struct kcs_bmc_client_ops kcs_bmc_ipmi_client_ops = {
> .event = kcs_bmc_ipmi_event,
> };
>
>-static inline struct kcs_bmc *file_to_kcs_bmc(struct file *filp)
>+static inline struct kcs_bmc_ipmi *to_kcs_bmc(struct file *filp)
> {
>- return container_of(filp->private_data, struct kcs_bmc, miscdev);
>+ 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 *kcs_bmc = file_to_kcs_bmc(filp);
>- int ret = 0;
>+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
>
>- spin_lock_irq(&kcs_bmc->lock);
>- if (!kcs_bmc->running)
>- kcs_bmc->running = 1;
>- else
>- ret = -EBUSY;
>- spin_unlock_irq(&kcs_bmc->lock);
>-
>- return ret;
>+ 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 *kcs_bmc = file_to_kcs_bmc(filp);
>+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
> __poll_t mask = 0;
>
>- poll_wait(filp, &kcs_bmc->queue, wait);
>+ poll_wait(filp, &priv->queue, wait);
>
>- spin_lock_irq(&kcs_bmc->lock);
>- if (kcs_bmc->data_in_avail)
>+ spin_lock_irq(&priv->lock);
>+ if (priv->data_in_avail)
> mask |= EPOLLIN;
>- spin_unlock_irq(&kcs_bmc->lock);
>+ spin_unlock_irq(&priv->lock);
>
> return mask;
> }
>@@ -255,24 +320,24 @@ static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
> static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
> size_t count, loff_t *ppos)
> {
>- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
>+ 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(kcs_bmc->queue,
>- kcs_bmc->data_in_avail);
>+ wait_event_interruptible(priv->queue,
>+ priv->data_in_avail);
>
>- mutex_lock(&kcs_bmc->mutex);
>+ mutex_lock(&priv->mutex);
>
>- spin_lock_irq(&kcs_bmc->lock);
>- data_avail = kcs_bmc->data_in_avail;
>+ spin_lock_irq(&priv->lock);
>+ data_avail = priv->data_in_avail;
> if (data_avail) {
>- data_len = kcs_bmc->data_in_idx;
>- memcpy(kcs_bmc->kbuffer, kcs_bmc->data_in, data_len);
>+ data_len = priv->data_in_idx;
>+ memcpy(priv->kbuffer, priv->data_in, data_len);
> }
>- spin_unlock_irq(&kcs_bmc->lock);
>+ spin_unlock_irq(&priv->lock);
>
> if (!data_avail) {
> ret = -EAGAIN;
>@@ -281,35 +346,35 @@ static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
>
> if (count < data_len) {
> pr_err("channel=%u with too large data : %zu\n",
>- kcs_bmc->channel, data_len);
>+ priv->client.dev->channel, data_len);
>
>- spin_lock_irq(&kcs_bmc->lock);
>- kcs_bmc_ipmi_force_abort(kcs_bmc);
>- spin_unlock_irq(&kcs_bmc->lock);
>+ 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, kcs_bmc->kbuffer, data_len)) {
>+ if (copy_to_user(buf, priv->kbuffer, data_len)) {
> ret = -EFAULT;
> goto out_unlock;
> }
>
> ret = data_len;
>
>- spin_lock_irq(&kcs_bmc->lock);
>- if (kcs_bmc->phase == KCS_PHASE_WRITE_DONE) {
>- kcs_bmc->phase = KCS_PHASE_WAIT_READ;
>- kcs_bmc->data_in_avail = false;
>- kcs_bmc->data_in_idx = 0;
>+ 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(&kcs_bmc->lock);
>+ spin_unlock_irq(&priv->lock);
>
> out_unlock:
>- mutex_unlock(&kcs_bmc->mutex);
>+ mutex_unlock(&priv->mutex);
>
> return ret;
> }
>@@ -317,35 +382,35 @@ static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
> static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
> size_t count, loff_t *ppos)
> {
>- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
>+ 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(&kcs_bmc->mutex);
>+ mutex_lock(&priv->mutex);
>
>- if (copy_from_user(kcs_bmc->kbuffer, buf, count)) {
>+ if (copy_from_user(priv->kbuffer, buf, count)) {
> ret = -EFAULT;
> goto out_unlock;
> }
>
>- spin_lock_irq(&kcs_bmc->lock);
>- if (kcs_bmc->phase == KCS_PHASE_WAIT_READ) {
>- kcs_bmc->phase = KCS_PHASE_READ;
>- kcs_bmc->data_out_idx = 1;
>- kcs_bmc->data_out_len = count;
>- memcpy(kcs_bmc->data_out, kcs_bmc->kbuffer, count);
>- kcs_bmc_write_data(kcs_bmc, kcs_bmc->data_out[0]);
>+ 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(&kcs_bmc->lock);
>+ spin_unlock_irq(&priv->lock);
>
> out_unlock:
>- mutex_unlock(&kcs_bmc->mutex);
>+ mutex_unlock(&priv->mutex);
>
> return ret;
> }
>@@ -353,22 +418,22 @@ static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
> static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
> unsigned long arg)
> {
>- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
>+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
> long ret = 0;
>
>- spin_lock_irq(&kcs_bmc->lock);
>+ spin_lock_irq(&priv->lock);
>
> switch (cmd) {
> case IPMI_BMC_IOCTL_SET_SMS_ATN:
>- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, KCS_STATUS_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(kcs_bmc, KCS_STATUS_SMS_ATN, 0);
>+ kcs_bmc_update_status(priv->client.dev, KCS_STATUS_SMS_ATN, 0);
> break;
>
> case IPMI_BMC_IOCTL_FORCE_ABORT:
>- kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ kcs_bmc_ipmi_force_abort(priv);
> break;
>
> default:
>@@ -376,19 +441,17 @@ static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
> break;
> }
>
>- spin_unlock_irq(&kcs_bmc->lock);
>+ spin_unlock_irq(&priv->lock);
>
> return ret;
> }
>
> static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
> {
>- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
>+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
>
>- spin_lock_irq(&kcs_bmc->lock);
>- kcs_bmc->running = 0;
>- kcs_bmc_ipmi_force_abort(kcs_bmc);
>- spin_unlock_irq(&kcs_bmc->lock);
>+ kcs_bmc_ipmi_force_abort(priv);
>+ kcs_bmc_disable_device(priv->client.dev, &priv->client);
>
> return 0;
> }
>@@ -403,56 +466,78 @@ static const struct file_operations kcs_bmc_ipmi_fops = {
> .unlocked_ioctl = kcs_bmc_ipmi_ioctl,
> };
>
>-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
>-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc)
>+static DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock);
>+static LIST_HEAD(kcs_bmc_ipmi_instances);
>+
>+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc);
>+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc)
> {
>+ struct kcs_bmc_ipmi *priv;
> int rc;
>
>- spin_lock_init(&kcs_bmc->lock);
>- mutex_init(&kcs_bmc->mutex);
>- init_waitqueue_head(&kcs_bmc->queue);
>-
>- kcs_bmc->client.dev = kcs_bmc;
>- kcs_bmc->client.ops = &kcs_bmc_ipmi_client_ops;
>- kcs_bmc->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>- kcs_bmc->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>- kcs_bmc->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>-
>- kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
>- kcs_bmc->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u",
>- DEVICE_NAME, kcs_bmc->channel);
>- if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
>- !kcs_bmc->miscdev.name)
>+ priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
>+ if (!priv)
> return -ENOMEM;
>
>- kcs_bmc->miscdev.fops = &kcs_bmc_ipmi_fops;
>+ spin_lock_init(&priv->lock);
>+ mutex_init(&priv->mutex);
>
>- rc = misc_register(&kcs_bmc->miscdev);
>+ 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;
> }
> EXPORT_SYMBOL(kcs_bmc_ipmi_attach_cdev);
>
>-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
>-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc)
>+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc);
>+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc)
> {
>- misc_deregister(&kcs_bmc->miscdev);
>+ struct kcs_bmc_ipmi *priv = NULL, *pos;
>
>- spin_lock_irq(&kcs_bmc->lock);
>- kcs_bmc->running = 0;
>- kcs_bmc_ipmi_force_abort(kcs_bmc);
>- spin_unlock_irq(&kcs_bmc->lock);
>+ 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);
>
>- devm_kfree(kcs_bmc->dev, kcs_bmc->kbuffer);
>- devm_kfree(kcs_bmc->dev, kcs_bmc->data_out);
>- devm_kfree(kcs_bmc->dev, kcs_bmc->data_in);
>- devm_kfree(kcs_bmc->dev, kcs_bmc);
>+ if (!priv)
>+ return 0;

Would -ENOENT or something be appropriate here if the thing we're trying
to detach isn't found?

>+
>+ 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;
> }
>@@ -460,4 +545,5 @@ EXPORT_SYMBOL(kcs_bmc_ipmi_detach_cdev);
>
> MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Haiyue Wang <[email protected]>");
>+MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
> MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
>diff --git a/drivers/char/ipmi/kcs_bmc_client.h b/drivers/char/ipmi/kcs_bmc_client.h
>index 140631d157d8..2dd710f4b4aa 100644
>--- a/drivers/char/ipmi/kcs_bmc_client.h
>+++ b/drivers/char/ipmi/kcs_bmc_client.h
>@@ -8,22 +8,24 @@
> #include <linux/notifier.h>
> #include <stdbool.h>
>
>-struct kcs_bmc;
>-struct kcs_bmc_client_ops;
>-
>-struct kcs_bmc_client {
>- const struct kcs_bmc_client_ops *ops;
>-
>- struct kcs_bmc *dev;
>-};
>+#include "kcs_bmc.h"
>
> struct kcs_bmc_client_ops {
> int (*event)(struct kcs_bmc_client *client);
> };
>
>-u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
>-void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
>-u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc);
>-void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data);
>-void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val);
>+struct kcs_bmc_client {
>+ const struct kcs_bmc_client_ops *ops;
>+
>+ struct kcs_bmc_device *dev;
>+};
>+
>+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);
>+
>+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
>diff --git a/drivers/char/ipmi/kcs_bmc_device.h b/drivers/char/ipmi/kcs_bmc_device.h
>index 33462174516d..57b7174b2bac 100644
>--- a/drivers/char/ipmi/kcs_bmc_device.h
>+++ b/drivers/char/ipmi/kcs_bmc_device.h
>@@ -7,13 +7,13 @@
> #include "kcs_bmc.h"
>
> struct kcs_bmc_device_ops {
>- u8 (*io_inputb)(struct kcs_bmc *kcs_bmc, u32 reg);
>- void (*io_outputb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 b);
>- void (*io_updateb)(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 b);
>+ 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);
> };
>
>-int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
>-int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc);
>-int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc);
>+int kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc);
>+int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc);
>+int kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc);
>
> #endif
>diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>index 1d21697fc585..dce93ec895fc 100644
>--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>@@ -65,7 +65,7 @@ struct npcm7xx_kcs_reg {
> };
>
> struct npcm7xx_kcs_bmc {
>- struct kcs_bmc kcs_bmc;
>+ struct kcs_bmc_device kcs_bmc;
>
> struct regmap *map;
>
>@@ -78,12 +78,12 @@ static const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = {
> { .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE },
> };
>
>-static inline struct npcm7xx_kcs_bmc *to_npcm7xx_kcs_bmc(struct kcs_bmc *kcs_bmc)
>+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 *kcs_bmc, u32 reg)
>+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;
>@@ -95,7 +95,7 @@ static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
> return rc == 0 ? (u8)val : 0;
> }
>
>-static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
>+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;
>@@ -104,7 +104,7 @@ static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
> WARN(rc != 0, "regmap_write() failed: %d\n", rc);
> }
>
>-static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 data)
>+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;
>@@ -113,7 +113,7 @@ static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 da
> WARN(rc != 0, "regmap_update_bits() failed: %d\n", rc);
> }
>
>-static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
>+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);
>
>@@ -126,7 +126,7 @@ static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
>
> static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
> {
>- struct kcs_bmc *kcs_bmc = arg;
>+ struct kcs_bmc_device *kcs_bmc = arg;
> int rc;
>
> rc = kcs_bmc_handle_event(kcs_bmc);
>@@ -136,7 +136,7 @@ static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
> return rc == KCS_BMC_EVENT_HANDLED ? IRQ_HANDLED : IRQ_NONE;
> }
>
>-static int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc,
>+static int npcm7xx_kcs_config_irq(struct kcs_bmc_device *kcs_bmc,
> struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
>@@ -160,7 +160,7 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> struct npcm7xx_kcs_bmc *priv;
>- struct kcs_bmc *kcs_bmc;
>+ struct kcs_bmc_device *kcs_bmc;
> u32 chan;
> int rc;
>
>@@ -207,7 +207,7 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
> static int npcm7xx_kcs_remove(struct platform_device *pdev)
> {
> struct npcm7xx_kcs_bmc *priv = platform_get_drvdata(pdev);
>- struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
>+ struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc;
>
> kcs_bmc_remove_device(kcs_bmc);
>
>--
>2.27.0
>

2021-04-09 04:27:14

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 06/21] ipmi: kcs_bmc_aspeed: Use of match data to extract KCS properties

On Fri, Mar 19, 2021 at 01:27:37AM CDT, Andrew Jeffery wrote:
>Unpack and remove the aspeed_kcs_probe_of_v[12]() functions to aid
>rearranging how the private device-driver memory is allocated.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> drivers/char/ipmi/kcs_bmc_aspeed.c | 146 ++++++++++++++---------------
> 1 file changed, 68 insertions(+), 78 deletions(-)
>
>diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
>index eefe362f65f0..061f53676206 100644
>--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
>+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
>@@ -13,6 +13,7 @@
> #include <linux/module.h>
> #include <linux/of.h>
> #include <linux/of_address.h>
>+#include <linux/of_device.h>
> #include <linux/platform_device.h>
> #include <linux/poll.h>
> #include <linux/regmap.h>
>@@ -63,6 +64,10 @@ struct aspeed_kcs_bmc {
> struct regmap *map;
> };
>
>+struct aspeed_kcs_of_ops {
>+ int (*get_channel)(struct platform_device *pdev);
>+ int (*get_io_address)(struct platform_device *pdev);
>+};
>
> static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
> {
>@@ -231,13 +236,10 @@ static const struct kcs_ioreg ast_kcs_bmc_ioregs[KCS_CHANNEL_MAX] = {
> { .idr = LPC_IDR4, .odr = LPC_ODR4, .str = LPC_STR4 },
> };
>
>-static struct kcs_bmc *aspeed_kcs_probe_of_v1(struct platform_device *pdev)
>+static int aspeed_kcs_of_v1_get_channel(struct platform_device *pdev)
> {
>- struct aspeed_kcs_bmc *priv;
> struct device_node *np;
>- struct kcs_bmc *kcs;
> u32 channel;
>- u32 slave;
> int rc;
>
> np = pdev->dev.of_node;
>@@ -245,105 +247,78 @@ static struct kcs_bmc *aspeed_kcs_probe_of_v1(struct platform_device *pdev)
> rc = of_property_read_u32(np, "kcs_chan", &channel);
> if ((rc != 0) || (channel == 0 || channel > KCS_CHANNEL_MAX)) {
> dev_err(&pdev->dev, "no valid 'kcs_chan' configured\n");
>- return ERR_PTR(-EINVAL);
>+ return -EINVAL;
> }
>
>- kcs = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
>- if (!kcs)
>- return ERR_PTR(-ENOMEM);
>+ return channel;
>+}
>
>- priv = kcs_bmc_priv(kcs);
>- 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 ERR_PTR(-ENODEV);
>- }
>+static int aspeed_kcs_of_v1_get_io_address(struct platform_device *pdev)
>+{
>+ u32 slave;
>+ int rc;
>
>- rc = of_property_read_u32(np, "kcs_addr", &slave);
>- if (rc) {
>+ rc = of_property_read_u32(pdev->dev.of_node, "kcs_addr", &slave);
>+ if (rc || slave > 0xffff) {
> dev_err(&pdev->dev, "no valid 'kcs_addr' configured\n");
>- return ERR_PTR(-EINVAL);
>+ return -EINVAL;
> }
>
>- kcs->ioreg = ast_kcs_bmc_ioregs[channel - 1];
>- aspeed_kcs_set_address(kcs, slave);
>-
>- return kcs;
>-}
>-
>-static int aspeed_kcs_calculate_channel(const struct kcs_ioreg *regs)
>-{
>- int i;
>-
>- for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) {
>- if (!memcmp(&ast_kcs_bmc_ioregs[i], regs, sizeof(*regs)))
>- return i + 1;
>- }
>-
>- return -EINVAL;
>+ return slave;
> }
>
>-static struct kcs_bmc *aspeed_kcs_probe_of_v2(struct platform_device *pdev)
>+static int aspeed_kcs_of_v2_get_channel(struct platform_device *pdev)
> {
>- struct aspeed_kcs_bmc *priv;
> struct device_node *np;
> struct kcs_ioreg ioreg;
>- struct kcs_bmc *kcs;
> const __be32 *reg;
>- int channel;
>- u32 slave;
>- int rc;
>+ 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 ERR_PTR(-EINVAL);
>+ return -EINVAL;
> ioreg.idr = be32_to_cpup(reg);
>
> reg = of_get_address(np, 1, NULL, NULL);
> if (!reg)
>- return ERR_PTR(-EINVAL);
>+ return -EINVAL;
> ioreg.odr = be32_to_cpup(reg);
>
> reg = of_get_address(np, 2, NULL, NULL);
> if (!reg)
>- return ERR_PTR(-EINVAL);
>+ return -EINVAL;
> ioreg.str = be32_to_cpup(reg);
>
>- channel = aspeed_kcs_calculate_channel(&ioreg);
>- if (channel < 0)
>- return ERR_PTR(channel);
>-
>- kcs = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
>- if (!kcs)
>- return ERR_PTR(-ENOMEM);
>-
>- kcs->ioreg = ioreg;
>-
>- priv = kcs_bmc_priv(kcs);
>- 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 ERR_PTR(-ENODEV);
>+ for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) {
>+ if (!memcmp(&ast_kcs_bmc_ioregs[i], &ioreg, sizeof(ioreg)))
>+ return i + 1;

Did some patches perhaps get a bit jumbled during a rebase here or
something? This patch removes the only initialization of priv->map
I can see, with patch #10 later reintroducing it, but in the interim
it seems like it would be in a fairly broken state (I'm guessing its
removal wasn't really intended to be part of this).

> }
>
>- rc = of_property_read_u32(np, "aspeed,lpc-io-reg", &slave);
>- if (rc)
>- return ERR_PTR(rc);
>+ return -EINVAL;
>+}
>
>- aspeed_kcs_set_address(kcs, slave);
>+static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
>+{
>+ uint32_t slave;
>+ int rc;
>
>- return kcs;
>+ rc = of_property_read_u32(pdev->dev.of_node, "aspeed,lpc-io-reg", &slave);
>+ if (rc || slave > 0xffff)
>+ return -EINVAL;
>+
>+ return slave;
> }
>
> static int aspeed_kcs_probe(struct platform_device *pdev)
> {
>+ const struct aspeed_kcs_of_ops *ops;
> struct device *dev = &pdev->dev;
> struct kcs_bmc *kcs_bmc;
> struct device_node *np;
>- int rc;
>+ int rc, channel, addr;
>
> np = dev->of_node->parent;
> if (!of_device_is_compatible(np, "aspeed,ast2400-lpc-v2") &&
>@@ -352,23 +327,28 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> dev_err(dev, "unsupported LPC device binding\n");
> return -ENODEV;
> }
>-
>- np = dev->of_node;
>- if (of_device_is_compatible(np, "aspeed,ast2400-kcs-bmc") ||
>- of_device_is_compatible(np, "aspeed,ast2500-kcs-bmc"))
>- kcs_bmc = aspeed_kcs_probe_of_v1(pdev);
>- else if (of_device_is_compatible(np, "aspeed,ast2400-kcs-bmc-v2") ||
>- of_device_is_compatible(np, "aspeed,ast2500-kcs-bmc-v2"))
>- kcs_bmc = aspeed_kcs_probe_of_v2(pdev);
>- else
>+ ops = of_device_get_match_data(&pdev->dev);
>+ if (!ops)
> return -EINVAL;
>
>- if (IS_ERR(kcs_bmc))
>- return PTR_ERR(kcs_bmc);
>+ channel = ops->get_channel(pdev);
>+ if (channel < 0)
>+ return channel;
>
>+ kcs_bmc = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
>+ if (!kcs_bmc)
>+ return -ENOMEM;
>+
>+ kcs_bmc->ioreg = ast_kcs_bmc_ioregs[channel - 1];
> kcs_bmc->io_inputb = aspeed_kcs_inb;
> kcs_bmc->io_outputb = aspeed_kcs_outb;
>
>+ addr = ops->get_io_address(pdev);
>+ if (addr < 0)
>+ return addr;
>+
>+ aspeed_kcs_set_address(kcs_bmc, addr);
>+
> rc = aspeed_kcs_config_irq(kcs_bmc, pdev);
> if (rc)
> return rc;
>@@ -400,11 +380,21 @@ static int aspeed_kcs_remove(struct platform_device *pdev)
> return 0;
> }
>
>+static const struct aspeed_kcs_of_ops of_v1_ops = {
>+ .get_channel = aspeed_kcs_of_v1_get_channel,
>+ .get_io_address = aspeed_kcs_of_v1_get_io_address,
>+};
>+
>+static const struct aspeed_kcs_of_ops of_v2_ops = {
>+ .get_channel = aspeed_kcs_of_v2_get_channel,
>+ .get_io_address = aspeed_kcs_of_v2_get_io_address,
>+};
>+
> static const struct of_device_id ast_kcs_bmc_match[] = {
>- { .compatible = "aspeed,ast2400-kcs-bmc" },
>- { .compatible = "aspeed,ast2500-kcs-bmc" },
>- { .compatible = "aspeed,ast2400-kcs-bmc-v2" },
>- { .compatible = "aspeed,ast2500-kcs-bmc-v2" },
>+ { .compatible = "aspeed,ast2400-kcs-bmc", .data = &of_v1_ops },
>+ { .compatible = "aspeed,ast2500-kcs-bmc", .data = &of_v1_ops },
>+ { .compatible = "aspeed,ast2400-kcs-bmc-v2", .data = &of_v2_ops },
>+ { .compatible = "aspeed,ast2500-kcs-bmc-v2", .data = &of_v2_ops },
> { }
> };
> MODULE_DEVICE_TABLE(of, ast_kcs_bmc_match);
>--
>2.27.0
>

2021-04-09 04:37:40

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 10/21] ipmi: kcs_bmc: Turn the driver data-structures inside-out

On Fri, Mar 19, 2021 at 01:27:41AM CDT, Andrew Jeffery wrote:
>Make the KCS device drivers responsible for allocating their own memory.
>
>Until now the private data for the device driver was allocated internal
>to the private data for the chardev interface. This coupling required
>the slightly awkward API of passing through the struct size for the
>driver private data to the chardev constructor, and then retrieving a
>pointer to the driver private data from the allocated chardev memory.
>
>In addition to being awkward, the arrangement prevents the
>implementation of alternative userspace interfaces as the device driver
>private data is not independent.
>
>Peel a layer off the onion and turn the data-structures inside out by
>exploiting container_of() and embedding `struct kcs_device` in the
>driver private data.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> drivers/char/ipmi/kcs_bmc.c | 15 +++++--
> drivers/char/ipmi/kcs_bmc.h | 12 ++----
> drivers/char/ipmi/kcs_bmc_aspeed.c | 60 ++++++++++++++++-----------
> drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 60 ++++++++++++++++++---------
> drivers/char/ipmi/kcs_bmc_npcm7xx.c | 37 ++++++++++-------
> 5 files changed, 113 insertions(+), 71 deletions(-)
>
>diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
>index ef5c48ffe74a..709b6bdec165 100644
>--- a/drivers/char/ipmi/kcs_bmc.c
>+++ b/drivers/char/ipmi/kcs_bmc.c
>@@ -44,12 +44,19 @@ int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
> }
> EXPORT_SYMBOL(kcs_bmc_handle_event);
>
>-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
>-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel)
>+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);

Another declaration perhaps intended for kcs_bmc.h?

>+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc)
> {
>- return kcs_bmc_ipmi_alloc(dev, sizeof_priv, channel);
>+ return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
> }
>-EXPORT_SYMBOL(kcs_bmc_alloc);
>+EXPORT_SYMBOL(kcs_bmc_add_device);
>+
>+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);

Here too.

>+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc)
>+{
>+ return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
>+}
>+EXPORT_SYMBOL(kcs_bmc_remove_device);
>
> MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Haiyue Wang <[email protected]>");
>diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
>index febea0c8deb4..bf0ae327997f 100644
>--- a/drivers/char/ipmi/kcs_bmc.h
>+++ b/drivers/char/ipmi/kcs_bmc.h
>@@ -67,6 +67,8 @@ struct kcs_ioreg {
> };
>
> struct kcs_bmc {
>+ struct device *dev;
>+
> spinlock_t lock;
>
> u32 channel;
>@@ -94,17 +96,11 @@ struct kcs_bmc {
> u8 *kbuffer;
>
> struct miscdevice miscdev;
>-
>- unsigned long priv[];
> };
>
>-static inline void *kcs_bmc_priv(struct kcs_bmc *kcs_bmc)
>-{
>- return kcs_bmc->priv;
>-}
>-
> int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
>-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel);
>+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc);
>+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc);
>
> u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
> void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
>diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
>index 630cf095560e..0416ac78ce68 100644
>--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
>+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
>@@ -61,6 +61,8 @@
> #define LPC_STR4 0x11C
>
> struct aspeed_kcs_bmc {
>+ struct kcs_bmc kcs_bmc;
>+
> struct regmap *map;
> };
>
>@@ -69,9 +71,14 @@ struct aspeed_kcs_of_ops {
> int (*get_io_address)(struct platform_device *pdev);
> };
>
>+static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc *kcs_bmc)
>+{
>+ return container_of(kcs_bmc, struct aspeed_kcs_bmc, kcs_bmc);
>+}
>+
> static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
> {
>- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
> u32 val = 0;
> int rc;
>
>@@ -83,7 +90,7 @@ static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
>
> static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
> {
>- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
> int rc;
>
> rc = regmap_write(priv->map, reg, data);
>@@ -92,7 +99,7 @@ static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
>
> static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val)
> {
>- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
> int rc;
>
> rc = regmap_update_bits(priv->map, reg, mask, val);
>@@ -114,7 +121,7 @@ static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val
> */
> static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
> {
>- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>
> switch (kcs_bmc->channel) {
> case 1:
>@@ -148,7 +155,7 @@ static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
>
> static void aspeed_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
> {
>- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>
> switch (kcs_bmc->channel) {
> case 1:
>@@ -323,16 +330,16 @@ static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
> static int aspeed_kcs_probe(struct platform_device *pdev)
> {
> const struct aspeed_kcs_of_ops *ops;
>- struct device *dev = &pdev->dev;
>+ struct aspeed_kcs_bmc *priv;
> struct kcs_bmc *kcs_bmc;
> struct device_node *np;
> int rc, channel, addr;
>
>- np = dev->of_node->parent;
>+ 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(dev, "unsupported LPC device binding\n");
>+ dev_err(&pdev->dev, "unsupported LPC device binding\n");
> return -ENODEV;
> }
> ops = of_device_get_match_data(&pdev->dev);
>@@ -343,18 +350,27 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> if (channel < 0)
> return channel;
>
>- kcs_bmc = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
>- if (!kcs_bmc)
>+ addr = ops->get_io_address(pdev);
>+ if (addr < 0)
>+ return addr;
>+
>+ 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->io_inputb = aspeed_kcs_inb;
> kcs_bmc->io_outputb = aspeed_kcs_outb;
> kcs_bmc->io_updateb = aspeed_kcs_updateb;
>
>- addr = ops->get_io_address(pdev);
>- if (addr < 0)
>- return addr;
>+ 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;
>+ }

The reanimated priv->map initialization I suspect wasn't meant to
have been removed in the first place...

>
> aspeed_kcs_set_address(kcs_bmc, addr);
>
>@@ -362,29 +378,25 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> if (rc)
> return rc;
>
>- dev_set_drvdata(dev, kcs_bmc);
>+ platform_set_drvdata(pdev, priv);
>
> aspeed_kcs_enable_channel(kcs_bmc, true);
>
>- rc = misc_register(&kcs_bmc->miscdev);
>- if (rc) {
>- dev_err(dev, "Unable to register device\n");
>+ rc = kcs_bmc_add_device(&priv->kcs_bmc);
>+ if (rc < 0)
> return rc;
>- }
>
>- dev_dbg(&pdev->dev,
>- "Probed KCS device %d (IDR=0x%x, ODR=0x%x, STR=0x%x)\n",
>- kcs_bmc->channel, kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr,
>- kcs_bmc->ioreg.str);
>+ dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n", kcs_bmc->channel, addr);

Is the dbg->info change here intentional? (I have no particular
objection if so, but it's often a change I make myself during
testing/debugging and then forget to revert...)

>
> return 0;
> }
>
> static int aspeed_kcs_remove(struct platform_device *pdev)
> {
>- struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
>+ struct aspeed_kcs_bmc *priv = platform_get_drvdata(pdev);
>+ struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
>
>- misc_deregister(&kcs_bmc->miscdev);
>+ kcs_bmc_remove_device(kcs_bmc);

Should we propagate the return value outward here?

>
> return 0;
> }
>diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>index 82c77994e481..0ca71c135a1a 100644
>--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>@@ -382,7 +382,7 @@ static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
> return 0;
> }
>
>-static const struct file_operations kcs_bmc_fops = {
>+static const struct file_operations kcs_bmc_ipmi_fops = {
> .owner = THIS_MODULE,
> .open = kcs_bmc_ipmi_open,
> .read = kcs_bmc_ipmi_read,
>@@ -392,36 +392,58 @@ static const struct file_operations kcs_bmc_fops = {
> .unlocked_ioctl = kcs_bmc_ipmi_ioctl,
> };
>
>-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
>-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel)
>+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);

Errant declaration again?

>+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc)
> {
>- struct kcs_bmc *kcs_bmc;
>-
>- kcs_bmc = devm_kzalloc(dev, sizeof(*kcs_bmc) + sizeof_priv, GFP_KERNEL);
>- if (!kcs_bmc)
>- return NULL;
>+ int rc;
>
> spin_lock_init(&kcs_bmc->lock);
>- kcs_bmc->channel = channel;
>-
> mutex_init(&kcs_bmc->mutex);
> init_waitqueue_head(&kcs_bmc->queue);
>
>- kcs_bmc->data_in = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>- kcs_bmc->data_out = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>- kcs_bmc->kbuffer = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>+ kcs_bmc->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>+ kcs_bmc->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>+ kcs_bmc->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>
> kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
>- kcs_bmc->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%u",
>- DEVICE_NAME, channel);
>+ kcs_bmc->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u",
>+ DEVICE_NAME, kcs_bmc->channel);
> if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
> !kcs_bmc->miscdev.name)
>- return NULL;
>- kcs_bmc->miscdev.fops = &kcs_bmc_fops;
>+ return -ENOMEM;
>
>- return kcs_bmc;
>+ kcs_bmc->miscdev.fops = &kcs_bmc_ipmi_fops;
>+
>+ rc = misc_register(&kcs_bmc->miscdev);
>+ if (rc) {
>+ dev_err(kcs_bmc->dev, "Unable to register device: %d\n", rc);
>+ return rc;
>+ }
>+
>+ dev_info(kcs_bmc->dev, "Initialised IPMI client for channel %d", kcs_bmc->channel);
>+
>+ return 0;
>+}
>+EXPORT_SYMBOL(kcs_bmc_ipmi_attach_cdev);
>+
>+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);

Same here.

>+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc)
>+{
>+ misc_deregister(&kcs_bmc->miscdev);
>+
>+ spin_lock_irq(&kcs_bmc->lock);
>+ kcs_bmc->running = 0;
>+ kcs_bmc_ipmi_force_abort(kcs_bmc);
>+ spin_unlock_irq(&kcs_bmc->lock);
>+
>+ devm_kfree(kcs_bmc->dev, kcs_bmc->kbuffer);
>+ devm_kfree(kcs_bmc->dev, kcs_bmc->data_out);
>+ devm_kfree(kcs_bmc->dev, kcs_bmc->data_in);
>+ devm_kfree(kcs_bmc->dev, kcs_bmc);
>+
>+ return 0;
> }
>-EXPORT_SYMBOL(kcs_bmc_ipmi_alloc);
>+EXPORT_SYMBOL(kcs_bmc_ipmi_detach_cdev);
>
> MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Haiyue Wang <[email protected]>");
>diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>index 1f44aadec9e8..5d017498dc69 100644
>--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>@@ -65,6 +65,8 @@ struct npcm7xx_kcs_reg {
> };
>
> struct npcm7xx_kcs_bmc {
>+ struct kcs_bmc kcs_bmc;
>+
> struct regmap *map;
>
> const struct npcm7xx_kcs_reg *reg;
>@@ -76,9 +78,14 @@ static const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = {
> { .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE },
> };
>
>+static inline struct npcm7xx_kcs_bmc *to_npcm7xx_kcs_bmc(struct kcs_bmc *kcs_bmc)
>+{
>+ return container_of(kcs_bmc, struct npcm7xx_kcs_bmc, kcs_bmc);
>+}
>+
> static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
> {
>- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
> u32 val = 0;
> int rc;
>
>@@ -90,7 +97,7 @@ static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
>
> static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
> {
>- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
> int rc;
>
> rc = regmap_write(priv->map, reg, data);
>@@ -99,7 +106,7 @@ static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
>
> static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 data)
> {
>- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
> int rc;
>
> rc = regmap_update_bits(priv->map, reg, mask, data);
>@@ -108,7 +115,7 @@ static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 da
>
> static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
> {
>- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
>
> regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
> enable ? KCS_CTL_IBFIE : 0);
>@@ -155,11 +162,10 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
> return -ENODEV;
> }
>
>- kcs_bmc = kcs_bmc_alloc(dev, sizeof(*priv), chan);
>- if (!kcs_bmc)
>+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
>+ if (!priv)
> return -ENOMEM;
>
>- priv = kcs_bmc_priv(kcs_bmc);
> priv->map = syscon_node_to_regmap(dev->parent->of_node);
> if (IS_ERR(priv->map)) {
> dev_err(dev, "Couldn't get regmap\n");
>@@ -167,6 +173,9 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
> }
> 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;
>@@ -174,31 +183,27 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
> kcs_bmc->io_outputb = npcm7xx_kcs_outb;
> kcs_bmc->io_updateb = npcm7xx_kcs_updateb;
>
>- dev_set_drvdata(dev, kcs_bmc);
>+ platform_set_drvdata(pdev, priv);
>
> npcm7xx_kcs_enable_channel(kcs_bmc, true);
> rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev);
> if (rc)
> return rc;
>
>- rc = misc_register(&kcs_bmc->miscdev);
>- if (rc) {
>- dev_err(dev, "Unable to register device\n");
>- 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;
>+ return kcs_bmc_add_device(kcs_bmc);
> }
>
> static int npcm7xx_kcs_remove(struct platform_device *pdev)
> {
>- struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
>+ struct npcm7xx_kcs_bmc *priv = platform_get_drvdata(pdev);
>+ struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
>
>- misc_deregister(&kcs_bmc->miscdev);
>+ kcs_bmc_remove_device(kcs_bmc);

As with the corresponding aspeed code, should we propagate the return
value here?

>
> return 0;
> }
>--
>2.27.0
>

2021-04-09 04:40:04

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 14/21] ipmi: kcs_bmc: Allow clients to control KCS IRQ state

On Fri, Mar 19, 2021 at 01:27:45AM CDT, Andrew Jeffery wrote:
>Add a mechanism for controlling whether the client associated with a
>KCS device will receive Input Buffer Full (IBF) and Output Buffer Empty
>(OBE) events. This enables an abstract implementation of poll() for KCS
>devices.
>
>A wart in the implementation is that the ASPEED KCS devices don't
>support an OBE interrupt for the BMC. Instead we pretend it has one by
>polling the status register waiting for the Output Buffer Full (OBF) bit
>to clear, and generating an event when OBE is observed.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> drivers/char/ipmi/kcs_bmc.c | 6 ++
> drivers/char/ipmi/kcs_bmc.h | 3 +
> drivers/char/ipmi/kcs_bmc_aspeed.c | 150 ++++++++++++++++++----------
> drivers/char/ipmi/kcs_bmc_client.h | 2 +
> drivers/char/ipmi/kcs_bmc_device.h | 1 +
> drivers/char/ipmi/kcs_bmc_npcm7xx.c | 25 ++++-
> 6 files changed, 130 insertions(+), 57 deletions(-)
>
>diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
>index 694db6ee2a92..05bbb72418b2 100644
>--- a/drivers/char/ipmi/kcs_bmc.c
>+++ b/drivers/char/ipmi/kcs_bmc.c
>@@ -184,6 +184,12 @@ int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev)
> }
> EXPORT_SYMBOL(kcs_bmc_unregister_cdev);
>
>+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 <[email protected]>");
> MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
>diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
>index 5deb9a0b8e60..11fff935218c 100644
>--- a/drivers/char/ipmi/kcs_bmc.h
>+++ b/drivers/char/ipmi/kcs_bmc.h
>@@ -11,6 +11,9 @@
> #define KCS_BMC_EVENT_NONE 0
> #define KCS_BMC_EVENT_HANDLED 1
>
>+#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)
>diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
>index 6f26e7366c0b..5f26471c038c 100644
>--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
>+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
>@@ -60,10 +60,18 @@
> #define LPC_ODR4 0x118
> #define LPC_STR4 0x11C
>
>+#define OBE_POLL_PERIOD (HZ / 2)
>+
> struct aspeed_kcs_bmc {
> struct kcs_bmc_device kcs_bmc;
>
> struct regmap *map;
>+
>+ struct {
>+ spinlock_t lock;
>+ bool remove;
>+ struct timer_list timer;
>+ } obe;
> };
>
> struct aspeed_kcs_of_ops {
>@@ -159,68 +167,89 @@ static void aspeed_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enabl
>
> switch (kcs_bmc->channel) {
> case 1:
>- if (enable) {
>- regmap_update_bits(priv->map, LPC_HICR2,
>- LPC_HICR2_IBFIF1, LPC_HICR2_IBFIF1);
>- regmap_update_bits(priv->map, LPC_HICR0,
>- LPC_HICR0_LPC1E, LPC_HICR0_LPC1E);
>- } else {
>- regmap_update_bits(priv->map, LPC_HICR0,
>- LPC_HICR0_LPC1E, 0);
>- regmap_update_bits(priv->map, LPC_HICR2,
>- LPC_HICR2_IBFIF1, 0);
>- }
>- break;
>-
>+ regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC1E, enable * LPC_HICR0_LPC1E);
>+ return;
> case 2:
>- if (enable) {
>- regmap_update_bits(priv->map, LPC_HICR2,
>- LPC_HICR2_IBFIF2, LPC_HICR2_IBFIF2);
>- regmap_update_bits(priv->map, LPC_HICR0,
>- LPC_HICR0_LPC2E, LPC_HICR0_LPC2E);
>- } else {
>- regmap_update_bits(priv->map, LPC_HICR0,
>- LPC_HICR0_LPC2E, 0);
>- regmap_update_bits(priv->map, LPC_HICR2,
>- LPC_HICR2_IBFIF2, 0);
>- }
>- break;
>-
>+ regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC2E, enable * LPC_HICR0_LPC2E);
>+ return;
> case 3:
>- if (enable) {
>- regmap_update_bits(priv->map, LPC_HICR2,
>- LPC_HICR2_IBFIF3, LPC_HICR2_IBFIF3);
>- regmap_update_bits(priv->map, LPC_HICR0,
>- LPC_HICR0_LPC3E, LPC_HICR0_LPC3E);
>- regmap_update_bits(priv->map, LPC_HICR4,
>- LPC_HICR4_KCSENBL, LPC_HICR4_KCSENBL);
>- } else {
>- regmap_update_bits(priv->map, LPC_HICR0,
>- LPC_HICR0_LPC3E, 0);
>- regmap_update_bits(priv->map, LPC_HICR4,
>- LPC_HICR4_KCSENBL, 0);
>- regmap_update_bits(priv->map, LPC_HICR2,
>- LPC_HICR2_IBFIF3, 0);
>- }
>- break;
>-
>+ 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:
>- if (enable)
>- regmap_update_bits(priv->map, LPC_HICRB,
>- LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E,
>- LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E);
>+ 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);
>+
>+ /* We don't have an OBE IRQ, emulate it */
>+ if (KCS_BMC_EVENT_TYPE_OBE & mask) {
>+ if (KCS_BMC_EVENT_TYPE_OBE & state)
>+ mod_timer(&priv->obe.timer, jiffies + OBE_POLL_PERIOD);
> else
>- regmap_update_bits(priv->map, LPC_HICRB,
>- LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E,
>- 0);
>- break;
>+ del_timer(&priv->obe.timer);
>+ }
>
>- default:
>- break;
>+ if (KCS_BMC_EVENT_TYPE_IBF & mask) {
>+ const bool enable = !!(KCS_BMC_EVENT_TYPE_IBF & state);

Totally superficial nitpick: the operand ordering for the bitmask tests
in this function seem a bit inconsistent with what I think is the usual
style of 'variable & BITMASK_MACRO' (reminiscent of "yoda
conditionals").

>+
>+ switch (kcs_bmc->channel) {
>+ case 1:
>+ regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF1,
>+ enable * LPC_HICR2_IBFIF1);
>+ return;
>+ case 2:
>+ regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF2,
>+ enable * LPC_HICR2_IBFIF2);
>+ return;
>+ case 3:
>+ regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF3,
>+ enable * LPC_HICR2_IBFIF3);
>+ return;
>+ case 4:
>+ regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_IBFIF4,
>+ enable * LPC_HICRB_IBFIF4);
>+ 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,
>@@ -378,6 +407,10 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> return -ENODEV;
> }
>
>+ spin_lock_init(&priv->obe.lock);
>+ priv->obe.remove = false;
>+ timer_setup(&priv->obe.timer, aspeed_kcs_check_obe, 0);
>+
> aspeed_kcs_set_address(kcs_bmc, addr);
>
> rc = aspeed_kcs_config_irq(kcs_bmc, pdev);
>@@ -386,6 +419,8 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
>
> platform_set_drvdata(pdev, priv);
>
>+ aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE),
>+ KCS_BMC_EVENT_TYPE_IBF);
> aspeed_kcs_enable_channel(kcs_bmc, true);
>
> rc = kcs_bmc_add_device(&priv->kcs_bmc);
>@@ -404,6 +439,15 @@ static int aspeed_kcs_remove(struct platform_device *pdev)
>
> 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);
>+ del_timer_sync(&priv->obe.timer);
>+
> return 0;
> }
>
>diff --git a/drivers/char/ipmi/kcs_bmc_client.h b/drivers/char/ipmi/kcs_bmc_client.h
>index d0a7404ff584..456796da33de 100644
>--- a/drivers/char/ipmi/kcs_bmc_client.h
>+++ b/drivers/char/ipmi/kcs_bmc_client.h
>@@ -37,6 +37,8 @@ int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev);
> 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);
>diff --git a/drivers/char/ipmi/kcs_bmc_device.h b/drivers/char/ipmi/kcs_bmc_device.h
>index 57b7174b2bac..f1ca8912496a 100644
>--- a/drivers/char/ipmi/kcs_bmc_device.h
>+++ b/drivers/char/ipmi/kcs_bmc_device.h
>@@ -7,6 +7,7 @@
> #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);
>diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>index dce93ec895fc..c2032728a03d 100644
>--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>@@ -38,6 +38,7 @@
> #define KCS2CTL 0x2A
> #define KCS3CTL 0x3C
> #define KCS_CTL_IBFIE BIT(0)
>+#define KCS_CTL_OBEIE BIT(0)
>
> #define KCS1IE 0x1C
> #define KCS2IE 0x2E
>@@ -117,13 +118,23 @@ static void npcm7xx_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enab
> {
> struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
>
>- regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
>- enable ? KCS_CTL_IBFIE : 0);
>-
> 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 (KCS_BMC_EVENT_TYPE_OBE & mask)
>+ regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_OBEIE,
>+ !!(KCS_BMC_EVENT_TYPE_OBE & state) * KCS_CTL_OBEIE);
>+
>+ if (KCS_BMC_EVENT_TYPE_IBF & mask)
>+ regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
>+ !!(KCS_BMC_EVENT_TYPE_IBF & state) * KCS_CTL_IBFIE);

Same operand ordering thing here...

>+}
>+
> static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg)
> {
> struct kcs_bmc_device *kcs_bmc = arg;
>@@ -151,6 +162,7 @@ static int npcm7xx_kcs_config_irq(struct kcs_bmc_device *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,
>@@ -191,11 +203,13 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
>
> platform_set_drvdata(pdev, priv);
>
>- npcm7xx_kcs_enable_channel(kcs_bmc, true);
> 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),
>+ KCS_BMC_EVENT_TYPE_IBF);
>+ npcm7xx_kcs_enable_channel(kcs_bmc, true);
>
> pr_info("channel=%u idr=0x%x odr=0x%x str=0x%x\n",
> chan,
>@@ -211,6 +225,9 @@ static int npcm7xx_kcs_remove(struct platform_device *pdev)
>
> 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);
>+
> return 0;
> }
>
>--
>2.27.0
>

2021-04-09 04:41:18

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 13/21] ipmi: kcs_bmc: Decouple the IPMI chardev from the core

On Fri, Mar 19, 2021 at 01:27:44AM CDT, Andrew Jeffery wrote:
>Now that we have untangled the data-structures, split the userspace
>interface out into its own module. Userspace interfaces and drivers are
>registered to the KCS BMC core to support arbitrary binding of either.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> drivers/char/ipmi/Kconfig | 13 +++++
> drivers/char/ipmi/Makefile | 3 +-
> drivers/char/ipmi/kcs_bmc.c | 78 ++++++++++++++++++++++++++-
> drivers/char/ipmi/kcs_bmc.h | 4 --
> drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 33 +++++++++---
> drivers/char/ipmi/kcs_bmc_client.h | 14 +++++
> 6 files changed, 132 insertions(+), 13 deletions(-)
>
>diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
>index 07847d9a459a..bc5f81899b62 100644
>--- a/drivers/char/ipmi/Kconfig
>+++ b/drivers/char/ipmi/Kconfig
>@@ -124,6 +124,19 @@ config NPCM7XX_KCS_IPMI_BMC
> 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 ASPEED_BT_IPMI_BMC
> depends on ARCH_ASPEED || COMPILE_TEST
> depends on REGMAP && REGMAP_MMIO && MFD_SYSCON
>diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
>index a302bc865370..fcfa676afddb 100644
>--- a/drivers/char/ipmi/Makefile
>+++ b/drivers/char/ipmi/Makefile
>@@ -22,7 +22,8 @@ obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o
> obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o
> obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
> obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
>-obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o kcs_bmc_cdev_ipmi.o
>+obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.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
>diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
>index 266ebec71d6f..694db6ee2a92 100644
>--- a/drivers/char/ipmi/kcs_bmc.c
>+++ b/drivers/char/ipmi/kcs_bmc.c
>@@ -5,7 +5,9 @@
> */
>
> #include <linux/device.h>
>+#include <linux/list.h>
> #include <linux/module.h>
>+#include <linux/mutex.h>
>
> #include "kcs_bmc.h"
>
>@@ -13,6 +15,11 @@
> #include "kcs_bmc_device.h"
> #include "kcs_bmc_client.h"
>
>+/* Record probed devices and cdevs */
>+static DEFINE_MUTEX(kcs_bmc_lock);
>+static LIST_HEAD(kcs_bmc_devices);
>+static LIST_HEAD(kcs_bmc_cdevs);
>+
> /* Consumer data access */
>
> u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc)
>@@ -100,16 +107,83 @@ EXPORT_SYMBOL(kcs_bmc_disable_device);
>
> int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc)
> {
>- return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
>+ struct kcs_bmc_cdev *cdev;
>+ 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(cdev, &kcs_bmc_cdevs, entry) {
>+ rc = cdev->ops->add_device(kcs_bmc);
>+ if (rc)
>+ dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
>+ kcs_bmc->channel, rc);
>+ }
>+ mutex_unlock(&kcs_bmc_lock);
>+
>+ return 0;

We're ignoring failed ->add_device() calls here?

> }
> EXPORT_SYMBOL(kcs_bmc_add_device);
>
> int kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc)
> {
>- return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
>+ struct kcs_bmc_cdev *cdev;
>+ int rc;
>+
>+ mutex_lock(&kcs_bmc_lock);
>+ list_del(&kcs_bmc->entry);
>+ list_for_each_entry(cdev, &kcs_bmc_cdevs, entry) {
>+ rc = cdev->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);
>+
>+ return 0;

Similarly with the return value here...

> }
> EXPORT_SYMBOL(kcs_bmc_remove_device);
>
>+int kcs_bmc_register_cdev(struct kcs_bmc_cdev *cdev)
>+{
>+ struct kcs_bmc_device *kcs_bmc;
>+ int rc;
>+
>+ mutex_lock(&kcs_bmc_lock);
>+ list_add(&cdev->entry, &kcs_bmc_cdevs);
>+ list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
>+ rc = cdev->ops->add_device(kcs_bmc);
>+ if (rc)
>+ dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
>+ kcs_bmc->channel, rc);
>+ }
>+ mutex_unlock(&kcs_bmc_lock);
>+
>+ return 0;

...return value again here...

>+}
>+EXPORT_SYMBOL(kcs_bmc_register_cdev);
>+
>+int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev)
>+{
>+ struct kcs_bmc_device *kcs_bmc;
>+ int rc;
>+
>+ mutex_lock(&kcs_bmc_lock);
>+ list_del(&cdev->entry);
>+ list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
>+ rc = cdev->ops->remove_device(kcs_bmc);
>+ if (rc)
>+ dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",

s/add/remove/

Might also want to differentiate the *_device() error messages from the
*_cdev() ones a bit more?

>+ kcs_bmc->channel, rc);
>+ }
>+ mutex_unlock(&kcs_bmc_lock);
>+
>+ return rc;

...but this one is a bit incongruous, propagating the return value of
only the last ->remove_device() call.

(I'd have expected this to trigger a warning about returning a
potentially uninitialized 'rc', but in some manual testing it doesn't
seem to do so for me...not certain why.)

>+}
>+EXPORT_SYMBOL(kcs_bmc_unregister_cdev);
>+
> MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Haiyue Wang <[email protected]>");
> MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
>diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
>index 3f266740c759..5deb9a0b8e60 100644
>--- a/drivers/char/ipmi/kcs_bmc.h
>+++ b/drivers/char/ipmi/kcs_bmc.h
>@@ -42,8 +42,4 @@ struct kcs_bmc_device {
> spinlock_t lock;
> struct kcs_bmc_client *client;
> };
>-
>-/* Temporary exports while refactoring */
>-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc);
>-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc);
> #endif /* __KCS_BMC_H__ */
>diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>index 58c42e76483d..df83d67851ac 100644
>--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>@@ -469,8 +469,7 @@ static const struct file_operations kcs_bmc_ipmi_fops = {
> static DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock);
> static LIST_HEAD(kcs_bmc_ipmi_instances);
>
>-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc);
>-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc)
>+static int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc)
> {
> struct kcs_bmc_ipmi *priv;
> int rc;
>@@ -512,10 +511,8 @@ int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc)
>
> return 0;
> }
>-EXPORT_SYMBOL(kcs_bmc_ipmi_attach_cdev);
>
>-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc);
>-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc)
>+static int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc)
> {
> struct kcs_bmc_ipmi *priv = NULL, *pos;
>
>@@ -541,7 +538,31 @@ int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc)
>
> return 0;
> }
>-EXPORT_SYMBOL(kcs_bmc_ipmi_detach_cdev);
>+
>+static const struct kcs_bmc_cdev_ops kcs_bmc_ipmi_cdev_ops = {
>+ .add_device = kcs_bmc_ipmi_attach_cdev,
>+ .remove_device = kcs_bmc_ipmi_detach_cdev,
>+};
>+
>+static struct kcs_bmc_cdev kcs_bmc_ipmi_cdev = {
>+ .ops = &kcs_bmc_ipmi_cdev_ops,
>+};
>+
>+static int kcs_bmc_ipmi_init(void)
>+{
>+ return kcs_bmc_register_cdev(&kcs_bmc_ipmi_cdev);
>+}
>+module_init(kcs_bmc_ipmi_init);
>+
>+static void kcs_bmc_ipmi_exit(void)
>+{
>+ int rc;
>+
>+ rc = kcs_bmc_unregister_cdev(&kcs_bmc_ipmi_cdev);
>+ if (rc)
>+ pr_warn("Failed to remove KCS BMC client: %d", rc);
>+}
>+module_exit(kcs_bmc_ipmi_exit);
>
> MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Haiyue Wang <[email protected]>");
>diff --git a/drivers/char/ipmi/kcs_bmc_client.h b/drivers/char/ipmi/kcs_bmc_client.h
>index 2dd710f4b4aa..d0a7404ff584 100644
>--- a/drivers/char/ipmi/kcs_bmc_client.h
>+++ b/drivers/char/ipmi/kcs_bmc_client.h
>@@ -10,6 +10,17 @@
>
> #include "kcs_bmc.h"
>
>+struct kcs_bmc_cdev_ops {
>+ int (*add_device)(struct kcs_bmc_device *kcs_bmc);
>+ int (*remove_device)(struct kcs_bmc_device *kcs_bmc);
>+};
>+
>+struct kcs_bmc_cdev {
>+ struct list_head entry;
>+
>+ const struct kcs_bmc_cdev_ops *ops;
>+};
>+
> struct kcs_bmc_client_ops {
> int (*event)(struct kcs_bmc_client *client);
> };
>@@ -20,6 +31,9 @@ struct kcs_bmc_client {
> struct kcs_bmc_device *dev;
> };
>
>+int kcs_bmc_register_cdev(struct kcs_bmc_cdev *cdev);
>+int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev);
>+
> 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);
>
>--
>2.27.0
>

2021-04-09 05:11:37

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 15/21] ipmi: kcs_bmc: Don't enforce single-open policy in the kernel

On Fri, Mar 19, 2021 at 01:27:46AM CDT, Andrew Jeffery wrote:
>Soon it will be possible for one KCS device to have multiple associated
>chardevs exposed to userspace (for IPMI and raw-style access). However,
>don't prevent userspace from:
>
>1. Opening more than one chardev at a time, or
>2. Opening the same chardev more than once.
>
>System behaviour is undefined for both classes of multiple access, so
>userspace must manage itself accordingly.
>
>The implementation delivers IBF and OBF events to the first chardev
>client to associate with the KCS device. An open on a related chardev
>cannot associate its client with the KCS device and so will not
>receive notification of events. However, any fd on any chardev may race
>their accesses to the data and status registers.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> drivers/char/ipmi/kcs_bmc.c | 34 ++++++++++-------------------
> drivers/char/ipmi/kcs_bmc_aspeed.c | 3 +--
> drivers/char/ipmi/kcs_bmc_npcm7xx.c | 3 +--
> 3 files changed, 14 insertions(+), 26 deletions(-)
>
>diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
>index 05bbb72418b2..2fafa9541934 100644
>--- a/drivers/char/ipmi/kcs_bmc.c
>+++ b/drivers/char/ipmi/kcs_bmc.c
>@@ -55,24 +55,12 @@ EXPORT_SYMBOL(kcs_bmc_update_status);
> int kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc)
> {
> struct kcs_bmc_client *client;
>- int rc;
>+ int rc = KCS_BMC_EVENT_NONE;
>
> spin_lock(&kcs_bmc->lock);
> client = kcs_bmc->client;
>- if (client) {
>+ if (!WARN_ON_ONCE(!client))
> rc = client->ops->event(client);

The double-negation split by a macro seems a bit confusing to me
readability-wise; could we simplify to something like

if (client)
rc = client->ops->event(client);
else
WARN_ONCE();

?

>- } else {
>- u8 status;
>-
>- status = kcs_bmc_read_status(kcs_bmc);
>- if (status & KCS_BMC_STR_IBF) {
>- /* Ack the event by reading the data */
>- kcs_bmc_read_data(kcs_bmc);
>- rc = KCS_BMC_EVENT_HANDLED;
>- } else {
>- rc = KCS_BMC_EVENT_NONE;
>- }
>- }
> spin_unlock(&kcs_bmc->lock);
>
> return rc;
>@@ -81,26 +69,28 @@ 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 {
>+ if (!kcs_bmc->client) {
>+ u8 mask = KCS_BMC_EVENT_TYPE_IBF;
>+
> kcs_bmc->client = client;
>- rc = 0;
>+ kcs_bmc_update_event_mask(kcs_bmc, mask, mask);
> }
> spin_unlock_irq(&kcs_bmc->lock);
>
>- return rc;
>+ return 0;

Since this function appears to be infallible now, should it just return
void? (Might be more churn than it's worth...shrug.)

> }
> 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)
>+ 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);
>diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
>index 5f26471c038c..271845eb2e26 100644
>--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
>+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
>@@ -419,8 +419,7 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
>
> platform_set_drvdata(pdev, priv);
>
>- aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE),
>- KCS_BMC_EVENT_TYPE_IBF);
>+ 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);
>diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>index c2032728a03d..fdf35cad2eba 100644
>--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>@@ -207,8 +207,7 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
> if (rc)
> return rc;
>
>- npcm7xx_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE),
>- KCS_BMC_EVENT_TYPE_IBF);
>+ 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);
>
> pr_info("channel=%u idr=0x%x odr=0x%x str=0x%x\n",
>--
>2.27.0
>

2021-04-09 05:19:20

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 17/21] dt-bindings: ipmi: Convert ASPEED KCS binding to schema

On Fri, Mar 19, 2021 at 01:27:48AM CDT, Andrew Jeffery wrote:
>Given the deprecated binding, improve the ability to detect issues in
>the platform devicetrees. Further, a subsequent patch will introduce a
>new interrupts property for specifying SerIRQ behaviour, so convert
>before we do any further additions.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> .../bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml | 92 +++++++++++++++++++
> .../bindings/ipmi/aspeed-kcs-bmc.txt | 33 -------
> 2 files changed, 92 insertions(+), 33 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
> delete mode 100644 Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt
>
>diff --git a/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
>new file mode 100644
>index 000000000000..697ca575454f
>--- /dev/null
>+++ b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
>@@ -0,0 +1,92 @@
>+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>+%YAML 1.2
>+---
>+$id: http://devicetree.org/schemas/ipmi/aspeed,ast2400-kcs-bmc.yaml
>+$schema: http://devicetree.org/meta-schemas/core.yaml
>+
>+title: ASPEED BMC KCS Devices
>+
>+maintainers:
>+ - Andrew Jeffery <[email protected]>
>+
>+description: |
>+ The Aspeed BMC SoCs typically use the Keyboard-Controller-Style (KCS)
>+ interfaces on the LPC bus for in-band IPMI communication with their host.
>+
>+properties:
>+ compatible:
>+ oneOf:
>+ - description: Channel ID derived from reg
>+ items:
>+ enum:
>+ - aspeed,ast2400-kcs-bmc-v2
>+ - aspeed,ast2500-kcs-bmc-v2
>+ - aspeed,ast2600-kcs-bmc

Should this have a "-v2" suffix?

>+
>+ - description: Old-style with explicit channel ID, no reg
>+ deprecated: true
>+ items:
>+ enum:
>+ - aspeed,ast2400-kcs-bmc
>+ - aspeed,ast2500-kcs-bmc
>+
>+ interrupts:
>+ maxItems: 1
>+
>+ reg:
>+ # maxItems: 3
>+ items:
>+ - description: IDR register
>+ - description: ODR register
>+ - description: STR register
>+
>+ aspeed,lpc-io-reg:
>+ $ref: '/schemas/types.yaml#/definitions/uint32-array'
>+ minItems: 1
>+ maxItems: 2
>+ description: |
>+ The host CPU LPC IO data and status addresses for the device. For most
>+ channels the status address is derived from the data address, but the
>+ status address may be optionally provided.
>+
>+ kcs_chan:
>+ deprecated: true
>+ $ref: '/schemas/types.yaml#/definitions/uint32'
>+ description: The LPC channel number in the controller
>+
>+ kcs_addr:
>+ deprecated: true
>+ $ref: '/schemas/types.yaml#/definitions/uint32'
>+ description: The host CPU IO map address
>+
>+required:
>+ - compatible
>+ - interrupts
>+
>+additionalProperties: false
>+
>+allOf:
>+ - if:
>+ properties:
>+ compatible:
>+ contains:
>+ enum:
>+ - aspeed,ast2400-kcs-bmc
>+ - aspeed,ast2500-kcs-bmc
>+ then:
>+ required:
>+ - kcs_chan
>+ - kcs_addr
>+ else:
>+ required:
>+ - reg
>+ - aspeed,lpc-io-reg
>+
>+examples:
>+ - |
>+ kcs3: kcs@24 {
>+ compatible = "aspeed,ast2600-kcs-bmc";

And likewise here.

>+ reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>;
>+ aspeed,lpc-io-reg = <0xca2>;
>+ interrupts = <8>;
>+ };
>diff --git a/Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt b/Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt
>deleted file mode 100644
>index 193e71ca96b0..000000000000
>--- a/Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt
>+++ /dev/null
>@@ -1,33 +0,0 @@
>-# Aspeed KCS (Keyboard Controller Style) IPMI interface
>-
>-The Aspeed SOCs (AST2400 and AST2500) are commonly used as BMCs
>-(Baseboard Management Controllers) and the KCS interface can be
>-used to perform in-band IPMI communication with their host.
>-
>-## v1
>-Required properties:
>-- compatible : should be one of
>- "aspeed,ast2400-kcs-bmc"
>- "aspeed,ast2500-kcs-bmc"
>-- interrupts : interrupt generated by the controller
>-- kcs_chan : The LPC channel number in the controller
>-- kcs_addr : The host CPU IO map address
>-
>-## v2
>-Required properties:
>-- compatible : should be one of
>- "aspeed,ast2400-kcs-bmc-v2"
>- "aspeed,ast2500-kcs-bmc-v2"
>-- reg : The address and size of the IDR, ODR and STR registers
>-- interrupts : interrupt generated by the controller
>-- aspeed,lpc-io-reg : The host CPU LPC IO address for the device
>-
>-Example:
>-
>- kcs3: kcs@24 {
>- compatible = "aspeed,ast2500-kcs-bmc-v2";
>- reg = <0x24 0x1>, <0x30 0x1>, <0x3c 0x1>;
>- aspeed,lpc-reg = <0xca2>;
>- interrupts = <8>;
>- status = "okay";
>- };
>--
>2.27.0
>

2021-04-09 05:20:33

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 16/21] ipmi: kcs_bmc: Add a "raw" character device interface

On Fri, Mar 19, 2021 at 01:27:47AM CDT, Andrew Jeffery wrote:
>The existing IPMI chardev encodes IPMI behaviours as the name suggests.
>However, KCS devices are useful beyond IPMI (or keyboards), as they
>provide a means to generate IRQs and exchange arbitrary data between a
>BMC and its host system.
>
>Implement a "raw" KCS character device that exposes the IDR, ODR and STR
>registers to userspace via read() and write() implemented on a character
>device:
>
>+--------+--------+---------+
>| Offset | read() | write() |
>+--------+--------+---------+
>| 0 | IDR | ODR |
>+--------+--------+---------+
>| 1 | STR | STR |
>+--------+--------+---------+
>
>This interface allows userspace to implement arbitrary (though somewhat
>inefficient) protocols for exchanging information between a BMC and host
>firmware. Conceptually the KCS interface can be used as an out-of-band
>machanism for interrupt-signaled control messages while bulk data

Typo ("mechanism")

>transfers occur over more appropriate interfaces between the BMC and the
>host (which may lack their own interrupt mechanism, e.g. LPC FW cycles).
>
>poll() is provided, which will wait for IBF or OBE conditions for data
>reads and writes respectively. Reads of STR on its own never blocks,
>though accessing both offsets in the one system call may block if the
>data registers are not ready.
>
>Signed-off-by: Andrew Jeffery <[email protected]>
>---
> Documentation/ABI/testing/dev-raw-kcs | 25 ++
> drivers/char/ipmi/Kconfig | 17 +
> drivers/char/ipmi/Makefile | 1 +
> drivers/char/ipmi/kcs_bmc_cdev_raw.c | 443 ++++++++++++++++++++++++++
> 4 files changed, 486 insertions(+)
> create mode 100644 Documentation/ABI/testing/dev-raw-kcs
> create mode 100644 drivers/char/ipmi/kcs_bmc_cdev_raw.c
>
>diff --git a/Documentation/ABI/testing/dev-raw-kcs b/Documentation/ABI/testing/dev-raw-kcs
>new file mode 100644
>index 000000000000..06e7e2071562
>--- /dev/null
>+++ b/Documentation/ABI/testing/dev-raw-kcs
>@@ -0,0 +1,25 @@
>+What: /dev/raw-kcs*
>+Date: 2021-02-15
>+KernelVersion: 5.13
>+Contact: [email protected]
>+Contact: [email protected]
>+Contact: Andrew Jeffery <[email protected]>
>+Description: ``/dev/raw-kcs*`` exposes to userspace the data and
>+ status registers of Keyboard-Controller-Style (KCS) IPMI
>+ interfaces via read() and write() syscalls. Direct
>+ exposure of the data and status registers enables
>+ inefficient but arbitrary protocols to be implemented
>+ over the device. A typical approach is to use KCS
>+ devices for out-of-band signalling for bulk data
>+ transfers over other interfaces between a Baseboard
>+ Management Controller and its host.
>+
>+ +--------+--------+---------+
>+ | Offset | read() | write() |
>+ +--------+--------+---------+
>+ | 0 | IDR | ODR |
>+ +--------+--------+---------+
>+ | 1 | STR | STR |
>+ +--------+--------+---------+
>+
>+Users: libmctp: https://github.com/openbmc/libmctp
>diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
>index bc5f81899b62..273ac1a1f870 100644
>--- a/drivers/char/ipmi/Kconfig
>+++ b/drivers/char/ipmi/Kconfig
>@@ -137,6 +137,23 @@ config IPMI_KCS_BMC_CDEV_IPMI
> This support is also available as a module. The module will be
> called kcs_bmc_cdev_ipmi.
>
>+config IPMI_KCS_BMC_CDEV_RAW
>+ depends on IPMI_KCS_BMC
>+ tristate "Raw character device interface for BMC KCS devices"
>+ help
>+ Provides a BMC-side character device directly exposing the
>+ data and status registers of a KCS device to userspace. While
>+ KCS devices are commonly used to implement IPMI message
>+ passing, they provide a general interface for exchange of
>+ interrupts, data and status information between the BMC and
>+ its host.
>+
>+ Say YES if you wish to use the KCS devices to implement
>+ protocols that are not IPMI.
>+
>+ This support is also available as a module. The module will be
>+ called kcs_bmc_cdev_raw.
>+
> config ASPEED_BT_IPMI_BMC
> depends on ARCH_ASPEED || COMPILE_TEST
> depends on REGMAP && REGMAP_MMIO && MFD_SYSCON
>diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
>index fcfa676afddb..c8cc248ddd90 100644
>--- a/drivers/char/ipmi/Makefile
>+++ b/drivers/char/ipmi/Makefile
>@@ -24,6 +24,7 @@ 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_CDEV_IPMI) += kcs_bmc_cdev_ipmi.o
>+obj-$(CONFIG_IPMI_KCS_BMC_CDEV_RAW) += kcs_bmc_cdev_raw.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
>diff --git a/drivers/char/ipmi/kcs_bmc_cdev_raw.c b/drivers/char/ipmi/kcs_bmc_cdev_raw.c
>new file mode 100644
>index 000000000000..bdd258648c8e
>--- /dev/null
>+++ b/drivers/char/ipmi/kcs_bmc_cdev_raw.c
>@@ -0,0 +1,443 @@
>+// 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/fs.h>
>+#include <linux/list.h>
>+#include <linux/miscdevice.h>
>+#include <linux/module.h>
>+#include <linux/poll.h>
>+
>+#include "kcs_bmc_client.h"
>+
>+#define DEVICE_NAME "raw-kcs"
>+
>+struct kcs_bmc_raw {
>+ struct list_head entry;
>+
>+ struct kcs_bmc_client client;
>+
>+ wait_queue_head_t queue;
>+ u8 events;
>+ bool writable;
>+ bool readable;
>+ u8 idr;
>+
>+ struct miscdevice miscdev;
>+};
>+
>+static inline struct kcs_bmc_raw *client_to_kcs_bmc_raw(struct kcs_bmc_client *client)
>+{
>+ return container_of(client, struct kcs_bmc_raw, client);
>+}
>+
>+/* Call under priv->queue.lock */
>+static void kcs_bmc_raw_update_event_mask(struct kcs_bmc_raw *priv, u8 mask, u8 state)
>+{
>+ kcs_bmc_update_event_mask(priv->client.dev, mask, state);
>+ priv->events &= ~mask;
>+ priv->events |= state & mask;
>+}
>+
>+static int kcs_bmc_raw_event(struct kcs_bmc_client *client)
>+{
>+ struct kcs_bmc_raw *priv;
>+ struct device *dev;
>+ u8 status, handled;
>+
>+ priv = client_to_kcs_bmc_raw(client);
>+ dev = priv->miscdev.this_device;
>+
>+ spin_lock(&priv->queue.lock);
>+
>+ status = kcs_bmc_read_status(client->dev);
>+ handled = 0;
>+
>+ if ((priv->events & KCS_BMC_EVENT_TYPE_IBF) && (status & KCS_BMC_STR_IBF)) {
>+ if (priv->readable)
>+ dev_err(dev, "Storm brewing!");

That seems a *touch* cryptic...

>+
>+ dev_dbg(dev, "Disabling IDR events for back-pressure\n");
>+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_IBF, 0);
>+ priv->idr = kcs_bmc_read_data(client->dev);
>+ priv->readable = true;
>+
>+ dev_dbg(dev, "IDR read, waking waiters\n");
>+ wake_up_locked(&priv->queue);
>+
>+ handled |= KCS_BMC_EVENT_TYPE_IBF;
>+ }
>+
>+ if ((priv->events & KCS_BMC_EVENT_TYPE_OBE) && !(status & KCS_BMC_STR_OBF)) {
>+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, 0);
>+ priv->writable = true;
>+
>+ dev_dbg(dev, "ODR writable, waking waiters\n");
>+ wake_up_locked(&priv->queue);
>+
>+ handled |= KCS_BMC_EVENT_TYPE_OBE;
>+ }
>+
>+ spin_unlock(&priv->queue.lock);
>+
>+ return handled ? KCS_BMC_EVENT_HANDLED : KCS_BMC_EVENT_NONE;

Hm, if we're just treating it as a boolean here, is there any need to
muck around with setting specific bits of 'handled' in the if-blocks
above?

>+}
>+
>+static const struct kcs_bmc_client_ops kcs_bmc_raw_client_ops = {
>+ .event = kcs_bmc_raw_event,
>+};
>+
>+static inline struct kcs_bmc_raw *file_to_kcs_bmc_raw(struct file *filp)
>+{
>+ return container_of(filp->private_data, struct kcs_bmc_raw, miscdev);
>+}
>+
>+static int kcs_bmc_raw_open(struct inode *inode, struct file *filp)
>+{
>+ struct kcs_bmc_raw *priv = file_to_kcs_bmc_raw(filp);
>+
>+ return kcs_bmc_enable_device(priv->client.dev, &priv->client);
>+}
>+
>+static bool kcs_bmc_raw_prepare_obe(struct kcs_bmc_raw *priv)
>+{
>+ bool writable;
>+
>+ /* Enable the OBE event so we can catch the host clearing OBF */
>+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, KCS_BMC_EVENT_TYPE_OBE);
>+
>+ /* Now that we'll catch an OBE event, check if it's already occurred */
>+ writable = !(kcs_bmc_read_status(priv->client.dev) & KCS_BMC_STR_OBF);
>+
>+ /* If OBF is clear we've missed the OBE event, so disable it */
>+ if (writable)
>+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, 0);
>+
>+ return writable;
>+}
>+
>+static __poll_t kcs_bmc_raw_poll(struct file *filp, poll_table *wait)
>+{
>+ struct kcs_bmc_raw *priv;
>+ __poll_t events = 0;
>+
>+ priv = file_to_kcs_bmc_raw(filp);
>+
>+ poll_wait(filp, &priv->queue, wait);
>+
>+ spin_lock_irq(&priv->queue.lock);
>+ if (kcs_bmc_raw_prepare_obe(priv))
>+ events |= (EPOLLOUT | EPOLLWRNORM);
>+
>+ if (priv->readable || (kcs_bmc_read_status(priv->client.dev) & KCS_BMC_STR_IBF))
>+ events |= (EPOLLIN | EPOLLRDNORM);
>+ spin_unlock_irq(&priv->queue.lock);
>+
>+ return events;
>+}
>+
>+static ssize_t kcs_bmc_raw_read(struct file *filp, char __user *buf,
>+ size_t count, loff_t *ppos)
>+{
>+ struct kcs_bmc_device *kcs_bmc;
>+ struct kcs_bmc_raw *priv;
>+ bool read_idr, read_str;
>+ struct device *dev;
>+ u8 idr, str;
>+ ssize_t rc;
>+
>+ priv = file_to_kcs_bmc_raw(filp);
>+ kcs_bmc = priv->client.dev;
>+ dev = priv->miscdev.this_device;
>+
>+ if (!count)
>+ return 0;
>+
>+ if (count > 2 || *ppos > 1)
>+ return -EINVAL;
>+
>+ if (*ppos + count > 2)
>+ return -EINVAL;
>+
>+ read_idr = (*ppos == 0);
>+ read_str = (*ppos == 1) || (count == 2);
>+
>+ spin_lock_irq(&priv->queue.lock);
>+ if (read_idr) {
>+ dev_dbg(dev, "Waiting for IBF\n");
>+ str = kcs_bmc_read_status(kcs_bmc);
>+ if ((filp->f_flags & O_NONBLOCK) && (str & KCS_BMC_STR_IBF)) {
>+ rc = -EWOULDBLOCK;
>+ goto out;
>+ }
>+
>+ rc = wait_event_interruptible_locked(priv->queue,
>+ priv->readable || (str & KCS_BMC_STR_IBF));
>+ if (rc < 0)
>+ goto out;
>+
>+ if (signal_pending(current)) {
>+ dev_dbg(dev, "Interrupted waiting for IBF\n");
>+ rc = -EINTR;
>+ goto out;
>+ }
>+
>+ /*
>+ * Re-enable events prior to possible read of IDR (which clears
>+ * IBF) to ensure we receive interrupts for subsequent writes
>+ * to IDR. Writes to IDR by the host should not occur while IBF
>+ * is set.
>+ */
>+ dev_dbg(dev, "Woken by IBF, enabling IRQ\n");
>+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_IBF,
>+ KCS_BMC_EVENT_TYPE_IBF);
>+
>+ /* Read data out of IDR into internal storage if necessary */
>+ if (!priv->readable) {
>+ WARN(!(str & KCS_BMC_STR_IBF), "Unknown reason for wakeup!");
>+
>+ priv->idr = kcs_bmc_read_data(kcs_bmc);
>+ }
>+
>+ /* Copy data from internal storage to userspace */
>+ idr = priv->idr;
>+
>+ /* We're done consuming the internally stored value */
>+ priv->readable = false;
>+ }
>+
>+ if (read_str) {
>+ str = kcs_bmc_read_status(kcs_bmc);
>+ if (*ppos == 0 || priv->readable)
>+ /*
>+ * If we got this far with `*ppos == 0` then we've read
>+ * data out of IDR, so set IBF when reporting back to
>+ * userspace so userspace knows the IDR value is valid.
>+ */
>+ str |= KCS_BMC_STR_IBF;
>+
>+ dev_dbg(dev, "Read status 0x%x\n", str);
>+
>+ }
>+
>+ rc = count;
>+out:
>+ spin_unlock_irq(&priv->queue.lock);
>+
>+ if (rc < 0)
>+ return rc;
>+
>+ /* Now copy the data in to the userspace buffer */
>+
>+ if (read_idr)
>+ if (copy_to_user(buf++, &idr, sizeof(idr)))
>+ return -EFAULT;
>+
>+ if (read_str)
>+ if (copy_to_user(buf, &str, sizeof(str)))
>+ return -EFAULT;
>+
>+ return count;
>+}
>+
>+static ssize_t kcs_bmc_raw_write(struct file *filp, const char __user *buf,
>+ size_t count, loff_t *ppos)
>+{
>+ struct kcs_bmc_device *kcs_bmc;
>+ bool write_odr, write_str;
>+ struct kcs_bmc_raw *priv;
>+ struct device *dev;
>+ uint8_t data[2];
>+ ssize_t result;
>+ u8 str;
>+
>+ priv = file_to_kcs_bmc_raw(filp);
>+ kcs_bmc = priv->client.dev;
>+ dev = priv->miscdev.this_device;
>+
>+ if (!count)
>+ return count;
>+
>+ if (count > 2)
>+ return -EINVAL;
>+
>+ if (*ppos >= 2)
>+ return -EINVAL;
>+
>+ if (*ppos + count > 2)
>+ return -EINVAL;
>+
>+ if (copy_from_user(data, buf, count))
>+ return -EFAULT;
>+
>+ write_odr = (*ppos == 0);
>+ write_str = (*ppos == 1) || (count == 2);
>+
>+ spin_lock_irq(&priv->queue.lock);
>+
>+ /* Always write status before data, we generate the SerIRQ by writing ODR */
>+ if (write_str) {
>+ /* The index of STR in the userspace buffer depends on whether ODR is written */
>+ str = data[*ppos == 0];
>+ if (!(str & KCS_BMC_STR_OBF))
>+ dev_warn(dev, "Clearing OBF with status write: 0x%x\n", str);
>+ dev_dbg(dev, "Writing status 0x%x\n", str);
>+ kcs_bmc_write_status(kcs_bmc, str);
>+ }
>+
>+ if (write_odr) {
>+ /* If we're writing ODR it's always the first byte in the buffer */
>+ u8 odr = data[0];
>+
>+ str = kcs_bmc_read_status(kcs_bmc);
>+ if (str & KCS_BMC_STR_OBF) {
>+ if (filp->f_flags & O_NONBLOCK) {
>+ result = -EWOULDBLOCK;
>+ goto out;
>+ }
>+
>+ priv->writable = kcs_bmc_raw_prepare_obe(priv);
>+
>+ /* Now either OBF is already clear, or we'll get an OBE event to wake us */
>+ dev_dbg(dev, "Waiting for OBF to clear\n");
>+ wait_event_interruptible_locked(priv->queue, priv->writable);
>+
>+ if (signal_pending(current)) {
>+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, 0);
>+ result = -EINTR;
>+ goto out;
>+ }
>+
>+ WARN_ON(kcs_bmc_read_status(kcs_bmc) & KCS_BMC_STR_OBF);
>+ }
>+
>+ dev_dbg(dev, "Writing 0x%x to ODR\n", odr);
>+ kcs_bmc_write_data(kcs_bmc, odr);
>+ }
>+
>+ result = count;
>+out:
>+ spin_unlock_irq(&priv->queue.lock);
>+
>+ return result;
>+}
>+
>+static int kcs_bmc_raw_release(struct inode *inode, struct file *filp)
>+{
>+ struct kcs_bmc_raw *priv = file_to_kcs_bmc_raw(filp);
>+
>+ kcs_bmc_disable_device(priv->client.dev, &priv->client);
>+
>+ return 0;
>+}
>+
>+static const struct file_operations kcs_bmc_raw_fops = {
>+ .owner = THIS_MODULE,
>+ .open = kcs_bmc_raw_open,
>+ .llseek = no_seek_end_llseek,
>+ .read = kcs_bmc_raw_read,
>+ .write = kcs_bmc_raw_write,
>+ .poll = kcs_bmc_raw_poll,
>+ .release = kcs_bmc_raw_release,
>+};
>+
>+static DEFINE_SPINLOCK(kcs_bmc_raw_instances_lock);
>+static LIST_HEAD(kcs_bmc_raw_instances);
>+
>+static int kcs_bmc_raw_attach_cdev(struct kcs_bmc_device *kcs_bmc)
>+{
>+ struct kcs_bmc_raw *priv;
>+ int rc;
>+
>+ priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
>+ if (!priv)
>+ return -ENOMEM;
>+
>+ priv->client.dev = kcs_bmc;
>+ priv->client.ops = &kcs_bmc_raw_client_ops;
>+
>+ init_waitqueue_head(&priv->queue);
>+ priv->writable = false;
>+ priv->readable = false;
>+
>+ 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->miscdev.name)
>+ return -EINVAL;
>+
>+ priv->miscdev.fops = &kcs_bmc_raw_fops;
>+
>+ /* Initialise our expected events. Listen for IBF but ignore OBE until necessary */
>+ kcs_bmc_raw_update_event_mask(priv, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE),
>+ KCS_BMC_EVENT_TYPE_IBF);
>+
>+ rc = misc_register(&priv->miscdev);
>+ if (rc) {
>+ dev_err(kcs_bmc->dev, "Unable to register device\n");
>+ return rc;
>+ }
>+
>+ spin_lock_irq(&kcs_bmc_raw_instances_lock);
>+ list_add(&priv->entry, &kcs_bmc_raw_instances);
>+ spin_unlock_irq(&kcs_bmc_raw_instances_lock);
>+
>+ dev_info(kcs_bmc->dev, "Initialised raw client for channel %d", kcs_bmc->channel);
>+
>+ return 0;
>+}
>+
>+static int kcs_bmc_raw_detach_cdev(struct kcs_bmc_device *kcs_bmc)
>+{
>+ struct kcs_bmc_raw *priv = NULL, *pos;
>+
>+ spin_lock_irq(&kcs_bmc_raw_instances_lock);
>+ list_for_each_entry(pos, &kcs_bmc_raw_instances, entry) {
>+ if (pos->client.dev == kcs_bmc) {
>+ priv = pos;
>+ list_del(&pos->entry);
>+ break;
>+ }
>+ }
>+ spin_unlock_irq(&kcs_bmc_raw_instances_lock);
>+
>+ if (!priv)
>+ return 0;

Similarly to patch #12, might we want to indicate some sort of failure
here, or is this a normal/expected case?

>+
>+ misc_deregister(&priv->miscdev);
>+ kcs_bmc_disable_device(kcs_bmc, &priv->client);
>+ devm_kfree(priv->client.dev->dev, priv);
>+
>+ return 0;
>+}
>+
>+static const struct kcs_bmc_cdev_ops kcs_bmc_raw_cdev_ops = {
>+ .add_device = kcs_bmc_raw_attach_cdev,
>+ .remove_device = kcs_bmc_raw_detach_cdev,
>+};
>+
>+static struct kcs_bmc_cdev kcs_bmc_raw_cdev = {
>+ .ops = &kcs_bmc_raw_cdev_ops,
>+};
>+
>+static int kcs_bmc_raw_init(void)
>+{
>+ return kcs_bmc_register_cdev(&kcs_bmc_raw_cdev);
>+}
>+module_init(kcs_bmc_raw_init);
>+
>+static void kcs_bmc_raw_exit(void)
>+{
>+ int rc;
>+
>+ rc = kcs_bmc_unregister_cdev(&kcs_bmc_raw_cdev);
>+ if (rc)
>+ pr_warn("Failed to remove KCS BMC client: %d", rc);
>+}
>+module_exit(kcs_bmc_raw_exit);
>+
>+MODULE_LICENSE("GPL v2");
>+MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
>+MODULE_DESCRIPTION("Character device for raw access to a KCS device");
>--
>2.27.0
>

2021-04-09 05:26:34

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 01/21] dt-bindings: aspeed-lpc: Remove LPC partitioning



On Fri, 9 Apr 2021, at 12:48, Joel Stanley wrote:
> On Fri, 19 Mar 2021 at 06:28, Andrew Jeffery <[email protected]> wrote:
> >
> > From: "Chia-Wei, Wang" <[email protected]>
> >
> > The LPC controller has no concept of the BMC and the Host partitions.
> > This patch fixes the documentation by removing the description on LPC
> > partitions. The register offsets illustrated in the DTS node examples
> > are also fixed to adapt to the LPC DTS change.
>
> Is this accurate:
>
> The node examples change their reg address to be an offset from the
> LPC HC to be an offset from the base of the LPC region.

Everything becomes based from the start of the LPC region, yes.

Andrew

2021-04-09 05:34:47

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 17/21] dt-bindings: ipmi: Convert ASPEED KCS binding to schema



On Fri, 9 Apr 2021, at 14:45, Zev Weiss wrote:
> On Fri, Mar 19, 2021 at 01:27:48AM CDT, Andrew Jeffery wrote:
> >Given the deprecated binding, improve the ability to detect issues in
> >the platform devicetrees. Further, a subsequent patch will introduce a
> >new interrupts property for specifying SerIRQ behaviour, so convert
> >before we do any further additions.
> >
> >Signed-off-by: Andrew Jeffery <[email protected]>
> >---
> > .../bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml | 92 +++++++++++++++++++
> > .../bindings/ipmi/aspeed-kcs-bmc.txt | 33 -------
> > 2 files changed, 92 insertions(+), 33 deletions(-)
> > create mode 100644 Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
> > delete mode 100644 Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt
> >
> >diff --git a/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
> >new file mode 100644
> >index 000000000000..697ca575454f
> >--- /dev/null
> >+++ b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
> >@@ -0,0 +1,92 @@
> >+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> >+%YAML 1.2
> >+---
> >+$id: http://devicetree.org/schemas/ipmi/aspeed,ast2400-kcs-bmc.yaml
> >+$schema: http://devicetree.org/meta-schemas/core.yaml
> >+
> >+title: ASPEED BMC KCS Devices
> >+
> >+maintainers:
> >+ - Andrew Jeffery <[email protected]>
> >+
> >+description: |
> >+ The Aspeed BMC SoCs typically use the Keyboard-Controller-Style (KCS)
> >+ interfaces on the LPC bus for in-band IPMI communication with their host.
> >+
> >+properties:
> >+ compatible:
> >+ oneOf:
> >+ - description: Channel ID derived from reg
> >+ items:
> >+ enum:
> >+ - aspeed,ast2400-kcs-bmc-v2
> >+ - aspeed,ast2500-kcs-bmc-v2
> >+ - aspeed,ast2600-kcs-bmc
>
> Should this have a "-v2" suffix?

Well, that was kind of a matter of perspective. The 2600 compatible was
added after we'd done the v2 of the binding for the 2400 and 2500 so it
never needed correcting. But it is a case of "don't use the deprecated
properties with the 2600 compatible".

I don't think a change is necessary?

Cheers,

Andrew

2021-04-09 05:35:17

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 07/21] ipmi: kcs_bmc: Make status update atomic

On Fri, Mar 19, 2021 at 01:27:38AM CDT, Andrew Jeffery wrote:
>Enable more efficient implementation of read-modify-write sequences.
>Both device drivers for the KCS BMC stack use regmaps. The new callback
>allows us to exploit regmap_update_bits().
>
>Signed-off-by: Andrew Jeffery <[email protected]>

Reviewed-by: Zev Weiss <[email protected]>

2021-04-09 05:37:36

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 08/21] ipmi: kcs_bmc: Rename {read, write}_{status, data}() functions

On Fri, Mar 19, 2021 at 01:27:39AM CDT, Andrew Jeffery wrote:
>Rename the functions in preparation for separating the IPMI chardev out
>from the KCS BMC core.
>
>Signed-off-by: Andrew Jeffery <[email protected]>

Reviewed-by: Zev Weiss <[email protected]>

2021-04-09 05:44:11

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 20/21] ipmi: kcs_bmc_aspeed: Fix IBFIE typo from datasheet

On Fri, Mar 19, 2021 at 01:27:51AM CDT, Andrew Jeffery wrote:
>Input Buffer Full Interrupt Enable (IBFIE) is typoed as IBFIF for some
>registers in the datasheet. Fix the driver to use the sensible acronym.
>
>Signed-off-by: Andrew Jeffery <[email protected]>

Reviewed-by: Zev Weiss <[email protected]>

2021-04-09 05:46:22

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 17/21] dt-bindings: ipmi: Convert ASPEED KCS binding to schema

On Fri, Apr 09, 2021 at 12:33:10AM CDT, Andrew Jeffery wrote:
>
>
>On Fri, 9 Apr 2021, at 14:45, Zev Weiss wrote:
>> On Fri, Mar 19, 2021 at 01:27:48AM CDT, Andrew Jeffery wrote:
>> >Given the deprecated binding, improve the ability to detect issues in
>> >the platform devicetrees. Further, a subsequent patch will introduce a
>> >new interrupts property for specifying SerIRQ behaviour, so convert
>> >before we do any further additions.
>> >
>> >Signed-off-by: Andrew Jeffery <[email protected]>
>> >---
>> > .../bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml | 92 +++++++++++++++++++
>> > .../bindings/ipmi/aspeed-kcs-bmc.txt | 33 -------
>> > 2 files changed, 92 insertions(+), 33 deletions(-)
>> > create mode 100644 Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
>> > delete mode 100644 Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt
>> >
>> >diff --git a/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
>> >new file mode 100644
>> >index 000000000000..697ca575454f
>> >--- /dev/null
>> >+++ b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
>> >@@ -0,0 +1,92 @@
>> >+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> >+%YAML 1.2
>> >+---
>> >+$id: http://devicetree.org/schemas/ipmi/aspeed,ast2400-kcs-bmc.yaml
>> >+$schema: http://devicetree.org/meta-schemas/core.yaml
>> >+
>> >+title: ASPEED BMC KCS Devices
>> >+
>> >+maintainers:
>> >+ - Andrew Jeffery <[email protected]>
>> >+
>> >+description: |
>> >+ The Aspeed BMC SoCs typically use the Keyboard-Controller-Style (KCS)
>> >+ interfaces on the LPC bus for in-band IPMI communication with their host.
>> >+
>> >+properties:
>> >+ compatible:
>> >+ oneOf:
>> >+ - description: Channel ID derived from reg
>> >+ items:
>> >+ enum:
>> >+ - aspeed,ast2400-kcs-bmc-v2
>> >+ - aspeed,ast2500-kcs-bmc-v2
>> >+ - aspeed,ast2600-kcs-bmc
>>
>> Should this have a "-v2" suffix?
>
>Well, that was kind of a matter of perspective. The 2600 compatible was
>added after we'd done the v2 of the binding for the 2400 and 2500 so it
>never needed correcting. But it is a case of "don't use the deprecated
>properties with the 2600 compatible".
>
>I don't think a change is necessary?
>

It just looked inconsistent with the corresponding string in the
ast_kcs_bmc_match[] table; perhaps that should be changed instead then?


Zev

2021-04-09 05:50:13

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 09/21] ipmi: kcs_bmc: Split out kcs_bmc_cdev_ipmi



On Fri, 9 Apr 2021, at 13:26, Zev Weiss wrote:
> On Fri, Mar 19, 2021 at 01:27:40AM CDT, Andrew Jeffery wrote:
> >Take steps towards defining a coherent API to separate the KCS device
> >drivers from the userspace interface. Decreasing the coupling will
> >improve the separation of concerns and enable the introduction of
> >alternative userspace interfaces.
> >
> >For now, simply split the chardev logic out to a separate file. The code
> >continues to build into the same module.
> >
> >Signed-off-by: Andrew Jeffery <[email protected]>
> >---
> > drivers/char/ipmi/Makefile | 2 +-
> > drivers/char/ipmi/kcs_bmc.c | 423 +------------------------
> > drivers/char/ipmi/kcs_bmc.h | 10 +-
> > drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 428 ++++++++++++++++++++++++++
> > 4 files changed, 451 insertions(+), 412 deletions(-)
> > create mode 100644 drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
> >
> >diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
> >index 0822adc2ec41..a302bc865370 100644
> >--- a/drivers/char/ipmi/Makefile
> >+++ b/drivers/char/ipmi/Makefile
> >@@ -22,7 +22,7 @@ obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o
> > obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o
> > obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
> > obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
> >-obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o
> >+obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o 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
> >diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
> >index c4336c1f2d6d..ef5c48ffe74a 100644
> >--- a/drivers/char/ipmi/kcs_bmc.c
> >+++ b/drivers/char/ipmi/kcs_bmc.c
> >@@ -3,446 +3,51 @@
> > * 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/module.h>
> >-#include <linux/platform_device.h>
> >-#include <linux/poll.h>
> >-#include <linux/sched.h>
> >-#include <linux/slab.h>
> >
> > #include "kcs_bmc.h"
> >
> >-#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 u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
> >+u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
> > {
> > return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
> > }
> >+EXPORT_SYMBOL(kcs_bmc_read_data);
> >
> >-static inline void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data)
> >+void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data)
> > {
> > kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
> > }
> >+EXPORT_SYMBOL(kcs_bmc_write_data);
> >
> >-static inline u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc)
> >+u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc)
> > {
> > return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
> > }
> >+EXPORT_SYMBOL(kcs_bmc_read_status);
> >
> >-static inline void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data)
> >+void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data)
> > {
> > kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
> > }
> >+EXPORT_SYMBOL(kcs_bmc_write_status);
> >
> >-static void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
> >+void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
> > {
> > kcs_bmc->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
> > }
> >+EXPORT_SYMBOL(kcs_bmc_update_status);
> >
> >-static inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
> >-{
> >- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_STATE_MASK,
> >- KCS_STATUS_STATE(state));
> >-}
> >-
> >-static void kcs_force_abort(struct kcs_bmc *kcs_bmc)
> >-{
> >- set_state(kcs_bmc, ERROR_STATE);
> >- kcs_bmc_read_data(kcs_bmc);
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >-
> >- kcs_bmc->phase = KCS_PHASE_ERROR;
> >- kcs_bmc->data_in_avail = false;
> >- kcs_bmc->data_in_idx = 0;
> >-}
> >-
> >-static void kcs_bmc_handle_data(struct kcs_bmc *kcs_bmc)
> >-{
> >- u8 data;
> >-
> >- switch (kcs_bmc->phase) {
> >- case KCS_PHASE_WRITE_START:
> >- kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
> >- fallthrough;
> >-
> >- case KCS_PHASE_WRITE_DATA:
> >- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
> >- set_state(kcs_bmc, WRITE_STATE);
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
> >- kcs_bmc_read_data(kcs_bmc);
> >- } else {
> >- kcs_force_abort(kcs_bmc);
> >- kcs_bmc->error = KCS_LENGTH_ERROR;
> >- }
> >- break;
> >-
> >- case KCS_PHASE_WRITE_END_CMD:
> >- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
> >- set_state(kcs_bmc, READ_STATE);
> >- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
> >- kcs_bmc_read_data(kcs_bmc);
> >- kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
> >- kcs_bmc->data_in_avail = true;
> >- wake_up_interruptible(&kcs_bmc->queue);
> >- } else {
> >- kcs_force_abort(kcs_bmc);
> >- kcs_bmc->error = KCS_LENGTH_ERROR;
> >- }
> >- break;
> >-
> >- case KCS_PHASE_READ:
> >- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
> >- set_state(kcs_bmc, IDLE_STATE);
> >-
> >- data = kcs_bmc_read_data(kcs_bmc);
> >- if (data != KCS_CMD_READ_BYTE) {
> >- set_state(kcs_bmc, ERROR_STATE);
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >- break;
> >- }
> >-
> >- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >- kcs_bmc->phase = KCS_PHASE_IDLE;
> >- break;
> >- }
> >-
> >- kcs_bmc_write_data(kcs_bmc,
> >- kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
> >- break;
> >-
> >- case KCS_PHASE_ABORT_ERROR1:
> >- set_state(kcs_bmc, READ_STATE);
> >- kcs_bmc_read_data(kcs_bmc);
> >- kcs_bmc_write_data(kcs_bmc, kcs_bmc->error);
> >- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
> >- break;
> >-
> >- case KCS_PHASE_ABORT_ERROR2:
> >- set_state(kcs_bmc, IDLE_STATE);
> >- kcs_bmc_read_data(kcs_bmc);
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >- kcs_bmc->phase = KCS_PHASE_IDLE;
> >- break;
> >-
> >- default:
> >- kcs_force_abort(kcs_bmc);
> >- break;
> >- }
> >-}
> >-
> >-static void kcs_bmc_handle_cmd(struct kcs_bmc *kcs_bmc)
> >-{
> >- u8 cmd;
> >-
> >- set_state(kcs_bmc, WRITE_STATE);
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >-
> >- cmd = kcs_bmc_read_data(kcs_bmc);
> >- switch (cmd) {
> >- case KCS_CMD_WRITE_START:
> >- kcs_bmc->phase = KCS_PHASE_WRITE_START;
> >- kcs_bmc->error = KCS_NO_ERROR;
> >- kcs_bmc->data_in_avail = false;
> >- kcs_bmc->data_in_idx = 0;
> >- break;
> >-
> >- case KCS_CMD_WRITE_END:
> >- if (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
> >- kcs_force_abort(kcs_bmc);
> >- break;
> >- }
> >-
> >- kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
> >- break;
> >-
> >- case KCS_CMD_GET_STATUS_ABORT:
> >- if (kcs_bmc->error == KCS_NO_ERROR)
> >- kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
> >-
> >- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
> >- kcs_bmc->data_in_avail = false;
> >- kcs_bmc->data_in_idx = 0;
> >- break;
> >-
> >- default:
> >- kcs_force_abort(kcs_bmc);
> >- kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
> >- break;
> >- }
> >-}
> >-
> >+int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);
>
> This declaration looks a bit out of place here; should it be in
> kcs_bmc.h instead?

These are only temporary and get removed later on in the series after
some shuffling of the code.

Andrew

2021-04-09 06:00:47

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 10/21] ipmi: kcs_bmc: Turn the driver data-structures inside-out



On Fri, 9 Apr 2021, at 13:27, Zev Weiss wrote:
> On Fri, Mar 19, 2021 at 01:27:41AM CDT, Andrew Jeffery wrote:
> >Make the KCS device drivers responsible for allocating their own memory.
> >
> >Until now the private data for the device driver was allocated internal
> >to the private data for the chardev interface. This coupling required
> >the slightly awkward API of passing through the struct size for the
> >driver private data to the chardev constructor, and then retrieving a
> >pointer to the driver private data from the allocated chardev memory.
> >
> >In addition to being awkward, the arrangement prevents the
> >implementation of alternative userspace interfaces as the device driver
> >private data is not independent.
> >
> >Peel a layer off the onion and turn the data-structures inside out by
> >exploiting container_of() and embedding `struct kcs_device` in the
> >driver private data.
> >
> >Signed-off-by: Andrew Jeffery <[email protected]>
> >---
> > drivers/char/ipmi/kcs_bmc.c | 15 +++++--
> > drivers/char/ipmi/kcs_bmc.h | 12 ++----
> > drivers/char/ipmi/kcs_bmc_aspeed.c | 60 ++++++++++++++++-----------
> > drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 60 ++++++++++++++++++---------
> > drivers/char/ipmi/kcs_bmc_npcm7xx.c | 37 ++++++++++-------
> > 5 files changed, 113 insertions(+), 71 deletions(-)
> >
> >diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
> >index ef5c48ffe74a..709b6bdec165 100644
> >--- a/drivers/char/ipmi/kcs_bmc.c
> >+++ b/drivers/char/ipmi/kcs_bmc.c
> >@@ -44,12 +44,19 @@ int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
> > }
> > EXPORT_SYMBOL(kcs_bmc_handle_event);
> >
> >-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
> >-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel)
> >+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
>
> Another declaration perhaps intended for kcs_bmc.h?

These are temporary while the code gets shuffled around. The symbol
name is an implementation detail, not a "public" part of the API; after
some further shuffling these are eventually assigned as callbacks in an
ops struct.

>
> >+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc)
> > {
> >- return kcs_bmc_ipmi_alloc(dev, sizeof_priv, channel);
> >+ return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
> > }
> >-EXPORT_SYMBOL(kcs_bmc_alloc);
> >+EXPORT_SYMBOL(kcs_bmc_add_device);
> >+
> >+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
>
> Here too.
>
> >+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc)
> >+{
> >+ return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
> >+}
> >+EXPORT_SYMBOL(kcs_bmc_remove_device);
> >
> > MODULE_LICENSE("GPL v2");
> > MODULE_AUTHOR("Haiyue Wang <[email protected]>");
> >diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
> >index febea0c8deb4..bf0ae327997f 100644
> >--- a/drivers/char/ipmi/kcs_bmc.h
> >+++ b/drivers/char/ipmi/kcs_bmc.h
> >@@ -67,6 +67,8 @@ struct kcs_ioreg {
> > };
> >
> > struct kcs_bmc {
> >+ struct device *dev;
> >+
> > spinlock_t lock;
> >
> > u32 channel;
> >@@ -94,17 +96,11 @@ struct kcs_bmc {
> > u8 *kbuffer;
> >
> > struct miscdevice miscdev;
> >-
> >- unsigned long priv[];
> > };
> >
> >-static inline void *kcs_bmc_priv(struct kcs_bmc *kcs_bmc)
> >-{
> >- return kcs_bmc->priv;
> >-}
> >-
> > int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
> >-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel);
> >+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc);
> >+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc);
> >
> > u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
> > void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
> >diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
> >index 630cf095560e..0416ac78ce68 100644
> >--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
> >+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
> >@@ -61,6 +61,8 @@
> > #define LPC_STR4 0x11C
> >
> > struct aspeed_kcs_bmc {
> >+ struct kcs_bmc kcs_bmc;
> >+
> > struct regmap *map;
> > };
> >
> >@@ -69,9 +71,14 @@ struct aspeed_kcs_of_ops {
> > int (*get_io_address)(struct platform_device *pdev);
> > };
> >
> >+static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc *kcs_bmc)
> >+{
> >+ return container_of(kcs_bmc, struct aspeed_kcs_bmc, kcs_bmc);
> >+}
> >+
> > static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
> > {
> >- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
> >+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
> > u32 val = 0;
> > int rc;
> >
> >@@ -83,7 +90,7 @@ static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
> >
> > static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
> > {
> >- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
> >+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
> > int rc;
> >
> > rc = regmap_write(priv->map, reg, data);
> >@@ -92,7 +99,7 @@ static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
> >
> > static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val)
> > {
> >- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
> >+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
> > int rc;
> >
> > rc = regmap_update_bits(priv->map, reg, mask, val);
> >@@ -114,7 +121,7 @@ static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val
> > */
> > static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
> > {
> >- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
> >+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
> >
> > switch (kcs_bmc->channel) {
> > case 1:
> >@@ -148,7 +155,7 @@ static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
> >
> > static void aspeed_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
> > {
> >- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
> >+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
> >
> > switch (kcs_bmc->channel) {
> > case 1:
> >@@ -323,16 +330,16 @@ static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
> > static int aspeed_kcs_probe(struct platform_device *pdev)
> > {
> > const struct aspeed_kcs_of_ops *ops;
> >- struct device *dev = &pdev->dev;
> >+ struct aspeed_kcs_bmc *priv;
> > struct kcs_bmc *kcs_bmc;
> > struct device_node *np;
> > int rc, channel, addr;
> >
> >- np = dev->of_node->parent;
> >+ 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(dev, "unsupported LPC device binding\n");
> >+ dev_err(&pdev->dev, "unsupported LPC device binding\n");
> > return -ENODEV;
> > }
> > ops = of_device_get_match_data(&pdev->dev);
> >@@ -343,18 +350,27 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> > if (channel < 0)
> > return channel;
> >
> >- kcs_bmc = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
> >- if (!kcs_bmc)
> >+ addr = ops->get_io_address(pdev);
> >+ if (addr < 0)
> >+ return addr;
> >+
> >+ 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->io_inputb = aspeed_kcs_inb;
> > kcs_bmc->io_outputb = aspeed_kcs_outb;
> > kcs_bmc->io_updateb = aspeed_kcs_updateb;
> >
> >- addr = ops->get_io_address(pdev);
> >- if (addr < 0)
> >- return addr;
> >+ 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;
> >+ }
>
> The reanimated priv->map initialization I suspect wasn't meant to
> have been removed in the first place...

Yeah, I'll have to go back and figure out what went wrong there!

Thanks for catching that.

>
> >
> > aspeed_kcs_set_address(kcs_bmc, addr);
> >
> >@@ -362,29 +378,25 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> > if (rc)
> > return rc;
> >
> >- dev_set_drvdata(dev, kcs_bmc);
> >+ platform_set_drvdata(pdev, priv);
> >
> > aspeed_kcs_enable_channel(kcs_bmc, true);
> >
> >- rc = misc_register(&kcs_bmc->miscdev);
> >- if (rc) {
> >- dev_err(dev, "Unable to register device\n");
> >+ rc = kcs_bmc_add_device(&priv->kcs_bmc);
> >+ if (rc < 0)
> > return rc;
> >- }
> >
> >- dev_dbg(&pdev->dev,
> >- "Probed KCS device %d (IDR=0x%x, ODR=0x%x, STR=0x%x)\n",
> >- kcs_bmc->channel, kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr,
> >- kcs_bmc->ioreg.str);
> >+ dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n", kcs_bmc->channel, addr);
>
> Is the dbg->info change here intentional? (I have no particular
> objection if so, but it's often a change I make myself during
> testing/debugging and then forget to revert...)

Yeah, it was possibly something I forgot to revert. If others have
issues with it staying at dev_info() I'll switch it back.

>
> >
> > return 0;
> > }
> >
> > static int aspeed_kcs_remove(struct platform_device *pdev)
> > {
> >- struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
> >+ struct aspeed_kcs_bmc *priv = platform_get_drvdata(pdev);
> >+ struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
> >
> >- misc_deregister(&kcs_bmc->miscdev);
> >+ kcs_bmc_remove_device(kcs_bmc);
>
> Should we propagate the return value outward here?

Probably!

>
> >
> > return 0;
> > }
> >diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
> >index 82c77994e481..0ca71c135a1a 100644
> >--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
> >+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
> >@@ -382,7 +382,7 @@ static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
> > return 0;
> > }
> >
> >-static const struct file_operations kcs_bmc_fops = {
> >+static const struct file_operations kcs_bmc_ipmi_fops = {
> > .owner = THIS_MODULE,
> > .open = kcs_bmc_ipmi_open,
> > .read = kcs_bmc_ipmi_read,
> >@@ -392,36 +392,58 @@ static const struct file_operations kcs_bmc_fops = {
> > .unlocked_ioctl = kcs_bmc_ipmi_ioctl,
> > };
> >
> >-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
> >-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel)
> >+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
>
> Errant declaration again?

As previously explained.

>
> >+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc)
> > {
> >- struct kcs_bmc *kcs_bmc;
> >-
> >- kcs_bmc = devm_kzalloc(dev, sizeof(*kcs_bmc) + sizeof_priv, GFP_KERNEL);
> >- if (!kcs_bmc)
> >- return NULL;
> >+ int rc;
> >
> > spin_lock_init(&kcs_bmc->lock);
> >- kcs_bmc->channel = channel;
> >-
> > mutex_init(&kcs_bmc->mutex);
> > init_waitqueue_head(&kcs_bmc->queue);
> >
> >- kcs_bmc->data_in = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
> >- kcs_bmc->data_out = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
> >- kcs_bmc->kbuffer = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
> >+ kcs_bmc->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
> >+ kcs_bmc->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
> >+ kcs_bmc->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
> >
> > kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
> >- kcs_bmc->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%u",
> >- DEVICE_NAME, channel);
> >+ kcs_bmc->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u",
> >+ DEVICE_NAME, kcs_bmc->channel);
> > if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
> > !kcs_bmc->miscdev.name)
> >- return NULL;
> >- kcs_bmc->miscdev.fops = &kcs_bmc_fops;
> >+ return -ENOMEM;
> >
> >- return kcs_bmc;
> >+ kcs_bmc->miscdev.fops = &kcs_bmc_ipmi_fops;
> >+
> >+ rc = misc_register(&kcs_bmc->miscdev);
> >+ if (rc) {
> >+ dev_err(kcs_bmc->dev, "Unable to register device: %d\n", rc);
> >+ return rc;
> >+ }
> >+
> >+ dev_info(kcs_bmc->dev, "Initialised IPMI client for channel %d", kcs_bmc->channel);
> >+
> >+ return 0;
> >+}
> >+EXPORT_SYMBOL(kcs_bmc_ipmi_attach_cdev);
> >+
> >+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
>
> Same here.

Same explanation.

>
> >+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc)
> >+{
> >+ misc_deregister(&kcs_bmc->miscdev);
> >+
> >+ spin_lock_irq(&kcs_bmc->lock);
> >+ kcs_bmc->running = 0;
> >+ kcs_bmc_ipmi_force_abort(kcs_bmc);
> >+ spin_unlock_irq(&kcs_bmc->lock);
> >+
> >+ devm_kfree(kcs_bmc->dev, kcs_bmc->kbuffer);
> >+ devm_kfree(kcs_bmc->dev, kcs_bmc->data_out);
> >+ devm_kfree(kcs_bmc->dev, kcs_bmc->data_in);
> >+ devm_kfree(kcs_bmc->dev, kcs_bmc);
> >+
> >+ return 0;
> > }
> >-EXPORT_SYMBOL(kcs_bmc_ipmi_alloc);
> >+EXPORT_SYMBOL(kcs_bmc_ipmi_detach_cdev);
> >
> > MODULE_LICENSE("GPL v2");
> > MODULE_AUTHOR("Haiyue Wang <[email protected]>");
> >diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
> >index 1f44aadec9e8..5d017498dc69 100644
> >--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
> >+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
> >@@ -65,6 +65,8 @@ struct npcm7xx_kcs_reg {
> > };
> >
> > struct npcm7xx_kcs_bmc {
> >+ struct kcs_bmc kcs_bmc;
> >+
> > struct regmap *map;
> >
> > const struct npcm7xx_kcs_reg *reg;
> >@@ -76,9 +78,14 @@ static const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = {
> > { .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE },
> > };
> >
> >+static inline struct npcm7xx_kcs_bmc *to_npcm7xx_kcs_bmc(struct kcs_bmc *kcs_bmc)
> >+{
> >+ return container_of(kcs_bmc, struct npcm7xx_kcs_bmc, kcs_bmc);
> >+}
> >+
> > static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
> > {
> >- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
> >+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
> > u32 val = 0;
> > int rc;
> >
> >@@ -90,7 +97,7 @@ static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
> >
> > static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
> > {
> >- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
> >+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
> > int rc;
> >
> > rc = regmap_write(priv->map, reg, data);
> >@@ -99,7 +106,7 @@ static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
> >
> > static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 data)
> > {
> >- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
> >+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
> > int rc;
> >
> > rc = regmap_update_bits(priv->map, reg, mask, data);
> >@@ -108,7 +115,7 @@ static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 da
> >
> > static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
> > {
> >- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
> >+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
> >
> > regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
> > enable ? KCS_CTL_IBFIE : 0);
> >@@ -155,11 +162,10 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
> > return -ENODEV;
> > }
> >
> >- kcs_bmc = kcs_bmc_alloc(dev, sizeof(*priv), chan);
> >- if (!kcs_bmc)
> >+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> >+ if (!priv)
> > return -ENOMEM;
> >
> >- priv = kcs_bmc_priv(kcs_bmc);
> > priv->map = syscon_node_to_regmap(dev->parent->of_node);
> > if (IS_ERR(priv->map)) {
> > dev_err(dev, "Couldn't get regmap\n");
> >@@ -167,6 +173,9 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
> > }
> > 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;
> >@@ -174,31 +183,27 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
> > kcs_bmc->io_outputb = npcm7xx_kcs_outb;
> > kcs_bmc->io_updateb = npcm7xx_kcs_updateb;
> >
> >- dev_set_drvdata(dev, kcs_bmc);
> >+ platform_set_drvdata(pdev, priv);
> >
> > npcm7xx_kcs_enable_channel(kcs_bmc, true);
> > rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev);
> > if (rc)
> > return rc;
> >
> >- rc = misc_register(&kcs_bmc->miscdev);
> >- if (rc) {
> >- dev_err(dev, "Unable to register device\n");
> >- 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;
> >+ return kcs_bmc_add_device(kcs_bmc);
> > }
> >
> > static int npcm7xx_kcs_remove(struct platform_device *pdev)
> > {
> >- struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
> >+ struct npcm7xx_kcs_bmc *priv = platform_get_drvdata(pdev);
> >+ struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
> >
> >- misc_deregister(&kcs_bmc->miscdev);
> >+ kcs_bmc_remove_device(kcs_bmc);
>
> As with the corresponding aspeed code, should we propagate the return
> value here?

I'll address this.

Thanks for the review!

Andrew

2021-04-09 06:08:34

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 11/21] ipmi: kcs_bmc: Split headers into device and client



On Fri, 9 Apr 2021, at 13:31, Zev Weiss wrote:
> On Fri, Mar 19, 2021 at 01:27:42AM CDT, Andrew Jeffery wrote:
> >Strengthen the distinction between code that abstracts the
> >implementation of the KCS behaviours (device drivers) and code that
> >exploits KCS behaviours (clients). Neither needs to know about the APIs
> >required by the other, so provide separate headers.
> >
> >Signed-off-by: Andrew Jeffery <[email protected]>
> >---
> > drivers/char/ipmi/kcs_bmc.c | 21 ++++++++++-----
> > drivers/char/ipmi/kcs_bmc.h | 30 ++++++++++-----------
> > drivers/char/ipmi/kcs_bmc_aspeed.c | 20 +++++++++-----
> > drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 39 ++++++++++++++++++---------
> > drivers/char/ipmi/kcs_bmc_client.h | 29 ++++++++++++++++++++
> > drivers/char/ipmi/kcs_bmc_device.h | 19 +++++++++++++
> > drivers/char/ipmi/kcs_bmc_npcm7xx.c | 20 +++++++++-----
> > 7 files changed, 129 insertions(+), 49 deletions(-)
> > create mode 100644 drivers/char/ipmi/kcs_bmc_client.h
> > create mode 100644 drivers/char/ipmi/kcs_bmc_device.h
> >
> >diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
> >index 709b6bdec165..1046ce2bbefc 100644
> >--- a/drivers/char/ipmi/kcs_bmc.c
> >+++ b/drivers/char/ipmi/kcs_bmc.c
> >@@ -1,46 +1,52 @@
> > // SPDX-License-Identifier: GPL-2.0
> > /*
> > * Copyright (c) 2015-2018, Intel Corporation.
> >+ * Copyright (c) 2021, IBM Corp.
> > */
> >
> > #include <linux/module.h>
> >
> > #include "kcs_bmc.h"
> >
> >+/* Implement both the device and client interfaces here */
> >+#include "kcs_bmc_device.h"
> >+#include "kcs_bmc_client.h"
> >+
> >+/* Consumer data access */
> >+
> > u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
> > {
> >- return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
> >+ 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 *kcs_bmc, u8 data)
> > {
> >- kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, 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 *kcs_bmc)
> > {
> >- return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
> >+ 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 *kcs_bmc, u8 data)
> > {
> >- kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, 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 *kcs_bmc, u8 mask, u8 val)
> > {
> >- kcs_bmc->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
> >+ kcs_bmc->ops->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
> > }
> > EXPORT_SYMBOL(kcs_bmc_update_status);
> >
> >-int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);
> > int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
> > {
> >- return kcs_bmc_ipmi_event(kcs_bmc);
> >+ return kcs_bmc->client.ops->event(&kcs_bmc->client);
> > }
> > EXPORT_SYMBOL(kcs_bmc_handle_event);
> >
> >@@ -60,4 +66,5 @@ EXPORT_SYMBOL(kcs_bmc_remove_device);
> >
> > MODULE_LICENSE("GPL v2");
> > MODULE_AUTHOR("Haiyue Wang <[email protected]>");
> >+MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
> > MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
> >diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
> >index bf0ae327997f..a1350e567723 100644
> >--- a/drivers/char/ipmi/kcs_bmc.h
> >+++ b/drivers/char/ipmi/kcs_bmc.h
> >@@ -8,6 +8,15 @@
> >
> > #include <linux/miscdevice.h>
> >
> >+#include "kcs_bmc_client.h"
> >+
> >+#define KCS_BMC_EVENT_NONE 0
> >+#define KCS_BMC_EVENT_HANDLED 1
>
> Is there a particular reason we're introducing these macros and using an
> int return type for kcs_bmc_client_ops.event instead of just having it
> be irqreturn_t? Other event types or outcomes we're anticipating needing
> to handle maybe?

In earlier iterations of the patches I was doing some extra work in the
event handling path and felt it was useful at the time. However I've
refactored things a little and this may have outlived its usefulness.

I'll reasses!

>
> >+
> >+#define KCS_BMC_STR_OBF BIT(0)
> >+#define KCS_BMC_STR_IBF BIT(1)
> >+#define KCS_BMC_STR_CMD_DAT BIT(3)
>
> The first two of these macros are used later in the series, but the third
> doesn't end up used at all I think?

I think I just added it as documentation as the hardware-defined bits
aren't contiguous.

Andrew

2021-04-09 06:17:36

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 12/21] ipmi: kcs_bmc: Strip private client data from struct kcs_bmc



On Fri, 9 Apr 2021, at 13:37, Zev Weiss wrote:
> On Fri, Mar 19, 2021 at 01:27:43AM CDT, Andrew Jeffery wrote:
> >Move all client-private data out of `struct kcs_bmc` into the KCS client
> >implementation.
> >
> >With this change the KCS BMC core code now only concerns itself with
> >abstract `struct kcs_bmc` and `struct kcs_bmc_client` types, achieving
> >expected separation of concerns. Further, the change clears the path for
> >implementation of alternative userspace interfaces.
> >
> >The chardev data-structures are rearranged in the same manner applied to
> >the KCS device driver data-structures in an earlier patch - `struct
> >kcs_bmc_client` is embedded in the client's private data and we exploit
> >container_of() to translate as required.
> >
> >Finally, now that it is free of client data, `struct kcs_bmc` is renamed
> >to `struct kcs_bmc_device` to contrast `struct kcs_bmc_client`.
> >
> >Signed-off-by: Andrew Jeffery <[email protected]>
> >---
> > drivers/char/ipmi/kcs_bmc.c | 68 +++-
> > drivers/char/ipmi/kcs_bmc.h | 86 +-----
> > drivers/char/ipmi/kcs_bmc_aspeed.c | 22 +-
> > drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 428 ++++++++++++++++----------
> > drivers/char/ipmi/kcs_bmc_client.h | 28 +-
> > drivers/char/ipmi/kcs_bmc_device.h | 12 +-
> > drivers/char/ipmi/kcs_bmc_npcm7xx.c | 20 +-
> > 7 files changed, 368 insertions(+), 296 deletions(-)
> >
> >diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
> >index 1046ce2bbefc..266ebec71d6f 100644
> >--- a/drivers/char/ipmi/kcs_bmc.c
> >+++ b/drivers/char/ipmi/kcs_bmc.c
> >@@ -4,6 +4,7 @@
> > * Copyright (c) 2021, IBM Corp.
> > */
> >
> >+#include <linux/device.h>
> > #include <linux/module.h>
> >
> > #include "kcs_bmc.h"
> >@@ -14,51 +15,96 @@
> >
> > /* Consumer data access */
> >
> >-u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
> >+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 *kcs_bmc, u8 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 *kcs_bmc)
> >+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 *kcs_bmc, u8 data)
> >+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 *kcs_bmc, u8 mask, u8 val)
> >+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);
> >
> >-int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
> >+int kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc)
> > {
> >- return kcs_bmc->client.ops->event(&kcs_bmc->client);
> >+ struct kcs_bmc_client *client;
> >+ int rc;
> >+
> >+ spin_lock(&kcs_bmc->lock);
> >+ client = kcs_bmc->client;
> >+ if (client) {
> >+ rc = client->ops->event(client);
> >+ } else {
> >+ u8 status;
> >+
> >+ status = kcs_bmc_read_status(kcs_bmc);
> >+ if (status & KCS_BMC_STR_IBF) {
> >+ /* Ack the event by reading the data */
> >+ kcs_bmc_read_data(kcs_bmc);
> >+ rc = KCS_BMC_EVENT_HANDLED;
> >+ } else {
> >+ rc = KCS_BMC_EVENT_NONE;
> >+ }
> >+ }
> >+ spin_unlock(&kcs_bmc->lock);
> >+
> >+ return rc;
> > }
> > EXPORT_SYMBOL(kcs_bmc_handle_event);
> >
> >-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
> >-int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc)
> >+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 {
> >+ kcs_bmc->client = client;
> >+ 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)
> >+ kcs_bmc->client = NULL;
>
> Is there any situation in which a non-matching client could be passed in
> here?

Yes; this allows the chardev modules to just unconditionally call
kcs_bmc_disable_device() without having to duplicate the state tracking
done here.

> Might we consider issuing a warning of some sort or returning an
> error to the caller if so?

No, see above.

>
> >+ spin_unlock_irq(&kcs_bmc->lock);
> >+}
> >+EXPORT_SYMBOL(kcs_bmc_disable_device);
> >+
> >+int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc)
> > {
> > return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
> > }
> > EXPORT_SYMBOL(kcs_bmc_add_device);
> >
> >-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
> >-int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc)
> >+int kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc)
> > {
> > return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
> > }
> >diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
> >index a1350e567723..3f266740c759 100644
> >--- a/drivers/char/ipmi/kcs_bmc.h
> >+++ b/drivers/char/ipmi/kcs_bmc.h
> >@@ -6,9 +6,7 @@
> > #ifndef __KCS_BMC_H__
> > #define __KCS_BMC_H__
> >
> >-#include <linux/miscdevice.h>
> >-
> >-#include "kcs_bmc_client.h"
> >+#include <linux/list.h>
> >
> > #define KCS_BMC_EVENT_NONE 0
> > #define KCS_BMC_EVENT_HANDLED 1
> >@@ -17,53 +15,6 @@
> > #define KCS_BMC_STR_IBF BIT(1)
> > #define KCS_BMC_STR_CMD_DAT BIT(3)
> >
> >-/* 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_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_errors {
> >- KCS_NO_ERROR = 0x00,
> >- KCS_ABORTED_BY_COMMAND = 0x01,
> >- KCS_ILLEGAL_CONTROL_CODE = 0x02,
> >- KCS_LENGTH_ERROR = 0x06,
> >- KCS_UNSPECIFIED_ERROR = 0xFF
> >-};
> >-
> > /* IPMI 2.0 - 9.5, KCS Interface Registers
> > * @idr: Input Data Register
> > * @odr: Output Data Register
> >@@ -76,36 +27,23 @@ struct kcs_ioreg {
> > };
> >
> > struct kcs_bmc_device_ops;
> >+struct kcs_bmc_client;
> >+
> >+struct kcs_bmc_device {
> >+ struct list_head entry;
> >
> >-struct kcs_bmc {
> > struct device *dev;
> >-
> >- const struct kcs_bmc_device_ops *ops;
> >-
> >- struct kcs_bmc_client client;
> >-
> >- spinlock_t lock;
> >-
> > u32 channel;
> >- int running;
> >
> > struct kcs_ioreg ioreg;
> >
> >- enum kcs_phases phase;
> >- enum kcs_errors error;
> >+ const struct kcs_bmc_device_ops *ops;
> >
> >- 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;
> >+ spinlock_t lock;
> >+ struct kcs_bmc_client *client;
> > };
> >+
> >+/* Temporary exports while refactoring */
> >+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc);
> >+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc);
> > #endif /* __KCS_BMC_H__ */
> >diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
> >index 1b313355b1c8..6f26e7366c0b 100644
> >--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
> >+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
> >@@ -61,7 +61,7 @@
> > #define LPC_STR4 0x11C
> >
> > struct aspeed_kcs_bmc {
> >- struct kcs_bmc kcs_bmc;
> >+ struct kcs_bmc_device kcs_bmc;
> >
> > struct regmap *map;
> > };
> >@@ -71,12 +71,12 @@ struct aspeed_kcs_of_ops {
> > int (*get_io_address)(struct platform_device *pdev);
> > };
> >
> >-static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc *kcs_bmc)
> >+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 *kcs_bmc, u32 reg)
> >+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;
> >@@ -88,7 +88,7 @@ static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
> > return rc == 0 ? (u8) val : 0;
> > }
> >
> >-static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
> >+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;
> >@@ -97,7 +97,7 @@ static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
> > WARN(rc != 0, "regmap_write() failed: %d\n", rc);
> > }
> >
> >-static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val)
> >+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;
> >@@ -119,7 +119,7 @@ static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val
> > * C. KCS4
> > * D / C : CA4h / CA5h
> > */
> >-static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
> >+static void aspeed_kcs_set_address(struct kcs_bmc_device *kcs_bmc, u16 addr)
> > {
> > struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
> >
> >@@ -153,7 +153,7 @@ static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
> > }
> > }
> >
> >-static void aspeed_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
> >+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);
> >
> >@@ -228,7 +228,7 @@ static const struct kcs_bmc_device_ops aspeed_kcs_ops = {
> >
> > static irqreturn_t aspeed_kcs_irq(int irq, void *arg)
> > {
> >- struct kcs_bmc *kcs_bmc = arg;
> >+ struct kcs_bmc_device *kcs_bmc = arg;
> > int rc;
> >
> > rc = kcs_bmc_handle_event(kcs_bmc);
> >@@ -238,7 +238,7 @@ static irqreturn_t aspeed_kcs_irq(int irq, void *arg)
> > return rc == KCS_BMC_EVENT_HANDLED ? IRQ_HANDLED : IRQ_NONE;
> > }
> >
> >-static int aspeed_kcs_config_irq(struct kcs_bmc *kcs_bmc,
> >+static int aspeed_kcs_config_irq(struct kcs_bmc_device *kcs_bmc,
> > struct platform_device *pdev)
> > {
> > struct device *dev = &pdev->dev;
> >@@ -338,8 +338,8 @@ static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
> > static int aspeed_kcs_probe(struct platform_device *pdev)
> > {
> > const struct aspeed_kcs_of_ops *ops;
> >+ struct kcs_bmc_device *kcs_bmc;
> > struct aspeed_kcs_bmc *priv;
> >- struct kcs_bmc *kcs_bmc;
> > struct device_node *np;
> > int rc, channel, addr;
> >
> >@@ -400,7 +400,7 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> > static int aspeed_kcs_remove(struct platform_device *pdev)
> > {
> > struct aspeed_kcs_bmc *priv = platform_get_drvdata(pdev);
> >- struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
> >+ struct kcs_bmc_device *kcs_bmc = &priv->kcs_bmc;
> >
> > kcs_bmc_remove_device(kcs_bmc);
> >
> >diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
> >index fd852d8abe48..58c42e76483d 100644
> >--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
> >+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
> >@@ -8,13 +8,88 @@
> > #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.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"
> >
> >@@ -44,171 +119,169 @@ enum kcs_states {
> > #define KCS_CMD_WRITE_END 0x62
> > #define KCS_CMD_READ_BYTE 0x68
> >
> >-static inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
> >+static inline void set_state(struct kcs_bmc_ipmi *priv, u8 state)
> > {
> >- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_STATE_MASK,
> >- KCS_STATUS_STATE(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 *kcs_bmc)
> >+static void kcs_bmc_ipmi_force_abort(struct kcs_bmc_ipmi *priv)
> > {
> >- set_state(kcs_bmc, ERROR_STATE);
> >- kcs_bmc_read_data(kcs_bmc);
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >+ set_state(priv, ERROR_STATE);
> >+ kcs_bmc_read_data(priv->client.dev);
> >+ kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
> >
> >- kcs_bmc->phase = KCS_PHASE_ERROR;
> >- kcs_bmc->data_in_avail = false;
> >- kcs_bmc->data_in_idx = 0;
> >+ 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 *kcs_bmc)
> >+static void kcs_bmc_ipmi_handle_data(struct kcs_bmc_ipmi *priv)
> > {
> >+ struct kcs_bmc_device *dev;
> > u8 data;
> >
> >- switch (kcs_bmc->phase) {
> >+ dev = priv->client.dev;
> >+
> >+ switch (priv->phase) {
> > case KCS_PHASE_WRITE_START:
> >- kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
> >+ priv->phase = KCS_PHASE_WRITE_DATA;
> > fallthrough;
> >
> > case KCS_PHASE_WRITE_DATA:
> >- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
> >- set_state(kcs_bmc, WRITE_STATE);
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
> >- kcs_bmc_read_data(kcs_bmc);
> >+ 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(kcs_bmc);
> >- kcs_bmc->error = KCS_LENGTH_ERROR;
> >+ kcs_bmc_ipmi_force_abort(priv);
> >+ priv->error = KCS_LENGTH_ERROR;
> > }
> > break;
> >
> > case KCS_PHASE_WRITE_END_CMD:
> >- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
> >- set_state(kcs_bmc, READ_STATE);
> >- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
> >- kcs_bmc_read_data(kcs_bmc);
> >- kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
> >- kcs_bmc->data_in_avail = true;
> >- wake_up_interruptible(&kcs_bmc->queue);
> >+ 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(kcs_bmc);
> >- kcs_bmc->error = KCS_LENGTH_ERROR;
> >+ kcs_bmc_ipmi_force_abort(priv);
> >+ priv->error = KCS_LENGTH_ERROR;
> > }
> > break;
> >
> > case KCS_PHASE_READ:
> >- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
> >- set_state(kcs_bmc, IDLE_STATE);
> >+ if (priv->data_out_idx == priv->data_out_len)
> >+ set_state(priv, IDLE_STATE);
> >
> >- data = kcs_bmc_read_data(kcs_bmc);
> >+ data = kcs_bmc_read_data(dev);
> > if (data != KCS_CMD_READ_BYTE) {
> >- set_state(kcs_bmc, ERROR_STATE);
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >+ set_state(priv, ERROR_STATE);
> >+ kcs_bmc_write_data(dev, KCS_ZERO_DATA);
> > break;
> > }
> >
> >- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >- kcs_bmc->phase = KCS_PHASE_IDLE;
> >+ 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(kcs_bmc,
> >- kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
> >+ kcs_bmc_write_data(dev, priv->data_out[priv->data_out_idx++]);
> > break;
> >
> > case KCS_PHASE_ABORT_ERROR1:
> >- set_state(kcs_bmc, READ_STATE);
> >- kcs_bmc_read_data(kcs_bmc);
> >- kcs_bmc_write_data(kcs_bmc, kcs_bmc->error);
> >- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
> >+ 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(kcs_bmc, IDLE_STATE);
> >- kcs_bmc_read_data(kcs_bmc);
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >- kcs_bmc->phase = KCS_PHASE_IDLE;
> >+ 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(kcs_bmc);
> >+ kcs_bmc_ipmi_force_abort(priv);
> > break;
> > }
> > }
> >
> >-static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc *kcs_bmc)
> >+static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc_ipmi *priv)
> > {
> > u8 cmd;
> >
> >- set_state(kcs_bmc, WRITE_STATE);
> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
> >+ set_state(priv, WRITE_STATE);
> >+ kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
> >
> >- cmd = kcs_bmc_read_data(kcs_bmc);
> >+ cmd = kcs_bmc_read_data(priv->client.dev);
> > switch (cmd) {
> > case KCS_CMD_WRITE_START:
> >- kcs_bmc->phase = KCS_PHASE_WRITE_START;
> >- kcs_bmc->error = KCS_NO_ERROR;
> >- kcs_bmc->data_in_avail = false;
> >- kcs_bmc->data_in_idx = 0;
> >+ 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 (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
> >- kcs_bmc_ipmi_force_abort(kcs_bmc);
> >+ if (priv->phase != KCS_PHASE_WRITE_DATA) {
> >+ kcs_bmc_ipmi_force_abort(priv);
> > break;
> > }
> >
> >- kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
> >+ priv->phase = KCS_PHASE_WRITE_END_CMD;
> > break;
> >
> > case KCS_CMD_GET_STATUS_ABORT:
> >- if (kcs_bmc->error == KCS_NO_ERROR)
> >- kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
> >+ if (priv->error == KCS_NO_ERROR)
> >+ priv->error = KCS_ABORTED_BY_COMMAND;
> >
> >- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
> >- kcs_bmc->data_in_avail = false;
> >- kcs_bmc->data_in_idx = 0;
> >+ priv->phase = KCS_PHASE_ABORT_ERROR1;
> >+ priv->data_in_avail = false;
> >+ priv->data_in_idx = 0;
> > break;
> >
> > default:
> >- kcs_bmc_ipmi_force_abort(kcs_bmc);
> >- kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
> >+ kcs_bmc_ipmi_force_abort(priv);
> >+ priv->error = KCS_ILLEGAL_CONTROL_CODE;
> > break;
> > }
> > }
> >
> >-static inline struct kcs_bmc *client_to_kcs_bmc(struct kcs_bmc_client *client)
> >+static inline struct kcs_bmc_ipmi *client_to_kcs_bmc_ipmi(struct kcs_bmc_client *client)
> > {
> >- return container_of(client, struct kcs_bmc, client);
> >+ return container_of(client, struct kcs_bmc_ipmi, client);
> > }
> >
> > static int kcs_bmc_ipmi_event(struct kcs_bmc_client *client)
> > {
> >- struct kcs_bmc *kcs_bmc;
> >- unsigned long flags;
> >+ struct kcs_bmc_ipmi *priv;
> > u8 status;
> > int ret;
> >
> >- kcs_bmc = client_to_kcs_bmc(client);
> >+ priv = client_to_kcs_bmc_ipmi(client);
> >+ if (!priv)
> >+ return KCS_BMC_EVENT_NONE;
> >
> >- spin_lock_irqsave(&kcs_bmc->lock, flags);
> >+ spin_lock(&priv->lock);
> >
> >- status = kcs_bmc_read_status(kcs_bmc);
> >+ status = kcs_bmc_read_status(client->dev);
> > if (status & KCS_STATUS_IBF) {
> >- if (!kcs_bmc->running)
> >- kcs_bmc_ipmi_force_abort(kcs_bmc);
> >- else if (status & KCS_STATUS_CMD_DAT)
> >- kcs_bmc_ipmi_handle_cmd(kcs_bmc);
> >+ if (status & KCS_STATUS_CMD_DAT)
> >+ kcs_bmc_ipmi_handle_cmd(priv);
> > else
> >- kcs_bmc_ipmi_handle_data(kcs_bmc);
> >+ kcs_bmc_ipmi_handle_data(priv);
> >
> > ret = KCS_BMC_EVENT_HANDLED;
> > } else {
> > ret = KCS_BMC_EVENT_NONE;
> > }
> >
> >- spin_unlock_irqrestore(&kcs_bmc->lock, flags);
> >+ spin_unlock(&priv->lock);
> >
> > return ret;
> > }
> >@@ -217,37 +290,29 @@ static const struct kcs_bmc_client_ops kcs_bmc_ipmi_client_ops = {
> > .event = kcs_bmc_ipmi_event,
> > };
> >
> >-static inline struct kcs_bmc *file_to_kcs_bmc(struct file *filp)
> >+static inline struct kcs_bmc_ipmi *to_kcs_bmc(struct file *filp)
> > {
> >- return container_of(filp->private_data, struct kcs_bmc, miscdev);
> >+ 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 *kcs_bmc = file_to_kcs_bmc(filp);
> >- int ret = 0;
> >+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
> >
> >- spin_lock_irq(&kcs_bmc->lock);
> >- if (!kcs_bmc->running)
> >- kcs_bmc->running = 1;
> >- else
> >- ret = -EBUSY;
> >- spin_unlock_irq(&kcs_bmc->lock);
> >-
> >- return ret;
> >+ 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 *kcs_bmc = file_to_kcs_bmc(filp);
> >+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
> > __poll_t mask = 0;
> >
> >- poll_wait(filp, &kcs_bmc->queue, wait);
> >+ poll_wait(filp, &priv->queue, wait);
> >
> >- spin_lock_irq(&kcs_bmc->lock);
> >- if (kcs_bmc->data_in_avail)
> >+ spin_lock_irq(&priv->lock);
> >+ if (priv->data_in_avail)
> > mask |= EPOLLIN;
> >- spin_unlock_irq(&kcs_bmc->lock);
> >+ spin_unlock_irq(&priv->lock);
> >
> > return mask;
> > }
> >@@ -255,24 +320,24 @@ static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
> > static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
> > size_t count, loff_t *ppos)
> > {
> >- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
> >+ 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(kcs_bmc->queue,
> >- kcs_bmc->data_in_avail);
> >+ wait_event_interruptible(priv->queue,
> >+ priv->data_in_avail);
> >
> >- mutex_lock(&kcs_bmc->mutex);
> >+ mutex_lock(&priv->mutex);
> >
> >- spin_lock_irq(&kcs_bmc->lock);
> >- data_avail = kcs_bmc->data_in_avail;
> >+ spin_lock_irq(&priv->lock);
> >+ data_avail = priv->data_in_avail;
> > if (data_avail) {
> >- data_len = kcs_bmc->data_in_idx;
> >- memcpy(kcs_bmc->kbuffer, kcs_bmc->data_in, data_len);
> >+ data_len = priv->data_in_idx;
> >+ memcpy(priv->kbuffer, priv->data_in, data_len);
> > }
> >- spin_unlock_irq(&kcs_bmc->lock);
> >+ spin_unlock_irq(&priv->lock);
> >
> > if (!data_avail) {
> > ret = -EAGAIN;
> >@@ -281,35 +346,35 @@ static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
> >
> > if (count < data_len) {
> > pr_err("channel=%u with too large data : %zu\n",
> >- kcs_bmc->channel, data_len);
> >+ priv->client.dev->channel, data_len);
> >
> >- spin_lock_irq(&kcs_bmc->lock);
> >- kcs_bmc_ipmi_force_abort(kcs_bmc);
> >- spin_unlock_irq(&kcs_bmc->lock);
> >+ 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, kcs_bmc->kbuffer, data_len)) {
> >+ if (copy_to_user(buf, priv->kbuffer, data_len)) {
> > ret = -EFAULT;
> > goto out_unlock;
> > }
> >
> > ret = data_len;
> >
> >- spin_lock_irq(&kcs_bmc->lock);
> >- if (kcs_bmc->phase == KCS_PHASE_WRITE_DONE) {
> >- kcs_bmc->phase = KCS_PHASE_WAIT_READ;
> >- kcs_bmc->data_in_avail = false;
> >- kcs_bmc->data_in_idx = 0;
> >+ 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(&kcs_bmc->lock);
> >+ spin_unlock_irq(&priv->lock);
> >
> > out_unlock:
> >- mutex_unlock(&kcs_bmc->mutex);
> >+ mutex_unlock(&priv->mutex);
> >
> > return ret;
> > }
> >@@ -317,35 +382,35 @@ static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
> > static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
> > size_t count, loff_t *ppos)
> > {
> >- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
> >+ 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(&kcs_bmc->mutex);
> >+ mutex_lock(&priv->mutex);
> >
> >- if (copy_from_user(kcs_bmc->kbuffer, buf, count)) {
> >+ if (copy_from_user(priv->kbuffer, buf, count)) {
> > ret = -EFAULT;
> > goto out_unlock;
> > }
> >
> >- spin_lock_irq(&kcs_bmc->lock);
> >- if (kcs_bmc->phase == KCS_PHASE_WAIT_READ) {
> >- kcs_bmc->phase = KCS_PHASE_READ;
> >- kcs_bmc->data_out_idx = 1;
> >- kcs_bmc->data_out_len = count;
> >- memcpy(kcs_bmc->data_out, kcs_bmc->kbuffer, count);
> >- kcs_bmc_write_data(kcs_bmc, kcs_bmc->data_out[0]);
> >+ 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(&kcs_bmc->lock);
> >+ spin_unlock_irq(&priv->lock);
> >
> > out_unlock:
> >- mutex_unlock(&kcs_bmc->mutex);
> >+ mutex_unlock(&priv->mutex);
> >
> > return ret;
> > }
> >@@ -353,22 +418,22 @@ static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
> > static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
> > unsigned long arg)
> > {
> >- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
> >+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
> > long ret = 0;
> >
> >- spin_lock_irq(&kcs_bmc->lock);
> >+ spin_lock_irq(&priv->lock);
> >
> > switch (cmd) {
> > case IPMI_BMC_IOCTL_SET_SMS_ATN:
> >- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, KCS_STATUS_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(kcs_bmc, KCS_STATUS_SMS_ATN, 0);
> >+ kcs_bmc_update_status(priv->client.dev, KCS_STATUS_SMS_ATN, 0);
> > break;
> >
> > case IPMI_BMC_IOCTL_FORCE_ABORT:
> >- kcs_bmc_ipmi_force_abort(kcs_bmc);
> >+ kcs_bmc_ipmi_force_abort(priv);
> > break;
> >
> > default:
> >@@ -376,19 +441,17 @@ static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
> > break;
> > }
> >
> >- spin_unlock_irq(&kcs_bmc->lock);
> >+ spin_unlock_irq(&priv->lock);
> >
> > return ret;
> > }
> >
> > static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
> > {
> >- struct kcs_bmc *kcs_bmc = file_to_kcs_bmc(filp);
> >+ struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
> >
> >- spin_lock_irq(&kcs_bmc->lock);
> >- kcs_bmc->running = 0;
> >- kcs_bmc_ipmi_force_abort(kcs_bmc);
> >- spin_unlock_irq(&kcs_bmc->lock);
> >+ kcs_bmc_ipmi_force_abort(priv);
> >+ kcs_bmc_disable_device(priv->client.dev, &priv->client);
> >
> > return 0;
> > }
> >@@ -403,56 +466,78 @@ static const struct file_operations kcs_bmc_ipmi_fops = {
> > .unlocked_ioctl = kcs_bmc_ipmi_ioctl,
> > };
> >
> >-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
> >-int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc)
> >+static DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock);
> >+static LIST_HEAD(kcs_bmc_ipmi_instances);
> >+
> >+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc);
> >+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc_device *kcs_bmc)
> > {
> >+ struct kcs_bmc_ipmi *priv;
> > int rc;
> >
> >- spin_lock_init(&kcs_bmc->lock);
> >- mutex_init(&kcs_bmc->mutex);
> >- init_waitqueue_head(&kcs_bmc->queue);
> >-
> >- kcs_bmc->client.dev = kcs_bmc;
> >- kcs_bmc->client.ops = &kcs_bmc_ipmi_client_ops;
> >- kcs_bmc->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
> >- kcs_bmc->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
> >- kcs_bmc->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
> >-
> >- kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
> >- kcs_bmc->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u",
> >- DEVICE_NAME, kcs_bmc->channel);
> >- if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
> >- !kcs_bmc->miscdev.name)
> >+ priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
> >+ if (!priv)
> > return -ENOMEM;
> >
> >- kcs_bmc->miscdev.fops = &kcs_bmc_ipmi_fops;
> >+ spin_lock_init(&priv->lock);
> >+ mutex_init(&priv->mutex);
> >
> >- rc = misc_register(&kcs_bmc->miscdev);
> >+ 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;
> > }
> > EXPORT_SYMBOL(kcs_bmc_ipmi_attach_cdev);
> >
> >-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
> >-int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc)
> >+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc);
> >+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc_device *kcs_bmc)
> > {
> >- misc_deregister(&kcs_bmc->miscdev);
> >+ struct kcs_bmc_ipmi *priv = NULL, *pos;
> >
> >- spin_lock_irq(&kcs_bmc->lock);
> >- kcs_bmc->running = 0;
> >- kcs_bmc_ipmi_force_abort(kcs_bmc);
> >- spin_unlock_irq(&kcs_bmc->lock);
> >+ 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);
> >
> >- devm_kfree(kcs_bmc->dev, kcs_bmc->kbuffer);
> >- devm_kfree(kcs_bmc->dev, kcs_bmc->data_out);
> >- devm_kfree(kcs_bmc->dev, kcs_bmc->data_in);
> >- devm_kfree(kcs_bmc->dev, kcs_bmc);
> >+ if (!priv)
> >+ return 0;
>
> Would -ENOENT or something be appropriate here if the thing we're trying
> to detach isn't found?

Maybe -ENODEV. Let me think about this.

Cheers,

Andrew

2021-04-09 06:26:24

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 13/21] ipmi: kcs_bmc: Decouple the IPMI chardev from the core



On Fri, 9 Apr 2021, at 14:05, Zev Weiss wrote:
> On Fri, Mar 19, 2021 at 01:27:44AM CDT, Andrew Jeffery wrote:
> >Now that we have untangled the data-structures, split the userspace
> >interface out into its own module. Userspace interfaces and drivers are
> >registered to the KCS BMC core to support arbitrary binding of either.
> >
> >Signed-off-by: Andrew Jeffery <[email protected]>
> >---
> > drivers/char/ipmi/Kconfig | 13 +++++
> > drivers/char/ipmi/Makefile | 3 +-
> > drivers/char/ipmi/kcs_bmc.c | 78 ++++++++++++++++++++++++++-
> > drivers/char/ipmi/kcs_bmc.h | 4 --
> > drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 33 +++++++++---
> > drivers/char/ipmi/kcs_bmc_client.h | 14 +++++
> > 6 files changed, 132 insertions(+), 13 deletions(-)
> >
> >diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
> >index 07847d9a459a..bc5f81899b62 100644
> >--- a/drivers/char/ipmi/Kconfig
> >+++ b/drivers/char/ipmi/Kconfig
> >@@ -124,6 +124,19 @@ config NPCM7XX_KCS_IPMI_BMC
> > 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 ASPEED_BT_IPMI_BMC
> > depends on ARCH_ASPEED || COMPILE_TEST
> > depends on REGMAP && REGMAP_MMIO && MFD_SYSCON
> >diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
> >index a302bc865370..fcfa676afddb 100644
> >--- a/drivers/char/ipmi/Makefile
> >+++ b/drivers/char/ipmi/Makefile
> >@@ -22,7 +22,8 @@ obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o
> > obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o
> > obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
> > obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
> >-obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o kcs_bmc_cdev_ipmi.o
> >+obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.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
> >diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
> >index 266ebec71d6f..694db6ee2a92 100644
> >--- a/drivers/char/ipmi/kcs_bmc.c
> >+++ b/drivers/char/ipmi/kcs_bmc.c
> >@@ -5,7 +5,9 @@
> > */
> >
> > #include <linux/device.h>
> >+#include <linux/list.h>
> > #include <linux/module.h>
> >+#include <linux/mutex.h>
> >
> > #include "kcs_bmc.h"
> >
> >@@ -13,6 +15,11 @@
> > #include "kcs_bmc_device.h"
> > #include "kcs_bmc_client.h"
> >
> >+/* Record probed devices and cdevs */
> >+static DEFINE_MUTEX(kcs_bmc_lock);
> >+static LIST_HEAD(kcs_bmc_devices);
> >+static LIST_HEAD(kcs_bmc_cdevs);
> >+
> > /* Consumer data access */
> >
> > u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc)
> >@@ -100,16 +107,83 @@ EXPORT_SYMBOL(kcs_bmc_disable_device);
> >
> > int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc)
> > {
> >- return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
> >+ struct kcs_bmc_cdev *cdev;
> >+ 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(cdev, &kcs_bmc_cdevs, entry) {
> >+ rc = cdev->ops->add_device(kcs_bmc);
> >+ if (rc)
> >+ dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
> >+ kcs_bmc->channel, rc);
> >+ }
> >+ mutex_unlock(&kcs_bmc_lock);
> >+
> >+ return 0;
>
> We're ignoring failed ->add_device() calls here?

Yep. If one chardev module is failing to accept new devices we don't
want to not add them to the remaining chardev modules.

What would the caller do with a error return value? Maybe it should
just be void.

>
> > }
> > EXPORT_SYMBOL(kcs_bmc_add_device);
> >
> > int kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc)
> > {
> >- return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
> >+ struct kcs_bmc_cdev *cdev;
> >+ int rc;
> >+
> >+ mutex_lock(&kcs_bmc_lock);
> >+ list_del(&kcs_bmc->entry);
> >+ list_for_each_entry(cdev, &kcs_bmc_cdevs, entry) {
> >+ rc = cdev->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);
> >+
> >+ return 0;
>
> Similarly with the return value here...

As above.

>
> > }
> > EXPORT_SYMBOL(kcs_bmc_remove_device);
> >
> >+int kcs_bmc_register_cdev(struct kcs_bmc_cdev *cdev)
> >+{
> >+ struct kcs_bmc_device *kcs_bmc;
> >+ int rc;
> >+
> >+ mutex_lock(&kcs_bmc_lock);
> >+ list_add(&cdev->entry, &kcs_bmc_cdevs);
> >+ list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
> >+ rc = cdev->ops->add_device(kcs_bmc);
> >+ if (rc)
> >+ dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
> >+ kcs_bmc->channel, rc);
> >+ }
> >+ mutex_unlock(&kcs_bmc_lock);
> >+
> >+ return 0;
>
> ...return value again here...

As above.

>
> >+}
> >+EXPORT_SYMBOL(kcs_bmc_register_cdev);
> >+
> >+int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev)
> >+{
> >+ struct kcs_bmc_device *kcs_bmc;
> >+ int rc;
> >+
> >+ mutex_lock(&kcs_bmc_lock);
> >+ list_del(&cdev->entry);
> >+ list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
> >+ rc = cdev->ops->remove_device(kcs_bmc);
> >+ if (rc)
> >+ dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
>
> s/add/remove/

Thanks.

>
> Might also want to differentiate the *_device() error messages from the
> *_cdev() ones a bit more?

I'll look into it.

>
> >+ kcs_bmc->channel, rc);
> >+ }
> >+ mutex_unlock(&kcs_bmc_lock);
> >+
> >+ return rc;
>
> ...but this one is a bit incongruous, propagating the return value of
> only the last ->remove_device() call.

Hah. good catch.

Andrew

2021-04-09 06:28:22

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 10/21] ipmi: kcs_bmc: Turn the driver data-structures inside-out

On Fri, Apr 09, 2021 at 12:59:09AM CDT, Andrew Jeffery wrote:
>
>
>On Fri, 9 Apr 2021, at 13:27, Zev Weiss wrote:
>> On Fri, Mar 19, 2021 at 01:27:41AM CDT, Andrew Jeffery wrote:
>> >Make the KCS device drivers responsible for allocating their own memory.
>> >
>> >Until now the private data for the device driver was allocated internal
>> >to the private data for the chardev interface. This coupling required
>> >the slightly awkward API of passing through the struct size for the
>> >driver private data to the chardev constructor, and then retrieving a
>> >pointer to the driver private data from the allocated chardev memory.
>> >
>> >In addition to being awkward, the arrangement prevents the
>> >implementation of alternative userspace interfaces as the device driver
>> >private data is not independent.
>> >
>> >Peel a layer off the onion and turn the data-structures inside out by
>> >exploiting container_of() and embedding `struct kcs_device` in the
>> >driver private data.
>> >
>> >Signed-off-by: Andrew Jeffery <[email protected]>
>> >---
>> > drivers/char/ipmi/kcs_bmc.c | 15 +++++--
>> > drivers/char/ipmi/kcs_bmc.h | 12 ++----
>> > drivers/char/ipmi/kcs_bmc_aspeed.c | 60 ++++++++++++++++-----------
>> > drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 60 ++++++++++++++++++---------
>> > drivers/char/ipmi/kcs_bmc_npcm7xx.c | 37 ++++++++++-------
>> > 5 files changed, 113 insertions(+), 71 deletions(-)
>> >
>> >diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
>> >index ef5c48ffe74a..709b6bdec165 100644
>> >--- a/drivers/char/ipmi/kcs_bmc.c
>> >+++ b/drivers/char/ipmi/kcs_bmc.c
>> >@@ -44,12 +44,19 @@ int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
>> > }
>> > EXPORT_SYMBOL(kcs_bmc_handle_event);
>> >
>> >-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
>> >-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel)
>> >+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
>>
>> Another declaration perhaps intended for kcs_bmc.h?
>
>These are temporary while the code gets shuffled around. The symbol
>name is an implementation detail, not a "public" part of the API; after
>some further shuffling these are eventually assigned as callbacks in an
>ops struct.
>

Okay, that makes sense.

>>
>> >+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc)
>> > {
>> >- return kcs_bmc_ipmi_alloc(dev, sizeof_priv, channel);
>> >+ return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
>> > }
>> >-EXPORT_SYMBOL(kcs_bmc_alloc);
>> >+EXPORT_SYMBOL(kcs_bmc_add_device);
>> >+
>> >+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
>>
>> Here too.
>>
>> >+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc)
>> >+{
>> >+ return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
>> >+}
>> >+EXPORT_SYMBOL(kcs_bmc_remove_device);
>> >
>> > MODULE_LICENSE("GPL v2");
>> > MODULE_AUTHOR("Haiyue Wang <[email protected]>");
>> >diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
>> >index febea0c8deb4..bf0ae327997f 100644
>> >--- a/drivers/char/ipmi/kcs_bmc.h
>> >+++ b/drivers/char/ipmi/kcs_bmc.h
>> >@@ -67,6 +67,8 @@ struct kcs_ioreg {
>> > };
>> >
>> > struct kcs_bmc {
>> >+ struct device *dev;
>> >+
>> > spinlock_t lock;
>> >
>> > u32 channel;
>> >@@ -94,17 +96,11 @@ struct kcs_bmc {
>> > u8 *kbuffer;
>> >
>> > struct miscdevice miscdev;
>> >-
>> >- unsigned long priv[];
>> > };
>> >
>> >-static inline void *kcs_bmc_priv(struct kcs_bmc *kcs_bmc)
>> >-{
>> >- return kcs_bmc->priv;
>> >-}
>> >-
>> > int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
>> >-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel);
>> >+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc);
>> >+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc);
>> >
>> > u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
>> > void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
>> >diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
>> >index 630cf095560e..0416ac78ce68 100644
>> >--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
>> >+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
>> >@@ -61,6 +61,8 @@
>> > #define LPC_STR4 0x11C
>> >
>> > struct aspeed_kcs_bmc {
>> >+ struct kcs_bmc kcs_bmc;
>> >+
>> > struct regmap *map;
>> > };
>> >
>> >@@ -69,9 +71,14 @@ struct aspeed_kcs_of_ops {
>> > int (*get_io_address)(struct platform_device *pdev);
>> > };
>> >
>> >+static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc *kcs_bmc)
>> >+{
>> >+ return container_of(kcs_bmc, struct aspeed_kcs_bmc, kcs_bmc);
>> >+}
>> >+
>> > static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
>> > {
>> >- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>> >+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>> > u32 val = 0;
>> > int rc;
>> >
>> >@@ -83,7 +90,7 @@ static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
>> >
>> > static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
>> > {
>> >- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>> >+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>> > int rc;
>> >
>> > rc = regmap_write(priv->map, reg, data);
>> >@@ -92,7 +99,7 @@ static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
>> >
>> > static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val)
>> > {
>> >- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>> >+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>> > int rc;
>> >
>> > rc = regmap_update_bits(priv->map, reg, mask, val);
>> >@@ -114,7 +121,7 @@ static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val
>> > */
>> > static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
>> > {
>> >- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>> >+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>> >
>> > switch (kcs_bmc->channel) {
>> > case 1:
>> >@@ -148,7 +155,7 @@ static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
>> >
>> > static void aspeed_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
>> > {
>> >- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>> >+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>> >
>> > switch (kcs_bmc->channel) {
>> > case 1:
>> >@@ -323,16 +330,16 @@ static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
>> > static int aspeed_kcs_probe(struct platform_device *pdev)
>> > {
>> > const struct aspeed_kcs_of_ops *ops;
>> >- struct device *dev = &pdev->dev;
>> >+ struct aspeed_kcs_bmc *priv;
>> > struct kcs_bmc *kcs_bmc;
>> > struct device_node *np;
>> > int rc, channel, addr;
>> >
>> >- np = dev->of_node->parent;
>> >+ 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(dev, "unsupported LPC device binding\n");
>> >+ dev_err(&pdev->dev, "unsupported LPC device binding\n");
>> > return -ENODEV;
>> > }
>> > ops = of_device_get_match_data(&pdev->dev);
>> >@@ -343,18 +350,27 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
>> > if (channel < 0)
>> > return channel;
>> >
>> >- kcs_bmc = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
>> >- if (!kcs_bmc)
>> >+ addr = ops->get_io_address(pdev);
>> >+ if (addr < 0)
>> >+ return addr;
>> >+
>> >+ 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->io_inputb = aspeed_kcs_inb;
>> > kcs_bmc->io_outputb = aspeed_kcs_outb;
>> > kcs_bmc->io_updateb = aspeed_kcs_updateb;
>> >
>> >- addr = ops->get_io_address(pdev);
>> >- if (addr < 0)
>> >- return addr;
>> >+ 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;
>> >+ }
>>
>> The reanimated priv->map initialization I suspect wasn't meant to
>> have been removed in the first place...
>
>Yeah, I'll have to go back and figure out what went wrong there!
>
>Thanks for catching that.
>
>>
>> >
>> > aspeed_kcs_set_address(kcs_bmc, addr);
>> >
>> >@@ -362,29 +378,25 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
>> > if (rc)
>> > return rc;
>> >
>> >- dev_set_drvdata(dev, kcs_bmc);
>> >+ platform_set_drvdata(pdev, priv);
>> >
>> > aspeed_kcs_enable_channel(kcs_bmc, true);
>> >
>> >- rc = misc_register(&kcs_bmc->miscdev);
>> >- if (rc) {
>> >- dev_err(dev, "Unable to register device\n");
>> >+ rc = kcs_bmc_add_device(&priv->kcs_bmc);
>> >+ if (rc < 0)
>> > return rc;
>> >- }
>> >
>> >- dev_dbg(&pdev->dev,
>> >- "Probed KCS device %d (IDR=0x%x, ODR=0x%x, STR=0x%x)\n",
>> >- kcs_bmc->channel, kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr,
>> >- kcs_bmc->ioreg.str);
>> >+ dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n", kcs_bmc->channel, addr);
>>
>> Is the dbg->info change here intentional? (I have no particular
>> objection if so, but it's often a change I make myself during
>> testing/debugging and then forget to revert...)
>
>Yeah, it was possibly something I forgot to revert. If others have
>issues with it staying at dev_info() I'll switch it back.
>
>>
>> >
>> > return 0;
>> > }
>> >
>> > static int aspeed_kcs_remove(struct platform_device *pdev)
>> > {
>> >- struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
>> >+ struct aspeed_kcs_bmc *priv = platform_get_drvdata(pdev);
>> >+ struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
>> >
>> >- misc_deregister(&kcs_bmc->miscdev);
>> >+ kcs_bmc_remove_device(kcs_bmc);
>>
>> Should we propagate the return value outward here?
>
>Probably!
>
>>
>> >
>> > return 0;
>> > }
>> >diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>> >index 82c77994e481..0ca71c135a1a 100644
>> >--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>> >+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>> >@@ -382,7 +382,7 @@ static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
>> > return 0;
>> > }
>> >
>> >-static const struct file_operations kcs_bmc_fops = {
>> >+static const struct file_operations kcs_bmc_ipmi_fops = {
>> > .owner = THIS_MODULE,
>> > .open = kcs_bmc_ipmi_open,
>> > .read = kcs_bmc_ipmi_read,
>> >@@ -392,36 +392,58 @@ static const struct file_operations kcs_bmc_fops = {
>> > .unlocked_ioctl = kcs_bmc_ipmi_ioctl,
>> > };
>> >
>> >-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
>> >-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel)
>> >+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
>>
>> Errant declaration again?
>
>As previously explained.
>

This one seems like a slightly different category, because...

>>
>> >+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc)

...it's immediately followed by the definition of the very same function
that it just declared, so I can't see how its presence or absence could
make any functional difference to anything. (So perhaps I should have
said "redundant" instead of "errant...again".)

It's fairly trivial of course given that it's gone by the end of the
series, but as long as there's going to be another iteration anyway it
seems like we might as well tidy it up?

>> > {
>> >- struct kcs_bmc *kcs_bmc;
>> >-
>> >- kcs_bmc = devm_kzalloc(dev, sizeof(*kcs_bmc) + sizeof_priv, GFP_KERNEL);
>> >- if (!kcs_bmc)
>> >- return NULL;
>> >+ int rc;
>> >
>> > spin_lock_init(&kcs_bmc->lock);
>> >- kcs_bmc->channel = channel;
>> >-
>> > mutex_init(&kcs_bmc->mutex);
>> > init_waitqueue_head(&kcs_bmc->queue);
>> >
>> >- kcs_bmc->data_in = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>> >- kcs_bmc->data_out = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>> >- kcs_bmc->kbuffer = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>> >+ kcs_bmc->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>> >+ kcs_bmc->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>> >+ kcs_bmc->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
>> >
>> > kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
>> >- kcs_bmc->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%u",
>> >- DEVICE_NAME, channel);
>> >+ kcs_bmc->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u",
>> >+ DEVICE_NAME, kcs_bmc->channel);
>> > if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
>> > !kcs_bmc->miscdev.name)
>> >- return NULL;
>> >- kcs_bmc->miscdev.fops = &kcs_bmc_fops;
>> >+ return -ENOMEM;
>> >
>> >- return kcs_bmc;
>> >+ kcs_bmc->miscdev.fops = &kcs_bmc_ipmi_fops;
>> >+
>> >+ rc = misc_register(&kcs_bmc->miscdev);
>> >+ if (rc) {
>> >+ dev_err(kcs_bmc->dev, "Unable to register device: %d\n", rc);
>> >+ return rc;
>> >+ }
>> >+
>> >+ dev_info(kcs_bmc->dev, "Initialised IPMI client for channel %d", kcs_bmc->channel);
>> >+
>> >+ return 0;
>> >+}
>> >+EXPORT_SYMBOL(kcs_bmc_ipmi_attach_cdev);
>> >+
>> >+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
>>
>> Same here.
>
>Same explanation.
>

Also a "category 2" I think.

>>
>> >+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc)
>> >+{
>> >+ misc_deregister(&kcs_bmc->miscdev);
>> >+
>> >+ spin_lock_irq(&kcs_bmc->lock);
>> >+ kcs_bmc->running = 0;
>> >+ kcs_bmc_ipmi_force_abort(kcs_bmc);
>> >+ spin_unlock_irq(&kcs_bmc->lock);
>> >+
>> >+ devm_kfree(kcs_bmc->dev, kcs_bmc->kbuffer);
>> >+ devm_kfree(kcs_bmc->dev, kcs_bmc->data_out);
>> >+ devm_kfree(kcs_bmc->dev, kcs_bmc->data_in);
>> >+ devm_kfree(kcs_bmc->dev, kcs_bmc);
>> >+
>> >+ return 0;
>> > }
>> >-EXPORT_SYMBOL(kcs_bmc_ipmi_alloc);
>> >+EXPORT_SYMBOL(kcs_bmc_ipmi_detach_cdev);
>> >
>> > MODULE_LICENSE("GPL v2");
>> > MODULE_AUTHOR("Haiyue Wang <[email protected]>");
>> >diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>> >index 1f44aadec9e8..5d017498dc69 100644
>> >--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>> >+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
>> >@@ -65,6 +65,8 @@ struct npcm7xx_kcs_reg {
>> > };
>> >
>> > struct npcm7xx_kcs_bmc {
>> >+ struct kcs_bmc kcs_bmc;
>> >+
>> > struct regmap *map;
>> >
>> > const struct npcm7xx_kcs_reg *reg;
>> >@@ -76,9 +78,14 @@ static const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = {
>> > { .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE },
>> > };
>> >
>> >+static inline struct npcm7xx_kcs_bmc *to_npcm7xx_kcs_bmc(struct kcs_bmc *kcs_bmc)
>> >+{
>> >+ return container_of(kcs_bmc, struct npcm7xx_kcs_bmc, kcs_bmc);
>> >+}
>> >+
>> > static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
>> > {
>> >- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>> >+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
>> > u32 val = 0;
>> > int rc;
>> >
>> >@@ -90,7 +97,7 @@ static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
>> >
>> > static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
>> > {
>> >- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>> >+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
>> > int rc;
>> >
>> > rc = regmap_write(priv->map, reg, data);
>> >@@ -99,7 +106,7 @@ static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
>> >
>> > static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 data)
>> > {
>> >- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>> >+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
>> > int rc;
>> >
>> > rc = regmap_update_bits(priv->map, reg, mask, data);
>> >@@ -108,7 +115,7 @@ static void npcm7xx_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 da
>> >
>> > static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
>> > {
>> >- struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>> >+ struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
>> >
>> > regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
>> > enable ? KCS_CTL_IBFIE : 0);
>> >@@ -155,11 +162,10 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
>> > return -ENODEV;
>> > }
>> >
>> >- kcs_bmc = kcs_bmc_alloc(dev, sizeof(*priv), chan);
>> >- if (!kcs_bmc)
>> >+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
>> >+ if (!priv)
>> > return -ENOMEM;
>> >
>> >- priv = kcs_bmc_priv(kcs_bmc);
>> > priv->map = syscon_node_to_regmap(dev->parent->of_node);
>> > if (IS_ERR(priv->map)) {
>> > dev_err(dev, "Couldn't get regmap\n");
>> >@@ -167,6 +173,9 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
>> > }
>> > 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;
>> >@@ -174,31 +183,27 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev)
>> > kcs_bmc->io_outputb = npcm7xx_kcs_outb;
>> > kcs_bmc->io_updateb = npcm7xx_kcs_updateb;
>> >
>> >- dev_set_drvdata(dev, kcs_bmc);
>> >+ platform_set_drvdata(pdev, priv);
>> >
>> > npcm7xx_kcs_enable_channel(kcs_bmc, true);
>> > rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev);
>> > if (rc)
>> > return rc;
>> >
>> >- rc = misc_register(&kcs_bmc->miscdev);
>> >- if (rc) {
>> >- dev_err(dev, "Unable to register device\n");
>> >- 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;
>> >+ return kcs_bmc_add_device(kcs_bmc);
>> > }
>> >
>> > static int npcm7xx_kcs_remove(struct platform_device *pdev)
>> > {
>> >- struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
>> >+ struct npcm7xx_kcs_bmc *priv = platform_get_drvdata(pdev);
>> >+ struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
>> >
>> >- misc_deregister(&kcs_bmc->miscdev);
>> >+ kcs_bmc_remove_device(kcs_bmc);
>>
>> As with the corresponding aspeed code, should we propagate the return
>> value here?
>
>I'll address this.
>
>Thanks for the review!
>
>Andrew

2021-04-09 06:40:52

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 14/21] ipmi: kcs_bmc: Allow clients to control KCS IRQ state



On Fri, 9 Apr 2021, at 14:07, Zev Weiss wrote:
> On Fri, Mar 19, 2021 at 01:27:45AM CDT, Andrew Jeffery wrote:
> >Add a mechanism for controlling whether the client associated with a
> >KCS device will receive Input Buffer Full (IBF) and Output Buffer Empty
> >(OBE) events. This enables an abstract implementation of poll() for KCS
> >devices.
> >
> >A wart in the implementation is that the ASPEED KCS devices don't
> >support an OBE interrupt for the BMC. Instead we pretend it has one by
> >polling the status register waiting for the Output Buffer Full (OBF) bit
> >to clear, and generating an event when OBE is observed.
> >
> >Signed-off-by: Andrew Jeffery <[email protected]>
> >---
> > drivers/char/ipmi/kcs_bmc.c | 6 ++
> > drivers/char/ipmi/kcs_bmc.h | 3 +
> > drivers/char/ipmi/kcs_bmc_aspeed.c | 150 ++++++++++++++++++----------
> > drivers/char/ipmi/kcs_bmc_client.h | 2 +
> > drivers/char/ipmi/kcs_bmc_device.h | 1 +
> > drivers/char/ipmi/kcs_bmc_npcm7xx.c | 25 ++++-
> > 6 files changed, 130 insertions(+), 57 deletions(-)
> >
> >diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
> >index 694db6ee2a92..05bbb72418b2 100644
> >--- a/drivers/char/ipmi/kcs_bmc.c
> >+++ b/drivers/char/ipmi/kcs_bmc.c
> >@@ -184,6 +184,12 @@ int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev)
> > }
> > EXPORT_SYMBOL(kcs_bmc_unregister_cdev);
> >
> >+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 <[email protected]>");
> > MODULE_AUTHOR("Andrew Jeffery <[email protected]>");
> >diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
> >index 5deb9a0b8e60..11fff935218c 100644
> >--- a/drivers/char/ipmi/kcs_bmc.h
> >+++ b/drivers/char/ipmi/kcs_bmc.h
> >@@ -11,6 +11,9 @@
> > #define KCS_BMC_EVENT_NONE 0
> > #define KCS_BMC_EVENT_HANDLED 1
> >
> >+#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)
> >diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
> >index 6f26e7366c0b..5f26471c038c 100644
> >--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
> >+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
> >@@ -60,10 +60,18 @@
> > #define LPC_ODR4 0x118
> > #define LPC_STR4 0x11C
> >
> >+#define OBE_POLL_PERIOD (HZ / 2)
> >+
> > struct aspeed_kcs_bmc {
> > struct kcs_bmc_device kcs_bmc;
> >
> > struct regmap *map;
> >+
> >+ struct {
> >+ spinlock_t lock;
> >+ bool remove;
> >+ struct timer_list timer;
> >+ } obe;
> > };
> >
> > struct aspeed_kcs_of_ops {
> >@@ -159,68 +167,89 @@ static void aspeed_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enabl
> >
> > switch (kcs_bmc->channel) {
> > case 1:
> >- if (enable) {
> >- regmap_update_bits(priv->map, LPC_HICR2,
> >- LPC_HICR2_IBFIF1, LPC_HICR2_IBFIF1);
> >- regmap_update_bits(priv->map, LPC_HICR0,
> >- LPC_HICR0_LPC1E, LPC_HICR0_LPC1E);
> >- } else {
> >- regmap_update_bits(priv->map, LPC_HICR0,
> >- LPC_HICR0_LPC1E, 0);
> >- regmap_update_bits(priv->map, LPC_HICR2,
> >- LPC_HICR2_IBFIF1, 0);
> >- }
> >- break;
> >-
> >+ regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC1E, enable * LPC_HICR0_LPC1E);
> >+ return;
> > case 2:
> >- if (enable) {
> >- regmap_update_bits(priv->map, LPC_HICR2,
> >- LPC_HICR2_IBFIF2, LPC_HICR2_IBFIF2);
> >- regmap_update_bits(priv->map, LPC_HICR0,
> >- LPC_HICR0_LPC2E, LPC_HICR0_LPC2E);
> >- } else {
> >- regmap_update_bits(priv->map, LPC_HICR0,
> >- LPC_HICR0_LPC2E, 0);
> >- regmap_update_bits(priv->map, LPC_HICR2,
> >- LPC_HICR2_IBFIF2, 0);
> >- }
> >- break;
> >-
> >+ regmap_update_bits(priv->map, LPC_HICR0, LPC_HICR0_LPC2E, enable * LPC_HICR0_LPC2E);
> >+ return;
> > case 3:
> >- if (enable) {
> >- regmap_update_bits(priv->map, LPC_HICR2,
> >- LPC_HICR2_IBFIF3, LPC_HICR2_IBFIF3);
> >- regmap_update_bits(priv->map, LPC_HICR0,
> >- LPC_HICR0_LPC3E, LPC_HICR0_LPC3E);
> >- regmap_update_bits(priv->map, LPC_HICR4,
> >- LPC_HICR4_KCSENBL, LPC_HICR4_KCSENBL);
> >- } else {
> >- regmap_update_bits(priv->map, LPC_HICR0,
> >- LPC_HICR0_LPC3E, 0);
> >- regmap_update_bits(priv->map, LPC_HICR4,
> >- LPC_HICR4_KCSENBL, 0);
> >- regmap_update_bits(priv->map, LPC_HICR2,
> >- LPC_HICR2_IBFIF3, 0);
> >- }
> >- break;
> >-
> >+ 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:
> >- if (enable)
> >- regmap_update_bits(priv->map, LPC_HICRB,
> >- LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E,
> >- LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E);
> >+ 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);
> >+
> >+ /* We don't have an OBE IRQ, emulate it */
> >+ if (KCS_BMC_EVENT_TYPE_OBE & mask) {
> >+ if (KCS_BMC_EVENT_TYPE_OBE & state)
> >+ mod_timer(&priv->obe.timer, jiffies + OBE_POLL_PERIOD);
> > else
> >- regmap_update_bits(priv->map, LPC_HICRB,
> >- LPC_HICRB_IBFIF4 | LPC_HICRB_LPC4E,
> >- 0);
> >- break;
> >+ del_timer(&priv->obe.timer);
> >+ }
> >
> >- default:
> >- break;
> >+ if (KCS_BMC_EVENT_TYPE_IBF & mask) {
> >+ const bool enable = !!(KCS_BMC_EVENT_TYPE_IBF & state);
>
> Totally superficial nitpick: the operand ordering for the bitmask tests
> in this function seem a bit inconsistent with what I think is the usual
> style of 'variable & BITMASK_MACRO' (reminiscent of "yoda
> conditionals").

Haha, I'm not sure what possessed me here.

>
> >+
> >+ switch (kcs_bmc->channel) {
> >+ case 1:
> >+ regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF1,
> >+ enable * LPC_HICR2_IBFIF1);
> >+ return;
> >+ case 2:
> >+ regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF2,
> >+ enable * LPC_HICR2_IBFIF2);
> >+ return;
> >+ case 3:
> >+ regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_IBFIF3,
> >+ enable * LPC_HICR2_IBFIF3);
> >+ return;
> >+ case 4:
> >+ regmap_update_bits(priv->map, LPC_HICRB, LPC_HICRB_IBFIF4,
> >+ enable * LPC_HICRB_IBFIF4);
> >+ 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,
> >@@ -378,6 +407,10 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> > return -ENODEV;
> > }
> >
> >+ spin_lock_init(&priv->obe.lock);
> >+ priv->obe.remove = false;
> >+ timer_setup(&priv->obe.timer, aspeed_kcs_check_obe, 0);
> >+
> > aspeed_kcs_set_address(kcs_bmc, addr);
> >
> > rc = aspeed_kcs_config_irq(kcs_bmc, pdev);
> >@@ -386,6 +419,8 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
> >
> > platform_set_drvdata(pdev, priv);
> >
> >+ aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE),
> >+ KCS_BMC_EVENT_TYPE_IBF);
> > aspeed_kcs_enable_channel(kcs_bmc, true);
> >
> > rc = kcs_bmc_add_device(&priv->kcs_bmc);
> >@@ -404,6 +439,15 @@ static int aspeed_kcs_remove(struct platform_device *pdev)
> >
> > 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);
> >+ del_timer_sync(&priv->obe.timer);
> >+
> > return 0;
> > }
> >
> >diff --git a/drivers/char/ipmi/kcs_bmc_client.h b/drivers/char/ipmi/kcs_bmc_client.h
> >index d0a7404ff584..456796da33de 100644
> >--- a/drivers/char/ipmi/kcs_bmc_client.h
> >+++ b/drivers/char/ipmi/kcs_bmc_client.h
> >@@ -37,6 +37,8 @@ int kcs_bmc_unregister_cdev(struct kcs_bmc_cdev *cdev);
> > 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);
> >diff --git a/drivers/char/ipmi/kcs_bmc_device.h b/drivers/char/ipmi/kcs_bmc_device.h
> >index 57b7174b2bac..f1ca8912496a 100644
> >--- a/drivers/char/ipmi/kcs_bmc_device.h
> >+++ b/drivers/char/ipmi/kcs_bmc_device.h
> >@@ -7,6 +7,7 @@
> > #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);
> >diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
> >index dce93ec895fc..c2032728a03d 100644
> >--- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c
> >+++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
> >@@ -38,6 +38,7 @@
> > #define KCS2CTL 0x2A
> > #define KCS3CTL 0x3C
> > #define KCS_CTL_IBFIE BIT(0)
> >+#define KCS_CTL_OBEIE BIT(0)
> >
> > #define KCS1IE 0x1C
> > #define KCS2IE 0x2E
> >@@ -117,13 +118,23 @@ static void npcm7xx_kcs_enable_channel(struct kcs_bmc_device *kcs_bmc, bool enab
> > {
> > struct npcm7xx_kcs_bmc *priv = to_npcm7xx_kcs_bmc(kcs_bmc);
> >
> >- regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
> >- enable ? KCS_CTL_IBFIE : 0);
> >-
> > 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 (KCS_BMC_EVENT_TYPE_OBE & mask)
> >+ regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_OBEIE,
> >+ !!(KCS_BMC_EVENT_TYPE_OBE & state) * KCS_CTL_OBEIE);
> >+
> >+ if (KCS_BMC_EVENT_TYPE_IBF & mask)
> >+ regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE,
> >+ !!(KCS_BMC_EVENT_TYPE_IBF & state) * KCS_CTL_IBFIE);
>
> Same operand ordering thing here...

Ack.

Andrew

2021-04-09 06:48:19

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 16/21] ipmi: kcs_bmc: Add a "raw" character device interface



On Fri, 9 Apr 2021, at 14:47, Zev Weiss wrote:
> On Fri, Mar 19, 2021 at 01:27:47AM CDT, Andrew Jeffery wrote:
> >The existing IPMI chardev encodes IPMI behaviours as the name suggests.
> >However, KCS devices are useful beyond IPMI (or keyboards), as they
> >provide a means to generate IRQs and exchange arbitrary data between a
> >BMC and its host system.
> >
> >Implement a "raw" KCS character device that exposes the IDR, ODR and STR
> >registers to userspace via read() and write() implemented on a character
> >device:
> >
> >+--------+--------+---------+
> >| Offset | read() | write() |
> >+--------+--------+---------+
> >| 0 | IDR | ODR |
> >+--------+--------+---------+
> >| 1 | STR | STR |
> >+--------+--------+---------+
> >
> >This interface allows userspace to implement arbitrary (though somewhat
> >inefficient) protocols for exchanging information between a BMC and host
> >firmware. Conceptually the KCS interface can be used as an out-of-band
> >machanism for interrupt-signaled control messages while bulk data
>
> Typo ("mechanism")

Ack.

>
> >transfers occur over more appropriate interfaces between the BMC and the
> >host (which may lack their own interrupt mechanism, e.g. LPC FW cycles).
> >
> >poll() is provided, which will wait for IBF or OBE conditions for data
> >reads and writes respectively. Reads of STR on its own never blocks,
> >though accessing both offsets in the one system call may block if the
> >data registers are not ready.
> >
> >Signed-off-by: Andrew Jeffery <[email protected]>
> >---
> > Documentation/ABI/testing/dev-raw-kcs | 25 ++
> > drivers/char/ipmi/Kconfig | 17 +
> > drivers/char/ipmi/Makefile | 1 +
> > drivers/char/ipmi/kcs_bmc_cdev_raw.c | 443 ++++++++++++++++++++++++++
> > 4 files changed, 486 insertions(+)
> > create mode 100644 Documentation/ABI/testing/dev-raw-kcs
> > create mode 100644 drivers/char/ipmi/kcs_bmc_cdev_raw.c
> >
> >diff --git a/Documentation/ABI/testing/dev-raw-kcs b/Documentation/ABI/testing/dev-raw-kcs
> >new file mode 100644
> >index 000000000000..06e7e2071562
> >--- /dev/null
> >+++ b/Documentation/ABI/testing/dev-raw-kcs
> >@@ -0,0 +1,25 @@
> >+What: /dev/raw-kcs*
> >+Date: 2021-02-15
> >+KernelVersion: 5.13
> >+Contact: [email protected]
> >+Contact: [email protected]
> >+Contact: Andrew Jeffery <[email protected]>
> >+Description: ``/dev/raw-kcs*`` exposes to userspace the data and
> >+ status registers of Keyboard-Controller-Style (KCS) IPMI
> >+ interfaces via read() and write() syscalls. Direct
> >+ exposure of the data and status registers enables
> >+ inefficient but arbitrary protocols to be implemented
> >+ over the device. A typical approach is to use KCS
> >+ devices for out-of-band signalling for bulk data
> >+ transfers over other interfaces between a Baseboard
> >+ Management Controller and its host.
> >+
> >+ +--------+--------+---------+
> >+ | Offset | read() | write() |
> >+ +--------+--------+---------+
> >+ | 0 | IDR | ODR |
> >+ +--------+--------+---------+
> >+ | 1 | STR | STR |
> >+ +--------+--------+---------+
> >+
> >+Users: libmctp: https://github.com/openbmc/libmctp
> >diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
> >index bc5f81899b62..273ac1a1f870 100644
> >--- a/drivers/char/ipmi/Kconfig
> >+++ b/drivers/char/ipmi/Kconfig
> >@@ -137,6 +137,23 @@ config IPMI_KCS_BMC_CDEV_IPMI
> > This support is also available as a module. The module will be
> > called kcs_bmc_cdev_ipmi.
> >
> >+config IPMI_KCS_BMC_CDEV_RAW
> >+ depends on IPMI_KCS_BMC
> >+ tristate "Raw character device interface for BMC KCS devices"
> >+ help
> >+ Provides a BMC-side character device directly exposing the
> >+ data and status registers of a KCS device to userspace. While
> >+ KCS devices are commonly used to implement IPMI message
> >+ passing, they provide a general interface for exchange of
> >+ interrupts, data and status information between the BMC and
> >+ its host.
> >+
> >+ Say YES if you wish to use the KCS devices to implement
> >+ protocols that are not IPMI.
> >+
> >+ This support is also available as a module. The module will be
> >+ called kcs_bmc_cdev_raw.
> >+
> > config ASPEED_BT_IPMI_BMC
> > depends on ARCH_ASPEED || COMPILE_TEST
> > depends on REGMAP && REGMAP_MMIO && MFD_SYSCON
> >diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
> >index fcfa676afddb..c8cc248ddd90 100644
> >--- a/drivers/char/ipmi/Makefile
> >+++ b/drivers/char/ipmi/Makefile
> >@@ -24,6 +24,7 @@ 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_CDEV_IPMI) += kcs_bmc_cdev_ipmi.o
> >+obj-$(CONFIG_IPMI_KCS_BMC_CDEV_RAW) += kcs_bmc_cdev_raw.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
> >diff --git a/drivers/char/ipmi/kcs_bmc_cdev_raw.c b/drivers/char/ipmi/kcs_bmc_cdev_raw.c
> >new file mode 100644
> >index 000000000000..bdd258648c8e
> >--- /dev/null
> >+++ b/drivers/char/ipmi/kcs_bmc_cdev_raw.c
> >@@ -0,0 +1,443 @@
> >+// 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/fs.h>
> >+#include <linux/list.h>
> >+#include <linux/miscdevice.h>
> >+#include <linux/module.h>
> >+#include <linux/poll.h>
> >+
> >+#include "kcs_bmc_client.h"
> >+
> >+#define DEVICE_NAME "raw-kcs"
> >+
> >+struct kcs_bmc_raw {
> >+ struct list_head entry;
> >+
> >+ struct kcs_bmc_client client;
> >+
> >+ wait_queue_head_t queue;
> >+ u8 events;
> >+ bool writable;
> >+ bool readable;
> >+ u8 idr;
> >+
> >+ struct miscdevice miscdev;
> >+};
> >+
> >+static inline struct kcs_bmc_raw *client_to_kcs_bmc_raw(struct kcs_bmc_client *client)
> >+{
> >+ return container_of(client, struct kcs_bmc_raw, client);
> >+}
> >+
> >+/* Call under priv->queue.lock */
> >+static void kcs_bmc_raw_update_event_mask(struct kcs_bmc_raw *priv, u8 mask, u8 state)
> >+{
> >+ kcs_bmc_update_event_mask(priv->client.dev, mask, state);
> >+ priv->events &= ~mask;
> >+ priv->events |= state & mask;
> >+}
> >+
> >+static int kcs_bmc_raw_event(struct kcs_bmc_client *client)
> >+{
> >+ struct kcs_bmc_raw *priv;
> >+ struct device *dev;
> >+ u8 status, handled;
> >+
> >+ priv = client_to_kcs_bmc_raw(client);
> >+ dev = priv->miscdev.this_device;
> >+
> >+ spin_lock(&priv->queue.lock);
> >+
> >+ status = kcs_bmc_read_status(client->dev);
> >+ handled = 0;
> >+
> >+ if ((priv->events & KCS_BMC_EVENT_TYPE_IBF) && (status & KCS_BMC_STR_IBF)) {
> >+ if (priv->readable)
> >+ dev_err(dev, "Storm brewing!");
>
> That seems a *touch* cryptic...

Uh, yeah. That wasn't meant to be there in that form.

>
> >+
> >+ dev_dbg(dev, "Disabling IDR events for back-pressure\n");
> >+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_IBF, 0);
> >+ priv->idr = kcs_bmc_read_data(client->dev);
> >+ priv->readable = true;
> >+
> >+ dev_dbg(dev, "IDR read, waking waiters\n");
> >+ wake_up_locked(&priv->queue);
> >+
> >+ handled |= KCS_BMC_EVENT_TYPE_IBF;
> >+ }
> >+
> >+ if ((priv->events & KCS_BMC_EVENT_TYPE_OBE) && !(status & KCS_BMC_STR_OBF)) {
> >+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, 0);
> >+ priv->writable = true;
> >+
> >+ dev_dbg(dev, "ODR writable, waking waiters\n");
> >+ wake_up_locked(&priv->queue);
> >+
> >+ handled |= KCS_BMC_EVENT_TYPE_OBE;
> >+ }
> >+
> >+ spin_unlock(&priv->queue.lock);
> >+
> >+ return handled ? KCS_BMC_EVENT_HANDLED : KCS_BMC_EVENT_NONE;
>
> Hm, if we're just treating it as a boolean here, is there any need to
> muck around with setting specific bits of 'handled' in the if-blocks
> above?

I don't think it matters? If we want to debug we can print the handled bitmask.

>
> >+}
> >+
> >+static const struct kcs_bmc_client_ops kcs_bmc_raw_client_ops = {
> >+ .event = kcs_bmc_raw_event,
> >+};
> >+
> >+static inline struct kcs_bmc_raw *file_to_kcs_bmc_raw(struct file *filp)
> >+{
> >+ return container_of(filp->private_data, struct kcs_bmc_raw, miscdev);
> >+}
> >+
> >+static int kcs_bmc_raw_open(struct inode *inode, struct file *filp)
> >+{
> >+ struct kcs_bmc_raw *priv = file_to_kcs_bmc_raw(filp);
> >+
> >+ return kcs_bmc_enable_device(priv->client.dev, &priv->client);
> >+}
> >+
> >+static bool kcs_bmc_raw_prepare_obe(struct kcs_bmc_raw *priv)
> >+{
> >+ bool writable;
> >+
> >+ /* Enable the OBE event so we can catch the host clearing OBF */
> >+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, KCS_BMC_EVENT_TYPE_OBE);
> >+
> >+ /* Now that we'll catch an OBE event, check if it's already occurred */
> >+ writable = !(kcs_bmc_read_status(priv->client.dev) & KCS_BMC_STR_OBF);
> >+
> >+ /* If OBF is clear we've missed the OBE event, so disable it */
> >+ if (writable)
> >+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, 0);
> >+
> >+ return writable;
> >+}
> >+
> >+static __poll_t kcs_bmc_raw_poll(struct file *filp, poll_table *wait)
> >+{
> >+ struct kcs_bmc_raw *priv;
> >+ __poll_t events = 0;
> >+
> >+ priv = file_to_kcs_bmc_raw(filp);
> >+
> >+ poll_wait(filp, &priv->queue, wait);
> >+
> >+ spin_lock_irq(&priv->queue.lock);
> >+ if (kcs_bmc_raw_prepare_obe(priv))
> >+ events |= (EPOLLOUT | EPOLLWRNORM);
> >+
> >+ if (priv->readable || (kcs_bmc_read_status(priv->client.dev) & KCS_BMC_STR_IBF))
> >+ events |= (EPOLLIN | EPOLLRDNORM);
> >+ spin_unlock_irq(&priv->queue.lock);
> >+
> >+ return events;
> >+}
> >+
> >+static ssize_t kcs_bmc_raw_read(struct file *filp, char __user *buf,
> >+ size_t count, loff_t *ppos)
> >+{
> >+ struct kcs_bmc_device *kcs_bmc;
> >+ struct kcs_bmc_raw *priv;
> >+ bool read_idr, read_str;
> >+ struct device *dev;
> >+ u8 idr, str;
> >+ ssize_t rc;
> >+
> >+ priv = file_to_kcs_bmc_raw(filp);
> >+ kcs_bmc = priv->client.dev;
> >+ dev = priv->miscdev.this_device;
> >+
> >+ if (!count)
> >+ return 0;
> >+
> >+ if (count > 2 || *ppos > 1)
> >+ return -EINVAL;
> >+
> >+ if (*ppos + count > 2)
> >+ return -EINVAL;
> >+
> >+ read_idr = (*ppos == 0);
> >+ read_str = (*ppos == 1) || (count == 2);
> >+
> >+ spin_lock_irq(&priv->queue.lock);
> >+ if (read_idr) {
> >+ dev_dbg(dev, "Waiting for IBF\n");
> >+ str = kcs_bmc_read_status(kcs_bmc);
> >+ if ((filp->f_flags & O_NONBLOCK) && (str & KCS_BMC_STR_IBF)) {
> >+ rc = -EWOULDBLOCK;
> >+ goto out;
> >+ }
> >+
> >+ rc = wait_event_interruptible_locked(priv->queue,
> >+ priv->readable || (str & KCS_BMC_STR_IBF));
> >+ if (rc < 0)
> >+ goto out;
> >+
> >+ if (signal_pending(current)) {
> >+ dev_dbg(dev, "Interrupted waiting for IBF\n");
> >+ rc = -EINTR;
> >+ goto out;
> >+ }
> >+
> >+ /*
> >+ * Re-enable events prior to possible read of IDR (which clears
> >+ * IBF) to ensure we receive interrupts for subsequent writes
> >+ * to IDR. Writes to IDR by the host should not occur while IBF
> >+ * is set.
> >+ */
> >+ dev_dbg(dev, "Woken by IBF, enabling IRQ\n");
> >+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_IBF,
> >+ KCS_BMC_EVENT_TYPE_IBF);
> >+
> >+ /* Read data out of IDR into internal storage if necessary */
> >+ if (!priv->readable) {
> >+ WARN(!(str & KCS_BMC_STR_IBF), "Unknown reason for wakeup!");
> >+
> >+ priv->idr = kcs_bmc_read_data(kcs_bmc);
> >+ }
> >+
> >+ /* Copy data from internal storage to userspace */
> >+ idr = priv->idr;
> >+
> >+ /* We're done consuming the internally stored value */
> >+ priv->readable = false;
> >+ }
> >+
> >+ if (read_str) {
> >+ str = kcs_bmc_read_status(kcs_bmc);
> >+ if (*ppos == 0 || priv->readable)
> >+ /*
> >+ * If we got this far with `*ppos == 0` then we've read
> >+ * data out of IDR, so set IBF when reporting back to
> >+ * userspace so userspace knows the IDR value is valid.
> >+ */
> >+ str |= KCS_BMC_STR_IBF;
> >+
> >+ dev_dbg(dev, "Read status 0x%x\n", str);
> >+
> >+ }
> >+
> >+ rc = count;
> >+out:
> >+ spin_unlock_irq(&priv->queue.lock);
> >+
> >+ if (rc < 0)
> >+ return rc;
> >+
> >+ /* Now copy the data in to the userspace buffer */
> >+
> >+ if (read_idr)
> >+ if (copy_to_user(buf++, &idr, sizeof(idr)))
> >+ return -EFAULT;
> >+
> >+ if (read_str)
> >+ if (copy_to_user(buf, &str, sizeof(str)))
> >+ return -EFAULT;
> >+
> >+ return count;
> >+}
> >+
> >+static ssize_t kcs_bmc_raw_write(struct file *filp, const char __user *buf,
> >+ size_t count, loff_t *ppos)
> >+{
> >+ struct kcs_bmc_device *kcs_bmc;
> >+ bool write_odr, write_str;
> >+ struct kcs_bmc_raw *priv;
> >+ struct device *dev;
> >+ uint8_t data[2];
> >+ ssize_t result;
> >+ u8 str;
> >+
> >+ priv = file_to_kcs_bmc_raw(filp);
> >+ kcs_bmc = priv->client.dev;
> >+ dev = priv->miscdev.this_device;
> >+
> >+ if (!count)
> >+ return count;
> >+
> >+ if (count > 2)
> >+ return -EINVAL;
> >+
> >+ if (*ppos >= 2)
> >+ return -EINVAL;
> >+
> >+ if (*ppos + count > 2)
> >+ return -EINVAL;
> >+
> >+ if (copy_from_user(data, buf, count))
> >+ return -EFAULT;
> >+
> >+ write_odr = (*ppos == 0);
> >+ write_str = (*ppos == 1) || (count == 2);
> >+
> >+ spin_lock_irq(&priv->queue.lock);
> >+
> >+ /* Always write status before data, we generate the SerIRQ by writing ODR */
> >+ if (write_str) {
> >+ /* The index of STR in the userspace buffer depends on whether ODR is written */
> >+ str = data[*ppos == 0];
> >+ if (!(str & KCS_BMC_STR_OBF))
> >+ dev_warn(dev, "Clearing OBF with status write: 0x%x\n", str);
> >+ dev_dbg(dev, "Writing status 0x%x\n", str);
> >+ kcs_bmc_write_status(kcs_bmc, str);
> >+ }
> >+
> >+ if (write_odr) {
> >+ /* If we're writing ODR it's always the first byte in the buffer */
> >+ u8 odr = data[0];
> >+
> >+ str = kcs_bmc_read_status(kcs_bmc);
> >+ if (str & KCS_BMC_STR_OBF) {
> >+ if (filp->f_flags & O_NONBLOCK) {
> >+ result = -EWOULDBLOCK;
> >+ goto out;
> >+ }
> >+
> >+ priv->writable = kcs_bmc_raw_prepare_obe(priv);
> >+
> >+ /* Now either OBF is already clear, or we'll get an OBE event to wake us */
> >+ dev_dbg(dev, "Waiting for OBF to clear\n");
> >+ wait_event_interruptible_locked(priv->queue, priv->writable);
> >+
> >+ if (signal_pending(current)) {
> >+ kcs_bmc_raw_update_event_mask(priv, KCS_BMC_EVENT_TYPE_OBE, 0);
> >+ result = -EINTR;
> >+ goto out;
> >+ }
> >+
> >+ WARN_ON(kcs_bmc_read_status(kcs_bmc) & KCS_BMC_STR_OBF);
> >+ }
> >+
> >+ dev_dbg(dev, "Writing 0x%x to ODR\n", odr);
> >+ kcs_bmc_write_data(kcs_bmc, odr);
> >+ }
> >+
> >+ result = count;
> >+out:
> >+ spin_unlock_irq(&priv->queue.lock);
> >+
> >+ return result;
> >+}
> >+
> >+static int kcs_bmc_raw_release(struct inode *inode, struct file *filp)
> >+{
> >+ struct kcs_bmc_raw *priv = file_to_kcs_bmc_raw(filp);
> >+
> >+ kcs_bmc_disable_device(priv->client.dev, &priv->client);
> >+
> >+ return 0;
> >+}
> >+
> >+static const struct file_operations kcs_bmc_raw_fops = {
> >+ .owner = THIS_MODULE,
> >+ .open = kcs_bmc_raw_open,
> >+ .llseek = no_seek_end_llseek,
> >+ .read = kcs_bmc_raw_read,
> >+ .write = kcs_bmc_raw_write,
> >+ .poll = kcs_bmc_raw_poll,
> >+ .release = kcs_bmc_raw_release,
> >+};
> >+
> >+static DEFINE_SPINLOCK(kcs_bmc_raw_instances_lock);
> >+static LIST_HEAD(kcs_bmc_raw_instances);
> >+
> >+static int kcs_bmc_raw_attach_cdev(struct kcs_bmc_device *kcs_bmc)
> >+{
> >+ struct kcs_bmc_raw *priv;
> >+ int rc;
> >+
> >+ priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
> >+ if (!priv)
> >+ return -ENOMEM;
> >+
> >+ priv->client.dev = kcs_bmc;
> >+ priv->client.ops = &kcs_bmc_raw_client_ops;
> >+
> >+ init_waitqueue_head(&priv->queue);
> >+ priv->writable = false;
> >+ priv->readable = false;
> >+
> >+ 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->miscdev.name)
> >+ return -EINVAL;
> >+
> >+ priv->miscdev.fops = &kcs_bmc_raw_fops;
> >+
> >+ /* Initialise our expected events. Listen for IBF but ignore OBE until necessary */
> >+ kcs_bmc_raw_update_event_mask(priv, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE),
> >+ KCS_BMC_EVENT_TYPE_IBF);
> >+
> >+ rc = misc_register(&priv->miscdev);
> >+ if (rc) {
> >+ dev_err(kcs_bmc->dev, "Unable to register device\n");
> >+ return rc;
> >+ }
> >+
> >+ spin_lock_irq(&kcs_bmc_raw_instances_lock);
> >+ list_add(&priv->entry, &kcs_bmc_raw_instances);
> >+ spin_unlock_irq(&kcs_bmc_raw_instances_lock);
> >+
> >+ dev_info(kcs_bmc->dev, "Initialised raw client for channel %d", kcs_bmc->channel);
> >+
> >+ return 0;
> >+}
> >+
> >+static int kcs_bmc_raw_detach_cdev(struct kcs_bmc_device *kcs_bmc)
> >+{
> >+ struct kcs_bmc_raw *priv = NULL, *pos;
> >+
> >+ spin_lock_irq(&kcs_bmc_raw_instances_lock);
> >+ list_for_each_entry(pos, &kcs_bmc_raw_instances, entry) {
> >+ if (pos->client.dev == kcs_bmc) {
> >+ priv = pos;
> >+ list_del(&pos->entry);
> >+ break;
> >+ }
> >+ }
> >+ spin_unlock_irq(&kcs_bmc_raw_instances_lock);
> >+
> >+ if (!priv)
> >+ return 0;
>
> Similarly to patch #12, might we want to indicate some sort of failure
> here, or is this a normal/expected case?

I replied on 12/21, I'll have another think about it.

Cheers,

Andrew

2021-04-09 07:59:09

by Arnd Bergmann

[permalink] [raw]
Subject: Re: [PATCH v2 16/21] ipmi: kcs_bmc: Add a "raw" character device interface

On Fri, Mar 19, 2021 at 7:31 AM Andrew Jeffery <[email protected]> wrote:
>
> The existing IPMI chardev encodes IPMI behaviours as the name suggests.
> However, KCS devices are useful beyond IPMI (or keyboards), as they
> provide a means to generate IRQs and exchange arbitrary data between a
> BMC and its host system.

I only noticed the series after Joel asked about the DT changes on the arm
side. One question though:

How does this related to the drivers/input/serio/ framework that also talks
to the keyboard controller for things that are not keyboards? Are these
separate communication channels on adjacent I/O ports, or does there
need to be some arbitration?

Arnd

2021-04-09 08:48:18

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 17/21] dt-bindings: ipmi: Convert ASPEED KCS binding to schema

On Fri, Apr 09, 2021 at 12:44:04AM CDT, Zev Weiss wrote:
>On Fri, Apr 09, 2021 at 12:33:10AM CDT, Andrew Jeffery wrote:
>>
>>
>>On Fri, 9 Apr 2021, at 14:45, Zev Weiss wrote:
>>>On Fri, Mar 19, 2021 at 01:27:48AM CDT, Andrew Jeffery wrote:
>>>>Given the deprecated binding, improve the ability to detect issues in
>>>>the platform devicetrees. Further, a subsequent patch will introduce a
>>>>new interrupts property for specifying SerIRQ behaviour, so convert
>>>>before we do any further additions.
>>>>
>>>>Signed-off-by: Andrew Jeffery <[email protected]>
>>>>---
>>>> .../bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml | 92 +++++++++++++++++++
>>>> .../bindings/ipmi/aspeed-kcs-bmc.txt | 33 -------
>>>> 2 files changed, 92 insertions(+), 33 deletions(-)
>>>> create mode 100644 Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
>>>> delete mode 100644 Documentation/devicetree/bindings/ipmi/aspeed-kcs-bmc.txt
>>>>
>>>>diff --git a/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
>>>>new file mode 100644
>>>>index 000000000000..697ca575454f
>>>>--- /dev/null
>>>>+++ b/Documentation/devicetree/bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml
>>>>@@ -0,0 +1,92 @@
>>>>+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>>+%YAML 1.2
>>>>+---
>>>>+$id: http://devicetree.org/schemas/ipmi/aspeed,ast2400-kcs-bmc.yaml
>>>>+$schema: http://devicetree.org/meta-schemas/core.yaml
>>>>+
>>>>+title: ASPEED BMC KCS Devices
>>>>+
>>>>+maintainers:
>>>>+ - Andrew Jeffery <[email protected]>
>>>>+
>>>>+description: |
>>>>+ The Aspeed BMC SoCs typically use the Keyboard-Controller-Style (KCS)
>>>>+ interfaces on the LPC bus for in-band IPMI communication with their host.
>>>>+
>>>>+properties:
>>>>+ compatible:
>>>>+ oneOf:
>>>>+ - description: Channel ID derived from reg
>>>>+ items:
>>>>+ enum:
>>>>+ - aspeed,ast2400-kcs-bmc-v2
>>>>+ - aspeed,ast2500-kcs-bmc-v2
>>>>+ - aspeed,ast2600-kcs-bmc
>>>
>>>Should this have a "-v2" suffix?
>>
>>Well, that was kind of a matter of perspective. The 2600 compatible was
>>added after we'd done the v2 of the binding for the 2400 and 2500 so it
>>never needed correcting. But it is a case of "don't use the deprecated
>>properties with the 2600 compatible".
>>
>>I don't think a change is necessary?
>>
>
>It just looked inconsistent with the corresponding string in the
>ast_kcs_bmc_match[] table; perhaps that should be changed instead then?
>

...except I realize now I only saw the 2600 v2 string in the match table
because I put it there myself in the process of resolving a conflict
when applying your series to the openbmc dev-5.10 branch for testing
purposes. So nevermind on this.

Reviewed-by: Zev Weiss <[email protected]>

2021-04-09 19:23:40

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 09/21] ipmi: kcs_bmc: Split out kcs_bmc_cdev_ipmi

On Fri, Apr 09, 2021 at 12:48:21AM CDT, Andrew Jeffery wrote:
>
>
>On Fri, 9 Apr 2021, at 13:26, Zev Weiss wrote:
>> On Fri, Mar 19, 2021 at 01:27:40AM CDT, Andrew Jeffery wrote:
>> >Take steps towards defining a coherent API to separate the KCS device
>> >drivers from the userspace interface. Decreasing the coupling will
>> >improve the separation of concerns and enable the introduction of
>> >alternative userspace interfaces.
>> >
>> >For now, simply split the chardev logic out to a separate file. The code
>> >continues to build into the same module.
>> >
>> >Signed-off-by: Andrew Jeffery <[email protected]>
>> >---
>> > drivers/char/ipmi/Makefile | 2 +-
>> > drivers/char/ipmi/kcs_bmc.c | 423 +------------------------
>> > drivers/char/ipmi/kcs_bmc.h | 10 +-
>> > drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 428 ++++++++++++++++++++++++++
>> > 4 files changed, 451 insertions(+), 412 deletions(-)
>> > create mode 100644 drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>> >
>> >diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
>> >index 0822adc2ec41..a302bc865370 100644
>> >--- a/drivers/char/ipmi/Makefile
>> >+++ b/drivers/char/ipmi/Makefile
>> >@@ -22,7 +22,7 @@ obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o
>> > obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o
>> > obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
>> > obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
>> >-obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o
>> >+obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o 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
>> >diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
>> >index c4336c1f2d6d..ef5c48ffe74a 100644
>> >--- a/drivers/char/ipmi/kcs_bmc.c
>> >+++ b/drivers/char/ipmi/kcs_bmc.c
>> >@@ -3,446 +3,51 @@
>> > * 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/module.h>
>> >-#include <linux/platform_device.h>
>> >-#include <linux/poll.h>
>> >-#include <linux/sched.h>
>> >-#include <linux/slab.h>
>> >
>> > #include "kcs_bmc.h"
>> >
>> >-#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 u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
>> >+u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc)
>> > {
>> > return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
>> > }
>> >+EXPORT_SYMBOL(kcs_bmc_read_data);
>> >
>> >-static inline void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data)
>> >+void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data)
>> > {
>> > kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
>> > }
>> >+EXPORT_SYMBOL(kcs_bmc_write_data);
>> >
>> >-static inline u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc)
>> >+u8 kcs_bmc_read_status(struct kcs_bmc *kcs_bmc)
>> > {
>> > return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
>> > }
>> >+EXPORT_SYMBOL(kcs_bmc_read_status);
>> >
>> >-static inline void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data)
>> >+void kcs_bmc_write_status(struct kcs_bmc *kcs_bmc, u8 data)
>> > {
>> > kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
>> > }
>> >+EXPORT_SYMBOL(kcs_bmc_write_status);
>> >
>> >-static void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
>> >+void kcs_bmc_update_status(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
>> > {
>> > kcs_bmc->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
>> > }
>> >+EXPORT_SYMBOL(kcs_bmc_update_status);
>> >
>> >-static inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
>> >-{
>> >- kcs_bmc_update_status(kcs_bmc, KCS_STATUS_STATE_MASK,
>> >- KCS_STATUS_STATE(state));
>> >-}
>> >-
>> >-static void kcs_force_abort(struct kcs_bmc *kcs_bmc)
>> >-{
>> >- set_state(kcs_bmc, ERROR_STATE);
>> >- kcs_bmc_read_data(kcs_bmc);
>> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>> >-
>> >- kcs_bmc->phase = KCS_PHASE_ERROR;
>> >- kcs_bmc->data_in_avail = false;
>> >- kcs_bmc->data_in_idx = 0;
>> >-}
>> >-
>> >-static void kcs_bmc_handle_data(struct kcs_bmc *kcs_bmc)
>> >-{
>> >- u8 data;
>> >-
>> >- switch (kcs_bmc->phase) {
>> >- case KCS_PHASE_WRITE_START:
>> >- kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
>> >- fallthrough;
>> >-
>> >- case KCS_PHASE_WRITE_DATA:
>> >- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
>> >- set_state(kcs_bmc, WRITE_STATE);
>> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>> >- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
>> >- kcs_bmc_read_data(kcs_bmc);
>> >- } else {
>> >- kcs_force_abort(kcs_bmc);
>> >- kcs_bmc->error = KCS_LENGTH_ERROR;
>> >- }
>> >- break;
>> >-
>> >- case KCS_PHASE_WRITE_END_CMD:
>> >- if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
>> >- set_state(kcs_bmc, READ_STATE);
>> >- kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
>> >- kcs_bmc_read_data(kcs_bmc);
>> >- kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
>> >- kcs_bmc->data_in_avail = true;
>> >- wake_up_interruptible(&kcs_bmc->queue);
>> >- } else {
>> >- kcs_force_abort(kcs_bmc);
>> >- kcs_bmc->error = KCS_LENGTH_ERROR;
>> >- }
>> >- break;
>> >-
>> >- case KCS_PHASE_READ:
>> >- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
>> >- set_state(kcs_bmc, IDLE_STATE);
>> >-
>> >- data = kcs_bmc_read_data(kcs_bmc);
>> >- if (data != KCS_CMD_READ_BYTE) {
>> >- set_state(kcs_bmc, ERROR_STATE);
>> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>> >- break;
>> >- }
>> >-
>> >- if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
>> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>> >- kcs_bmc->phase = KCS_PHASE_IDLE;
>> >- break;
>> >- }
>> >-
>> >- kcs_bmc_write_data(kcs_bmc,
>> >- kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
>> >- break;
>> >-
>> >- case KCS_PHASE_ABORT_ERROR1:
>> >- set_state(kcs_bmc, READ_STATE);
>> >- kcs_bmc_read_data(kcs_bmc);
>> >- kcs_bmc_write_data(kcs_bmc, kcs_bmc->error);
>> >- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
>> >- break;
>> >-
>> >- case KCS_PHASE_ABORT_ERROR2:
>> >- set_state(kcs_bmc, IDLE_STATE);
>> >- kcs_bmc_read_data(kcs_bmc);
>> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>> >- kcs_bmc->phase = KCS_PHASE_IDLE;
>> >- break;
>> >-
>> >- default:
>> >- kcs_force_abort(kcs_bmc);
>> >- break;
>> >- }
>> >-}
>> >-
>> >-static void kcs_bmc_handle_cmd(struct kcs_bmc *kcs_bmc)
>> >-{
>> >- u8 cmd;
>> >-
>> >- set_state(kcs_bmc, WRITE_STATE);
>> >- kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
>> >-
>> >- cmd = kcs_bmc_read_data(kcs_bmc);
>> >- switch (cmd) {
>> >- case KCS_CMD_WRITE_START:
>> >- kcs_bmc->phase = KCS_PHASE_WRITE_START;
>> >- kcs_bmc->error = KCS_NO_ERROR;
>> >- kcs_bmc->data_in_avail = false;
>> >- kcs_bmc->data_in_idx = 0;
>> >- break;
>> >-
>> >- case KCS_CMD_WRITE_END:
>> >- if (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
>> >- kcs_force_abort(kcs_bmc);
>> >- break;
>> >- }
>> >-
>> >- kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
>> >- break;
>> >-
>> >- case KCS_CMD_GET_STATUS_ABORT:
>> >- if (kcs_bmc->error == KCS_NO_ERROR)
>> >- kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
>> >-
>> >- kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
>> >- kcs_bmc->data_in_avail = false;
>> >- kcs_bmc->data_in_idx = 0;
>> >- break;
>> >-
>> >- default:
>> >- kcs_force_abort(kcs_bmc);
>> >- kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
>> >- break;
>> >- }
>> >-}
>> >-
>> >+int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);
>>
>> This declaration looks a bit out of place here; should it be in
>> kcs_bmc.h instead?
>
>These are only temporary and get removed later on in the series after
>some shuffling of the code.
>

Okay -- there were a couple others further down in the patch that I'm
pretty sure were strictly redundant and could perhaps be cleaned up
(kcs_bmc_ipmi_event and kcs_bmc_ipmi_alloc in kcs_bmc_cdev_ipmi.c), but
aside from that:

Reviewed-by: Zev Weiss <[email protected]>

2021-04-09 19:30:21

by Zev Weiss

[permalink] [raw]
Subject: Re: [PATCH v2 10/21] ipmi: kcs_bmc: Turn the driver data-structures inside-out

On Fri, Apr 09, 2021 at 01:25:26AM CDT, Zev Weiss wrote:
>On Fri, Apr 09, 2021 at 12:59:09AM CDT, Andrew Jeffery wrote:
>>
>>
>>On Fri, 9 Apr 2021, at 13:27, Zev Weiss wrote:
>>>On Fri, Mar 19, 2021 at 01:27:41AM CDT, Andrew Jeffery wrote:
>>>>Make the KCS device drivers responsible for allocating their own memory.
>>>>
>>>>Until now the private data for the device driver was allocated internal
>>>>to the private data for the chardev interface. This coupling required
>>>>the slightly awkward API of passing through the struct size for the
>>>>driver private data to the chardev constructor, and then retrieving a
>>>>pointer to the driver private data from the allocated chardev memory.
>>>>
>>>>In addition to being awkward, the arrangement prevents the
>>>>implementation of alternative userspace interfaces as the device driver
>>>>private data is not independent.
>>>>
>>>>Peel a layer off the onion and turn the data-structures inside out by
>>>>exploiting container_of() and embedding `struct kcs_device` in the
>>>>driver private data.
>>>>
>>>>Signed-off-by: Andrew Jeffery <[email protected]>
>>>>---
>>>> drivers/char/ipmi/kcs_bmc.c | 15 +++++--
>>>> drivers/char/ipmi/kcs_bmc.h | 12 ++----
>>>> drivers/char/ipmi/kcs_bmc_aspeed.c | 60 ++++++++++++++++-----------
>>>> drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 60 ++++++++++++++++++---------
>>>> drivers/char/ipmi/kcs_bmc_npcm7xx.c | 37 ++++++++++-------
>>>> 5 files changed, 113 insertions(+), 71 deletions(-)
>>>>
>>>>diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c
>>>>index ef5c48ffe74a..709b6bdec165 100644
>>>>--- a/drivers/char/ipmi/kcs_bmc.c
>>>>+++ b/drivers/char/ipmi/kcs_bmc.c
>>>>@@ -44,12 +44,19 @@ int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
>>>> }
>>>> EXPORT_SYMBOL(kcs_bmc_handle_event);
>>>>
>>>>-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
>>>>-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel)
>>>>+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
>>>
>>>Another declaration perhaps intended for kcs_bmc.h?
>>
>>These are temporary while the code gets shuffled around. The symbol
>>name is an implementation detail, not a "public" part of the API; after
>>some further shuffling these are eventually assigned as callbacks in an
>>ops struct.
>>
>
>Okay, that makes sense.
>
>>>
>>>>+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc)
>>>> {
>>>>- return kcs_bmc_ipmi_alloc(dev, sizeof_priv, channel);
>>>>+ return kcs_bmc_ipmi_attach_cdev(kcs_bmc);
>>>> }
>>>>-EXPORT_SYMBOL(kcs_bmc_alloc);
>>>>+EXPORT_SYMBOL(kcs_bmc_add_device);
>>>>+
>>>>+int kcs_bmc_ipmi_detach_cdev(struct kcs_bmc *kcs_bmc);
>>>
>>>Here too.
>>>
>>>>+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc)
>>>>+{
>>>>+ return kcs_bmc_ipmi_detach_cdev(kcs_bmc);
>>>>+}
>>>>+EXPORT_SYMBOL(kcs_bmc_remove_device);
>>>>
>>>> MODULE_LICENSE("GPL v2");
>>>> MODULE_AUTHOR("Haiyue Wang <[email protected]>");
>>>>diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h
>>>>index febea0c8deb4..bf0ae327997f 100644
>>>>--- a/drivers/char/ipmi/kcs_bmc.h
>>>>+++ b/drivers/char/ipmi/kcs_bmc.h
>>>>@@ -67,6 +67,8 @@ struct kcs_ioreg {
>>>> };
>>>>
>>>> struct kcs_bmc {
>>>>+ struct device *dev;
>>>>+
>>>> spinlock_t lock;
>>>>
>>>> u32 channel;
>>>>@@ -94,17 +96,11 @@ struct kcs_bmc {
>>>> u8 *kbuffer;
>>>>
>>>> struct miscdevice miscdev;
>>>>-
>>>>- unsigned long priv[];
>>>> };
>>>>
>>>>-static inline void *kcs_bmc_priv(struct kcs_bmc *kcs_bmc)
>>>>-{
>>>>- return kcs_bmc->priv;
>>>>-}
>>>>-
>>>> int kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc);
>>>>-struct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel);
>>>>+int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc);
>>>>+int kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc);
>>>>
>>>> u8 kcs_bmc_read_data(struct kcs_bmc *kcs_bmc);
>>>> void kcs_bmc_write_data(struct kcs_bmc *kcs_bmc, u8 data);
>>>>diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c
>>>>index 630cf095560e..0416ac78ce68 100644
>>>>--- a/drivers/char/ipmi/kcs_bmc_aspeed.c
>>>>+++ b/drivers/char/ipmi/kcs_bmc_aspeed.c
>>>>@@ -61,6 +61,8 @@
>>>> #define LPC_STR4 0x11C
>>>>
>>>> struct aspeed_kcs_bmc {
>>>>+ struct kcs_bmc kcs_bmc;
>>>>+
>>>> struct regmap *map;
>>>> };
>>>>
>>>>@@ -69,9 +71,14 @@ struct aspeed_kcs_of_ops {
>>>> int (*get_io_address)(struct platform_device *pdev);
>>>> };
>>>>
>>>>+static inline struct aspeed_kcs_bmc *to_aspeed_kcs_bmc(struct kcs_bmc *kcs_bmc)
>>>>+{
>>>>+ return container_of(kcs_bmc, struct aspeed_kcs_bmc, kcs_bmc);
>>>>+}
>>>>+
>>>> static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
>>>> {
>>>>- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>>>>+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>>>> u32 val = 0;
>>>> int rc;
>>>>
>>>>@@ -83,7 +90,7 @@ static u8 aspeed_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg)
>>>>
>>>> static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
>>>> {
>>>>- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>>>>+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>>>> int rc;
>>>>
>>>> rc = regmap_write(priv->map, reg, data);
>>>>@@ -92,7 +99,7 @@ static void aspeed_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data)
>>>>
>>>> static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val)
>>>> {
>>>>- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>>>>+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>>>> int rc;
>>>>
>>>> rc = regmap_update_bits(priv->map, reg, mask, val);
>>>>@@ -114,7 +121,7 @@ static void aspeed_kcs_updateb(struct kcs_bmc *kcs_bmc, u32 reg, u8 mask, u8 val
>>>> */
>>>> static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
>>>> {
>>>>- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>>>>+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>>>>
>>>> switch (kcs_bmc->channel) {
>>>> case 1:
>>>>@@ -148,7 +155,7 @@ static void aspeed_kcs_set_address(struct kcs_bmc *kcs_bmc, u16 addr)
>>>>
>>>> static void aspeed_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable)
>>>> {
>>>>- struct aspeed_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc);
>>>>+ struct aspeed_kcs_bmc *priv = to_aspeed_kcs_bmc(kcs_bmc);
>>>>
>>>> switch (kcs_bmc->channel) {
>>>> case 1:
>>>>@@ -323,16 +330,16 @@ static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev)
>>>> static int aspeed_kcs_probe(struct platform_device *pdev)
>>>> {
>>>> const struct aspeed_kcs_of_ops *ops;
>>>>- struct device *dev = &pdev->dev;
>>>>+ struct aspeed_kcs_bmc *priv;
>>>> struct kcs_bmc *kcs_bmc;
>>>> struct device_node *np;
>>>> int rc, channel, addr;
>>>>
>>>>- np = dev->of_node->parent;
>>>>+ 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(dev, "unsupported LPC device binding\n");
>>>>+ dev_err(&pdev->dev, "unsupported LPC device binding\n");
>>>> return -ENODEV;
>>>> }
>>>> ops = of_device_get_match_data(&pdev->dev);
>>>>@@ -343,18 +350,27 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
>>>> if (channel < 0)
>>>> return channel;
>>>>
>>>>- kcs_bmc = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel);
>>>>- if (!kcs_bmc)
>>>>+ addr = ops->get_io_address(pdev);
>>>>+ if (addr < 0)
>>>>+ return addr;
>>>>+
>>>>+ 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->io_inputb = aspeed_kcs_inb;
>>>> kcs_bmc->io_outputb = aspeed_kcs_outb;
>>>> kcs_bmc->io_updateb = aspeed_kcs_updateb;
>>>>
>>>>- addr = ops->get_io_address(pdev);
>>>>- if (addr < 0)
>>>>- return addr;
>>>>+ 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;
>>>>+ }
>>>
>>>The reanimated priv->map initialization I suspect wasn't meant to
>>>have been removed in the first place...
>>
>>Yeah, I'll have to go back and figure out what went wrong there!
>>
>>Thanks for catching that.
>>
>>>
>>>>
>>>> aspeed_kcs_set_address(kcs_bmc, addr);
>>>>
>>>>@@ -362,29 +378,25 @@ static int aspeed_kcs_probe(struct platform_device *pdev)
>>>> if (rc)
>>>> return rc;
>>>>
>>>>- dev_set_drvdata(dev, kcs_bmc);
>>>>+ platform_set_drvdata(pdev, priv);
>>>>
>>>> aspeed_kcs_enable_channel(kcs_bmc, true);
>>>>
>>>>- rc = misc_register(&kcs_bmc->miscdev);
>>>>- if (rc) {
>>>>- dev_err(dev, "Unable to register device\n");
>>>>+ rc = kcs_bmc_add_device(&priv->kcs_bmc);
>>>>+ if (rc < 0)
>>>> return rc;
>>>>- }
>>>>
>>>>- dev_dbg(&pdev->dev,
>>>>- "Probed KCS device %d (IDR=0x%x, ODR=0x%x, STR=0x%x)\n",
>>>>- kcs_bmc->channel, kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr,
>>>>- kcs_bmc->ioreg.str);
>>>>+ dev_info(&pdev->dev, "Initialised channel %d at 0x%x\n", kcs_bmc->channel, addr);
>>>
>>>Is the dbg->info change here intentional? (I have no particular
>>>objection if so, but it's often a change I make myself during
>>>testing/debugging and then forget to revert...)
>>
>>Yeah, it was possibly something I forgot to revert. If others have
>>issues with it staying at dev_info() I'll switch it back.
>>
>>>
>>>>
>>>> return 0;
>>>> }
>>>>
>>>> static int aspeed_kcs_remove(struct platform_device *pdev)
>>>> {
>>>>- struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev);
>>>>+ struct aspeed_kcs_bmc *priv = platform_get_drvdata(pdev);
>>>>+ struct kcs_bmc *kcs_bmc = &priv->kcs_bmc;
>>>>
>>>>- misc_deregister(&kcs_bmc->miscdev);
>>>>+ kcs_bmc_remove_device(kcs_bmc);
>>>
>>>Should we propagate the return value outward here?
>>
>>Probably!
>>
>>>
>>>>
>>>> return 0;
>>>> }
>>>>diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>>>>index 82c77994e481..0ca71c135a1a 100644
>>>>--- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>>>>+++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c
>>>>@@ -382,7 +382,7 @@ static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
>>>> return 0;
>>>> }
>>>>
>>>>-static const struct file_operations kcs_bmc_fops = {
>>>>+static const struct file_operations kcs_bmc_ipmi_fops = {
>>>> .owner = THIS_MODULE,
>>>> .open = kcs_bmc_ipmi_open,
>>>> .read = kcs_bmc_ipmi_read,
>>>>@@ -392,36 +392,58 @@ static const struct file_operations kcs_bmc_fops = {
>>>> .unlocked_ioctl = kcs_bmc_ipmi_ioctl,
>>>> };
>>>>
>>>>-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
>>>>-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel)
>>>>+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
>>>
>>>Errant declaration again?
>>
>>As previously explained.
>>
>
>This one seems like a slightly different category, because...
>
>>>
>>>>+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc)
>
>...it's immediately followed by the definition of the very same function
>that it just declared, so I can't see how its presence or absence could
>make any functional difference to anything. (So perhaps I should have
>said "redundant" instead of "errant...again".)
>
>It's fairly trivial of course given that it's gone by the end of the
>series, but as long as there's going to be another iteration anyway it
>seems like we might as well tidy it up?
>

Oh, and otherwise:

Reviewed-by: Zev Weiss <[email protected]>

2021-04-12 00:01:00

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 10/21] ipmi: kcs_bmc: Turn the driver data-structures inside-out



On Sat, 10 Apr 2021, at 04:56, Zev Weiss wrote:
> On Fri, Apr 09, 2021 at 01:25:26AM CDT, Zev Weiss wrote:
> >On Fri, Apr 09, 2021 at 12:59:09AM CDT, Andrew Jeffery wrote:
> >>On Fri, 9 Apr 2021, at 13:27, Zev Weiss wrote:
> >>>On Fri, Mar 19, 2021 at 01:27:41AM CDT, Andrew Jeffery wrote:
> >>>>-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel);
> >>>>-struct kcs_bmc *kcs_bmc_ipmi_alloc(struct device *dev, int sizeof_priv, u32 channel)
> >>>>+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc);
> >>>
> >>>Errant declaration again?
> >>
> >>As previously explained.
> >>
> >
> >This one seems like a slightly different category, because...
> >
> >>>
> >>>>+int kcs_bmc_ipmi_attach_cdev(struct kcs_bmc *kcs_bmc)
> >
> >...it's immediately followed by the definition of the very same function
> >that it just declared, so I can't see how its presence or absence could
> >make any functional difference to anything. (So perhaps I should have
> >said "redundant" instead of "errant...again".)

This is is a small hack to fend off warnings from -Wmissing-declarations.

> >
> >It's fairly trivial of course given that it's gone by the end of the
> >series, but as long as there's going to be another iteration anyway it
> >seems like we might as well tidy it up?
> >
>
> Oh, and otherwise:
>
> Reviewed-by: Zev Weiss <[email protected]>

Thanks.

Andrew

2021-04-12 01:44:39

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 16/21] ipmi: kcs_bmc: Add a "raw" character device interface

On Fri, 9 Apr 2021, at 17:25, Arnd Bergmann wrote:
> On Fri, Mar 19, 2021 at 7:31 AM Andrew Jeffery <[email protected]> wrote:
> >
> > The existing IPMI chardev encodes IPMI behaviours as the name suggests.
> > However, KCS devices are useful beyond IPMI (or keyboards), as they
> > provide a means to generate IRQs and exchange arbitrary data between a
> > BMC and its host system.
>
> I only noticed the series after Joel asked about the DT changes on the arm
> side. One question though:
>
> How does this related to the drivers/input/serio/ framework that also talks
> to the keyboard controller for things that are not keyboards?

I've taken a brief look and I feel they're somewhat closely related.

It's plausible that we could wrangle the code so the Aspeed and Nuvoton
KCS drivers move under drivers/input/serio. If you squint, the i8042
serio device driver has similarities with what the Aspeed and Nuvoton
device drivers are providing to the KCS IPMI stack.

Both the KCS IPMI and raw chardev I've implemented in this patch need
both read and write access to the status register (STR). serio could
potentially expose its value through serio_interrupt() using the
SERIO_OOB_DATA flag, but I haven't put any thought into it beyond this
sentence. We'd need some extra support for writing STR via the serio
API. I'm not sure that fits into the abstraction (unless we make
serio_write() take a flags argument?).

In that vein, the serio_raw interface is close to the functionality
that the raw chardev provides in this patch, though again serio_raw
lacks userspace access to STR. Flags are ignored in the ->interrupt()
callback so all values received via ->interrupt() are exposed as data.
The result is there's no way to take care of SERIO_OOB_DATA in the
read() path. Given that, I think we'd have to expose an ioctl() to
access the STR value after taking care of SERIO_OOB_DATA in
->interrupt().

I'm not sure where that lands us.

Dmitry, any thoughts here?

> Are these
> separate communication channels on adjacent I/O ports, or does there
> need to be some arbitration?

As it stands there's no arbitration.

Cheers,

Andrew

2021-04-12 09:02:44

by Arnd Bergmann

[permalink] [raw]
Subject: Re: [PATCH v2 16/21] ipmi: kcs_bmc: Add a "raw" character device interface

On Mon, Apr 12, 2021 at 3:33 AM Andrew Jeffery <[email protected]> wrote:
> On Fri, 9 Apr 2021, at 17:25, Arnd Bergmann wrote:
> > On Fri, Mar 19, 2021 at 7:31 AM Andrew Jeffery <[email protected]> wrote:
> > >
> > > The existing IPMI chardev encodes IPMI behaviours as the name suggests.
> > > However, KCS devices are useful beyond IPMI (or keyboards), as they
> > > provide a means to generate IRQs and exchange arbitrary data between a
> > > BMC and its host system.
> >
> > I only noticed the series after Joel asked about the DT changes on the arm
> > side. One question though:
> >
> > How does this related to the drivers/input/serio/ framework that also talks
> > to the keyboard controller for things that are not keyboards?
>
> I've taken a brief look and I feel they're somewhat closely related.
>
> It's plausible that we could wrangle the code so the Aspeed and Nuvoton
> KCS drivers move under drivers/input/serio. If you squint, the i8042
> serio device driver has similarities with what the Aspeed and Nuvoton
> device drivers are providing to the KCS IPMI stack.

After looking some more into it, I finally understood that the two are
rather complementary. While the drivers/char/ipmi/kcs_bmc.c
is the other (bmc) end of drivers/char/ipmi/ipmi_kcs_sm.c, it seems
that the proposed kcs_bmc_cdev_raw.c interface would be
what corresponds to the other side of
drivers/input/serio/i8042.c+userio.c. Then again, these are also on
separate ports (0x60 for the keyboard controller, 0xca2 for the BMC
KCS), so they would never actually talk to one another.

> Both the KCS IPMI and raw chardev I've implemented in this patch need
> both read and write access to the status register (STR). serio could
> potentially expose its value through serio_interrupt() using the
> SERIO_OOB_DATA flag, but I haven't put any thought into it beyond this
> sentence. We'd need some extra support for writing STR via the serio
> API. I'm not sure that fits into the abstraction (unless we make
> serio_write() take a flags argument?).
>
> In that vein, the serio_raw interface is close to the functionality
> that the raw chardev provides in this patch, though again serio_raw
> lacks userspace access to STR. Flags are ignored in the ->interrupt()
> callback so all values received via ->interrupt() are exposed as data.
> The result is there's no way to take care of SERIO_OOB_DATA in the
> read() path. Given that, I think we'd have to expose an ioctl() to
> access the STR value after taking care of SERIO_OOB_DATA in
> ->interrupt().
>
> I'm not sure where that lands us.

Based on what I looked up, I think you can just forget about my original
question. We have two separate interfaces that use an Intel 8042-style
protocol, but they don't really interact.

Arnd

2021-04-13 09:35:54

by Arnd Bergmann

[permalink] [raw]
Subject: Re: [PATCH v2 16/21] ipmi: kcs_bmc: Add a "raw" character device interface

On Tue, Apr 13, 2021 at 1:45 AM Andrew Jeffery <[email protected]> wrote:
> On Mon, 12 Apr 2021, at 18:18, Arnd Bergmann wrote:
> > On Mon, Apr 12, 2021 at 3:33 AM Andrew Jeffery <[email protected]> wrote:
> > > On Fri, 9 Apr 2021, at 17:25, Arnd Bergmann wrote:
> > > > On Fri, Mar 19, 2021 at 7:31 AM Andrew Jeffery <[email protected]> wrote:
> > > > >
> > > > > The existing IPMI chardev encodes IPMI behaviours as the name suggests.
> > > > > However, KCS devices are useful beyond IPMI (or keyboards), as they
> > > > > provide a means to generate IRQs and exchange arbitrary data between a
> > > > > BMC and its host system.
> > > >
> > > > I only noticed the series after Joel asked about the DT changes on the arm
> > > > side. One question though:
> > > >
> > > > How does this related to the drivers/input/serio/ framework that also talks
> > > > to the keyboard controller for things that are not keyboards?
> > >
> > > I've taken a brief look and I feel they're somewhat closely related.
> > >
> > > It's plausible that we could wrangle the code so the Aspeed and Nuvoton
> > > KCS drivers move under drivers/input/serio. If you squint, the i8042
> > > serio device driver has similarities with what the Aspeed and Nuvoton
> > > device drivers are providing to the KCS IPMI stack.
> >
> > After looking some more into it, I finally understood that the two are
> > rather complementary. While the drivers/char/ipmi/kcs_bmc.c
> > is the other (bmc) end of drivers/char/ipmi/ipmi_kcs_sm.c, it seems
> > that the proposed kcs_bmc_cdev_raw.c interface would be
> > what corresponds to the other side of
> > drivers/input/serio/i8042.c+userio.c.
>
> Right. I guess the question is should we be splitting kernel subsystems
> along host/bmc lines? Doesn't feel intuitive, it's all Linux, but maybe
> we can consolidate in the future if it makes sense?

We actually have a number of subsystems with somewhat overlapping
functionality. I brought up serio, because it has an abstraction for multiple
things that communicate over the keyboard controller and I thought
the problem you were trying to solve was also related to the keyboard
controller.
It is also one of multiple abstractions that allow you to connect a device
to a uart (along with serdev and tty_ldisc, probably at least one more that
you can nest above or below these).

Consolidating the kcs_bmc.c interface into something that already
exists would obviously be best, but it's not clear which of these that
should be, that depends on the fundamental properties of the hardware
interface.

> > Then again, these are also on
> > separate ports (0x60 for the keyboard controller, 0xca2 for the BMC
> > KCS), so they would never actually talk to one another.
>
> Well, sort of I guess. On Power systems we don't use the keyboard
> controller for IPMI or keyboards, so we're just kinda exploiting the
> hardware for our own purposes.

Can you describe in an abstract form what the hardware interface
can do here and what you want from it? I wonder if it could be
part of a higher-level interface such as drivers/mailbox/ instead.

Arnd

2021-04-13 12:07:40

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 16/21] ipmi: kcs_bmc: Add a "raw" character device interface



On Mon, 12 Apr 2021, at 18:18, Arnd Bergmann wrote:
> On Mon, Apr 12, 2021 at 3:33 AM Andrew Jeffery <[email protected]> wrote:
> > On Fri, 9 Apr 2021, at 17:25, Arnd Bergmann wrote:
> > > On Fri, Mar 19, 2021 at 7:31 AM Andrew Jeffery <[email protected]> wrote:
> > > >
> > > > The existing IPMI chardev encodes IPMI behaviours as the name suggests.
> > > > However, KCS devices are useful beyond IPMI (or keyboards), as they
> > > > provide a means to generate IRQs and exchange arbitrary data between a
> > > > BMC and its host system.
> > >
> > > I only noticed the series after Joel asked about the DT changes on the arm
> > > side. One question though:
> > >
> > > How does this related to the drivers/input/serio/ framework that also talks
> > > to the keyboard controller for things that are not keyboards?
> >
> > I've taken a brief look and I feel they're somewhat closely related.
> >
> > It's plausible that we could wrangle the code so the Aspeed and Nuvoton
> > KCS drivers move under drivers/input/serio. If you squint, the i8042
> > serio device driver has similarities with what the Aspeed and Nuvoton
> > device drivers are providing to the KCS IPMI stack.
>
> After looking some more into it, I finally understood that the two are
> rather complementary. While the drivers/char/ipmi/kcs_bmc.c
> is the other (bmc) end of drivers/char/ipmi/ipmi_kcs_sm.c, it seems
> that the proposed kcs_bmc_cdev_raw.c interface would be
> what corresponds to the other side of
> drivers/input/serio/i8042.c+userio.c.

Right. I guess the question is should we be splitting kernel subsystems
along host/bmc lines? Doesn't feel intuitive, it's all Linux, but maybe
we can consolidate in the future if it makes sense?

> Then again, these are also on
> separate ports (0x60 for the keyboard controller, 0xca2 for the BMC
> KCS), so they would never actually talk to one another.

Well, sort of I guess. On Power systems we don't use the keyboard
controller for IPMI or keyboards, so we're just kinda exploiting the
hardware for our own purposes.

>
> > Both the KCS IPMI and raw chardev I've implemented in this patch need
> > both read and write access to the status register (STR). serio could
> > potentially expose its value through serio_interrupt() using the
> > SERIO_OOB_DATA flag, but I haven't put any thought into it beyond this
> > sentence. We'd need some extra support for writing STR via the serio
> > API. I'm not sure that fits into the abstraction (unless we make
> > serio_write() take a flags argument?).
> >
> > In that vein, the serio_raw interface is close to the functionality
> > that the raw chardev provides in this patch, though again serio_raw
> > lacks userspace access to STR. Flags are ignored in the ->interrupt()
> > callback so all values received via ->interrupt() are exposed as data.
> > The result is there's no way to take care of SERIO_OOB_DATA in the
> > read() path. Given that, I think we'd have to expose an ioctl() to
> > access the STR value after taking care of SERIO_OOB_DATA in
> > ->interrupt().
> >
> > I'm not sure where that lands us.
>
> Based on what I looked up, I think you can just forget about my original
> question. We have two separate interfaces that use an Intel 8042-style
> protocol, but they don't really interact.

Right, this is still true given Power doesn't care for keyboards or
IPMI via the keyboard controllers; the two still don't interact.

Andrew

2021-04-14 09:38:19

by Andrew Jeffery

[permalink] [raw]
Subject: Re: [PATCH v2 16/21] ipmi: kcs_bmc: Add a "raw" character device interface



On Tue, 13 Apr 2021, at 17:52, Arnd Bergmann wrote:
> On Tue, Apr 13, 2021 at 1:45 AM Andrew Jeffery <[email protected]> wrote:
> > On Mon, 12 Apr 2021, at 18:18, Arnd Bergmann wrote:
> > > On Mon, Apr 12, 2021 at 3:33 AM Andrew Jeffery <[email protected]> wrote:
> > > > On Fri, 9 Apr 2021, at 17:25, Arnd Bergmann wrote:
> > > > > On Fri, Mar 19, 2021 at 7:31 AM Andrew Jeffery <[email protected]> wrote:
> > > > > >
> > > > > > The existing IPMI chardev encodes IPMI behaviours as the name suggests.
> > > > > > However, KCS devices are useful beyond IPMI (or keyboards), as they
> > > > > > provide a means to generate IRQs and exchange arbitrary data between a
> > > > > > BMC and its host system.
> > > > >
> > > > > I only noticed the series after Joel asked about the DT changes on the arm
> > > > > side. One question though:
> > > > >
> > > > > How does this related to the drivers/input/serio/ framework that also talks
> > > > > to the keyboard controller for things that are not keyboards?
> > > >
> > > > I've taken a brief look and I feel they're somewhat closely related.
> > > >
> > > > It's plausible that we could wrangle the code so the Aspeed and Nuvoton
> > > > KCS drivers move under drivers/input/serio. If you squint, the i8042
> > > > serio device driver has similarities with what the Aspeed and Nuvoton
> > > > device drivers are providing to the KCS IPMI stack.
> > >
> > > After looking some more into it, I finally understood that the two are
> > > rather complementary. While the drivers/char/ipmi/kcs_bmc.c
> > > is the other (bmc) end of drivers/char/ipmi/ipmi_kcs_sm.c, it seems
> > > that the proposed kcs_bmc_cdev_raw.c interface would be
> > > what corresponds to the other side of
> > > drivers/input/serio/i8042.c+userio.c.
> >
> > Right. I guess the question is should we be splitting kernel subsystems
> > along host/bmc lines? Doesn't feel intuitive, it's all Linux, but maybe
> > we can consolidate in the future if it makes sense?
>
> We actually have a number of subsystems with somewhat overlapping
> functionality. I brought up serio, because it has an abstraction for multiple
> things that communicate over the keyboard controller and I thought
> the problem you were trying to solve was also related to the keyboard
> controller.
> It is also one of multiple abstractions that allow you to connect a device
> to a uart (along with serdev and tty_ldisc, probably at least one more that
> you can nest above or below these).
>
> Consolidating the kcs_bmc.c interface into something that already
> exists would obviously be best, but it's not clear which of these that
> should be, that depends on the fundamental properties of the hardware
> interface.
>
> > > Then again, these are also on
> > > separate ports (0x60 for the keyboard controller, 0xca2 for the BMC
> > > KCS), so they would never actually talk to one another.
> >
> > Well, sort of I guess. On Power systems we don't use the keyboard
> > controller for IPMI or keyboards, so we're just kinda exploiting the
> > hardware for our own purposes.
>
> Can you describe in an abstract form what the hardware interface
> can do here and what you want from it? I wonder if it could be
> part of a higher-level interface such as drivers/mailbox/ instead.

It gives us interrupts each way between the host and BMC when we send
some (small amount of) data/metadata. Mailbox is possibly a fit for
this? We're (ab)using the keyboard controllers to implement a vendor
MCTP binding over LPC[1] and also a simple protocol for the (Power)
host to trigger BMC debug data capture in the event of issues with
other (more complex) in-band communication stacks. The MCTP binding is
what requires access to STR.

It's feasible that we could implement the debug capture protocol with
the serio_raw interface now that I think about it (as it only makes use
of data and not status). What's unclear to me right now is what impact
that has on the Aspeed/Nuvoton KCS drivers we have in the IPMI
subsystem. If we can do something sensible to service both serio and
IPMI with the one driver implementation then I can put together a PoC
for the debug data stuff using serio_raw.

Regarding the MCTP binding, Jeremy Kerr is working in an in-kernel,
socket-based implementation of MCTP. Eventually this will allow us to
bury the KCS details in the MCTP subsystem, which removes some of the
motivation for the raw interface here.

Andrew

[1] https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-ibm-astlpc.md