From 2253042d86f57d90a621ac2513a7a7a13afcf809 Mon Sep 17 00:00:00 2001 From: Petr Pavlu Date: Thu, 13 May 2021 14:26:36 +0200 Subject: ipmi/watchdog: Stop watchdog timer when the current action is 'none' When an IPMI watchdog timer is being stopped in ipmi_close() or ipmi_ioctl(WDIOS_DISABLECARD), the current watchdog action is updated to WDOG_TIMEOUT_NONE and _ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB) is called to install this action. The latter function ends up invoking __ipmi_set_timeout() which makes the actual 'Set Watchdog Timer' IPMI request. For IPMI 1.0, this operation results in fully stopping the watchdog timer. For IPMI >= 1.5, function __ipmi_set_timeout() always specifies the "don't stop" flag in the prepared 'Set Watchdog Timer' IPMI request. This causes that the watchdog timer has its action correctly updated to 'none' but the timer continues to run. A problem is that IPMI firmware can then still log an expiration event when the configured timeout is reached, which is unexpected because the watchdog timer was requested to be stopped. The patch fixes this problem by not setting the "don't stop" flag in __ipmi_set_timeout() when the current action is WDOG_TIMEOUT_NONE which results in stopping the watchdog timer. This makes the behaviour for IPMI >= 1.5 consistent with IPMI 1.0. It also matches the logic in __ipmi_heartbeat() which does not allow to reset the watchdog if the current action is WDOG_TIMEOUT_NONE as that would start the timer. Signed-off-by: Petr Pavlu Message-Id: <10a41bdc-9c99-089c-8d89-fa98ce5ea080@suse.com> Cc: stable@vger.kernel.org Signed-off-by: Corey Minyard --- drivers/char/ipmi/ipmi_watchdog.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/drivers/char/ipmi/ipmi_watchdog.c b/drivers/char/ipmi/ipmi_watchdog.c index 32c334e34d55..e4ff3b50de7f 100644 --- a/drivers/char/ipmi/ipmi_watchdog.c +++ b/drivers/char/ipmi/ipmi_watchdog.c @@ -371,16 +371,18 @@ static int __ipmi_set_timeout(struct ipmi_smi_msg *smi_msg, data[0] = 0; WDOG_SET_TIMER_USE(data[0], WDOG_TIMER_USE_SMS_OS); - if ((ipmi_version_major > 1) - || ((ipmi_version_major == 1) && (ipmi_version_minor >= 5))) { - /* This is an IPMI 1.5-only feature. */ - data[0] |= WDOG_DONT_STOP_ON_SET; - } else if (ipmi_watchdog_state != WDOG_TIMEOUT_NONE) { - /* - * In ipmi 1.0, setting the timer stops the watchdog, we - * need to start it back up again. - */ - hbnow = 1; + if (ipmi_watchdog_state != WDOG_TIMEOUT_NONE) { + if ((ipmi_version_major > 1) || + ((ipmi_version_major == 1) && (ipmi_version_minor >= 5))) { + /* This is an IPMI 1.5-only feature. */ + data[0] |= WDOG_DONT_STOP_ON_SET; + } else { + /* + * In ipmi 1.0, setting the timer stops the watchdog, we + * need to start it back up again. + */ + hbnow = 1; + } } data[1] = 0; -- cgit v1.2.3 From 78ca0c0549803ab0f5ecab43e295111b7ccdc7f2 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:42 +0930 Subject: 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 Message-Id: <20210608104757.582199-2-andrew@aj.id.au> Reviewed-by: Zev Weiss Signed-off-by: Corey Minyard --- drivers/char/ipmi/kcs_bmc_aspeed.c | 152 ++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c index eefe362f65f0..69be873f40ac 100644 --- a/drivers/char/ipmi/kcs_bmc_aspeed.c +++ b/drivers/char/ipmi/kcs_bmc_aspeed.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -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,81 @@ 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); - } - - kcs = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel); - if (!kcs) - return ERR_PTR(-ENOMEM); - - 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); - } - - rc = of_property_read_u32(np, "kcs_addr", &slave); - if (rc) { - 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; + return channel; } -static int aspeed_kcs_calculate_channel(const struct kcs_ioreg *regs) +static int aspeed_kcs_of_v1_get_io_address(struct platform_device *pdev) { - int i; + u32 slave; + int rc; - for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) { - if (!memcmp(&ast_kcs_bmc_ioregs[i], regs, sizeof(*regs))) - return i + 1; + 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 -EINVAL; } - 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); + for (i = 0; i < ARRAY_SIZE(ast_kcs_bmc_ioregs); i++) { + if (!memcmp(&ast_kcs_bmc_ioregs[i], &ioreg, sizeof(ioreg))) + return i + 1; + } - kcs = kcs_bmc_alloc(&pdev->dev, sizeof(struct aspeed_kcs_bmc), channel); - if (!kcs) - return ERR_PTR(-ENOMEM); + return -EINVAL; +} - kcs->ioreg = ioreg; +static int aspeed_kcs_of_v2_get_io_address(struct platform_device *pdev) +{ + uint32_t slave; + int rc; - 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); + rc = of_property_read_u32(pdev->dev.of_node, "aspeed,lpc-io-reg", &slave); + if (rc || slave > 0xffff) { + dev_err(&pdev->dev, "no valid 'aspeed,lpc-io-reg' configured\n"); + return -EINVAL; } - rc = of_property_read_u32(np, "aspeed,lpc-io-reg", &slave); - if (rc) - return ERR_PTR(rc); - - aspeed_kcs_set_address(kcs, slave); - - return kcs; + return slave; } 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; + int rc, channel, addr; np = dev->of_node->parent; if (!of_device_is_compatible(np, "aspeed,ast2400-lpc-v2") && @@ -352,23 +330,35 @@ 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; + + priv = kcs_bmc_priv(kcs_bmc); + 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); + rc = aspeed_kcs_config_irq(kcs_bmc, pdev); if (rc) return rc; @@ -400,11 +390,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); -- cgit v1.2.3 From ec6f0cf17a639743e622d4156b0a5009263fd479 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:43 +0930 Subject: 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 Reviewed-by: Zev Weiss Message-Id: <20210608104757.582199-3-andrew@aj.id.au> Signed-off-by: Corey Minyard --- 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 69be873f40ac..01ebb9da3d49 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 @@ -345,6 +353,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); -- cgit v1.2.3 From 961f7a3af9a9e60bbd2f95ae2d3e9e5e6f41fe6a Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:44 +0930 Subject: 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 Reviewed-by: Zev Weiss Message-Id: <20210608104757.582199-4-andrew@aj.id.au> Signed-off-by: Corey Minyard --- 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: -- cgit v1.2.3 From 55ab48b4e356212fbe084ca110db73bb9a6e7058 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:45 +0930 Subject: 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 Reviewed-by: Zev Weiss Message-Id: <20210608104757.582199-5-andrew@aj.id.au> Signed-off-by: Corey Minyard --- 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 -#include -#include #include -#include -#include -#include -#include #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 +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software"); -- cgit v1.2.3 From d7096970075ef47c9906fd241cc4939cc11ddd01 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:46 +0930 Subject: 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 Reviewed-by: Zev Weiss Message-Id: <20210608104757.582199-6-andrew@aj.id.au> Signed-off-by: Corey Minyard --- drivers/char/ipmi/kcs_bmc.c | 17 +++++++--- drivers/char/ipmi/kcs_bmc.h | 12 +++---- drivers/char/ipmi/kcs_bmc_aspeed.c | 54 +++++++++++++++++-------------- drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 60 ++++++++++++++++++++++++----------- drivers/char/ipmi/kcs_bmc_npcm7xx.c | 34 +++++++++++++------- 5 files changed, 110 insertions(+), 67 deletions(-) diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c index ef5c48ffe74a..07bb6747f29a 100644 --- a/drivers/char/ipmi/kcs_bmc.c +++ b/drivers/char/ipmi/kcs_bmc.c @@ -44,12 +44,21 @@ 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_add_device(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_add_device(kcs_bmc); } -EXPORT_SYMBOL(kcs_bmc_alloc); +EXPORT_SYMBOL(kcs_bmc_add_device); + +int kcs_bmc_ipmi_remove_device(struct kcs_bmc *kcs_bmc); +void kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc) +{ + if (kcs_bmc_ipmi_remove_device(kcs_bmc)) + pr_warn("Failed to remove device for KCS channel %d\n", + kcs_bmc->channel); +} +EXPORT_SYMBOL(kcs_bmc_remove_device); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Haiyue Wang "); diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h index febea0c8deb4..f3ed89e7da98 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); +void 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 01ebb9da3d49..b07cbc423dd5 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: @@ -325,17 +332,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); @@ -346,20 +352,22 @@ 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 = kcs_bmc_priv(kcs_bmc); 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"); @@ -372,29 +380,27 @@ 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); + rc = kcs_bmc_add_device(&priv->kcs_bmc); if (rc) { - dev_err(dev, "Unable to register device\n"); + dev_warn(&pdev->dev, "Failed to register channel %d: %d\n", kcs_bmc->channel, rc); 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..5060643bf530 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_add_device(struct kcs_bmc *kcs_bmc); +int kcs_bmc_ipmi_add_device(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; + + 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_add_device); - return kcs_bmc; +int kcs_bmc_ipmi_remove_device(struct kcs_bmc *kcs_bmc); +int kcs_bmc_ipmi_remove_device(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_remove_device); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Haiyue Wang "); diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c index 1f44aadec9e8..e06250285113 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,16 +183,16 @@ 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); + rc = kcs_bmc_add_device(kcs_bmc); if (rc) { - dev_err(dev, "Unable to register device\n"); + dev_warn(&pdev->dev, "Failed to register channel %d: %d\n", kcs_bmc->channel, rc); return rc; } @@ -196,9 +205,10 @@ static int npcm7xx_kcs_probe(struct platform_device *pdev) 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; } -- cgit v1.2.3 From faae6e391eda73a5b9870c78349064282a625bfa Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:47 +0930 Subject: 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 Message-Id: <20210608104757.582199-7-andrew@aj.id.au> Reviewed-by: Zev Weiss Signed-off-by: Corey Minyard --- drivers/char/ipmi/kcs_bmc.c | 23 ++++++++++++++------- drivers/char/ipmi/kcs_bmc.h | 27 +++++++++++------------- drivers/char/ipmi/kcs_bmc_aspeed.c | 17 ++++++++------- drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 39 +++++++++++++++++++++++------------ drivers/char/ipmi/kcs_bmc_client.h | 27 ++++++++++++++++++++++++ drivers/char/ipmi/kcs_bmc_device.h | 19 +++++++++++++++++ drivers/char/ipmi/kcs_bmc_npcm7xx.c | 17 ++++++++------- 7 files changed, 117 insertions(+), 52 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 07bb6747f29a..c347cf6f9337 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 #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) +irqreturn_t 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); @@ -62,4 +68,5 @@ EXPORT_SYMBOL(kcs_bmc_remove_device); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Haiyue Wang "); +MODULE_AUTHOR("Andrew Jeffery "); 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 f3ed89e7da98..f42843d240ed 100644 --- a/drivers/char/ipmi/kcs_bmc.h +++ b/drivers/char/ipmi/kcs_bmc.h @@ -8,6 +8,12 @@ #include +#include "kcs_bmc_client.h" + +#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 +72,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 +105,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); -void 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 b07cbc423dd5..fdfba745302a 100644 --- a/drivers/char/ipmi/kcs_bmc_aspeed.c +++ b/drivers/char/ipmi/kcs_bmc_aspeed.c @@ -21,7 +21,7 @@ #include #include -#include "kcs_bmc.h" +#include "kcs_bmc_device.h" #define DEVICE_NAME "ast-kcs-bmc" @@ -220,14 +220,17 @@ 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; - if (!kcs_bmc_handle_event(kcs_bmc)) - return IRQ_HANDLED; - - return IRQ_NONE; + return kcs_bmc_handle_event(kcs_bmc); } static int aspeed_kcs_config_irq(struct kcs_bmc *kcs_bmc, @@ -364,9 +367,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 5060643bf530..476ad6d541d5 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 irqreturn_t 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 = IRQ_HANDLED; + } else { + ret = IRQ_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 @@ out_unlock: 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 @@ out_unlock: 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_add_device(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..dad8774aebce --- /dev/null +++ b/drivers/char/ipmi/kcs_bmc_client.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2021, IBM Corp. */ + +#ifndef __KCS_BMC_CONSUMER_H__ +#define __KCS_BMC_CONSUMER_H__ + +#include + +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 { + irqreturn_t (*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..dd8bf1307ad2 --- /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); +}; + +irqreturn_t kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc); +int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc); +void 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 e06250285113..ebb691af28c5 100644 --- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c +++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c @@ -17,7 +17,7 @@ #include #include -#include "kcs_bmc.h" +#include "kcs_bmc_device.h" #define DEVICE_NAME "npcm-kcs-bmc" #define KCS_CHANNEL_MAX 3 @@ -128,10 +128,7 @@ static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg) { struct kcs_bmc *kcs_bmc = arg; - if (!kcs_bmc_handle_event(kcs_bmc)) - return IRQ_HANDLED; - - return IRQ_NONE; + return kcs_bmc_handle_event(kcs_bmc); } static int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc, @@ -148,6 +145,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 +182,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); -- cgit v1.2.3 From d4e7ac68f771addc19352121706d8584eb0166cd Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:48 +0930 Subject: 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 Reviewed-by: Zev Weiss Message-Id: <20210608104757.582199-8-andrew@aj.id.au> Signed-off-by: Corey Minyard --- drivers/char/ipmi/kcs_bmc.c | 70 +++++- drivers/char/ipmi/kcs_bmc.h | 83 +------ drivers/char/ipmi/kcs_bmc_aspeed.c | 22 +- drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 428 ++++++++++++++++++++-------------- drivers/char/ipmi/kcs_bmc_client.h | 24 +- drivers/char/ipmi/kcs_bmc_device.h | 14 +- drivers/char/ipmi/kcs_bmc_npcm7xx.c | 20 +- 7 files changed, 367 insertions(+), 294 deletions(-) diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c index c347cf6f9337..2ec934f0dba3 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 #include #include "kcs_bmc.h" @@ -14,51 +15,98 @@ /* 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); -irqreturn_t kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc) +irqreturn_t kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc) { - return kcs_bmc->client.ops->event(&kcs_bmc->client); + struct kcs_bmc_client *client; + irqreturn_t 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 = IRQ_HANDLED; + } else { + rc = IRQ_NONE; + } + } + spin_unlock(&kcs_bmc->lock); + + return rc; } EXPORT_SYMBOL(kcs_bmc_handle_event); -int kcs_bmc_ipmi_add_device(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_ipmi_add_device(struct kcs_bmc_device *kcs_bmc); +int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc) { return kcs_bmc_ipmi_add_device(kcs_bmc); } EXPORT_SYMBOL(kcs_bmc_add_device); -int kcs_bmc_ipmi_remove_device(struct kcs_bmc *kcs_bmc); -void kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc) +int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc); +void kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc) { if (kcs_bmc_ipmi_remove_device(kcs_bmc)) pr_warn("Failed to remove device for KCS channel %d\n", diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h index f42843d240ed..3f0cce315b4f 100644 --- a/drivers/char/ipmi/kcs_bmc.h +++ b/drivers/char/ipmi/kcs_bmc.h @@ -6,61 +6,12 @@ #ifndef __KCS_BMC_H__ #define __KCS_BMC_H__ -#include - -#include "kcs_bmc_client.h" +#include #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. - * 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 @@ -73,36 +24,20 @@ struct kcs_ioreg { }; struct kcs_bmc_device_ops; +struct kcs_bmc_client; -struct kcs_bmc { - struct device *dev; - - const struct kcs_bmc_device_ops *ops; - - struct kcs_bmc_client client; - - spinlock_t lock; +struct kcs_bmc_device { + struct list_head entry; + struct device *dev; u32 channel; - int running; struct kcs_ioreg ioreg; - enum kcs_phases phase; - enum kcs_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; + const struct kcs_bmc_device_ops *ops; - struct miscdevice miscdev; + spinlock_t lock; + struct kcs_bmc_client *client; }; + #endif /* __KCS_BMC_H__ */ diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c index fdfba745302a..08d4290464d8 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,12 +228,12 @@ 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; return kcs_bmc_handle_event(kcs_bmc); } -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; @@ -335,8 +335,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; @@ -399,7 +399,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 476ad6d541d5..865d8b93f3b7 100644 --- a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c +++ b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c @@ -8,13 +8,88 @@ #include #include #include +#include +#include #include +#include #include #include #include #include -#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 irqreturn_t 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 IRQ_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 = IRQ_HANDLED; } else { ret = IRQ_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 @@ out_unlock: 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 @@ out_unlock: 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_add_device(struct kcs_bmc *kcs_bmc); -int kcs_bmc_ipmi_add_device(struct kcs_bmc *kcs_bmc) +static DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock); +static LIST_HEAD(kcs_bmc_ipmi_instances); + +int kcs_bmc_ipmi_add_device(struct kcs_bmc_device *kcs_bmc); +int kcs_bmc_ipmi_add_device(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); + + init_waitqueue_head(&priv->queue); - rc = misc_register(&kcs_bmc->miscdev); + 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_add_device); -int kcs_bmc_ipmi_remove_device(struct kcs_bmc *kcs_bmc); -int kcs_bmc_ipmi_remove_device(struct kcs_bmc *kcs_bmc) +int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc); +int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc) { - misc_deregister(&kcs_bmc->miscdev); + struct kcs_bmc_ipmi *priv = NULL, *pos; + + spin_lock_irq(&kcs_bmc_ipmi_instances_lock); + list_for_each_entry(pos, &kcs_bmc_ipmi_instances, entry) { + if (pos->client.dev == kcs_bmc) { + priv = pos; + list_del(&pos->entry); + break; + } + } + spin_unlock_irq(&kcs_bmc_ipmi_instances_lock); - spin_lock_irq(&kcs_bmc->lock); - kcs_bmc->running = 0; - kcs_bmc_ipmi_force_abort(kcs_bmc); - spin_unlock_irq(&kcs_bmc->lock); + if (!priv) + return -ENODEV; - 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); + 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_remove_device); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Haiyue Wang "); +MODULE_AUTHOR("Andrew Jeffery "); 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 dad8774aebce..a32fdc4a7707 100644 --- a/drivers/char/ipmi/kcs_bmc_client.h +++ b/drivers/char/ipmi/kcs_bmc_client.h @@ -6,22 +6,24 @@ #include -struct kcs_bmc; -struct kcs_bmc_client_ops; +#include "kcs_bmc.h" + +struct kcs_bmc_client_ops { + irqreturn_t (*event)(struct kcs_bmc_client *client); +}; struct kcs_bmc_client { const struct kcs_bmc_client_ops *ops; - struct kcs_bmc *dev; + struct kcs_bmc_device *dev; }; -struct kcs_bmc_client_ops { - irqreturn_t (*event)(struct kcs_bmc_client *client); -}; +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 *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); +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 dd8bf1307ad2..c9a4237bbf40 100644 --- a/drivers/char/ipmi/kcs_bmc_device.h +++ b/drivers/char/ipmi/kcs_bmc_device.h @@ -4,16 +4,18 @@ #ifndef __KCS_BMC_DEVICE_H__ #define __KCS_BMC_DEVICE_H__ +#include + #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); }; -irqreturn_t kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc); -int kcs_bmc_add_device(struct kcs_bmc *kcs_bmc); -void kcs_bmc_remove_device(struct kcs_bmc *kcs_bmc); +irqreturn_t kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc); +int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc); +void kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc); #endif diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c index ebb691af28c5..3b96486c25c3 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,12 +126,12 @@ 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; return kcs_bmc_handle_event(kcs_bmc); } -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; @@ -155,7 +155,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); -- cgit v1.2.3 From 7cafff991e32d4b97251982ab0665601b65f2736 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:49 +0930 Subject: 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 Message-Id: <20210608104757.582199-9-andrew@aj.id.au> Reviewed-by: Zev Weiss Signed-off-by: Corey Minyard --- drivers/char/ipmi/Kconfig | 13 ++++++ drivers/char/ipmi/Makefile | 3 +- drivers/char/ipmi/kcs_bmc.c | 80 ++++++++++++++++++++++++++++++++--- drivers/char/ipmi/kcs_bmc_cdev_ipmi.c | 31 +++++++++++--- drivers/char/ipmi/kcs_bmc_client.h | 14 ++++++ 5 files changed, 128 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 2ec934f0dba3..9860c7b75157 100644 --- a/drivers/char/ipmi/kcs_bmc.c +++ b/drivers/char/ipmi/kcs_bmc.c @@ -5,7 +5,9 @@ */ #include +#include #include +#include #include "kcs_bmc.h" @@ -13,6 +15,11 @@ #include "kcs_bmc_device.h" #include "kcs_bmc_client.h" +/* Record registered devices and drivers */ +static DEFINE_MUTEX(kcs_bmc_lock); +static LIST_HEAD(kcs_bmc_devices); +static LIST_HEAD(kcs_bmc_drivers); + /* Consumer data access */ u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc) @@ -98,22 +105,83 @@ void kcs_bmc_disable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_clien } EXPORT_SYMBOL(kcs_bmc_disable_device); -int kcs_bmc_ipmi_add_device(struct kcs_bmc_device *kcs_bmc); int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc) { - return kcs_bmc_ipmi_add_device(kcs_bmc); + struct kcs_bmc_driver *drv; + int error = 0; + int rc; + + spin_lock_init(&kcs_bmc->lock); + kcs_bmc->client = NULL; + + mutex_lock(&kcs_bmc_lock); + list_add(&kcs_bmc->entry, &kcs_bmc_devices); + list_for_each_entry(drv, &kcs_bmc_drivers, entry) { + rc = drv->ops->add_device(kcs_bmc); + if (!rc) + continue; + + dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d", + kcs_bmc->channel, rc); + error = rc; + } + mutex_unlock(&kcs_bmc_lock); + + return error; } EXPORT_SYMBOL(kcs_bmc_add_device); -int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc); void kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc) { - if (kcs_bmc_ipmi_remove_device(kcs_bmc)) - pr_warn("Failed to remove device for KCS channel %d\n", - kcs_bmc->channel); + struct kcs_bmc_driver *drv; + int rc; + + mutex_lock(&kcs_bmc_lock); + list_del(&kcs_bmc->entry); + list_for_each_entry(drv, &kcs_bmc_drivers, entry) { + rc = drv->ops->remove_device(kcs_bmc); + if (rc) + dev_err(kcs_bmc->dev, "Failed to remove chardev for KCS channel %d: %d", + kcs_bmc->channel, rc); + } + mutex_unlock(&kcs_bmc_lock); } EXPORT_SYMBOL(kcs_bmc_remove_device); +void kcs_bmc_register_driver(struct kcs_bmc_driver *drv) +{ + struct kcs_bmc_device *kcs_bmc; + int rc; + + mutex_lock(&kcs_bmc_lock); + list_add(&drv->entry, &kcs_bmc_drivers); + list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) { + rc = drv->ops->add_device(kcs_bmc); + if (rc) + dev_err(kcs_bmc->dev, "Failed to add driver for KCS channel %d: %d", + kcs_bmc->channel, rc); + } + mutex_unlock(&kcs_bmc_lock); +} +EXPORT_SYMBOL(kcs_bmc_register_driver); + +void kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv) +{ + struct kcs_bmc_device *kcs_bmc; + int rc; + + mutex_lock(&kcs_bmc_lock); + list_del(&drv->entry); + list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) { + rc = drv->ops->remove_device(kcs_bmc); + if (rc) + dev_err(kcs_bmc->dev, "Failed to remove driver for KCS channel %d: %d", + kcs_bmc->channel, rc); + } + mutex_unlock(&kcs_bmc_lock); +} +EXPORT_SYMBOL(kcs_bmc_unregister_driver); + MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Haiyue Wang "); MODULE_AUTHOR("Andrew Jeffery "); diff --git a/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c b/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c index 865d8b93f3b7..486834a962c3 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_add_device(struct kcs_bmc_device *kcs_bmc); -int kcs_bmc_ipmi_add_device(struct kcs_bmc_device *kcs_bmc) +static int kcs_bmc_ipmi_add_device(struct kcs_bmc_device *kcs_bmc) { struct kcs_bmc_ipmi *priv; int rc; @@ -512,10 +511,8 @@ int kcs_bmc_ipmi_add_device(struct kcs_bmc_device *kcs_bmc) return 0; } -EXPORT_SYMBOL(kcs_bmc_ipmi_add_device); -int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc); -int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc) +static int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc) { struct kcs_bmc_ipmi *priv = NULL, *pos; @@ -541,7 +538,29 @@ int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc) return 0; } -EXPORT_SYMBOL(kcs_bmc_ipmi_remove_device); + +static const struct kcs_bmc_driver_ops kcs_bmc_ipmi_driver_ops = { + .add_device = kcs_bmc_ipmi_add_device, + .remove_device = kcs_bmc_ipmi_remove_device, +}; + +static struct kcs_bmc_driver kcs_bmc_ipmi_driver = { + .ops = &kcs_bmc_ipmi_driver_ops, +}; + +static int kcs_bmc_ipmi_init(void) +{ + kcs_bmc_register_driver(&kcs_bmc_ipmi_driver); + + return 0; +} +module_init(kcs_bmc_ipmi_init); + +static void kcs_bmc_ipmi_exit(void) +{ + kcs_bmc_unregister_driver(&kcs_bmc_ipmi_driver); +} +module_exit(kcs_bmc_ipmi_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Haiyue Wang "); diff --git a/drivers/char/ipmi/kcs_bmc_client.h b/drivers/char/ipmi/kcs_bmc_client.h index a32fdc4a7707..814ad8e052ef 100644 --- a/drivers/char/ipmi/kcs_bmc_client.h +++ b/drivers/char/ipmi/kcs_bmc_client.h @@ -8,6 +8,17 @@ #include "kcs_bmc.h" +struct kcs_bmc_driver_ops { + int (*add_device)(struct kcs_bmc_device *kcs_bmc); + int (*remove_device)(struct kcs_bmc_device *kcs_bmc); +}; + +struct kcs_bmc_driver { + struct list_head entry; + + const struct kcs_bmc_driver_ops *ops; +}; + struct kcs_bmc_client_ops { irqreturn_t (*event)(struct kcs_bmc_client *client); }; @@ -18,6 +29,9 @@ struct kcs_bmc_client { struct kcs_bmc_device *dev; }; +void kcs_bmc_register_driver(struct kcs_bmc_driver *drv); +void kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv); + int kcs_bmc_enable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client); void kcs_bmc_disable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client); -- cgit v1.2.3 From 28651e6c4237f4aee5e0744ce37d3a50e7b1f36b Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:50 +0930 Subject: 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. Cc: CS20 KWLiu Signed-off-by: Andrew Jeffery Reviewed-by: Zev Weiss Message-Id: <20210608104757.582199-10-andrew@aj.id.au> Signed-off-by: Corey Minyard --- 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 | 26 ++++++- 6 files changed, 131 insertions(+), 57 deletions(-) diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c index 9860c7b75157..91eb43746cef 100644 --- a/drivers/char/ipmi/kcs_bmc.c +++ b/drivers/char/ipmi/kcs_bmc.c @@ -182,6 +182,12 @@ void kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv) } EXPORT_SYMBOL(kcs_bmc_unregister_driver); +void kcs_bmc_update_event_mask(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 events) +{ + kcs_bmc->ops->irq_mask_update(kcs_bmc, mask, events); +} +EXPORT_SYMBOL(kcs_bmc_update_event_mask); + MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Haiyue Wang "); MODULE_AUTHOR("Andrew Jeffery "); diff --git a/drivers/char/ipmi/kcs_bmc.h b/drivers/char/ipmi/kcs_bmc.h index 3f0cce315b4f..fa408b802c79 100644 --- a/drivers/char/ipmi/kcs_bmc.h +++ b/drivers/char/ipmi/kcs_bmc.h @@ -8,6 +8,9 @@ #include +#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 08d4290464d8..5bf8d013e4ae 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 (mask & KCS_BMC_EVENT_TYPE_OBE) { + 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 (mask & KCS_BMC_EVENT_TYPE_IBF) { + const bool enable = !!(state & KCS_BMC_EVENT_TYPE_IBF); + + switch (kcs_bmc->channel) { + case 1: + regmap_update_bits(priv->map, LPC_HICR2, LPC_HICR2_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, @@ -375,6 +404,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); @@ -383,6 +416,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); @@ -403,6 +438,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 814ad8e052ef..6fdcde0a7169 100644 --- a/drivers/char/ipmi/kcs_bmc_client.h +++ b/drivers/char/ipmi/kcs_bmc_client.h @@ -35,6 +35,8 @@ void kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv); int kcs_bmc_enable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client); void kcs_bmc_disable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client); +void kcs_bmc_update_event_mask(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 events); + u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc); void kcs_bmc_write_data(struct kcs_bmc_device *kcs_bmc, u8 data); u8 kcs_bmc_read_status(struct kcs_bmc_device *kcs_bmc); diff --git a/drivers/char/ipmi/kcs_bmc_device.h b/drivers/char/ipmi/kcs_bmc_device.h index c9a4237bbf40..17c572f25c54 100644 --- a/drivers/char/ipmi/kcs_bmc_device.h +++ b/drivers/char/ipmi/kcs_bmc_device.h @@ -9,6 +9,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 3b96486c25c3..ef81a715f88a 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(1) #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 (mask & KCS_BMC_EVENT_TYPE_OBE) + regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_OBEIE, + !!(state & KCS_BMC_EVENT_TYPE_OBE) * KCS_CTL_OBEIE); + + if (mask & KCS_BMC_EVENT_TYPE_IBF) + regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE, + !!(state & KCS_BMC_EVENT_TYPE_IBF) * KCS_CTL_IBFIE); +} + static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg) { struct kcs_bmc_device *kcs_bmc = arg; @@ -146,6 +157,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, @@ -186,11 +198,14 @@ 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); + rc = kcs_bmc_add_device(kcs_bmc); if (rc) { dev_warn(&pdev->dev, "Failed to register channel %d: %d\n", kcs_bmc->channel, rc); @@ -211,6 +226,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; } -- cgit v1.2.3 From fb6379f524ff56314fa857bb5c84bd22eee41eb7 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:51 +0930 Subject: ipmi: kcs_bmc: Enable IBF on open This way devices don't get delivered IRQs when no-one is interested. Signed-off-by: Andrew Jeffery Message-Id: <20210608104757.582199-11-andrew@aj.id.au> Signed-off-by: Corey Minyard --- drivers/char/ipmi/kcs_bmc.c | 25 ++++++++++--------------- drivers/char/ipmi/kcs_bmc_aspeed.c | 3 +-- drivers/char/ipmi/kcs_bmc_npcm7xx.c | 3 +-- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/drivers/char/ipmi/kcs_bmc.c b/drivers/char/ipmi/kcs_bmc.c index 91eb43746cef..03d02a848f3a 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); irqreturn_t kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc) { struct kcs_bmc_client *client; - irqreturn_t rc; + irqreturn_t rc = IRQ_NONE; spin_lock(&kcs_bmc->lock); client = kcs_bmc->client; - if (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 = IRQ_HANDLED; - } else { - rc = IRQ_NONE; - } - } spin_unlock(&kcs_bmc->lock); return rc; @@ -87,7 +75,10 @@ int kcs_bmc_enable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client if (kcs_bmc->client) { rc = -EBUSY; } else { + u8 mask = KCS_BMC_EVENT_TYPE_IBF; + kcs_bmc->client = client; + kcs_bmc_update_event_mask(kcs_bmc, mask, mask); rc = 0; } spin_unlock_irq(&kcs_bmc->lock); @@ -99,8 +90,12 @@ 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 5bf8d013e4ae..43994688dc8a 100644 --- a/drivers/char/ipmi/kcs_bmc_aspeed.c +++ b/drivers/char/ipmi/kcs_bmc_aspeed.c @@ -416,8 +416,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 ef81a715f88a..7961fec56476 100644 --- a/drivers/char/ipmi/kcs_bmc_npcm7xx.c +++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c @@ -202,8 +202,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); rc = kcs_bmc_add_device(kcs_bmc); -- cgit v1.2.3 From 3a3d2f6a4c64b9e709edc13b685ce46c8629060e Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:52 +0930 Subject: ipmi: kcs_bmc: Add serio adaptor kcs_bmc_serio acts as a bridge between the KCS drivers in the IPMI subsystem and the existing userspace interfaces available through the serio subsystem. This is useful when userspace would like to make use of the BMC KCS devices for purposes that aren't IPMI. Signed-off-by: Andrew Jeffery Message-Id: <20210608104757.582199-12-andrew@aj.id.au> Reviewed-by: Zev Weiss Signed-off-by: Corey Minyard --- drivers/char/ipmi/Kconfig | 14 ++++ drivers/char/ipmi/Makefile | 1 + drivers/char/ipmi/kcs_bmc_serio.c | 157 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 drivers/char/ipmi/kcs_bmc_serio.c diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index bc5f81899b62..249b31197eea 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig @@ -137,6 +137,20 @@ 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_SERIO + depends on IPMI_KCS_BMC && SERIO + tristate "SerIO adaptor for BMC KCS devices" + help + Adapts the BMC KCS device for the SerIO subsystem. This allows users + to take advantage of userspace interfaces provided by SerIO where + appropriate. + + Say YES if you wish to expose KCS devices on the BMC via SerIO + interfaces. + + This support is also available as a module. The module will be + called kcs_bmc_serio. + config ASPEED_BT_IPMI_BMC depends on ARCH_ASPEED || COMPILE_TEST depends on REGMAP && REGMAP_MMIO && MFD_SYSCON diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile index fcfa676afddb..84f47d18007f 100644 --- a/drivers/char/ipmi/Makefile +++ b/drivers/char/ipmi/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o +obj-$(CONFIG_IPMI_KCS_BMC_SERIO) += kcs_bmc_serio.o obj-$(CONFIG_IPMI_KCS_BMC_CDEV_IPMI) += kcs_bmc_cdev_ipmi.o obj-$(CONFIG_ASPEED_BT_IPMI_BMC) += bt-bmc.o obj-$(CONFIG_ASPEED_KCS_IPMI_BMC) += kcs_bmc_aspeed.o diff --git a/drivers/char/ipmi/kcs_bmc_serio.c b/drivers/char/ipmi/kcs_bmc_serio.c new file mode 100644 index 000000000000..7948cabde50b --- /dev/null +++ b/drivers/char/ipmi/kcs_bmc_serio.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (c) 2021 IBM Corp. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kcs_bmc_client.h" + +struct kcs_bmc_serio { + struct list_head entry; + + struct kcs_bmc_client client; + struct serio *port; + + spinlock_t lock; +}; + +static inline struct kcs_bmc_serio *client_to_kcs_bmc_serio(struct kcs_bmc_client *client) +{ + return container_of(client, struct kcs_bmc_serio, client); +} + +static irqreturn_t kcs_bmc_serio_event(struct kcs_bmc_client *client) +{ + struct kcs_bmc_serio *priv; + u8 handled = IRQ_NONE; + u8 status; + + priv = client_to_kcs_bmc_serio(client); + + spin_lock(&priv->lock); + + status = kcs_bmc_read_status(client->dev); + + if (status & KCS_BMC_STR_IBF) + handled = serio_interrupt(priv->port, kcs_bmc_read_data(client->dev), 0); + + spin_unlock(&priv->lock); + + return handled; +} + +static const struct kcs_bmc_client_ops kcs_bmc_serio_client_ops = { + .event = kcs_bmc_serio_event, +}; + +static int kcs_bmc_serio_open(struct serio *port) +{ + struct kcs_bmc_serio *priv = port->port_data; + + return kcs_bmc_enable_device(priv->client.dev, &priv->client); +} + +static void kcs_bmc_serio_close(struct serio *port) +{ + struct kcs_bmc_serio *priv = port->port_data; + + kcs_bmc_disable_device(priv->client.dev, &priv->client); +} + +static DEFINE_SPINLOCK(kcs_bmc_serio_instances_lock); +static LIST_HEAD(kcs_bmc_serio_instances); + +static int kcs_bmc_serio_add_device(struct kcs_bmc_device *kcs_bmc) +{ + struct kcs_bmc_serio *priv; + struct serio *port; + + priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL); + + /* Use kzalloc() as the allocation is cleaned up with kfree() via serio_unregister_port() */ + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!(priv && port)) + return -ENOMEM; + + port->id.type = SERIO_8042; + port->open = kcs_bmc_serio_open; + port->close = kcs_bmc_serio_close; + port->port_data = priv; + port->dev.parent = kcs_bmc->dev; + + spin_lock_init(&priv->lock); + priv->port = port; + priv->client.dev = kcs_bmc; + priv->client.ops = &kcs_bmc_serio_client_ops; + + spin_lock_irq(&kcs_bmc_serio_instances_lock); + list_add(&priv->entry, &kcs_bmc_serio_instances); + spin_unlock_irq(&kcs_bmc_serio_instances_lock); + + serio_register_port(port); + + dev_info(kcs_bmc->dev, "Initialised serio client for channel %d", kcs_bmc->channel); + + return 0; +} + +static int kcs_bmc_serio_remove_device(struct kcs_bmc_device *kcs_bmc) +{ + struct kcs_bmc_serio *priv = NULL, *pos; + + spin_lock_irq(&kcs_bmc_serio_instances_lock); + list_for_each_entry(pos, &kcs_bmc_serio_instances, entry) { + if (pos->client.dev == kcs_bmc) { + priv = pos; + list_del(&pos->entry); + break; + } + } + spin_unlock_irq(&kcs_bmc_serio_instances_lock); + + if (!priv) + return -ENODEV; + + /* kfree()s priv->port via put_device() */ + serio_unregister_port(priv->port); + + /* Ensure the IBF IRQ is disabled if we were the active client */ + kcs_bmc_disable_device(kcs_bmc, &priv->client); + + devm_kfree(priv->client.dev->dev, priv); + + return 0; +} + +static const struct kcs_bmc_driver_ops kcs_bmc_serio_driver_ops = { + .add_device = kcs_bmc_serio_add_device, + .remove_device = kcs_bmc_serio_remove_device, +}; + +static struct kcs_bmc_driver kcs_bmc_serio_driver = { + .ops = &kcs_bmc_serio_driver_ops, +}; + +static int kcs_bmc_serio_init(void) +{ + kcs_bmc_register_driver(&kcs_bmc_serio_driver); + + return 0; +} +module_init(kcs_bmc_serio_init); + +static void kcs_bmc_serio_exit(void) +{ + kcs_bmc_unregister_driver(&kcs_bmc_serio_driver); +} +module_exit(kcs_bmc_serio_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Andrew Jeffery "); +MODULE_DESCRIPTION("Adapter driver for serio access to BMC KCS devices"); -- cgit v1.2.3 From e880275ccfa120bf6235180ca76f01271b7b97ec Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:53 +0930 Subject: 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 Reviewed-by: Rob Herring Reviewed-by: Zev Weiss Message-Id: <20210608104757.582199-13-andrew@aj.id.au> Signed-off-by: Corey Minyard --- .../bindings/ipmi/aspeed,ast2400-kcs-bmc.yaml | 92 ++++++++++++++++++++++ .../devicetree/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 + +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"; - }; -- cgit v1.2.3 From a7fd43d95054fe03cac3878538dcf12caa854889 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:54 +0930 Subject: 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 Reviewed-by: Rob Herring Message-Id: <20210608104757.582199-14-andrew@aj.id.au> Signed-off-by: Corey Minyard --- .../devicetree/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 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>; }; -- cgit v1.2.3 From a5e4236d560579a20e2bc870f6e6f32e4e7c0354 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:55 +0930 Subject: 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 Message-Id: <20210608104757.582199-15-andrew@aj.id.au> Signed-off-by: Corey Minyard --- drivers/char/ipmi/kcs_bmc_aspeed.c | 182 ++++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-) diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c index 43994688dc8a..922fe41f6b05 100644 --- a/drivers/char/ipmi/kcs_bmc_aspeed.c +++ b/drivers/char/ipmi/kcs_bmc_aspeed.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -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); @@ -262,7 +419,7 @@ static irqreturn_t aspeed_kcs_irq(int irq, void *arg) return kcs_bmc_handle_event(kcs_bmc); } -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; @@ -368,6 +525,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") && @@ -376,6 +535,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; @@ -388,6 +548,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; @@ -410,10 +577,20 @@ 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) { + rc = aspeed_kcs_config_upstream_irq(priv, upstream_irq[0], upstream_irq[1]); + if (rc < 0) + return rc; + } else { + priv->upstream_irq.mode = aspeed_kcs_irq_none; + } + platform_set_drvdata(pdev, priv); aspeed_kcs_irq_mask_update(kcs_bmc, (KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE), 0); @@ -480,4 +657,5 @@ module_platform_driver(ast_kcs_bmc_driver); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Haiyue Wang "); +MODULE_AUTHOR("Andrew Jeffery "); MODULE_DESCRIPTION("Aspeed device interface to the KCS BMC device"); -- cgit v1.2.3 From 78ff5a1666cd4a1129ca53dad5c68700fe63ab6a Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:56 +0930 Subject: 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 Reviewed-by: Zev Weiss Message-Id: <20210608104757.582199-16-andrew@aj.id.au> Signed-off-by: Corey Minyard --- 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 922fe41f6b05..461cb2c9cc7e 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); -- cgit v1.2.3 From d0ec795c8c275ceca2076861f7fe3cc5f18da348 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 8 Jun 2021 20:17:57 +0930 Subject: 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 Reviewed-by: Chia-Wei Wang Message-Id: <20210608104757.582199-17-andrew@aj.id.au> Signed-off-by: Corey Minyard --- drivers/char/ipmi/kcs_bmc_aspeed.c | 116 ++++++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 33 deletions(-) diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c index 461cb2c9cc7e..0401089f8895 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) @@ -457,18 +490,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) @@ -504,18 +537,30 @@ 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) { - dev_err(&pdev->dev, "no valid 'aspeed,lpc-io-reg' configured\n"); + rc = of_property_read_variable_u32_array(pdev->dev.of_node, + "aspeed,lpc-io-reg", + addrs, 1, 2); + if (rc < 0) { + dev_err(&pdev->dev, "No valid 'aspeed,lpc-io-reg' configured\n"); + return rc; + } + + if (addrs[0] > 0xffff) { + dev_err(&pdev->dev, "Invalid data address in 'aspeed,lpc-io-reg'\n"); + return -EINVAL; + } + + if (rc == 2 && addrs[1] > 0xffff) { + dev_err(&pdev->dev, "Invalid status address in 'aspeed,lpc-io-reg'\n"); return -EINVAL; } - return slave; + return rc; } static int aspeed_kcs_probe(struct platform_device *pdev) @@ -524,9 +569,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") && @@ -544,9 +591,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); @@ -575,7 +622,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); @@ -602,7 +651,8 @@ static int aspeed_kcs_probe(struct platform_device *pdev) 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; } -- cgit v1.2.3 From 5b32dd281ee0a269f39ecf6b48f0cd3f37264842 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 16 Jun 2021 17:29:13 +0100 Subject: ipmi: kcs_bmc_aspeed: Fix less than zero comparison of a unsigned int The comparisons of the unsigned int hw_type to less than zero always false because it is unsigned. Fix this by using an int for the assignment and less than zero check. Addresses-Coverity: ("Unsigned compared against 0") Fixes: 9d2df9a0ad80 ("ipmi: kcs_bmc_aspeed: Implement KCS SerIRQ configuration") Signed-off-by: Colin Ian King Message-Id: <20210616162913.15259-1-colin.king@canonical.com> Signed-off-by: Corey Minyard --- drivers/char/ipmi/kcs_bmc_aspeed.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/char/ipmi/kcs_bmc_aspeed.c b/drivers/char/ipmi/kcs_bmc_aspeed.c index 0401089f8895..92a37b33494c 100644 --- a/drivers/char/ipmi/kcs_bmc_aspeed.c +++ b/drivers/char/ipmi/kcs_bmc_aspeed.c @@ -301,13 +301,15 @@ static inline int aspeed_kcs_map_serirq_type(u32 dt_type) static int aspeed_kcs_config_upstream_irq(struct aspeed_kcs_bmc *priv, u32 id, u32 dt_type) { unsigned int mask, val, hw_type; + int ret; if (id > 15) return -EINVAL; - hw_type = aspeed_kcs_map_serirq_type(dt_type); - if (hw_type < 0) - return hw_type; + ret = aspeed_kcs_map_serirq_type(dt_type); + if (ret < 0) + return ret; + hw_type = ret; priv->upstream_irq.mode = aspeed_kcs_irq_serirq; priv->upstream_irq.id = id; -- cgit v1.2.3