diff --git a/Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml b/Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml new file mode 100644 index 000000000000..82bf7d1848c9 --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mailbox/axiado,ax3005-mailbox.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Axiado AX3005 Mailbox Controller + +maintainers: + - Krishnakumar Kesavan + - Prasad Bolisetty + - Swark Yang + +description: + The Axiado mailbox controller provides inter-processor communication + channels between subsystems within the Axiado SoC. The hardware has + separate TX and RX register regions. Only the RX channels (channels 8-15) + generate interrupts; TX channels are purely poll/write-based. + +properties: + compatible: + const: axiado,ax3005-mailbox + + reg: + items: + - description: TX mailbox register region + - description: RX mailbox register region + + reg-names: + items: + - const: tx + - const: rx + + interrupts: + description: + One interrupt per RX channel. The hardware exposes eight RX interrupt + lines corresponding to channels 8 through 15. + maxItems: 8 + + interrupt-names: + description: + Named interrupt lines for each RX channel, from ch8 to ch15. + items: + - const: ch8 + - const: ch9 + - const: ch10 + - const: ch11 + - const: ch12 + - const: ch13 + - const: ch14 + - const: ch15 + + "#mbox-cells": + description: + Number of cells in a mailbox specifier. A single cell encodes the + channel index as seen by the client. + const: 1 + +required: + - compatible + - reg + - reg-names + - interrupts + - interrupt-names + - "#mbox-cells" + +additionalProperties: false + +examples: + - | + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + mailbox@330f2000 { + compatible = "axiado,ax3005-mailbox"; + reg = <0x0 0x330f2000 0x0 0x2000>, + <0x0 0x331b0000 0x0 0x1000>; + reg-names = "tx", "rx"; + /* Only RX channels (8-15) have IRQs */ + interrupts = , + , + , + , + , + , + , + ; + interrupt-names = "ch8", "ch9", "ch10", "ch11", + "ch12", "ch13", "ch14", "ch15"; + interrupt-parent = <&gic500>; + #mbox-cells = <1>; + }; + }; diff --git a/Documentation/devicetree/bindings/mailbox/google,gs101-mbox.yaml b/Documentation/devicetree/bindings/mailbox/google,gs101-mbox.yaml index e249db4c1fbc..c109c1f7af24 100644 --- a/Documentation/devicetree/bindings/mailbox/google,gs101-mbox.yaml +++ b/Documentation/devicetree/bindings/mailbox/google,gs101-mbox.yaml @@ -20,7 +20,9 @@ description: properties: compatible: - const: google,gs101-mbox + enum: + - google,gs101-mbox + - samsung,exynos850-mbox reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/mailbox/qcom,apcs-kpss-global.yaml b/Documentation/devicetree/bindings/mailbox/qcom,apcs-kpss-global.yaml index 1b4ef0688ca7..39bc374300c3 100644 --- a/Documentation/devicetree/bindings/mailbox/qcom,apcs-kpss-global.yaml +++ b/Documentation/devicetree/bindings/mailbox/qcom,apcs-kpss-global.yaml @@ -19,6 +19,7 @@ properties: - items: - enum: - qcom,ipq5018-apcs-apps-global + - qcom,ipq5210-apcs-apps-global - qcom,ipq5332-apcs-apps-global - qcom,ipq5424-apcs-apps-global - qcom,ipq8074-apcs-apps-global diff --git a/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml b/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml index 3839e1f5f904..a378fe8c7148 100644 --- a/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml +++ b/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml @@ -30,6 +30,7 @@ properties: - qcom,kaanapali-ipcc - qcom,maili-ipcc - qcom,milos-ipcc + - qcom,nord-ipcc - qcom,qcs8300-ipcc - qcom,qdu1000-ipcc - qcom,sa8255p-ipcc diff --git a/Documentation/devicetree/bindings/mailbox/ti,k2g-message-manager.yaml b/Documentation/devicetree/bindings/mailbox/ti,k2g-message-manager.yaml new file mode 100644 index 000000000000..481b7c8c0e96 --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/ti,k2g-message-manager.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mailbox/ti,k2g-message-manager.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments Message Manager + +maintainers: + - Eduard Bostina + +description: + The Texas Instruments Message Manager is a mailbox controller that has + configurable queues selectable at SoC (System on Chip) integration. The + Message manager is broken up into queues in different address regions + that are called "proxies" - each instance is unidirectional and is + instantiated at SoC integration level to indicate receive or transmit path. + +properties: + compatible: + const: ti,k2g-message-manager + + reg: + maxItems: 2 + + reg-names: + items: + - const: queue_proxy_region + - const: queue_state_debug_region + + "#mbox-cells": + const: 2 + description: + Contains the queue ID and proxy ID in that order referring to the + transfer path. + + interrupts: + maxItems: 2 + + interrupt-names: + items: + - const: rx_005 + - const: rx_057 + +required: + - compatible + - reg + - reg-names + - "#mbox-cells" + - interrupts + - interrupt-names + +additionalProperties: false + +examples: + - | + #include + + msgmgr@2a00000 { + compatible = "ti,k2g-message-manager"; + #mbox-cells = <2>; + reg-names = "queue_proxy_region", "queue_state_debug_region"; + reg = <0x02a00000 0x400000>, <0x028c3400 0x400>; + interrupt-names = "rx_005", "rx_057"; + interrupts = , + ; + }; diff --git a/Documentation/devicetree/bindings/mailbox/ti,message-manager.txt b/Documentation/devicetree/bindings/mailbox/ti,message-manager.txt deleted file mode 100644 index ebf0e3710cee..000000000000 --- a/Documentation/devicetree/bindings/mailbox/ti,message-manager.txt +++ /dev/null @@ -1,50 +0,0 @@ -Texas Instruments' Message Manager Driver -======================================== - -The Texas Instruments' Message Manager is a mailbox controller that has -configurable queues selectable at SoC(System on Chip) integration. The Message -manager is broken up into queues in different address regions that are called -"proxies" - each instance is unidirectional and is instantiated at SoC -integration level to indicate receive or transmit path. - -Message Manager Device Node: -=========================== -Required properties: --------------------- -- compatible: Shall be: "ti,k2g-message-manager" -- reg-names queue_proxy_region - Map the queue proxy region. - queue_state_debug_region - Map the queue state debug - region. -- reg: Contains the register map per reg-names. -- #mbox-cells Shall be 2. Contains the queue ID and proxy ID in that - order referring to the transfer path. -- interrupt-names: Contains interrupt names matching the rx transfer path - for a given SoC. Receive interrupts shall be of the - format: "rx_". - For ti,k2g-message-manager, this shall contain: - "rx_005", "rx_057" -- interrupts: Contains the interrupt information corresponding to - interrupt-names property. - -Example(K2G): ------------- - - msgmgr: msgmgr@2a00000 { - compatible = "ti,k2g-message-manager"; - #mbox-cells = <2>; - reg-names = "queue_proxy_region", "queue_state_debug_region"; - reg = <0x02a00000 0x400000>, <0x028c3400 0x400>; - interrupt-names = "rx_005", "rx_057"; - interrupts = , - ; - }; - - pmmc: pmmc { - [...] - mbox-names = "rx", "tx"; - # RX queue ID is 5, proxy ID is 2 - # TX queue ID is 0, proxy ID is 0 - mboxes= <&msgmgr 5 2>, - <&msgmgr 0 0>; - [...] - }; diff --git a/MAINTAINERS b/MAINTAINERS index 8d9648737e92..e33ca1adb6a8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4444,6 +4444,14 @@ F: Documentation/devicetree/bindings/phy/axiado,ax3000-emmc-phy.yaml F: drivers/phy/axiado/Kconfig F: drivers/phy/axiado/phy-axiado-emmc.c +AXIADO MAILBOX DRIVER +M: Krishnakumar Kesavan +M: Prasad Bolisetty +M: Swark Yang +S: Supported +F: Documentation/devicetree/bindings/mailbox/axiado,ax3005-mailbox.yaml +F: drivers/mailbox/axiado-mailbox.c + AXI SPI ENGINE M: Michael Hennerich M: Nuno Sá diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index 3062ee352f78..ab869769e278 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -399,4 +399,14 @@ config RISCV_SBI_MPXY_MBOX or HS-mode hypervisor). Say Y here, unless you are sure you do not need this. +config AXIADO_MAILBOX + tristate "Axiado mailbox driver" + depends on ARCH_AXIADO || COMPILE_TEST + depends on OF + help + Enable support for the Axiado mailbox controller. The driver provides + communication channels between the host CPU and the coprocessor on + Axiado SoCs. + If unsure, say N. + endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index 944d8ea39f34..45dc59bb3e7f 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -84,3 +84,5 @@ obj-$(CONFIG_CIX_MBOX) += cix-mailbox.o obj-$(CONFIG_BCM74110_MAILBOX) += bcm74110-mailbox.o obj-$(CONFIG_RISCV_SBI_MPXY_MBOX) += riscv-sbi-mpxy-mbox.o + +obj-$(CONFIG_AXIADO_MAILBOX) += axiado-mailbox.o diff --git a/drivers/mailbox/arm_mhu_db.c b/drivers/mailbox/arm_mhu_db.c index 9e937b09c5fb..a39239e38a47 100644 --- a/drivers/mailbox/arm_mhu_db.c +++ b/drivers/mailbox/arm_mhu_db.c @@ -318,7 +318,6 @@ static int mhu_db_probe(struct amba_device *adev, const struct amba_id *id) mhu_db_mbox_rx_handler, IRQF_ONESHOT, "mhu_db_link", mhu); if (err) { - dev_err(dev, "Can't claim IRQ %d\n", irq); mbox_controller_unregister(&mhu->mbox); return err; } diff --git a/drivers/mailbox/arm_mhuv2.c b/drivers/mailbox/arm_mhuv2.c index f035284944c0..d4077cd49360 100644 --- a/drivers/mailbox/arm_mhuv2.c +++ b/drivers/mailbox/arm_mhuv2.c @@ -789,14 +789,14 @@ static const struct mbox_chan_ops mhuv2_receiver_ops = { .last_tx_done = mhuv2_receiver_last_tx_done, }; -static struct mbox_chan *mhuv2_mbox_of_xlate(struct mbox_controller *mbox, - const struct of_phandle_args *pa) +static struct mbox_chan *mhuv2_mbox_fw_xlate(struct mbox_controller *mbox, + const struct fwnode_reference_args *pa) { struct mhuv2 *mhu = mhu_from_mbox(mbox); struct mbox_chan *chans = mbox->chans; int channel = 0, i, offset, doorbell, protocol, windows; - if (pa->args_count != 2) + if (pa->nargs != 2) return ERR_PTR(-EINVAL); offset = pa->args[0]; @@ -828,7 +828,7 @@ static struct mbox_chan *mhuv2_mbox_of_xlate(struct mbox_controller *mbox, } out: - dev_err(mbox->dev, "Couldn't xlate to a valid channel (%d: %d)\n", + dev_err(mbox->dev, "Couldn't xlate to a valid channel (%llu: %d)\n", pa->args[0], doorbell); return ERR_PTR(-ENODEV); } @@ -931,11 +931,10 @@ static int mhuv2_allocate_channels(struct mhuv2 *mhu) static int mhuv2_parse_channels(struct mhuv2 *mhu) { struct device *dev = mhu->mbox.dev; - const struct device_node *np = dev->of_node; int ret, count; u32 *protocols; - count = of_property_count_u32_elems(np, MHUV2_PROTOCOL_PROP); + count = device_property_count_u32(dev, MHUV2_PROTOCOL_PROP); if (count <= 0 || count % 2) { dev_err(dev, "Invalid %s property (%d)\n", MHUV2_PROTOCOL_PROP, count); @@ -946,7 +945,7 @@ static int mhuv2_parse_channels(struct mhuv2 *mhu) if (!protocols) return -ENOMEM; - ret = of_property_read_u32_array(np, MHUV2_PROTOCOL_PROP, protocols, count); + ret = device_property_read_u32_array(dev, MHUV2_PROTOCOL_PROP, protocols, count); if (ret) { dev_err(dev, "Failed to read %s property: %d\n", MHUV2_PROTOCOL_PROP, ret); @@ -986,10 +985,7 @@ static int mhuv2_tx_init(struct amba_device *adev, struct mhuv2 *mhu, ret = devm_request_threaded_irq(dev, adev->irq[0], NULL, mhuv2_sender_interrupt, IRQF_ONESHOT, "mhuv2-tx", mhu); - if (ret) { - dev_err(dev, "Failed to request tx IRQ, fallback to polling mode: %d\n", - ret); - } else { + if (!ret) { mhu->mbox.txdone_irq = true; mhu->mbox.txdone_poll = false; mhu->irq = adev->irq[0]; @@ -1039,10 +1035,8 @@ static int mhuv2_rx_init(struct amba_device *adev, struct mhuv2 *mhu, ret = devm_request_threaded_irq(dev, mhu->irq, NULL, mhuv2_receiver_interrupt, IRQF_ONESHOT, "mhuv2-rx", mhu); - if (ret) { - dev_err(dev, "Failed to request rx IRQ\n"); + if (ret) return ret; - } /* Mask all the channel windows */ for (i = 0; i < mhu->windows; i++) @@ -1071,7 +1065,7 @@ static int mhuv2_probe(struct amba_device *adev, const struct amba_id *id) return -ENOMEM; mhu->mbox.dev = dev; - mhu->mbox.of_xlate = mhuv2_mbox_of_xlate; + mhu->mbox.fw_xlate = mhuv2_mbox_fw_xlate; if (of_device_is_compatible(np, "arm,mhuv2-tx")) ret = mhuv2_tx_init(adev, mhu, reg); diff --git a/drivers/mailbox/arm_mhuv3.c b/drivers/mailbox/arm_mhuv3.c index a1c528be47f3..6569c3dda377 100644 --- a/drivers/mailbox/arm_mhuv3.c +++ b/drivers/mailbox/arm_mhuv3.c @@ -982,8 +982,7 @@ static int mhuv3_setup_pbx(struct mhuv3 *mhu) mhuv3_pbx_comb_interrupt, IRQF_ONESHOT, "mhuv3-pbx", mhu); if (ret) - return dev_err_probe(dev, ret, - "Failed to request PBX IRQ\n"); + return ret; mhu->mbox.txdone_irq = true; mhu->mbox.txdone_poll = false; @@ -1020,7 +1019,7 @@ static int mhuv3_setup_mbx(struct mhuv3 *mhu) mhuv3_mbx_comb_interrupt, IRQF_ONESHOT, "mhuv3-mbx", mhu); if (ret) - return dev_err_probe(dev, ret, "Failed to request MBX IRQ\n"); + return ret; for (i = 0; i < NUM_EXT; i++) if (mhu->ext[i]) diff --git a/drivers/mailbox/armada-37xx-rwtm-mailbox.c b/drivers/mailbox/armada-37xx-rwtm-mailbox.c index 456a117a65fd..047926711e96 100644 --- a/drivers/mailbox/armada-37xx-rwtm-mailbox.c +++ b/drivers/mailbox/armada-37xx-rwtm-mailbox.c @@ -108,10 +108,8 @@ static int a37xx_mbox_startup(struct mbox_chan *chan) ret = devm_request_irq(mbox->dev, mbox->irq, a37xx_mbox_irq_handler, 0, DRIVER_NAME, chan); - if (ret < 0) { - dev_err(mbox->dev, "Cannot request irq\n"); + if (ret < 0) return ret; - } /* enable IRQ generation */ reg = readl(mbox->base + RWTM_HOST_INT_MASK); diff --git a/drivers/mailbox/axiado-mailbox.c b/drivers/mailbox/axiado-mailbox.c new file mode 100644 index 000000000000..3a3cf7421863 --- /dev/null +++ b/drivers/mailbox/axiado-mailbox.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2021-2026 Axiado Corporation (or its affiliates). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AXIADO_MBOX_TX_CHANS 8 /* 0-7 */ +#define AXIADO_MBOX_RX_CHANS 8 /* 8-15 */ +#define AXIADO_MBOX_CHAN_STRIDE 0x4 +#define AXIADO_MBOX_TX_REG_STRIDE 0x40 +#define AXIADO_MBOX_RX_REG_STRIDE 0x30 +#define AXIADO_MBOX_RX_POLL_US 10 +#define AXIADO_MBOX_RX_TIMEOUT_US 1000 + +/* Mailbox CSR bit definitions */ +#define AXIADO_MBOX_CSR_EMPTY BIT(0) /* 1 = FIFO empty; 0 = data available */ +#define AXIADO_MBOX_CSR_OVERFLOW BIT(2) /* write 1 to clear (W1C) */ +#define AXIADO_MBOX_CSR_UNDERFLOW BIT(3) /* write 1 to clear (W1C) */ +#define AXIADO_MBOX_CSR_FLUSH BIT(4) /* W1TRG flush; startup/shutdown */ +#define AXIADO_MBOX_CSR_LEVEL GENMASK(11, 5) + +#define AXIADO_MBOX_CSR_ERRORS (AXIADO_MBOX_CSR_OVERFLOW | \ + AXIADO_MBOX_CSR_UNDERFLOW) + +struct axiado_mbox_data { + u8 num_chans; + u16 msg_size; +}; + +struct axiado_channel_data { + void __iomem *mbox_reg; + void __iomem *csr_reg; + void *rx_buffer; + u8 channel_num; + int irq; + struct mbox_chan *chan; + bool active; + atomic_t fifo_errors; +}; + +/* mailbox side */ +struct axiado_mbox { + struct mbox_controller mbox; + const struct axiado_mbox_data *drv_data; + void __iomem *tx_base; + void __iomem *rx_base; + struct dentry *debugfs_dir; +}; + +/* + * Checks OVERFLOW/UNDERFLOW, logs and W1C-clears them if set. Shared by the + * TX and RX paths; RX additionally flushes the FIFO afterwards to resync + * framing, which TX must not do since it could discard data the peer has + * not read yet. + */ +static bool axiado_mbox_clear_fifo_errors(struct axiado_channel_data *priv) +{ + struct device *dev = priv->chan->mbox->dev; + u32 errors; + + errors = readl(priv->csr_reg) & AXIADO_MBOX_CSR_ERRORS; + if (!errors) + return false; + + writel(errors, priv->csr_reg); + atomic_inc(&priv->fifo_errors); + + dev_warn_ratelimited(dev, "Channel %u FIFO error: %#x\n", + priv->channel_num, errors); + + return true; +} + +static int axiado_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct axiado_mbox *mb = dev_get_drvdata(chan->mbox->dev); + struct axiado_channel_data *priv = chan->con_priv; + unsigned int idx = priv->channel_num; + u8 tail[sizeof(u32)] = { 0 }; + const u8 *buf = data; + unsigned int tail_len; + unsigned int offset; + u32 msg_len; + + if (!data) + return -EINVAL; + + /* + * The Axiado mailbox message ABI stores the total message length, + * including this length word, as a little-endian byte count in the + * first word of every message. + */ + msg_len = get_unaligned_le32(data); + if (msg_len < sizeof(u32) || msg_len > mb->drv_data->msg_size) + return -EINVAL; + + /* Only touch the hardware after validating the message. */ + axiado_mbox_clear_fifo_errors(priv); + + if (!(readl(priv->csr_reg) & AXIADO_MBOX_CSR_EMPTY)) { + dev_warn_ratelimited(mb->mbox.dev, "Channel %u is busy\n", idx); + return -EBUSY; + } + + for (offset = 0; offset + sizeof(u32) <= msg_len; + offset += sizeof(u32)) + writel(get_unaligned_le32(buf + offset), priv->mbox_reg); + + tail_len = msg_len - offset; + if (tail_len) { + memcpy(tail, buf + offset, tail_len); + writel(get_unaligned_le32(tail), priv->mbox_reg); + } + + dev_dbg(mb->mbox.dev, "%s: Ch-%u sent\n", __func__, idx); + + return 0; +} + +static irqreturn_t axiado_rx_thread(int irq, void *dev_id) +{ + struct axiado_channel_data *priv = dev_id; + struct mbox_chan *chan = priv->chan; + struct axiado_mbox *mb = dev_get_drvdata(chan->mbox->dev); + u8 *buf = priv->rx_buffer; + unsigned int num_words; + unsigned int remaining; + unsigned int i; + u32 msg_len; + u32 word; + u32 csr; + int ret; + + /* + * Clear and log/count any pending errors, but don't discard data + * on their account alone: a rejected overflow write doesn't + * corrupt what was already safely queued ahead of it, so let the + * length-based read below decide whether what's here is usable. + */ + axiado_mbox_clear_fifo_errors(priv); + + csr = readl(priv->csr_reg); + if (csr & AXIADO_MBOX_CSR_EMPTY) + return IRQ_NONE; + + /* + * The first word contains the total message length in bytes, + * including the length word itself. + */ + word = readl(priv->mbox_reg); + msg_len = word; + put_unaligned_le32(word, buf); + + if (msg_len < sizeof(u32) || msg_len > mb->drv_data->msg_size) + goto invalid_message; + + num_words = DIV_ROUND_UP(msg_len, sizeof(u32)); + remaining = num_words - 1; + + /* + * The not-empty interrupt may occur as soon as the first DW enters + * the FIFO. Wait until all remaining DWs of this message arrive. + */ + if (remaining) { + ret = readl_poll_timeout(priv->csr_reg, csr, + (csr & AXIADO_MBOX_CSR_ERRORS) || + FIELD_GET(AXIADO_MBOX_CSR_LEVEL, csr) >= + remaining, + AXIADO_MBOX_RX_POLL_US, + AXIADO_MBOX_RX_TIMEOUT_US); + if (ret) + goto incomplete_message; + + axiado_mbox_clear_fifo_errors(priv); + } + + for (i = 1; i < num_words; i++) { + word = readl(priv->mbox_reg); + put_unaligned_le32(word, buf + i * sizeof(u32)); + } + + axiado_mbox_clear_fifo_errors(priv); + + if (READ_ONCE(priv->active)) + mbox_chan_received_data(chan, priv->rx_buffer); + + return IRQ_HANDLED; + +incomplete_message: + dev_warn_ratelimited(chan->mbox->dev, + "Channel %u received an incomplete message\n", + priv->channel_num); + +invalid_message: + writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg); + + return IRQ_HANDLED; +} + +static int axiado_mbox_startup(struct mbox_chan *chan) +{ + struct axiado_channel_data *priv = chan->con_priv; + + /* + * Only flush on a genuine error. A blind flush here would discard + * a message the peer legitimately sent before this side started + * up (e.g. during normal boot sequencing), which is not corrupt + * and does not need resyncing. + */ + if (axiado_mbox_clear_fifo_errors(priv)) + writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg); + + WRITE_ONCE(priv->active, true); + + if (priv->channel_num >= AXIADO_MBOX_TX_CHANS) + enable_irq(priv->irq); + + return 0; +} + +static void axiado_mbox_shutdown(struct mbox_chan *chan) +{ + struct axiado_channel_data *priv = chan->con_priv; + + WRITE_ONCE(priv->active, false); + + if (priv->channel_num >= AXIADO_MBOX_TX_CHANS) + disable_irq(priv->irq); + + if (axiado_mbox_clear_fifo_errors(priv)) + writel(AXIADO_MBOX_CSR_FLUSH, priv->csr_reg); +} + +static bool axiado_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct axiado_channel_data *priv = chan->con_priv; + + return !!(readl(priv->csr_reg) & AXIADO_MBOX_CSR_EMPTY); +} + +static const struct mbox_chan_ops axiado_mbox_chan_ops = { + .send_data = axiado_mbox_send_data, + .startup = axiado_mbox_startup, + .shutdown = axiado_mbox_shutdown, + .last_tx_done = axiado_mbox_last_tx_done, +}; + +static void axiado_mbox_debugfs_remove(void *dentry) +{ + debugfs_remove_recursive(dentry); +} + +static int axiado_mbox_probe(struct platform_device *pdev) +{ + const struct axiado_mbox_data *drv_data; + struct axiado_channel_data *ch_data; + struct device *dev = &pdev->dev; + struct axiado_mbox *mb; + unsigned int irq_idx = 0; + unsigned int rx_chan; + unsigned int i; + int ret; + + drv_data = device_get_match_data(dev); + if (!drv_data) + return -ENODEV; + + mb = devm_kzalloc(dev, sizeof(*mb), GFP_KERNEL); + if (!mb) + return -ENOMEM; + + mb->mbox.dev = dev; + mb->mbox.num_chans = drv_data->num_chans; + + mb->mbox.chans = devm_kcalloc(&pdev->dev, + drv_data->num_chans, + sizeof(*mb->mbox.chans), + GFP_KERNEL); + if (!mb->mbox.chans) + return -ENOMEM; + + mb->tx_base = devm_platform_ioremap_resource_byname(pdev, "tx"); + if (IS_ERR(mb->tx_base)) + return PTR_ERR(mb->tx_base); + + mb->rx_base = devm_platform_ioremap_resource_byname(pdev, "rx"); + if (IS_ERR(mb->rx_base)) + return PTR_ERR(mb->rx_base); + + ch_data = devm_kcalloc(&pdev->dev, + drv_data->num_chans, + sizeof(*ch_data), + GFP_KERNEL); + if (!ch_data) + return -ENOMEM; + + mb->debugfs_dir = debugfs_create_dir(dev_name(dev), NULL); + ret = devm_add_action_or_reset(dev, axiado_mbox_debugfs_remove, + mb->debugfs_dir); + if (ret) + return ret; + + for (i = 0; i < drv_data->num_chans; i++) { + char name[16]; + + ch_data[i].channel_num = i; + ch_data[i].chan = &mb->mbox.chans[i]; + + mb->mbox.chans[i].con_priv = &ch_data[i]; + + snprintf(name, sizeof(name), "chan%u-errors", i); + debugfs_create_atomic_t(name, 0444, mb->debugfs_dir, + &ch_data[i].fifo_errors); + + if (i < AXIADO_MBOX_TX_CHANS) { + ch_data[i].mbox_reg = mb->tx_base + (i * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].csr_reg = mb->tx_base + AXIADO_MBOX_TX_REG_STRIDE + + (i * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].irq = -1; + continue; + } + + ch_data[i].rx_buffer = devm_kzalloc(dev, drv_data->msg_size, + GFP_KERNEL); + if (!ch_data[i].rx_buffer) + return -ENOMEM; + + rx_chan = i - AXIADO_MBOX_TX_CHANS; + ch_data[i].mbox_reg = mb->rx_base + + (rx_chan * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].csr_reg = mb->rx_base + AXIADO_MBOX_RX_REG_STRIDE + + (rx_chan * AXIADO_MBOX_CHAN_STRIDE); + ch_data[i].irq = platform_get_irq(pdev, irq_idx++); + if (ch_data[i].irq < 0) + return dev_err_probe(dev, ch_data[i].irq, + "Failed to get IRQ for channel %u\n", i); + + ret = devm_request_threaded_irq(dev, ch_data[i].irq, NULL, + axiado_rx_thread, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + dev_name(dev), &ch_data[i]); + if (ret) + return dev_err_probe(dev, ret, + "Failed to request IRQ for channel %u\n", i); + + dev_dbg(dev, "RX chan %u -> irq %d\n", i, ch_data[i].irq); + } + + platform_set_drvdata(pdev, mb); + mb->drv_data = drv_data; + mb->mbox.ops = &axiado_mbox_chan_ops; + mb->mbox.txdone_irq = false; + mb->mbox.txdone_poll = true; + mb->mbox.txpoll_period = 5; + + return devm_mbox_controller_register(dev, &mb->mbox); +} + +static const struct axiado_mbox_data axiado_drv_data = { + .num_chans = AXIADO_MBOX_TX_CHANS + AXIADO_MBOX_RX_CHANS, + .msg_size = 256, +}; + +static const struct of_device_id axiado_mbox_of_match[] = { + { .compatible = "axiado,ax3005-mailbox", .data = &axiado_drv_data }, + { } +}; +MODULE_DEVICE_TABLE(of, axiado_mbox_of_match); + +static struct platform_driver axiado_mbox_driver = { + .driver = { + .name = "axiado-mailbox", + .of_match_table = axiado_mbox_of_match, + }, + .probe = axiado_mbox_probe, +}; +module_platform_driver(axiado_mbox_driver); + +MODULE_AUTHOR("Axiado Corporation"); +MODULE_DESCRIPTION("Axiado Mailbox driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mailbox/bcm-pdc-mailbox.c b/drivers/mailbox/bcm-pdc-mailbox.c index 406bc41cba60..6fcc4002ad4f 100644 --- a/drivers/mailbox/bcm-pdc-mailbox.c +++ b/drivers/mailbox/bcm-pdc-mailbox.c @@ -1403,11 +1403,8 @@ static int pdc_interrupts_init(struct pdc_state *pdcs) err = devm_request_irq(dev, pdcs->pdc_irq, pdc_irq_handler, 0, dev_name(dev), dev); - if (err) { - dev_err(dev, "IRQ %u request failed with err %d\n", - pdcs->pdc_irq, err); + if (err) return err; - } return PDC_SUCCESS; } diff --git a/drivers/mailbox/bcm2835-mailbox.c b/drivers/mailbox/bcm2835-mailbox.c index ea12fb8d2401..0ca75c378a60 100644 --- a/drivers/mailbox/bcm2835-mailbox.c +++ b/drivers/mailbox/bcm2835-mailbox.c @@ -136,28 +136,30 @@ static struct mbox_chan *bcm2835_mbox_index_xlate(struct mbox_controller *mbox, static int bcm2835_mbox_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + void __iomem *regs; int ret = 0; + int irq; struct bcm2835_mbox *mbox; + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); if (mbox == NULL) return -ENOMEM; + spin_lock_init(&mbox->lock); + mbox->regs = regs; - ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0), - bcm2835_mbox_irq, IRQF_NO_SUSPEND, dev_name(dev), - mbox); - if (ret) { - dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n", - ret); - return -ENODEV; - } - - mbox->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(mbox->regs)) { - ret = PTR_ERR(mbox->regs); + ret = devm_request_irq(dev, irq, bcm2835_mbox_irq, + IRQF_NO_SUSPEND, dev_name(dev), mbox); + if (ret) return ret; - } mbox->controller.txdone_poll = true; mbox->controller.txpoll_period = 5; @@ -170,14 +172,7 @@ static int bcm2835_mbox_probe(struct platform_device *pdev) if (!mbox->controller.chans) return -ENOMEM; - ret = devm_mbox_controller_register(dev, &mbox->controller); - if (ret) - return ret; - - platform_set_drvdata(pdev, mbox); - dev_info(dev, "mailbox enabled\n"); - - return ret; + return devm_mbox_controller_register(dev, &mbox->controller); } static const struct of_device_id bcm2835_mbox_of_match[] = { diff --git a/drivers/mailbox/bcm74110-mailbox.c b/drivers/mailbox/bcm74110-mailbox.c index 344cfc35984b..c8709d509912 100644 --- a/drivers/mailbox/bcm74110-mailbox.c +++ b/drivers/mailbox/bcm74110-mailbox.c @@ -601,7 +601,7 @@ static int bcm74110_mbox_probe(struct platform_device *pdev) ret = devm_request_irq(dev, mbox->rx_irq, bcm74110_mbox_isr, IRQF_NO_SUSPEND, pdev->name, mbox); if (ret) - return dev_err_probe(dev, ret, "Failed to request irq\n"); + return ret; mbox->controller.ops = &bcm74110_mbox_chan_ops; mbox->controller.dev = dev; diff --git a/drivers/mailbox/cix-mailbox.c b/drivers/mailbox/cix-mailbox.c index 43c76cdab24a..7fa979c1b8f0 100644 --- a/drivers/mailbox/cix-mailbox.c +++ b/drivers/mailbox/cix-mailbox.c @@ -587,19 +587,15 @@ static int cix_mbox_probe(struct platform_device *pdev) if (priv->irq < 0) return priv->irq; - if (device_property_read_string(dev, "cix,mbox-dir", &dir_str)) { - dev_err(priv->dev, "cix,mbox_dir property not found\n"); - return -EINVAL; - } + if (device_property_read_string(dev, "cix,mbox-dir", &dir_str)) + return dev_err_probe(dev, -EINVAL, "cix,mbox-dir property not found\n"); if (!strcmp(dir_str, "tx")) priv->dir = 0; else if (!strcmp(dir_str, "rx")) priv->dir = 1; - else { - dev_err(priv->dev, "cix,mbox_dir=%s is not expected\n", dir_str); - return -EINVAL; - } + else + return dev_err_probe(dev, -EINVAL, "cix,mbox-dir=%s is not expected\n", dir_str); cix_mbox_init(priv); @@ -613,9 +609,9 @@ static int cix_mbox_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); ret = devm_mbox_controller_register(dev, &priv->mbox); if (ret) - dev_err(dev, "Failed to register mailbox %d\n", ret); + return dev_err_probe(dev, ret, "Failed to register mailbox\n"); - return ret; + return 0; } static const struct of_device_id cix_mbox_dt_ids[] = { diff --git a/drivers/mailbox/cv1800-mailbox.c b/drivers/mailbox/cv1800-mailbox.c index 4bca9d8be4ba..b062c2c4fc61 100644 --- a/drivers/mailbox/cv1800-mailbox.c +++ b/drivers/mailbox/cv1800-mailbox.c @@ -189,7 +189,7 @@ static int cv1800_mbox_probe(struct platform_device *pdev) cv1800_mbox_isr, IRQF_ONESHOT, dev_name(&pdev->dev), mb); if (err < 0) - return dev_err_probe(dev, err, "Failed to register irq\n"); + return err; for (idx = 0; idx < MAILBOX_MAX_CHAN; idx++) { mb->priv[idx].idx = idx; diff --git a/drivers/mailbox/exynos-mailbox.c b/drivers/mailbox/exynos-mailbox.c index fa02f18948cf..ff75c1c7633c 100644 --- a/drivers/mailbox/exynos-mailbox.c +++ b/drivers/mailbox/exynos-mailbox.c @@ -24,14 +24,60 @@ #define EXYNOS_MBOX_CHAN_COUNT HWEIGHT32(EXYNOS_MBOX_INTGR1_MASK) +#define EXYNOS850_MBOX_INTGR0 0x8 /* Interrupt Generation Register 0 */ +#define EXYNOS850_MBOX_INTMR1 0x24 /* Interrupt Mask Register 1 */ + +#define EXYNOS850_MBOX_INTMR1_MASK GENMASK(15, 0) +#define EXYNOS850_MBOX_INTGR0_MASK GENMASK(31, 16) + +#define EXYNOS850_MBOX_CHAN_COUNT HWEIGHT32(EXYNOS850_MBOX_INTGR0_MASK) + +/** + * struct exynos_mbox_driver_data - platform-specific mailbox configuration. + * @intgr: offset to the IRQ generation register, doorbell + * to APM co-processor. + * @intgr_shift: shift to apply to the value written to IRQ generation + * register. + * @intmr: offset to the IRQ mask register. + * @intmr_mask: value to write to the mask register to mask out all + * interrupts. + * @num_chans: number of channels the mailbox can support (hardware + * capability). + */ +struct exynos_mbox_driver_data { + u32 intgr; + u32 intgr_shift; + u32 intmr; + u32 intmr_mask; + int num_chans; +}; + /** * struct exynos_mbox - driver's private data. * @regs: mailbox registers base address. * @mbox: pointer to the mailbox controller. + * @data: pointer to driver platform-specific data. */ struct exynos_mbox { void __iomem *regs; struct mbox_controller *mbox; + const struct exynos_mbox_driver_data *data; +}; + +static const struct exynos_mbox_driver_data exynos850_mbox_data = { + .intgr = EXYNOS850_MBOX_INTGR0, + .intgr_shift = 16, + .intmr = EXYNOS850_MBOX_INTMR1, + .intmr_mask = EXYNOS850_MBOX_INTMR1_MASK, + .num_chans = EXYNOS850_MBOX_CHAN_COUNT, +}; + +static const struct exynos_mbox_driver_data exynos_gs101_mbox_data = { + .intgr = EXYNOS_MBOX_INTGR1, + .intgr_shift = 0, + .intmr = EXYNOS_MBOX_INTMR0, + .intmr_mask = EXYNOS_MBOX_INTMR0_MASK, + .num_chans = EXYNOS_MBOX_CHAN_COUNT, }; static int exynos_mbox_send_data(struct mbox_chan *chan, void *data) @@ -50,7 +96,9 @@ static int exynos_mbox_send_data(struct mbox_chan *chan, void *data) return -EINVAL; } - writel(BIT(msg->chan_id), exynos_mbox->regs + EXYNOS_MBOX_INTGR1); + /* Ring the doorbell */ + writel(BIT(msg->chan_id) << exynos_mbox->data->intgr_shift, + exynos_mbox->regs + exynos_mbox->data->intgr); return 0; } @@ -80,19 +128,31 @@ static struct mbox_chan *exynos_mbox_of_xlate(struct mbox_controller *mbox, } static const struct of_device_id exynos_mbox_match[] = { - { .compatible = "google,gs101-mbox" }, + { + .compatible = "google,gs101-mbox", + .data = &exynos_gs101_mbox_data + }, + { + .compatible = "samsung,exynos850-mbox", + .data = &exynos850_mbox_data + }, {}, }; MODULE_DEVICE_TABLE(of, exynos_mbox_match); static int exynos_mbox_probe(struct platform_device *pdev) { + const struct exynos_mbox_driver_data *data; struct device *dev = &pdev->dev; struct exynos_mbox *exynos_mbox; struct mbox_controller *mbox; struct mbox_chan *chans; struct clk *pclk; + data = device_get_match_data(&pdev->dev); + if (!data) + return -ENODEV; + exynos_mbox = devm_kzalloc(dev, sizeof(*exynos_mbox), GFP_KERNEL); if (!exynos_mbox) return -ENOMEM; @@ -101,8 +161,7 @@ static int exynos_mbox_probe(struct platform_device *pdev) if (!mbox) return -ENOMEM; - chans = devm_kcalloc(dev, EXYNOS_MBOX_CHAN_COUNT, sizeof(*chans), - GFP_KERNEL); + chans = devm_kcalloc(dev, data->num_chans, sizeof(*chans), GFP_KERNEL); if (!chans) return -ENOMEM; @@ -115,7 +174,8 @@ static int exynos_mbox_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(pclk), "Failed to enable clock.\n"); - mbox->num_chans = EXYNOS_MBOX_CHAN_COUNT; + exynos_mbox->data = data; + mbox->num_chans = data->num_chans; mbox->chans = chans; mbox->dev = dev; mbox->ops = &exynos_mbox_chan_ops; @@ -126,7 +186,7 @@ static int exynos_mbox_probe(struct platform_device *pdev) platform_set_drvdata(pdev, exynos_mbox); /* Mask out all interrupts. We support just polling channels for now. */ - writel(EXYNOS_MBOX_INTMR0_MASK, exynos_mbox->regs + EXYNOS_MBOX_INTMR0); + writel(data->intmr_mask, exynos_mbox->regs + data->intmr); return devm_mbox_controller_register(dev, mbox); } diff --git a/drivers/mailbox/hi6220-mailbox.c b/drivers/mailbox/hi6220-mailbox.c index 69d15b6283e9..8eb8242209be 100644 --- a/drivers/mailbox/hi6220-mailbox.c +++ b/drivers/mailbox/hi6220-mailbox.c @@ -297,11 +297,8 @@ static int hi6220_mbox_probe(struct platform_device *pdev) err = devm_request_irq(dev, mbox->irq, hi6220_mbox_interrupt, 0, dev_name(dev), mbox); - if (err) { - dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n", - err); + if (err) return -ENODEV; - } mbox->controller.dev = dev; mbox->controller.chans = &mbox->chan[0]; diff --git a/drivers/mailbox/mailbox-mchp-ipc-sbi.c b/drivers/mailbox/mailbox-mchp-ipc-sbi.c index b87bf2fb4b9b..f081f8a9bcf8 100644 --- a/drivers/mailbox/mailbox-mchp-ipc-sbi.c +++ b/drivers/mailbox/mailbox-mchp-ipc-sbi.c @@ -378,6 +378,8 @@ static int mchp_ipc_get_cluster_aggr_irq(struct mchp_ipc_sbi_mbox *ipc) for_each_online_cpu(cpuid) { hartid = cpuid_to_hartid_map(cpuid); irq_name = devm_kasprintf(ipc->dev, GFP_KERNEL, "hart-%lu", hartid); + if (!irq_name) + return -ENOMEM; ret = platform_get_irq_byname_optional(pdev, irq_name); if (ret <= 0) continue; diff --git a/drivers/mailbox/mailbox-mpfs.c b/drivers/mailbox/mailbox-mpfs.c index 6c40d865b3f1..704a259bbca8 100644 --- a/drivers/mailbox/mailbox-mpfs.c +++ b/drivers/mailbox/mailbox-mpfs.c @@ -219,8 +219,6 @@ static int mpfs_mbox_startup(struct mbox_chan *chan) return -EINVAL; ret = devm_request_irq(mbox->dev, mbox->irq, mpfs_mbox_inbox_isr, 0, "mpfs-mailbox", chan); - if (ret) - dev_err(mbox->dev, "failed to register mailbox interrupt:%d\n", ret); return ret; } diff --git a/drivers/mailbox/mailbox-sti.c b/drivers/mailbox/mailbox-sti.c index b6c9ecbbc8ec..4fb5d1b5504a 100644 --- a/drivers/mailbox/mailbox-sti.c +++ b/drivers/mailbox/mailbox-sti.c @@ -468,10 +468,8 @@ static int sti_mbox_probe(struct platform_device *pdev) sti_mbox_irq_handler, sti_mbox_thread_handler, IRQF_ONESHOT, mdev->name, mdev); - if (ret) { - dev_err(&pdev->dev, "Can't claim IRQ %d\n", irq); + if (ret) return -EINVAL; - } dev_info(&pdev->dev, "%s: Registered Tx/Rx Mailbox\n", mdev->name); diff --git a/drivers/mailbox/pcc.c b/drivers/mailbox/pcc.c index 636879ae1db7..9888dab64639 100644 --- a/drivers/mailbox/pcc.c +++ b/drivers/mailbox/pcc.c @@ -91,12 +91,11 @@ struct pcc_chan_reg { * @plat_irq: platform interrupt * @type: PCC subspace type * @plat_irq_flags: platform interrupt flags - * @chan_in_use: this flag is used just to check if the interrupt needs - * handling when it is shared. Since only one transfer can occur - * at a time and mailbox takes care of locking, this flag can be - * accessed without a lock. Note: the type only support the - * communication from OSPM to Platform, like type3, use it, and - * other types completely ignore it. + * @chan_in_use: lockless flag used by type 3 initiator subspaces to filter + * platform interrupts. Only one transfer can occur at a time, but + * the interrupt handler may sample the flag on another CPU, so all + * accesses must use READ_ONCE() or WRITE_ONCE(). Other subspace + * types do not test it. */ struct pcc_chan_info { struct pcc_mbox_chan chan; @@ -320,8 +319,13 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) if (pcc_chan_reg_read_modify_write(&pchan->plat_irq_ack)) return IRQ_NONE; + /* + * Initiator subspaces use this flag to filter shared interrupts. Use + * READ_ONCE() to sample the lockless flag written by pcc_send_data() + * on another CPU. + */ if (pchan->type == ACPI_PCCT_TYPE_EXT_PCC_MASTER_SUBSPACE && - !pchan->chan_in_use) + !READ_ONCE(pchan->chan_in_use)) return IRQ_NONE; if (!pcc_mbox_cmd_complete_check(pchan)) @@ -331,12 +335,12 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) return IRQ_NONE; /* - * Clear this flag after updating interrupt ack register and just - * before mbox_chan_received_data() which might call pcc_send_data() - * where the flag is set again to start new transfer. This is - * required to avoid any possible race in updatation of this flag. + * Clear this flag after updating the interrupt ack register and before + * notifying the client and mailbox core. mbox_chan_txdone() may submit + * the next queued transfer and set the flag again. Use WRITE_ONCE() for + * the lockless update observed by the send and interrupt paths. */ - pchan->chan_in_use = false; + WRITE_ONCE(pchan->chan_in_use, false); mbox_chan_received_data(chan, NULL); mbox_chan_txdone(chan, 0); @@ -345,6 +349,26 @@ static irqreturn_t pcc_mbox_irq(int irq, void *p) return IRQ_HANDLED; } +static int pcc_mbox_validate_signature(struct pcc_mbox_chan *pcc_mchan, + int subspace_id) +{ + u32 expected_signature = PCC_SIGNATURE | subspace_id; + u32 signature; + + if (pcc_mchan->shmem_size < sizeof(signature)) { + pr_err("PCC subspace %d shared memory is too small\n", + subspace_id); + return -EINVAL; + } + + signature = ioread32(pcc_mchan->shmem); + if (signature != expected_signature) + pr_warn("PCC subspace %d invalid signature %#x expected %#x\n", + subspace_id, signature, expected_signature); + + return 0; +} + /** * pcc_mbox_request_channel - PCC clients call this function to * request a pointer to their PCC subspace, from which they @@ -381,14 +405,20 @@ pcc_mbox_request_channel(struct mbox_client *cl, int subspace_id) if (!pcc_mchan->shmem) return ERR_PTR(-ENXIO); + rc = pcc_mbox_validate_signature(pcc_mchan, subspace_id); + if (rc) + goto err_unmap_shmem; + rc = mbox_bind_client(chan, cl); - if (rc) { - iounmap(pcc_mchan->shmem); - pcc_mchan->shmem = NULL; - return ERR_PTR(rc); - } + if (rc) + goto err_unmap_shmem; return pcc_mchan; + +err_unmap_shmem: + iounmap(pcc_mchan->shmem); + pcc_mchan->shmem = NULL; + return ERR_PTR(rc); } EXPORT_SYMBOL_GPL(pcc_mbox_request_channel); @@ -438,9 +468,18 @@ static int pcc_send_data(struct mbox_chan *chan, void *data) if (ret) return ret; + /* + * Set chan_in_use before ringing the doorbell so a fast completion + * interrupt is not mistaken for a shared interrupt from another + * subspace. Use WRITE_ONCE() for the lockless flag update. The + * ordered I/O accessor used to ring the doorbell orders this store + * before the platform is notified. + */ + if (pchan->plat_irq > 0) + WRITE_ONCE(pchan->chan_in_use, true); ret = pcc_chan_reg_read_modify_write(&pchan->db); - if (!ret && pchan->plat_irq > 0) - pchan->chan_in_use = true; + if (ret && pchan->plat_irq > 0) + WRITE_ONCE(pchan->chan_in_use, false); return ret; } @@ -449,7 +488,15 @@ static bool pcc_last_tx_done(struct mbox_chan *chan) { struct pcc_chan_info *pchan = chan->con_priv; - return pcc_mbox_cmd_complete_check(pchan); + if (!(chan->txdone_method & MBOX_TXDONE_BY_POLL)) + return false; + + if (!pcc_mbox_cmd_complete_check(pchan)) + return false; + + mbox_chan_received_data(chan, NULL); + + return true; } /** diff --git a/drivers/mailbox/qcom-cpucp-mbox.c b/drivers/mailbox/qcom-cpucp-mbox.c index 862e45e8fbd5..515d2d12c01b 100644 --- a/drivers/mailbox/qcom-cpucp-mbox.c +++ b/drivers/mailbox/qcom-cpucp-mbox.c @@ -63,14 +63,25 @@ static irqreturn_t qcom_cpucp_mbox_irq_fn(int irq, void *data) for_each_set_bit(i, (unsigned long *)&status, cpucp->mbox.num_chans) { u32 val = readl(cpucp->rx_base + APSS_CPUCP_RX_MBOX_CMD(i) + APSS_CPUCP_MBOX_CMD_OFF); struct mbox_chan *chan = &cpucp->chans[i]; + struct mbox_client *cl; unsigned long flags; - /* Provide mutual exclusion with changes to chan->cl */ + /* + * Provide mutual exclusion with changes to chan->cl. + * Save cl locally and clear the HW interrupt inside the lock, + * then invoke mbox_chan_received_data() outside the lock to + * avoid a PREEMPT_RT self-deadlock: mbox_chan_received_data() + * can call back into mbox_send_message() via scmi_rx_callback() + * -> mailbox_clear_channel(), which re-acquires chan->lock + * (converted to an rt_spinlock under PREEMPT_RT). + */ spin_lock_irqsave(&chan->lock, flags); - if (chan->cl) - mbox_chan_received_data(chan, &val); + cl = chan->cl; writeq(BIT(i), cpucp->rx_base + APSS_CPUCP_RX_MBOX_CLEAR); spin_unlock_irqrestore(&chan->lock, flags); + + if (cl) + mbox_chan_received_data(chan, &val); } return IRQ_HANDLED; @@ -106,6 +117,14 @@ static int qcom_cpucp_mbox_send_data(struct mbox_chan *chan, void *data) unsigned long chan_id = channel_number(chan); u32 *val = data; + /* + * mailbox_clear_channel() calls mbox_send_message() with NULL data to + * signal the remote side that the channel has been cleared. Nothing + * needs to be written to the TX register in that case, so just return. + */ + if (!val) + return 0; + writel(*val, cpucp->tx_base + APSS_CPUCP_TX_MBOX_CMD(chan_id) + APSS_CPUCP_MBOX_CMD_OFF); return 0; @@ -156,7 +175,7 @@ static int qcom_cpucp_mbox_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, qcom_cpucp_mbox_irq_fn, IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND, "apss_cpucp_mbox", cpucp); if (ret < 0) - return dev_err_probe(dev, ret, "Failed to register irq: %d\n", irq); + return ret; writeq(APSS_CPUCP_RX_MBOX_CMD_MASK, cpucp->rx_base + APSS_CPUCP_RX_MBOX_MAP); diff --git a/drivers/mailbox/qcom-ipcc.c b/drivers/mailbox/qcom-ipcc.c index d957d989c0ce..185b63f724d4 100644 --- a/drivers/mailbox/qcom-ipcc.c +++ b/drivers/mailbox/qcom-ipcc.c @@ -167,7 +167,7 @@ static struct mbox_chan *qcom_ipcc_mbox_xlate(struct mbox_controller *mbox, { struct qcom_ipcc *ipcc = to_qcom_ipcc(mbox); struct qcom_ipcc_chan_info *mchan; - struct mbox_chan *chan; + struct mbox_chan *chan, *free_chan = NULL; struct device *dev; int chan_id; @@ -180,16 +180,21 @@ static struct mbox_chan *qcom_ipcc_mbox_xlate(struct mbox_controller *mbox, chan = &ipcc->chans[chan_id]; mchan = chan->con_priv; - if (!mchan) - break; - else if (mchan->client_id == ph->args[0] && - mchan->signal_id == ph->args[1]) + if (!mchan) { + /* Keep scanning past holes to reject duplicate channel requests. */ + if (!free_chan) + free_chan = chan; + } else if (mchan->client_id == ph->args[0] && + mchan->signal_id == ph->args[1]) { return ERR_PTR(-EBUSY); + } } - if (chan_id >= mbox->num_chans) + if (!free_chan) return ERR_PTR(-EBUSY); + chan = free_chan; + mchan = devm_kzalloc(dev, sizeof(*mchan), GFP_KERNEL); if (!mchan) return ERR_PTR(-ENOMEM); @@ -323,10 +328,8 @@ static int qcom_ipcc_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, ipcc->irq, qcom_ipcc_irq_fn, IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND | IRQF_NO_THREAD, name, ipcc); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret); + if (ret < 0) goto err_req_irq; - } platform_set_drvdata(pdev, ipcc); diff --git a/drivers/mailbox/riscv-sbi-mpxy-mbox.c b/drivers/mailbox/riscv-sbi-mpxy-mbox.c index 7c9c006b7244..714f7fb97a2f 100644 --- a/drivers/mailbox/riscv-sbi-mpxy-mbox.c +++ b/drivers/mailbox/riscv-sbi-mpxy-mbox.c @@ -314,8 +314,13 @@ static int mpxy_get_notifications(u32 channel_id, channel_id, 0, 0, 0, 0, 0); if (sret.error) goto err_put_cpu; + if (sret.value < 0 || mpxy_shmem_size < sizeof(*notif_data) || + sret.value > mpxy_shmem_size - sizeof(*notif_data)) { + put_cpu(); + return -EOVERFLOW; + } - memcpy(notif_data, mpxy->shmem, sret.value + 16); + memcpy(notif_data, mpxy->shmem, sret.value + sizeof(*notif_data)); *events_data_len = sret.value; err_put_cpu: @@ -480,11 +485,14 @@ static void mpxy_mbox_peek_rpmi_data(struct mbox_chan *chan, struct rpmi_mbox_message msg; unsigned long pos = 0; - while (pos < events_data_len && (events_data_len - pos) <= sizeof(*event)) { + while (events_data_len - pos >= sizeof(*event)) { event = (struct rpmi_notification_event *)(notif->events_data + pos); msg.type = RPMI_MBOX_MSG_TYPE_NOTIFICATION_EVENT; msg.notif.event_datalen = le16_to_cpu(event->event_datalen); + if (msg.notif.event_datalen > + events_data_len - pos - sizeof(*event)) + break; msg.notif.event_id = event->event_id; msg.notif.event_data = event->event_data; msg.error = 0; diff --git a/drivers/mailbox/rockchip-mailbox.c b/drivers/mailbox/rockchip-mailbox.c index a1a7dee64356..bc2f9dd798dd 100644 --- a/drivers/mailbox/rockchip-mailbox.c +++ b/drivers/mailbox/rockchip-mailbox.c @@ -40,7 +40,6 @@ struct rockchip_mbox_chan { struct rockchip_mbox { struct mbox_controller mbox; - struct clk *pclk; void __iomem *mbox_base; /* The maximum size of buf for each channel */ @@ -166,6 +165,7 @@ static int rockchip_mbox_probe(struct platform_device *pdev) struct rockchip_mbox *mb; const struct rockchip_mbox_data *drv_data; struct resource *res; + struct clk *pclk; int ret, irq, i; if (!pdev->dev.of_node) @@ -196,19 +196,10 @@ static int rockchip_mbox_probe(struct platform_device *pdev) /* Each channel has two buffers for A2B and B2A */ mb->buf_size = (size_t)resource_size(res) / (drv_data->num_chans * 2); - mb->pclk = devm_clk_get(&pdev->dev, "pclk_mailbox"); - if (IS_ERR(mb->pclk)) { - ret = PTR_ERR(mb->pclk); - dev_err(&pdev->dev, "failed to get pclk_mailbox clock: %d\n", - ret); - return ret; - } - - ret = clk_prepare_enable(mb->pclk); - if (ret) { - dev_err(&pdev->dev, "failed to enable pclk: %d\n", ret); - return ret; - } + pclk = devm_clk_get_enabled(&pdev->dev, "pclk_mailbox"); + if (IS_ERR(pclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pclk), + "failed to get and enable pclk_mailbox clock\n"); for (i = 0; i < mb->mbox.num_chans; i++) { irq = platform_get_irq(pdev, i); diff --git a/drivers/mailbox/sprd-mailbox.c b/drivers/mailbox/sprd-mailbox.c index 565502904e1f..9013d2ece55f 100644 --- a/drivers/mailbox/sprd-mailbox.c +++ b/drivers/mailbox/sprd-mailbox.c @@ -377,10 +377,8 @@ static int sprd_mbox_probe(struct platform_device *pdev) ret = devm_request_irq(dev, inbox_irq, sprd_mbox_inbox_isr, IRQF_NO_SUSPEND, dev_name(dev), priv); - if (ret) { - dev_err(dev, "failed to request inbox IRQ: %d\n", ret); + if (ret) return ret; - } outbox_irq = platform_get_irq_byname(pdev, "outbox"); if (outbox_irq < 0) @@ -388,20 +386,16 @@ static int sprd_mbox_probe(struct platform_device *pdev) ret = devm_request_irq(dev, outbox_irq, sprd_mbox_outbox_isr, IRQF_NO_SUSPEND, dev_name(dev), priv); - if (ret) { - dev_err(dev, "failed to request outbox IRQ: %d\n", ret); + if (ret) return ret; - } /* Supplementary outbox IRQ is optional */ supp_irq = platform_get_irq_byname(pdev, "supp-outbox"); if (supp_irq > 0) { ret = devm_request_irq(dev, supp_irq, sprd_mbox_supp_isr, IRQF_NO_SUSPEND, dev_name(dev), priv); - if (ret) { - dev_err(dev, "failed to request outbox IRQ: %d\n", ret); + if (ret) return ret; - } if (!priv->info->supp_id) { dev_err(dev, "no supplementary outbox specified\n"); diff --git a/drivers/mailbox/stm32-ipcc.c b/drivers/mailbox/stm32-ipcc.c index 4f63f1a14ca6..c6134de1a93b 100644 --- a/drivers/mailbox/stm32-ipcc.c +++ b/drivers/mailbox/stm32-ipcc.c @@ -263,10 +263,8 @@ static int stm32_ipcc_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, ipcc->irqs[i], NULL, irq_thread[i], IRQF_ONESHOT, dev_name(dev), ipcc); - if (ret) { - dev_err(dev, "failed to request irq %lu (%d)\n", i, ret); + if (ret) goto err_clk; - } } /* mask and enable rx/tx irq */ diff --git a/drivers/mailbox/sun6i-msgbox.c b/drivers/mailbox/sun6i-msgbox.c index 6ba6920f4645..1cbc4d478beb 100644 --- a/drivers/mailbox/sun6i-msgbox.c +++ b/drivers/mailbox/sun6i-msgbox.c @@ -257,10 +257,8 @@ static int sun6i_msgbox_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0), sun6i_msgbox_irq, 0, dev_name(dev), mbox); - if (ret) { - dev_err(dev, "Failed to register IRQ handler: %d\n", ret); + if (ret) goto err_disable_unprepare; - } mbox->controller.dev = dev; mbox->controller.ops = &sun6i_msgbox_chan_ops; diff --git a/drivers/mailbox/tegra-hsp.c b/drivers/mailbox/tegra-hsp.c index 500fa77c7d53..03ee73aad488 100644 --- a/drivers/mailbox/tegra-hsp.c +++ b/drivers/mailbox/tegra-hsp.c @@ -708,11 +708,8 @@ static int tegra_hsp_request_shared_irq(struct tegra_hsp *hsp) err = devm_request_irq(hsp->dev, irq, tegra_hsp_shared_irq, 0, dev_name(hsp->dev), hsp); - if (err < 0) { - dev_err(hsp->dev, "failed to request interrupt: %d\n", - err); + if (err < 0) continue; - } hsp->shared_irq = i; @@ -856,12 +853,8 @@ static int tegra_hsp_probe(struct platform_device *pdev) err = devm_request_irq(&pdev->dev, hsp->doorbell_irq, tegra_hsp_doorbell_irq, IRQF_NO_SUSPEND, dev_name(&pdev->dev), hsp); - if (err < 0) { - dev_err(&pdev->dev, - "failed to request doorbell IRQ#%u: %d\n", - hsp->doorbell_irq, err); + if (err < 0) return err; - } } if (hsp->shared_irqs) {