From ed5298c7d500abaf34ed7783969e953a1f028e5b Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Mon, 28 Sep 2020 09:39:50 +0530 Subject: bus: mhi: Remove auto-start option There is really no point having an auto-start for channels. This is confusing for the device drivers, some have to enable the channels, others don't have... and waste resources (e.g. pre allocated buffers) that may never be used. This is really up to the MHI device(channel) driver to manage the state of its channels. While at it, let's also remove the auto-start option from ath11k mhi controller. Signed-off-by: Loic Poulain Acked-by: Kalle Valo Reviewed-by: Manivannan Sadhasivam [mani: clubbed ath11k change] Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/init.c | 9 --------- drivers/bus/mhi/core/internal.h | 1 - 2 files changed, 10 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/init.c b/drivers/bus/mhi/core/init.c index 0ffdebde8265..381fdea2eb9f 100644 --- a/drivers/bus/mhi/core/init.c +++ b/drivers/bus/mhi/core/init.c @@ -758,7 +758,6 @@ static int parse_ch_cfg(struct mhi_controller *mhi_cntrl, mhi_chan->offload_ch = ch_cfg->offload_channel; mhi_chan->db_cfg.reset_req = ch_cfg->doorbell_mode_switch; mhi_chan->pre_alloc = ch_cfg->auto_queue; - mhi_chan->auto_start = ch_cfg->auto_start; /* * If MHI host allocates buffers, then the channel direction @@ -1160,11 +1159,6 @@ static int mhi_driver_probe(struct device *dev) goto exit_probe; ul_chan->xfer_cb = mhi_drv->ul_xfer_cb; - if (ul_chan->auto_start) { - ret = mhi_prepare_channel(mhi_cntrl, ul_chan); - if (ret) - goto exit_probe; - } } ret = -EINVAL; @@ -1198,9 +1192,6 @@ static int mhi_driver_probe(struct device *dev) if (ret) goto exit_probe; - if (dl_chan && dl_chan->auto_start) - mhi_prepare_channel(mhi_cntrl, dl_chan); - mhi_device_put(mhi_dev); return ret; diff --git a/drivers/bus/mhi/core/internal.h b/drivers/bus/mhi/core/internal.h index 7989269ddd96..33c23203c531 100644 --- a/drivers/bus/mhi/core/internal.h +++ b/drivers/bus/mhi/core/internal.h @@ -563,7 +563,6 @@ struct mhi_chan { bool configured; bool offload_ch; bool pre_alloc; - bool auto_start; bool wake_capable; }; -- cgit v1.2.3 From 9b627c25e70816a5e1dca940444b5029065b4d60 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Thu, 15 Oct 2020 11:47:51 -0700 Subject: bus: mhi: core: Remove double locking from mhi_driver_remove() There is double acquisition of the pm_lock from mhi_driver_remove() function. Remove the read_lock_bh/read_unlock_bh calls for pm_lock taken during a call to mhi_device_put() as the lock is acquired within the function already. This will help avoid a potential kernel panic. Fixes: 189ff97cca53 ("bus: mhi: core: Add support for data transfer") Reported-by: Shuah Khan Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/init.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/init.c b/drivers/bus/mhi/core/init.c index 0ffdebde8265..0a09f8215057 100644 --- a/drivers/bus/mhi/core/init.c +++ b/drivers/bus/mhi/core/init.c @@ -1276,10 +1276,8 @@ static int mhi_driver_remove(struct device *dev) mutex_unlock(&mhi_chan->mutex); } - read_lock_bh(&mhi_cntrl->pm_lock); while (mhi_dev->dev_wake) mhi_device_put(mhi_dev); - read_unlock_bh(&mhi_cntrl->pm_lock); return 0; } -- cgit v1.2.3 From 8ff3f7bdde45b32f9294fc87e4bd76f369178664 Mon Sep 17 00:00:00 2001 From: Jeffrey Hugo Date: Tue, 20 Oct 2020 14:29:45 -0600 Subject: bus: mhi: core: fix potential operator-precedence with BHI macros The BHI_MSMHWID and BHI_OEMPKHASH macros take a value 'n' which is a BHI register index. If 'n' is an expression rather than a simple value, there can be an operator precedence issue which can result in the incorrect calculation of the register offset. Adding parentheses around the macro parameter can prevent such issues. Signed-off-by: Jeffrey Hugo Reviewed-by: Manivannan Sadhasivam Reviewed-by: Hemant Kumar Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/internal.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/internal.h b/drivers/bus/mhi/core/internal.h index 7989269ddd96..78e4e84d6743 100644 --- a/drivers/bus/mhi/core/internal.h +++ b/drivers/bus/mhi/core/internal.h @@ -153,8 +153,8 @@ extern struct bus_type mhi_bus_type; #define BHI_SERIALNU (0x40) #define BHI_SBLANTIROLLVER (0x44) #define BHI_NUMSEG (0x48) -#define BHI_MSMHWID(n) (0x4C + (0x4 * n)) -#define BHI_OEMPKHASH(n) (0x64 + (0x4 * n)) +#define BHI_MSMHWID(n) (0x4C + (0x4 * (n))) +#define BHI_OEMPKHASH(n) (0x64 + (0x4 * (n))) #define BHI_RSVD5 (0xC4) #define BHI_STATUS_MASK (0xC0000000) #define BHI_STATUS_SHIFT (30) -- cgit v1.2.3 From 855a70c12021bdc5df60512f1d3f6d492dc715be Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Wed, 21 Oct 2020 19:18:19 +0200 Subject: bus: mhi: Add MHI PCI support for WWAN modems This is a generic MHI-over-PCI controller driver for MHI only devices such as QCOM modems. For now it supports registering of Qualcomm SDX55 based PCIe modules. The MHI channels have been extracted from mhi downstream driver. This driver is for MHI-only devices which have all functionalities exposed through MHI channels and accessed by the corresponding MHI device drivers (no out-of-band communication). Signed-off-by: Loic Poulain Reviewed-by: Bhaumik Bhatt Reviewed-by: Hemant Kumar Reviewed-by: Manivannan Sadhasivam [mani: fixed up the Makefile rule] Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/Kconfig | 9 ++ drivers/bus/mhi/Makefile | 4 + drivers/bus/mhi/pci_generic.c | 345 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 358 insertions(+) create mode 100644 drivers/bus/mhi/pci_generic.c (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/Kconfig b/drivers/bus/mhi/Kconfig index e841c1097fb4..da5cd0c9fc62 100644 --- a/drivers/bus/mhi/Kconfig +++ b/drivers/bus/mhi/Kconfig @@ -20,3 +20,12 @@ config MHI_BUS_DEBUG Enable debugfs support for use with the MHI transport. Allows reading and/or modifying some values within the MHI controller for debug and test purposes. + +config MHI_BUS_PCI_GENERIC + tristate "MHI PCI controller driver" + depends on MHI_BUS + depends on PCI + help + This driver provides MHI PCI controller driver for devices such as + Qualcomm SDX55 based PCIe modems. + diff --git a/drivers/bus/mhi/Makefile b/drivers/bus/mhi/Makefile index 19e6443b72df..0a2d778d6fb4 100644 --- a/drivers/bus/mhi/Makefile +++ b/drivers/bus/mhi/Makefile @@ -1,2 +1,6 @@ # core layer obj-y += core/ + +obj-$(CONFIG_MHI_BUS_PCI_GENERIC) += mhi_pci_generic.o +mhi_pci_generic-y += pci_generic.o + diff --git a/drivers/bus/mhi/pci_generic.c b/drivers/bus/mhi/pci_generic.c new file mode 100644 index 000000000000..e3df838c3c80 --- /dev/null +++ b/drivers/bus/mhi/pci_generic.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MHI PCI driver - MHI over PCI controller driver + * + * This module is a generic driver for registering MHI-over-PCI devices, + * such as PCIe QCOM modems. + * + * Copyright (C) 2020 Linaro Ltd + */ + +#include +#include +#include +#include + +#define MHI_PCI_DEFAULT_BAR_NUM 0 + +/** + * struct mhi_pci_dev_info - MHI PCI device specific information + * @config: MHI controller configuration + * @name: name of the PCI module + * @fw: firmware path (if any) + * @edl: emergency download mode firmware path (if any) + * @bar_num: PCI base address register to use for MHI MMIO register space + * @dma_data_width: DMA transfer word size (32 or 64 bits) + */ +struct mhi_pci_dev_info { + const struct mhi_controller_config *config; + const char *name; + const char *fw; + const char *edl; + unsigned int bar_num; + unsigned int dma_data_width; +}; + +#define MHI_CHANNEL_CONFIG_UL(ch_num, ch_name, el_count, ev_ring) \ + { \ + .num = ch_num, \ + .name = ch_name, \ + .num_elements = el_count, \ + .event_ring = ev_ring, \ + .dir = DMA_TO_DEVICE, \ + .ee_mask = BIT(MHI_EE_AMSS), \ + .pollcfg = 0, \ + .doorbell = MHI_DB_BRST_DISABLE, \ + .lpm_notify = false, \ + .offload_channel = false, \ + .doorbell_mode_switch = false, \ + } \ + +#define MHI_CHANNEL_CONFIG_DL(ch_num, ch_name, el_count, ev_ring) \ + { \ + .num = ch_num, \ + .name = ch_name, \ + .num_elements = el_count, \ + .event_ring = ev_ring, \ + .dir = DMA_FROM_DEVICE, \ + .ee_mask = BIT(MHI_EE_AMSS), \ + .pollcfg = 0, \ + .doorbell = MHI_DB_BRST_DISABLE, \ + .lpm_notify = false, \ + .offload_channel = false, \ + .doorbell_mode_switch = false, \ + } + +#define MHI_EVENT_CONFIG_CTRL(ev_ring) \ + { \ + .num_elements = 64, \ + .irq_moderation_ms = 0, \ + .irq = (ev_ring) + 1, \ + .priority = 1, \ + .mode = MHI_DB_BRST_DISABLE, \ + .data_type = MHI_ER_CTRL, \ + .hardware_event = false, \ + .client_managed = false, \ + .offload_channel = false, \ + } + +#define MHI_EVENT_CONFIG_DATA(ev_ring) \ + { \ + .num_elements = 128, \ + .irq_moderation_ms = 5, \ + .irq = (ev_ring) + 1, \ + .priority = 1, \ + .mode = MHI_DB_BRST_DISABLE, \ + .data_type = MHI_ER_DATA, \ + .hardware_event = false, \ + .client_managed = false, \ + .offload_channel = false, \ + } + +#define MHI_EVENT_CONFIG_HW_DATA(ev_ring, ch_num) \ + { \ + .num_elements = 128, \ + .irq_moderation_ms = 5, \ + .irq = (ev_ring) + 1, \ + .priority = 1, \ + .mode = MHI_DB_BRST_DISABLE, \ + .data_type = MHI_ER_DATA, \ + .hardware_event = true, \ + .client_managed = false, \ + .offload_channel = false, \ + .channel = ch_num, \ + } + +static const struct mhi_channel_config modem_qcom_v1_mhi_channels[] = { + MHI_CHANNEL_CONFIG_UL(12, "MBIM", 4, 0), + MHI_CHANNEL_CONFIG_DL(13, "MBIM", 4, 0), + MHI_CHANNEL_CONFIG_UL(14, "QMI", 4, 0), + MHI_CHANNEL_CONFIG_DL(15, "QMI", 4, 0), + MHI_CHANNEL_CONFIG_UL(20, "IPCR", 8, 0), + MHI_CHANNEL_CONFIG_DL(21, "IPCR", 8, 0), + MHI_CHANNEL_CONFIG_UL(100, "IP_HW0", 128, 1), + MHI_CHANNEL_CONFIG_DL(101, "IP_HW0", 128, 2), +}; + +static const struct mhi_event_config modem_qcom_v1_mhi_events[] = { + /* first ring is control+data ring */ + MHI_EVENT_CONFIG_CTRL(0), + /* Hardware channels request dedicated hardware event rings */ + MHI_EVENT_CONFIG_HW_DATA(1, 100), + MHI_EVENT_CONFIG_HW_DATA(2, 101) +}; + +static const struct mhi_controller_config modem_qcom_v1_mhiv_config = { + .max_channels = 128, + .timeout_ms = 5000, + .num_channels = ARRAY_SIZE(modem_qcom_v1_mhi_channels), + .ch_cfg = modem_qcom_v1_mhi_channels, + .num_events = ARRAY_SIZE(modem_qcom_v1_mhi_events), + .event_cfg = modem_qcom_v1_mhi_events, +}; + +static const struct mhi_pci_dev_info mhi_qcom_sdx55_info = { + .name = "qcom-sdx55m", + .fw = "qcom/sdx55m/sbl1.mbn", + .edl = "qcom/sdx55m/edl.mbn", + .config = &modem_qcom_v1_mhiv_config, + .bar_num = MHI_PCI_DEFAULT_BAR_NUM, + .dma_data_width = 32 +}; + +static const struct pci_device_id mhi_pci_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x0306), + .driver_data = (kernel_ulong_t) &mhi_qcom_sdx55_info }, + { } +}; +MODULE_DEVICE_TABLE(pci, mhi_pci_id_table); + +static int mhi_pci_read_reg(struct mhi_controller *mhi_cntrl, + void __iomem *addr, u32 *out) +{ + *out = readl(addr); + return 0; +} + +static void mhi_pci_write_reg(struct mhi_controller *mhi_cntrl, + void __iomem *addr, u32 val) +{ + writel(val, addr); +} + +static void mhi_pci_status_cb(struct mhi_controller *mhi_cntrl, + enum mhi_callback cb) +{ + /* Nothing to do for now */ +} + +static int mhi_pci_claim(struct mhi_controller *mhi_cntrl, + unsigned int bar_num, u64 dma_mask) +{ + struct pci_dev *pdev = to_pci_dev(mhi_cntrl->cntrl_dev); + int err; + + err = pci_assign_resource(pdev, bar_num); + if (err) + return err; + + err = pcim_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "failed to enable pci device: %d\n", err); + return err; + } + + err = pcim_iomap_regions(pdev, 1 << bar_num, pci_name(pdev)); + if (err) { + dev_err(&pdev->dev, "failed to map pci region: %d\n", err); + return err; + } + mhi_cntrl->regs = pcim_iomap_table(pdev)[bar_num]; + + err = pci_set_dma_mask(pdev, dma_mask); + if (err) { + dev_err(&pdev->dev, "Cannot set proper DMA mask\n"); + return err; + } + + err = pci_set_consistent_dma_mask(pdev, dma_mask); + if (err) { + dev_err(&pdev->dev, "set consistent dma mask failed\n"); + return err; + } + + pci_set_master(pdev); + + return 0; +} + +static int mhi_pci_get_irqs(struct mhi_controller *mhi_cntrl, + const struct mhi_controller_config *mhi_cntrl_config) +{ + struct pci_dev *pdev = to_pci_dev(mhi_cntrl->cntrl_dev); + int nr_vectors, i; + int *irq; + + /* + * Alloc one MSI vector for BHI + one vector per event ring, ideally... + * No explicit pci_free_irq_vectors required, done by pcim_release. + */ + mhi_cntrl->nr_irqs = 1 + mhi_cntrl_config->num_events; + + nr_vectors = pci_alloc_irq_vectors(pdev, 1, mhi_cntrl->nr_irqs, PCI_IRQ_MSI); + if (nr_vectors < 0) { + dev_err(&pdev->dev, "Error allocating MSI vectors %d\n", + nr_vectors); + return nr_vectors; + } + + if (nr_vectors < mhi_cntrl->nr_irqs) { + dev_warn(&pdev->dev, "Not enough MSI vectors (%d/%d), use shared MSI\n", + nr_vectors, mhi_cntrl_config->num_events); + } + + irq = devm_kcalloc(&pdev->dev, mhi_cntrl->nr_irqs, sizeof(int), GFP_KERNEL); + if (!irq) + return -ENOMEM; + + for (i = 0; i < mhi_cntrl->nr_irqs; i++) { + int vector = i >= nr_vectors ? (nr_vectors - 1) : i; + + irq[i] = pci_irq_vector(pdev, vector); + } + + mhi_cntrl->irq = irq; + + return 0; +} + +static int mhi_pci_runtime_get(struct mhi_controller *mhi_cntrl) +{ + /* no PM for now */ + return 0; +} + +static void mhi_pci_runtime_put(struct mhi_controller *mhi_cntrl) +{ + /* no PM for now */ +} + +static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + const struct mhi_pci_dev_info *info = (struct mhi_pci_dev_info *) id->driver_data; + const struct mhi_controller_config *mhi_cntrl_config; + struct mhi_controller *mhi_cntrl; + int err; + + dev_dbg(&pdev->dev, "MHI PCI device found: %s\n", info->name); + + mhi_cntrl = mhi_alloc_controller(); + if (!mhi_cntrl) + return -ENOMEM; + + mhi_cntrl_config = info->config; + mhi_cntrl->cntrl_dev = &pdev->dev; + mhi_cntrl->iova_start = 0; + mhi_cntrl->iova_stop = DMA_BIT_MASK(info->dma_data_width); + mhi_cntrl->fw_image = info->fw; + mhi_cntrl->edl_image = info->edl; + + mhi_cntrl->read_reg = mhi_pci_read_reg; + mhi_cntrl->write_reg = mhi_pci_write_reg; + mhi_cntrl->status_cb = mhi_pci_status_cb; + mhi_cntrl->runtime_get = mhi_pci_runtime_get; + mhi_cntrl->runtime_put = mhi_pci_runtime_put; + + err = mhi_pci_claim(mhi_cntrl, info->bar_num, DMA_BIT_MASK(info->dma_data_width)); + if (err) + goto err_release; + + err = mhi_pci_get_irqs(mhi_cntrl, mhi_cntrl_config); + if (err) + goto err_release; + + pci_set_drvdata(pdev, mhi_cntrl); + + err = mhi_register_controller(mhi_cntrl, mhi_cntrl_config); + if (err) + goto err_release; + + /* MHI bus does not power up the controller by default */ + err = mhi_prepare_for_power_up(mhi_cntrl); + if (err) { + dev_err(&pdev->dev, "failed to prepare MHI controller\n"); + goto err_unregister; + } + + err = mhi_sync_power_up(mhi_cntrl); + if (err) { + dev_err(&pdev->dev, "failed to power up MHI controller\n"); + goto err_unprepare; + } + + return 0; + +err_unprepare: + mhi_unprepare_after_power_down(mhi_cntrl); +err_unregister: + mhi_unregister_controller(mhi_cntrl); +err_release: + mhi_free_controller(mhi_cntrl); + + return err; +} + +static void mhi_pci_remove(struct pci_dev *pdev) +{ + struct mhi_controller *mhi_cntrl = pci_get_drvdata(pdev); + + mhi_power_down(mhi_cntrl, true); + mhi_unprepare_after_power_down(mhi_cntrl); + mhi_unregister_controller(mhi_cntrl); + mhi_free_controller(mhi_cntrl); +} + +static struct pci_driver mhi_pci_driver = { + .name = "mhi-pci-generic", + .id_table = mhi_pci_id_table, + .probe = mhi_pci_probe, + .remove = mhi_pci_remove +}; +module_pci_driver(mhi_pci_driver); + +MODULE_AUTHOR("Loic Poulain "); +MODULE_DESCRIPTION("Modem Host Interface (MHI) PCI controller driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From f4d0b39c842585c74bce8f8a80553369181b72df Mon Sep 17 00:00:00 2001 From: Carl Yin Date: Mon, 2 Nov 2020 20:27:10 +0800 Subject: bus: mhi: core: Fix null pointer access when parsing MHI configuration Functions parse_ev_cfg() and parse_ch_cfg() access mhi_cntrl->mhi_dev before it is set in function mhi_register_controller(), use cntrl_dev instead of mhi_dev. Fixes: 0cbf260820fa ("bus: mhi: core: Add support for registering MHI controllers") Signed-off-by: Carl Yin Reviewed-by: Bhaumik Bhatt Reviewed-by: Hemant Kumar Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/init.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/init.c b/drivers/bus/mhi/core/init.c index 0a09f8215057..8cefa359fccd 100644 --- a/drivers/bus/mhi/core/init.c +++ b/drivers/bus/mhi/core/init.c @@ -610,7 +610,7 @@ static int parse_ev_cfg(struct mhi_controller *mhi_cntrl, { struct mhi_event *mhi_event; const struct mhi_event_config *event_cfg; - struct device *dev = &mhi_cntrl->mhi_dev->dev; + struct device *dev = mhi_cntrl->cntrl_dev; int i, num; num = config->num_events; @@ -692,7 +692,7 @@ static int parse_ch_cfg(struct mhi_controller *mhi_cntrl, const struct mhi_controller_config *config) { const struct mhi_channel_config *ch_cfg; - struct device *dev = &mhi_cntrl->mhi_dev->dev; + struct device *dev = mhi_cntrl->cntrl_dev; int i; u32 chan; -- cgit v1.2.3 From a7f422f2f89e7d48aa66e6488444a4c7f01269d5 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Fri, 9 Oct 2020 11:07:14 +0200 Subject: bus: mhi: Fix channel close issue on driver remove Some MHI device drivers need to stop the channels in their driver remove callback (e.g. module unloading), but the unprepare function is aborted because MHI core moved the channels to suspended state prior calling driver remove callback. This prevents the driver to send a proper MHI RESET CHAN command to the device. Device is then unaware of the stopped state of these channels. This causes issue when driver tries to start the channels again (e.g. module is reloaded), since device considers channels as already started (inconsistent state). Fix this by allowing channel reset when channel is suspended. Signed-off-by: Loic Poulain Reviewed-by: Manivannan Sadhasivam Reviewed-by: Bhaumik Bhatt Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/main.c b/drivers/bus/mhi/core/main.c index 2cff5ddff225..cea5eab83f48 100644 --- a/drivers/bus/mhi/core/main.c +++ b/drivers/bus/mhi/core/main.c @@ -1224,7 +1224,8 @@ static void __mhi_unprepare_channel(struct mhi_controller *mhi_cntrl, /* no more processing events for this channel */ mutex_lock(&mhi_chan->mutex); write_lock_irq(&mhi_chan->lock); - if (mhi_chan->ch_state != MHI_CH_STATE_ENABLED) { + if (mhi_chan->ch_state != MHI_CH_STATE_ENABLED && + mhi_chan->ch_state != MHI_CH_STATE_SUSPENDED) { write_unlock_irq(&mhi_chan->lock); mutex_unlock(&mhi_chan->mutex); return; -- cgit v1.2.3 From 56c8ea864018a995c96810c9536d98a879b89218 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Fri, 6 Nov 2020 09:44:45 -0800 Subject: bus: mhi: core: Remove unnecessary counter from mhi_firmware_copy() There is an extra 'i' counter in the mhi_firmware_copy() function which is unused. Remove it to clean-up code and reduce stack space as well as improve efficiency of the function. Fixes: cd457afb1667 ("bus: mhi: core: Add support for downloading firmware over BHIe") Reported-by: kernel test robot Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/boot.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/boot.c b/drivers/bus/mhi/core/boot.c index 24422f5c3d80..6b6fd9668c3b 100644 --- a/drivers/bus/mhi/core/boot.c +++ b/drivers/bus/mhi/core/boot.c @@ -365,7 +365,6 @@ static void mhi_firmware_copy(struct mhi_controller *mhi_cntrl, size_t remainder = firmware->size; size_t to_cpy; const u8 *buf = firmware->data; - int i = 0; struct mhi_buf *mhi_buf = img_info->mhi_buf; struct bhi_vec_entry *bhi_vec = img_info->bhi_vec; @@ -377,7 +376,6 @@ static void mhi_firmware_copy(struct mhi_controller *mhi_cntrl, buf += to_cpy; remainder -= to_cpy; - i++; bhi_vec++; mhi_buf++; } -- cgit v1.2.3 From a8ca15a9c73f92703492e8afe25103a34504851b Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Fri, 6 Nov 2020 09:44:46 -0800 Subject: bus: mhi: core: Add missing EXPORT_SYMBOL for mhi_get_mhi_state() Add missing EXPORT_SYMBOL_GPL() declaration for mhi_get_mhi_state() API. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/main.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/main.c b/drivers/bus/mhi/core/main.c index cea5eab83f48..6ecaacaa8b54 100644 --- a/drivers/bus/mhi/core/main.c +++ b/drivers/bus/mhi/core/main.c @@ -132,6 +132,7 @@ enum mhi_state mhi_get_mhi_state(struct mhi_controller *mhi_cntrl) MHISTATUS_MHISTATE_SHIFT, &state); return ret ? MHI_STATE_MAX : state; } +EXPORT_SYMBOL_GPL(mhi_get_mhi_state); int mhi_map_single_no_bb(struct mhi_controller *mhi_cntrl, struct mhi_buf_info *buf_info) -- cgit v1.2.3 From 78e1d22687ff1fd320abac12e8a607ea79782c48 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Fri, 6 Nov 2020 09:44:47 -0800 Subject: bus: mhi: core: Expose mhi_get_exec_env() API for controllers The mhi_get_exec_env() APIs can be used by the controller drivers to query the execution environment of the MHI device. Expose it so it can be used in some scenarios to determine behavior of controllers. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/internal.h | 1 - drivers/bus/mhi/core/main.c | 1 + include/linux/mhi.h | 6 ++++++ 3 files changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/internal.h b/drivers/bus/mhi/core/internal.h index 78e4e84d6743..d8af8a702493 100644 --- a/drivers/bus/mhi/core/internal.h +++ b/drivers/bus/mhi/core/internal.h @@ -609,7 +609,6 @@ enum mhi_pm_state __must_check mhi_tryset_pm_state( struct mhi_controller *mhi_cntrl, enum mhi_pm_state state); const char *to_mhi_pm_state_str(enum mhi_pm_state state); -enum mhi_ee_type mhi_get_exec_env(struct mhi_controller *mhi_cntrl); int mhi_queue_state_transition(struct mhi_controller *mhi_cntrl, enum dev_st_transition state); void mhi_pm_st_worker(struct work_struct *work); diff --git a/drivers/bus/mhi/core/main.c b/drivers/bus/mhi/core/main.c index 6ecaacaa8b54..f953e2a6d58a 100644 --- a/drivers/bus/mhi/core/main.c +++ b/drivers/bus/mhi/core/main.c @@ -123,6 +123,7 @@ enum mhi_ee_type mhi_get_exec_env(struct mhi_controller *mhi_cntrl) return (ret) ? MHI_EE_MAX : exec; } +EXPORT_SYMBOL_GPL(mhi_get_exec_env); enum mhi_state mhi_get_mhi_state(struct mhi_controller *mhi_cntrl) { diff --git a/include/linux/mhi.h b/include/linux/mhi.h index d4841e5a5f45..9225d5551d69 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -658,6 +658,12 @@ int mhi_download_rddm_img(struct mhi_controller *mhi_cntrl, bool in_panic); */ int mhi_force_rddm_mode(struct mhi_controller *mhi_cntrl); +/** + * mhi_get_exec_env - Get BHI execution environment of the device + * @mhi_cntrl: MHI controller + */ +enum mhi_ee_type mhi_get_exec_env(struct mhi_controller *mhi_cntrl); + /** * mhi_get_mhi_state - Get MHI state of the device * @mhi_cntrl: MHI controller -- cgit v1.2.3 From bca7218099e0796901a9c27366e145d9f485c81a Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Fri, 6 Nov 2020 09:44:48 -0800 Subject: bus: mhi: core: Remove unused mhi_fw_load_worker() declaration The mhi_fw_load_worker() function no longer exists. Remove its declaration as part of code clean-up. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/internal.h | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/internal.h b/drivers/bus/mhi/core/internal.h index d8af8a702493..2df8de5432f9 100644 --- a/drivers/bus/mhi/core/internal.h +++ b/drivers/bus/mhi/core/internal.h @@ -613,7 +613,6 @@ int mhi_queue_state_transition(struct mhi_controller *mhi_cntrl, enum dev_st_transition state); void mhi_pm_st_worker(struct work_struct *work); void mhi_pm_sys_err_handler(struct mhi_controller *mhi_cntrl); -void mhi_fw_load_worker(struct work_struct *work); int mhi_ready_state_transition(struct mhi_controller *mhi_cntrl); int mhi_pm_m0_transition(struct mhi_controller *mhi_cntrl); void mhi_pm_m1_transition(struct mhi_controller *mhi_cntrl); -- cgit v1.2.3 From 9e1660e5c396ee081907386d0b95b8e0804a6c86 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Fri, 6 Nov 2020 09:44:49 -0800 Subject: bus: mhi: core: Rename RDDM download function to use proper words mhi_download_rddm_img() uses a shorter version of the word image. Expand it and rename the function to mhi_download_rddm_image(). Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/boot.c | 4 ++-- include/linux/mhi.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/boot.c b/drivers/bus/mhi/core/boot.c index 6b6fd9668c3b..16244cc8fbe7 100644 --- a/drivers/bus/mhi/core/boot.c +++ b/drivers/bus/mhi/core/boot.c @@ -147,7 +147,7 @@ static int __mhi_download_rddm_in_panic(struct mhi_controller *mhi_cntrl) } /* Download RDDM image from device */ -int mhi_download_rddm_img(struct mhi_controller *mhi_cntrl, bool in_panic) +int mhi_download_rddm_image(struct mhi_controller *mhi_cntrl, bool in_panic) { void __iomem *base = mhi_cntrl->bhie; struct device *dev = &mhi_cntrl->mhi_dev->dev; @@ -169,7 +169,7 @@ int mhi_download_rddm_img(struct mhi_controller *mhi_cntrl, bool in_panic) return (rx_status == BHIE_RXVECSTATUS_STATUS_XFER_COMPL) ? 0 : -EIO; } -EXPORT_SYMBOL_GPL(mhi_download_rddm_img); +EXPORT_SYMBOL_GPL(mhi_download_rddm_image); static int mhi_fw_load_amss(struct mhi_controller *mhi_cntrl, const struct mhi_buf *mhi_buf) diff --git a/include/linux/mhi.h b/include/linux/mhi.h index 9225d5551d69..52b3c60bf9bb 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -645,12 +645,12 @@ int mhi_pm_suspend(struct mhi_controller *mhi_cntrl); int mhi_pm_resume(struct mhi_controller *mhi_cntrl); /** - * mhi_download_rddm_img - Download ramdump image from device for - * debugging purpose. + * mhi_download_rddm_image - Download ramdump image from device for + * debugging purpose. * @mhi_cntrl: MHI controller * @in_panic: Download rddm image during kernel panic */ -int mhi_download_rddm_img(struct mhi_controller *mhi_cntrl, bool in_panic); +int mhi_download_rddm_image(struct mhi_controller *mhi_cntrl, bool in_panic); /** * mhi_force_rddm_mode - Force device into rddm mode -- cgit v1.2.3 From da7bdbf67db232234bb55e083c57b930c2010f95 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Fri, 6 Nov 2020 09:44:50 -0800 Subject: bus: mhi: core: Skip RDDM download for unknown execution environment If MHI is unable to determine the execution environment during the panic path, host must skip the RDDM download. This can happen if the BHI offset read or the BHI_EXECENV register read fails indicating that the underlying transport is unresponsive. Hence, there is no need to trigger an RDDM using SYSERR or request an SOC reset. Suggested-by: Hemant Kumar Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/boot.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/boot.c b/drivers/bus/mhi/core/boot.c index 16244cc8fbe7..6f0cfb9922b4 100644 --- a/drivers/bus/mhi/core/boot.c +++ b/drivers/bus/mhi/core/boot.c @@ -92,6 +92,9 @@ static int __mhi_download_rddm_in_panic(struct mhi_controller *mhi_cntrl) * image download completion. */ ee = mhi_get_exec_env(mhi_cntrl); + if (ee == MHI_EE_MAX) + goto error_exit_rddm; + if (ee != MHI_EE_RDDM) { dev_dbg(dev, "Trigger device into RDDM mode using SYS ERR\n"); mhi_set_mhi_state(mhi_cntrl, MHI_STATE_SYS_ERR); @@ -139,10 +142,12 @@ static int __mhi_download_rddm_in_panic(struct mhi_controller *mhi_cntrl) ee = mhi_get_exec_env(mhi_cntrl); ret = mhi_read_reg(mhi_cntrl, base, BHIE_RXVECSTATUS_OFFS, &rx_status); - dev_err(dev, "Did not complete RDDM transfer\n"); - dev_err(dev, "Current EE: %s\n", TO_MHI_EXEC_STR(ee)); dev_err(dev, "RXVEC_STATUS: 0x%x\n", rx_status); +error_exit_rddm: + dev_err(dev, "RDDM transfer failed. Current EE: %s\n", + TO_MHI_EXEC_STR(ee)); + return -EIO; } -- cgit v1.2.3 From 1b55c16a5e4718f58ed58cfdfe37fe4cd4d89f88 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:20 -0800 Subject: bus: mhi: core: Use appropriate names for firmware load functions mhi_fw_load_sbl() function is currently used to transfer SBL or EDL images over BHI (Boot Host Interface). Same goes with mhi_fw_load_amss() which uses BHIe. However, the contents of these functions do not indicate support for a specific set of images. Since these can be used for any image download over BHI or BHIe, rename them based on the protocol used. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/boot.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/boot.c b/drivers/bus/mhi/core/boot.c index 6f0cfb9922b4..2d7752cd9ccc 100644 --- a/drivers/bus/mhi/core/boot.c +++ b/drivers/bus/mhi/core/boot.c @@ -176,7 +176,7 @@ int mhi_download_rddm_image(struct mhi_controller *mhi_cntrl, bool in_panic) } EXPORT_SYMBOL_GPL(mhi_download_rddm_image); -static int mhi_fw_load_amss(struct mhi_controller *mhi_cntrl, +static int mhi_fw_load_bhie(struct mhi_controller *mhi_cntrl, const struct mhi_buf *mhi_buf) { void __iomem *base = mhi_cntrl->bhie; @@ -192,7 +192,7 @@ static int mhi_fw_load_amss(struct mhi_controller *mhi_cntrl, } sequence_id = MHI_RANDOM_U32_NONZERO(BHIE_TXVECSTATUS_SEQNUM_BMSK); - dev_dbg(dev, "Starting AMSS download via BHIe. Sequence ID:%u\n", + dev_dbg(dev, "Starting image download via BHIe. Sequence ID: %u\n", sequence_id); mhi_write_reg(mhi_cntrl, base, BHIE_TXVECADDR_HIGH_OFFS, upper_32_bits(mhi_buf->dma_addr)); @@ -223,7 +223,7 @@ static int mhi_fw_load_amss(struct mhi_controller *mhi_cntrl, return (!ret) ? -ETIMEDOUT : 0; } -static int mhi_fw_load_sbl(struct mhi_controller *mhi_cntrl, +static int mhi_fw_load_bhi(struct mhi_controller *mhi_cntrl, dma_addr_t dma_addr, size_t size) { @@ -250,7 +250,7 @@ static int mhi_fw_load_sbl(struct mhi_controller *mhi_cntrl, } session_id = MHI_RANDOM_U32_NONZERO(BHI_TXDB_SEQNUM_BMSK); - dev_dbg(dev, "Starting SBL download via BHI. Session ID:%u\n", + dev_dbg(dev, "Starting image download via BHI. Session ID: %u\n", session_id); mhi_write_reg(mhi_cntrl, base, BHI_STATUS, 0); mhi_write_reg(mhi_cntrl, base, BHI_IMGADDR_HIGH, @@ -449,9 +449,9 @@ void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl) return; } - /* Download SBL image */ + /* Download image using BHI */ memcpy(buf, firmware->data, size); - ret = mhi_fw_load_sbl(mhi_cntrl, dma_addr, size); + ret = mhi_fw_load_bhi(mhi_cntrl, dma_addr, size); mhi_free_coherent(mhi_cntrl, size, buf, dma_addr); if (!mhi_cntrl->fbc_download || ret || mhi_cntrl->ee == MHI_EE_EDL) @@ -459,7 +459,7 @@ void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl) /* Error or in EDL mode, we're done */ if (ret) { - dev_err(dev, "MHI did not load SBL, ret:%d\n", ret); + dev_err(dev, "MHI did not load image over BHI, ret: %d\n", ret); return; } @@ -509,11 +509,12 @@ fw_load_ee_pthru: /* Start full firmware image download */ image_info = mhi_cntrl->fbc_image; - ret = mhi_fw_load_amss(mhi_cntrl, + ret = mhi_fw_load_bhie(mhi_cntrl, /* Vector table is the last entry */ &image_info->mhi_buf[image_info->entries - 1]); if (ret) - dev_err(dev, "MHI did not load AMSS, ret:%d\n", ret); + dev_err(dev, "MHI did not load image over BHIe, ret: %d\n", + ret); release_firmware(firmware); -- cgit v1.2.3 From 8f70397876872789b2a5deba804eb6216fb5deb7 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:21 -0800 Subject: bus: mhi: core: Move to using high priority workqueue MHI work is currently scheduled on the global/system workqueue and can encounter delays on a stressed system. To avoid those unforeseen delays which can hamper bootup or shutdown times, use a dedicated high priority workqueue instead of the global/system workqueue. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/init.c | 9 +++++++++ drivers/bus/mhi/core/pm.c | 2 +- include/linux/mhi.h | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/init.c b/drivers/bus/mhi/core/init.c index 8cefa359fccd..877e40c86801 100644 --- a/drivers/bus/mhi/core/init.c +++ b/drivers/bus/mhi/core/init.c @@ -880,6 +880,13 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, INIT_WORK(&mhi_cntrl->st_worker, mhi_pm_st_worker); init_waitqueue_head(&mhi_cntrl->state_event); + mhi_cntrl->hiprio_wq = alloc_ordered_workqueue + ("mhi_hiprio_wq", WQ_MEM_RECLAIM | WQ_HIGHPRI); + if (!mhi_cntrl->hiprio_wq) { + dev_err(mhi_cntrl->cntrl_dev, "Failed to allocate workqueue\n"); + goto error_alloc_cmd; + } + mhi_cmd = mhi_cntrl->mhi_cmd; for (i = 0; i < NR_OF_CMD_RINGS; i++, mhi_cmd++) spin_lock_init(&mhi_cmd->lock); @@ -969,6 +976,7 @@ error_alloc_dev: error_alloc_cmd: vfree(mhi_cntrl->mhi_chan); kfree(mhi_cntrl->mhi_event); + destroy_workqueue(mhi_cntrl->hiprio_wq); return ret; } @@ -982,6 +990,7 @@ void mhi_unregister_controller(struct mhi_controller *mhi_cntrl) mhi_destroy_debugfs(mhi_cntrl); + destroy_workqueue(mhi_cntrl->hiprio_wq); kfree(mhi_cntrl->mhi_cmd); kfree(mhi_cntrl->mhi_event); diff --git a/drivers/bus/mhi/core/pm.c b/drivers/bus/mhi/core/pm.c index 3de7b1639ec6..805b6fa748f0 100644 --- a/drivers/bus/mhi/core/pm.c +++ b/drivers/bus/mhi/core/pm.c @@ -597,7 +597,7 @@ int mhi_queue_state_transition(struct mhi_controller *mhi_cntrl, list_add_tail(&item->node, &mhi_cntrl->transition_list); spin_unlock_irqrestore(&mhi_cntrl->transition_lock, flags); - schedule_work(&mhi_cntrl->st_worker); + queue_work(mhi_cntrl->hiprio_wq, &mhi_cntrl->st_worker); return 0; } diff --git a/include/linux/mhi.h b/include/linux/mhi.h index 52b3c60bf9bb..1ed5f2aa224b 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -337,6 +337,7 @@ struct mhi_controller_config { * @wlock: Lock for protecting device wakeup * @mhi_link_info: Device bandwidth info * @st_worker: State transition worker + * @hiprio_wq: High priority workqueue for MHI work such as state transitions * @state_event: State change event * @status_cb: CB function to notify power states of the device (required) * @wake_get: CB function to assert device wake (optional) @@ -419,6 +420,7 @@ struct mhi_controller { spinlock_t wlock; struct mhi_link_info mhi_link_info; struct work_struct st_worker; + struct workqueue_struct *hiprio_wq; wait_queue_head_t state_event; void (*status_cb)(struct mhi_controller *mhi_cntrl, -- cgit v1.2.3 From 8e0559921f9afc01fe0457c5e136ce4a7ae8f0d3 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:22 -0800 Subject: bus: mhi: core: Skip device wake in error or shutdown states MHI client drivers can request a device wake even if the device may be in an error state or undergoing a shutdown. To prevent unnecessary device wake processing, check for the device state and bail out early so that the clients are made aware of the device state sooner. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/pm.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/pm.c b/drivers/bus/mhi/core/pm.c index 805b6fa748f0..029919647002 100644 --- a/drivers/bus/mhi/core/pm.c +++ b/drivers/bus/mhi/core/pm.c @@ -827,6 +827,10 @@ int __mhi_device_get_sync(struct mhi_controller *mhi_cntrl) /* Wake up the device */ read_lock_bh(&mhi_cntrl->pm_lock); + if (MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) { + read_unlock_bh(&mhi_cntrl->pm_lock); + return -EIO; + } mhi_cntrl->wake_get(mhi_cntrl, true); if (MHI_PM_IN_SUSPEND_STATE(mhi_cntrl->pm_state)) mhi_trigger_resume(mhi_cntrl); -- cgit v1.2.3 From 3fb81a4d5f2f55b1596cb5cffca98c97ca43f07a Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:23 -0800 Subject: bus: mhi: core: Move to SYS_ERROR regardless of RDDM capability In some cases, the entry of device to RDDM execution environment can occur after a significant amount of time has elapsed and a SYS_ERROR state change event has already arrived. This can result in scenarios where MHI controller and client drivers are unaware of the error state of the device. Remove the check for rddm_image when processing the SYS_ERROR state change as it is present in mhi_pm_sys_err_handler() already and prevent further activity until the expected RDDM execution environment change occurs or the controller driver decides further action. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/main.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/main.c b/drivers/bus/mhi/core/main.c index f953e2a6d58a..91f8b8d05a62 100644 --- a/drivers/bus/mhi/core/main.c +++ b/drivers/bus/mhi/core/main.c @@ -737,11 +737,6 @@ int mhi_process_ctrl_ev_ring(struct mhi_controller *mhi_cntrl, { enum mhi_pm_state new_state; - /* skip SYS_ERROR handling if RDDM supported */ - if (mhi_cntrl->ee == MHI_EE_RDDM || - mhi_cntrl->rddm_image) - break; - dev_dbg(dev, "System error detected\n"); write_lock_irq(&mhi_cntrl->pm_lock); new_state = mhi_tryset_pm_state(mhi_cntrl, -- cgit v1.2.3 From 0c76b3fa580da8f65333b8774205f0a72ffe844c Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:24 -0800 Subject: bus: mhi: core: Prevent sending multiple RDDM entry callbacks If an mhi_power_down() is initiated after the device has entered RDDM and a status callback was provided for it, it is possible that another BHI interrupt fires while waiting for the MHI RESET to be cleared. If that happens, MHI host would have moved a "disabled" execution environment and the check to allow sending an RDDM status callback will pass when it is should not. Add a check to see if MHI is in an active state before proceeding. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/main.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/main.c b/drivers/bus/mhi/core/main.c index 91f8b8d05a62..4eb93d8bea1d 100644 --- a/drivers/bus/mhi/core/main.c +++ b/drivers/bus/mhi/core/main.c @@ -401,6 +401,10 @@ irqreturn_t mhi_intvec_threaded_handler(int irq_number, void *priv) /* If device supports RDDM don't bother processing SYS error */ if (mhi_cntrl->rddm_image) { + /* host may be performing a device power down already */ + if (!mhi_is_active(mhi_cntrl)) + goto exit_intvec; + if (mhi_cntrl->ee == MHI_EE_RDDM && mhi_cntrl->ee != ee) { mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_RDDM); wake_up_all(&mhi_cntrl->state_event); -- cgit v1.2.3 From 12e050c77be036377fee479dccd479202d8d6853 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:25 -0800 Subject: bus: mhi: core: Move to an error state on any firmware load failure Move MHI to a firmware download error state for a failure to find the firmware files or to load SBL or EBL image using BHI/BHIe. This helps detect an error state sooner and shortens the wait for a synchronous power up timeout. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/boot.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/boot.c b/drivers/bus/mhi/core/boot.c index 2d7752cd9ccc..7b57bb9a3080 100644 --- a/drivers/bus/mhi/core/boot.c +++ b/drivers/bus/mhi/core/boot.c @@ -428,13 +428,13 @@ void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl) !mhi_cntrl->seg_len))) { dev_err(dev, "No firmware image defined or !sbl_size || !seg_len\n"); - return; + goto error_fw_load; } ret = request_firmware(&firmware, fw_name, dev); if (ret) { dev_err(dev, "Error loading firmware: %d\n", ret); - return; + goto error_fw_load; } size = (mhi_cntrl->fbc_download) ? mhi_cntrl->sbl_size : firmware->size; @@ -446,7 +446,7 @@ void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl) buf = mhi_alloc_coherent(mhi_cntrl, size, &dma_addr, GFP_KERNEL); if (!buf) { release_firmware(firmware); - return; + goto error_fw_load; } /* Download image using BHI */ @@ -454,17 +454,17 @@ void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl) ret = mhi_fw_load_bhi(mhi_cntrl, dma_addr, size); mhi_free_coherent(mhi_cntrl, size, buf, dma_addr); - if (!mhi_cntrl->fbc_download || ret || mhi_cntrl->ee == MHI_EE_EDL) - release_firmware(firmware); - /* Error or in EDL mode, we're done */ if (ret) { dev_err(dev, "MHI did not load image over BHI, ret: %d\n", ret); - return; + release_firmware(firmware); + goto error_fw_load; } - if (mhi_cntrl->ee == MHI_EE_EDL) + if (mhi_cntrl->ee == MHI_EE_EDL) { + release_firmware(firmware); return; + } write_lock_irq(&mhi_cntrl->pm_lock); mhi_cntrl->dev_state = MHI_STATE_RESET; @@ -477,13 +477,17 @@ void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl) if (mhi_cntrl->fbc_download) { ret = mhi_alloc_bhie_table(mhi_cntrl, &mhi_cntrl->fbc_image, firmware->size); - if (ret) - goto error_alloc_fw_table; + if (ret) { + release_firmware(firmware); + goto error_fw_load; + } /* Load the firmware into BHIE vec table */ mhi_firmware_copy(mhi_cntrl, firmware, mhi_cntrl->fbc_image); } + release_firmware(firmware); + fw_load_ee_pthru: /* Transitioning into MHI RESET->READY state */ ret = mhi_ready_state_transition(mhi_cntrl); @@ -512,11 +516,11 @@ fw_load_ee_pthru: ret = mhi_fw_load_bhie(mhi_cntrl, /* Vector table is the last entry */ &image_info->mhi_buf[image_info->entries - 1]); - if (ret) + if (ret) { dev_err(dev, "MHI did not load image over BHIe, ret: %d\n", ret); - - release_firmware(firmware); + goto error_fw_load; + } return; @@ -524,6 +528,7 @@ error_read: mhi_free_bhie_table(mhi_cntrl, mhi_cntrl->fbc_image); mhi_cntrl->fbc_image = NULL; -error_alloc_fw_table: - release_firmware(firmware); +error_fw_load: + mhi_cntrl->pm_state = MHI_PM_FW_DL_ERR; + wake_up_all(&mhi_cntrl->state_event); } -- cgit v1.2.3 From faa257075bcc35e2c2ee68eb83e4e53d3bb02a90 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:26 -0800 Subject: bus: mhi: core: Use appropriate label in firmware load handler API Correct the "error_read" label to say "error_ready_state" as that is the appropriate usage of the label. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/boot.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/boot.c b/drivers/bus/mhi/core/boot.c index 7b57bb9a3080..c2546bf229fb 100644 --- a/drivers/bus/mhi/core/boot.c +++ b/drivers/bus/mhi/core/boot.c @@ -497,7 +497,7 @@ fw_load_ee_pthru: if (ret) { dev_err(dev, "MHI did not enter READY state\n"); - goto error_read; + goto error_ready_state; } /* Wait for the SBL event */ @@ -508,7 +508,7 @@ fw_load_ee_pthru: if (!ret || MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state)) { dev_err(dev, "MHI did not enter SBL\n"); - goto error_read; + goto error_ready_state; } /* Start full firmware image download */ @@ -524,7 +524,7 @@ fw_load_ee_pthru: return; -error_read: +error_ready_state: mhi_free_bhie_table(mhi_cntrl, mhi_cntrl->fbc_image); mhi_cntrl->fbc_image = NULL; -- cgit v1.2.3 From dc53d862eab89000ebc87240274943793f0af682 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:27 -0800 Subject: bus: mhi: core: Move to an error state on mission mode failure If the host receives a mission mode event and by the time it can get to processing it, the register accesses fail implying a connectivity error, MHI should move to an error state. This helps avoid longer wait times from a synchronous power up perspective and accurately reflects the MHI execution environment and power management states. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/pm.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/pm.c b/drivers/bus/mhi/core/pm.c index 029919647002..06adea2580d2 100644 --- a/drivers/bus/mhi/core/pm.c +++ b/drivers/bus/mhi/core/pm.c @@ -383,10 +383,14 @@ static int mhi_pm_mission_mode_transition(struct mhi_controller *mhi_cntrl) write_lock_irq(&mhi_cntrl->pm_lock); if (MHI_REG_ACCESS_VALID(mhi_cntrl->pm_state)) mhi_cntrl->ee = mhi_get_exec_env(mhi_cntrl); - write_unlock_irq(&mhi_cntrl->pm_lock); - if (!MHI_IN_MISSION_MODE(mhi_cntrl->ee)) + if (!MHI_IN_MISSION_MODE(mhi_cntrl->ee)) { + mhi_cntrl->pm_state = MHI_PM_LD_ERR_FATAL_DETECT; + write_unlock_irq(&mhi_cntrl->pm_lock); + wake_up_all(&mhi_cntrl->state_event); return -EIO; + } + write_unlock_irq(&mhi_cntrl->pm_lock); wake_up_all(&mhi_cntrl->state_event); -- cgit v1.2.3 From 40c3127187cb38ed255ba4473e11cd70385c7431 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:28 -0800 Subject: bus: mhi: core: Check for IRQ availability during registration Current design allows a controller to register with MHI successfully without the need to have any IRQs available for use. If no IRQs are available, power up requests to MHI can fail after a successful registration with MHI. Improve the design by checking for the number of IRQs available sooner within the mhi_regsiter_controller() API as it is required to be specified by the controller. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/init.c | 2 +- drivers/bus/mhi/core/pm.c | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/init.c b/drivers/bus/mhi/core/init.c index 877e40c86801..2534f1c9c153 100644 --- a/drivers/bus/mhi/core/init.c +++ b/drivers/bus/mhi/core/init.c @@ -858,7 +858,7 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, if (!mhi_cntrl->runtime_get || !mhi_cntrl->runtime_put || !mhi_cntrl->status_cb || !mhi_cntrl->read_reg || - !mhi_cntrl->write_reg) + !mhi_cntrl->write_reg || !mhi_cntrl->nr_irqs) return -EINVAL; ret = parse_config(mhi_cntrl, config); diff --git a/drivers/bus/mhi/core/pm.c b/drivers/bus/mhi/core/pm.c index 06adea2580d2..1d04e401b67f 100644 --- a/drivers/bus/mhi/core/pm.c +++ b/drivers/bus/mhi/core/pm.c @@ -926,9 +926,6 @@ int mhi_async_power_up(struct mhi_controller *mhi_cntrl) dev_info(dev, "Requested to power ON\n"); - if (mhi_cntrl->nr_irqs < 1) - return -EINVAL; - /* Supply default wake routines if not provided by controller driver */ if (!mhi_cntrl->wake_get || !mhi_cntrl->wake_put || !mhi_cntrl->wake_toggle) { -- cgit v1.2.3 From 556bbb442bbb44f429dbaa9f8b48e0b4cda6e088 Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:29 -0800 Subject: bus: mhi: core: Separate system error and power down handling Currently, there exist a set of if...else statements in the mhi_pm_disable_transition() function which make handling system error and disable transitions differently complex. To make that cleaner and facilitate differences in behavior, separate these two transitions for MHI host. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/pm.c | 159 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 137 insertions(+), 22 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/pm.c b/drivers/bus/mhi/core/pm.c index 1d04e401b67f..347ae7dd6cf8 100644 --- a/drivers/bus/mhi/core/pm.c +++ b/drivers/bus/mhi/core/pm.c @@ -444,7 +444,7 @@ error_mission_mode: return ret; } -/* Handle SYS_ERR and Shutdown transitions */ +/* Handle shutdown transitions */ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl, enum mhi_pm_state transition_state) { @@ -460,10 +460,6 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl, to_mhi_pm_state_str(mhi_cntrl->pm_state), to_mhi_pm_state_str(transition_state)); - /* We must notify MHI control driver so it can clean up first */ - if (transition_state == MHI_PM_SYS_ERR_PROCESS) - mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_SYS_ERROR); - mutex_lock(&mhi_cntrl->pm_mutex); write_lock_irq(&mhi_cntrl->pm_lock); prev_state = mhi_cntrl->pm_state; @@ -502,11 +498,8 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl, MHICTRL_RESET_SHIFT, &in_reset) || !in_reset, timeout); - if ((!ret || in_reset) && cur_state == MHI_PM_SYS_ERR_PROCESS) { + if (!ret || in_reset) dev_err(dev, "Device failed to exit MHI Reset state\n"); - mutex_unlock(&mhi_cntrl->pm_mutex); - return; - } /* * Device will clear BHI_INTVEC as a part of RESET processing, @@ -566,19 +559,142 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl, er_ctxt->wp = er_ctxt->rbase; } - if (cur_state == MHI_PM_SYS_ERR_PROCESS) { - mhi_ready_state_transition(mhi_cntrl); - } else { - /* Move to disable state */ - write_lock_irq(&mhi_cntrl->pm_lock); - cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_DISABLE); - write_unlock_irq(&mhi_cntrl->pm_lock); - if (unlikely(cur_state != MHI_PM_DISABLE)) - dev_err(dev, "Error moving from PM state: %s to: %s\n", - to_mhi_pm_state_str(cur_state), - to_mhi_pm_state_str(MHI_PM_DISABLE)); + /* Move to disable state */ + write_lock_irq(&mhi_cntrl->pm_lock); + cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_DISABLE); + write_unlock_irq(&mhi_cntrl->pm_lock); + if (unlikely(cur_state != MHI_PM_DISABLE)) + dev_err(dev, "Error moving from PM state: %s to: %s\n", + to_mhi_pm_state_str(cur_state), + to_mhi_pm_state_str(MHI_PM_DISABLE)); + + dev_dbg(dev, "Exiting with PM state: %s, MHI state: %s\n", + to_mhi_pm_state_str(mhi_cntrl->pm_state), + TO_MHI_STATE_STR(mhi_cntrl->dev_state)); + + mutex_unlock(&mhi_cntrl->pm_mutex); +} + +/* Handle system error transitions */ +static void mhi_pm_sys_error_transition(struct mhi_controller *mhi_cntrl) +{ + enum mhi_pm_state cur_state, prev_state; + struct mhi_event *mhi_event; + struct mhi_cmd_ctxt *cmd_ctxt; + struct mhi_cmd *mhi_cmd; + struct mhi_event_ctxt *er_ctxt; + struct device *dev = &mhi_cntrl->mhi_dev->dev; + int ret, i; + + dev_dbg(dev, "Transitioning from PM state: %s to: %s\n", + to_mhi_pm_state_str(mhi_cntrl->pm_state), + to_mhi_pm_state_str(MHI_PM_SYS_ERR_PROCESS)); + + /* We must notify MHI control driver so it can clean up first */ + mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_SYS_ERROR); + + mutex_lock(&mhi_cntrl->pm_mutex); + write_lock_irq(&mhi_cntrl->pm_lock); + prev_state = mhi_cntrl->pm_state; + cur_state = mhi_tryset_pm_state(mhi_cntrl, MHI_PM_SYS_ERR_PROCESS); + write_unlock_irq(&mhi_cntrl->pm_lock); + + if (cur_state != MHI_PM_SYS_ERR_PROCESS) { + dev_err(dev, "Failed to transition from PM state: %s to: %s\n", + to_mhi_pm_state_str(cur_state), + to_mhi_pm_state_str(MHI_PM_SYS_ERR_PROCESS)); + goto exit_sys_error_transition; + } + + mhi_cntrl->ee = MHI_EE_DISABLE_TRANSITION; + mhi_cntrl->dev_state = MHI_STATE_RESET; + + /* Wake up threads waiting for state transition */ + wake_up_all(&mhi_cntrl->state_event); + + /* Trigger MHI RESET so that the device will not access host memory */ + if (MHI_REG_ACCESS_VALID(prev_state)) { + u32 in_reset = -1; + unsigned long timeout = msecs_to_jiffies(mhi_cntrl->timeout_ms); + + dev_dbg(dev, "Triggering MHI Reset in device\n"); + mhi_set_mhi_state(mhi_cntrl, MHI_STATE_RESET); + + /* Wait for the reset bit to be cleared by the device */ + ret = wait_event_timeout(mhi_cntrl->state_event, + mhi_read_reg_field(mhi_cntrl, + mhi_cntrl->regs, + MHICTRL, + MHICTRL_RESET_MASK, + MHICTRL_RESET_SHIFT, + &in_reset) || + !in_reset, timeout); + if (!ret || in_reset) { + dev_err(dev, "Device failed to exit MHI Reset state\n"); + goto exit_sys_error_transition; + } + + /* + * Device will clear BHI_INTVEC as a part of RESET processing, + * hence re-program it + */ + mhi_write_reg(mhi_cntrl, mhi_cntrl->bhi, BHI_INTVEC, 0); + } + + dev_dbg(dev, + "Waiting for all pending event ring processing to complete\n"); + mhi_event = mhi_cntrl->mhi_event; + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) { + if (mhi_event->offload_ev) + continue; + tasklet_kill(&mhi_event->task); } + /* Release lock and wait for all pending threads to complete */ + mutex_unlock(&mhi_cntrl->pm_mutex); + dev_dbg(dev, "Waiting for all pending threads to complete\n"); + wake_up_all(&mhi_cntrl->state_event); + + dev_dbg(dev, "Reset all active channels and remove MHI devices\n"); + device_for_each_child(mhi_cntrl->cntrl_dev, NULL, mhi_destroy_device); + + mutex_lock(&mhi_cntrl->pm_mutex); + + WARN_ON(atomic_read(&mhi_cntrl->dev_wake)); + WARN_ON(atomic_read(&mhi_cntrl->pending_pkts)); + + /* Reset the ev rings and cmd rings */ + dev_dbg(dev, "Resetting EV CTXT and CMD CTXT\n"); + mhi_cmd = mhi_cntrl->mhi_cmd; + cmd_ctxt = mhi_cntrl->mhi_ctxt->cmd_ctxt; + for (i = 0; i < NR_OF_CMD_RINGS; i++, mhi_cmd++, cmd_ctxt++) { + struct mhi_ring *ring = &mhi_cmd->ring; + + ring->rp = ring->base; + ring->wp = ring->base; + cmd_ctxt->rp = cmd_ctxt->rbase; + cmd_ctxt->wp = cmd_ctxt->rbase; + } + + mhi_event = mhi_cntrl->mhi_event; + er_ctxt = mhi_cntrl->mhi_ctxt->er_ctxt; + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, er_ctxt++, + mhi_event++) { + struct mhi_ring *ring = &mhi_event->ring; + + /* Skip offload events */ + if (mhi_event->offload_ev) + continue; + + ring->rp = ring->base; + ring->wp = ring->base; + er_ctxt->rp = er_ctxt->rbase; + er_ctxt->wp = er_ctxt->rbase; + } + + mhi_ready_state_transition(mhi_cntrl); + +exit_sys_error_transition: dev_dbg(dev, "Exiting with PM state: %s, MHI state: %s\n", to_mhi_pm_state_str(mhi_cntrl->pm_state), TO_MHI_STATE_STR(mhi_cntrl->dev_state)); @@ -666,8 +782,7 @@ void mhi_pm_st_worker(struct work_struct *work) mhi_ready_state_transition(mhi_cntrl); break; case DEV_ST_TRANSITION_SYS_ERR: - mhi_pm_disable_transition - (mhi_cntrl, MHI_PM_SYS_ERR_PROCESS); + mhi_pm_sys_error_transition(mhi_cntrl); break; case DEV_ST_TRANSITION_DISABLE: mhi_pm_disable_transition -- cgit v1.2.3 From a03c7a86e12721da9f6bb509dddda19fd9ae8c6c Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:30 -0800 Subject: bus: mhi: core: Mark and maintain device states early on after power down mhi_power_down() does not ensure that the PM state is moved to an inaccessible state soon enough as the system can encounter scheduling delays till mhi_pm_disable_transition() gets called. Additionally, if an MHI controller decides that the device is now inaccessible and issues a power down, the register inaccessible state is not maintained by moving from MHI_PM_LD_ERR_FATAL_DETECT to MHI_PM_SHUTDOWN_PROCESS. This can result in bus errors if a client driver attempted to read registers when powering down. Close these gaps and avoid any race conditions to prevent such activity. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/pm.c | 77 ++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 44 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/pm.c b/drivers/bus/mhi/core/pm.c index 347ae7dd6cf8..ffbf6f539510 100644 --- a/drivers/bus/mhi/core/pm.c +++ b/drivers/bus/mhi/core/pm.c @@ -37,9 +37,10 @@ * M0 -> FW_DL_ERR * M0 -> M3_ENTER -> M3 -> M3_EXIT --> M0 * L1: SYS_ERR_DETECT -> SYS_ERR_PROCESS --> POR - * L2: SHUTDOWN_PROCESS -> DISABLE + * L2: SHUTDOWN_PROCESS -> LD_ERR_FATAL_DETECT + * SHUTDOWN_PROCESS -> DISABLE * L3: LD_ERR_FATAL_DETECT <--> LD_ERR_FATAL_DETECT - * LD_ERR_FATAL_DETECT -> SHUTDOWN_PROCESS + * LD_ERR_FATAL_DETECT -> DISABLE */ static struct mhi_pm_transitions const dev_state_transitions[] = { /* L0 States */ @@ -72,7 +73,7 @@ static struct mhi_pm_transitions const dev_state_transitions[] = { { MHI_PM_M3, MHI_PM_M3_EXIT | MHI_PM_SYS_ERR_DETECT | - MHI_PM_SHUTDOWN_PROCESS | MHI_PM_LD_ERR_FATAL_DETECT + MHI_PM_LD_ERR_FATAL_DETECT }, { MHI_PM_M3_EXIT, @@ -103,7 +104,7 @@ static struct mhi_pm_transitions const dev_state_transitions[] = { /* L3 States */ { MHI_PM_LD_ERR_FATAL_DETECT, - MHI_PM_LD_ERR_FATAL_DETECT | MHI_PM_SHUTDOWN_PROCESS + MHI_PM_LD_ERR_FATAL_DETECT | MHI_PM_DISABLE }, }; @@ -445,10 +446,9 @@ error_mission_mode: } /* Handle shutdown transitions */ -static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl, - enum mhi_pm_state transition_state) +static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl) { - enum mhi_pm_state cur_state, prev_state; + enum mhi_pm_state cur_state; struct mhi_event *mhi_event; struct mhi_cmd_ctxt *cmd_ctxt; struct mhi_cmd *mhi_cmd; @@ -456,33 +456,13 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl, struct device *dev = &mhi_cntrl->mhi_dev->dev; int ret, i; - dev_dbg(dev, "Transitioning from PM state: %s to: %s\n", - to_mhi_pm_state_str(mhi_cntrl->pm_state), - to_mhi_pm_state_str(transition_state)); + dev_dbg(dev, "Processing disable transition with PM state: %s\n", + to_mhi_pm_state_str(mhi_cntrl->pm_state)); mutex_lock(&mhi_cntrl->pm_mutex); - write_lock_irq(&mhi_cntrl->pm_lock); - prev_state = mhi_cntrl->pm_state; - cur_state = mhi_tryset_pm_state(mhi_cntrl, transition_state); - if (cur_state == transition_state) { - mhi_cntrl->ee = MHI_EE_DISABLE_TRANSITION; - mhi_cntrl->dev_state = MHI_STATE_RESET; - } - write_unlock_irq(&mhi_cntrl->pm_lock); - - /* Wake up threads waiting for state transition */ - wake_up_all(&mhi_cntrl->state_event); - - if (cur_state != transition_state) { - dev_err(dev, "Failed to transition to state: %s from: %s\n", - to_mhi_pm_state_str(transition_state), - to_mhi_pm_state_str(cur_state)); - mutex_unlock(&mhi_cntrl->pm_mutex); - return; - } /* Trigger MHI RESET so that the device will not access host memory */ - if (MHI_REG_ACCESS_VALID(prev_state)) { + if (!MHI_PM_IN_FATAL_STATE(mhi_cntrl->pm_state)) { u32 in_reset = -1; unsigned long timeout = msecs_to_jiffies(mhi_cntrl->timeout_ms); @@ -785,8 +765,7 @@ void mhi_pm_st_worker(struct work_struct *work) mhi_pm_sys_error_transition(mhi_cntrl); break; case DEV_ST_TRANSITION_DISABLE: - mhi_pm_disable_transition - (mhi_cntrl, MHI_PM_SHUTDOWN_PROCESS); + mhi_pm_disable_transition(mhi_cntrl); break; default: break; @@ -1153,23 +1132,33 @@ EXPORT_SYMBOL_GPL(mhi_async_power_up); void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful) { - enum mhi_pm_state cur_state; + enum mhi_pm_state cur_state, transition_state; struct device *dev = &mhi_cntrl->mhi_dev->dev; /* If it's not a graceful shutdown, force MHI to linkdown state */ - if (!graceful) { - mutex_lock(&mhi_cntrl->pm_mutex); - write_lock_irq(&mhi_cntrl->pm_lock); - cur_state = mhi_tryset_pm_state(mhi_cntrl, - MHI_PM_LD_ERR_FATAL_DETECT); - write_unlock_irq(&mhi_cntrl->pm_lock); - mutex_unlock(&mhi_cntrl->pm_mutex); - if (cur_state != MHI_PM_LD_ERR_FATAL_DETECT) - dev_dbg(dev, "Failed to move to state: %s from: %s\n", - to_mhi_pm_state_str(MHI_PM_LD_ERR_FATAL_DETECT), - to_mhi_pm_state_str(mhi_cntrl->pm_state)); + transition_state = (graceful) ? MHI_PM_SHUTDOWN_PROCESS : + MHI_PM_LD_ERR_FATAL_DETECT; + + mutex_lock(&mhi_cntrl->pm_mutex); + write_lock_irq(&mhi_cntrl->pm_lock); + cur_state = mhi_tryset_pm_state(mhi_cntrl, transition_state); + if (cur_state != transition_state) { + dev_err(dev, "Failed to move to state: %s from: %s\n", + to_mhi_pm_state_str(transition_state), + to_mhi_pm_state_str(mhi_cntrl->pm_state)); + /* Force link down or error fatal detected state */ + mhi_cntrl->pm_state = MHI_PM_LD_ERR_FATAL_DETECT; } + /* mark device inactive to avoid any further host processing */ + mhi_cntrl->ee = MHI_EE_DISABLE_TRANSITION; + mhi_cntrl->dev_state = MHI_STATE_RESET; + + wake_up_all(&mhi_cntrl->state_event); + + write_unlock_irq(&mhi_cntrl->pm_lock); + mutex_unlock(&mhi_cntrl->pm_mutex); + mhi_queue_state_transition(mhi_cntrl, DEV_ST_TRANSITION_DISABLE); /* Wait for shutdown to complete */ -- cgit v1.2.3 From 6cc1716102b55497dd557e8295a3177315332f9a Mon Sep 17 00:00:00 2001 From: Bhaumik Bhatt Date: Mon, 9 Nov 2020 12:47:31 -0800 Subject: bus: mhi: core: Remove MHI event ring IRQ handlers when powering down While powering down, the device may or may not acknowledge an MHI RESET issued by host for a graceful shutdown scenario and end up sending an incoming data packet after tasklets have been killed. If a rogue device sends this interrupt for a data transfer event ring update, it can result in a tasklet getting scheduled while a clean up is ongoing or has completed and cause access to freed memory leading to a NULL pointer exception. Remove the interrupt handlers for MHI event rings early on to avoid this scenario. Signed-off-by: Bhaumik Bhatt Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/pm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/pm.c b/drivers/bus/mhi/core/pm.c index ffbf6f539510..a671f585ce35 100644 --- a/drivers/bus/mhi/core/pm.c +++ b/drivers/bus/mhi/core/pm.c @@ -494,6 +494,7 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl) for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) { if (mhi_event->offload_ev) continue; + free_irq(mhi_cntrl->irq[mhi_event->irq], mhi_event); tasklet_kill(&mhi_event->task); } @@ -1164,7 +1165,7 @@ void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful) /* Wait for shutdown to complete */ flush_work(&mhi_cntrl->st_worker); - mhi_deinit_free_irq(mhi_cntrl); + free_irq(mhi_cntrl->irq[0], mhi_cntrl); if (!mhi_cntrl->pre_init) { /* Free all allocated resources */ -- cgit v1.2.3 From 206e7383b34316cf56cdde96eab9d97e9a1dbd70 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Thu, 26 Nov 2020 11:20:35 +0100 Subject: bus: mhi: core: Indexed MHI controller name Today the MHI controller name is simply cloned from the underlying bus device (its parent), that gives the following device structure for e.g. a MHI/PCI controller: devices/pci0000:00/0000:00:01.2/0000:02:00.0/0000:02:00.0 devices/pci0000:00/0000:00:01.2/0000:02:00.0/0000:02:00.0/0000:02:00.0_IPCR ... That's quite misleading/confusing and can cause device registering issues because of duplicate dev name (e.g. if a PCI device register two different MHI instances). This patch changes MHI core to create indexed mhi controller names (mhi0, mhi1...) in the same way as other busses (i2c0, usb0...). The previous example becomes: devices/pci0000:00/0000:00:01.2/0000:02:00.0/mhi0 devices/pci0000:00/0000:00:01.2/0000:02:00.0/mhi0/mhi0_IPCR ... v2: move index field at the end of mhi_controller struct (before bool) to avoid breaking well packed alignment. Signed-off-by: Loic Poulain Reviewed-by: Jeffrey Hugo Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/init.c | 18 ++++++++++++++++-- drivers/bus/mhi/core/main.c | 2 +- include/linux/mhi.h | 2 ++ 3 files changed, 19 insertions(+), 3 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/init.c b/drivers/bus/mhi/core/init.c index 655d539c6808..1d6f7b6c1fcd 100644 --- a/drivers/bus/mhi/core/init.c +++ b/drivers/bus/mhi/core/init.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,8 @@ #include #include "internal.h" +static DEFINE_IDA(mhi_controller_ida); + const char * const mhi_ee_str[MHI_EE_MAX] = { [MHI_EE_PBL] = "PBL", [MHI_EE_SBL] = "SBL", @@ -940,6 +943,12 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, mhi_cntrl->minor_version = (soc_info & SOC_HW_VERSION_MINOR_VER_BMSK) >> SOC_HW_VERSION_MINOR_VER_SHFT; + mhi_cntrl->index = ida_alloc(&mhi_controller_ida, GFP_KERNEL); + if (mhi_cntrl->index < 0) { + ret = mhi_cntrl->index; + goto error_ida_alloc; + } + /* Register controller with MHI bus */ mhi_dev = mhi_alloc_device(mhi_cntrl); if (IS_ERR(mhi_dev)) { @@ -950,8 +959,8 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, mhi_dev->dev_type = MHI_DEVICE_CONTROLLER; mhi_dev->mhi_cntrl = mhi_cntrl; - dev_set_name(&mhi_dev->dev, "%s", dev_name(mhi_cntrl->cntrl_dev)); - mhi_dev->name = dev_name(mhi_cntrl->cntrl_dev); + dev_set_name(&mhi_dev->dev, "mhi%d", mhi_cntrl->index); + mhi_dev->name = dev_name(&mhi_dev->dev); /* Init wakeup source */ device_init_wakeup(&mhi_dev->dev, true); @@ -970,6 +979,9 @@ error_add_dev: put_device(&mhi_dev->dev); error_alloc_dev: + ida_free(&mhi_controller_ida, mhi_cntrl->index); + +error_ida_alloc: kfree(mhi_cntrl->mhi_cmd); error_alloc_cmd: @@ -1004,6 +1016,8 @@ void mhi_unregister_controller(struct mhi_controller *mhi_cntrl) device_del(&mhi_dev->dev); put_device(&mhi_dev->dev); + + ida_free(&mhi_controller_ida, mhi_cntrl->index); } EXPORT_SYMBOL_GPL(mhi_unregister_controller); diff --git a/drivers/bus/mhi/core/main.c b/drivers/bus/mhi/core/main.c index 4eb93d8bea1d..702c31b6aefa 100644 --- a/drivers/bus/mhi/core/main.c +++ b/drivers/bus/mhi/core/main.c @@ -331,7 +331,7 @@ void mhi_create_devices(struct mhi_controller *mhi_cntrl) /* Channel name is same for both UL and DL */ mhi_dev->name = mhi_chan->name; dev_set_name(&mhi_dev->dev, "%s_%s", - dev_name(mhi_cntrl->cntrl_dev), + dev_name(&mhi_cntrl->mhi_dev->dev), mhi_dev->name); /* Init wakeup source if available */ diff --git a/include/linux/mhi.h b/include/linux/mhi.h index d31efcf02ae7..aa9757e71f1f 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -348,6 +348,7 @@ struct mhi_controller_config { * @read_reg: Read a MHI register via the physical link (required) * @write_reg: Write a MHI register via the physical link (required) * @buffer_len: Bounce buffer length + * @index: Index of the MHI controller instance * @bounce_buf: Use of bounce buffer * @fbc_download: MHI host needs to do complete image transfer (optional) * @pre_init: MHI host needs to do pre-initialization before power up @@ -438,6 +439,7 @@ struct mhi_controller { u32 val); size_t buffer_len; + int index; bool bounce_buf; bool fbc_download; bool pre_init; -- cgit v1.2.3 From 10ea8bcda5ae5463889e15dfc98aa2a73270ea58 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Wed, 25 Nov 2020 14:24:49 +0100 Subject: bus: mhi: core: Fix device hierarchy This patch fixes the hierarchical structure of MHI devices. Indeed, MHI client devices are directly 'enumerated' from the mhi controller and therefore must be direct descendants/children of their mhi controller device, in accordance with the Linux Device Model. Today both MHI clients and controller devices are at the same level, this patch ensures that MHI controller is parent of its client devices. The hierarchy is especially important for power management (safe suspend/resume order). It is also useful for userspace to determine relationship between MHI client devices and controllers. Signed-off-by: Loic Poulain Reviewed-by: Bjorn Andersson Reviewed-by: Manivannan Sadhasivam Reviewed-by: Hemant Kumar Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/debugfs.c | 4 +++- drivers/bus/mhi/core/init.c | 10 +++++++++- drivers/bus/mhi/core/pm.c | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/debugfs.c b/drivers/bus/mhi/core/debugfs.c index 3a48801e01f4..7d43138ce66d 100644 --- a/drivers/bus/mhi/core/debugfs.c +++ b/drivers/bus/mhi/core/debugfs.c @@ -159,7 +159,9 @@ static int mhi_debugfs_devices_show(struct seq_file *m, void *d) return -ENODEV; } - device_for_each_child(mhi_cntrl->cntrl_dev, m, mhi_device_info_show); + /* Show controller and client(s) info */ + mhi_device_info_show(&mhi_cntrl->mhi_dev->dev, m); + device_for_each_child(&mhi_cntrl->mhi_dev->dev, m, mhi_device_info_show); return 0; } diff --git a/drivers/bus/mhi/core/init.c b/drivers/bus/mhi/core/init.c index 1d6f7b6c1fcd..96cde9c0034c 100644 --- a/drivers/bus/mhi/core/init.c +++ b/drivers/bus/mhi/core/init.c @@ -1144,7 +1144,15 @@ struct mhi_device *mhi_alloc_device(struct mhi_controller *mhi_cntrl) device_initialize(dev); dev->bus = &mhi_bus_type; dev->release = mhi_release_device; - dev->parent = mhi_cntrl->cntrl_dev; + + if (mhi_cntrl->mhi_dev) { + /* for MHI client devices, parent is the MHI controller device */ + dev->parent = &mhi_cntrl->mhi_dev->dev; + } else { + /* for MHI controller device, parent is the bus device (e.g. pci device) */ + dev->parent = mhi_cntrl->cntrl_dev; + } + mhi_dev->mhi_cntrl = mhi_cntrl; mhi_dev->dev_wake = 0; diff --git a/drivers/bus/mhi/core/pm.c b/drivers/bus/mhi/core/pm.c index a671f585ce35..681960c72d2a 100644 --- a/drivers/bus/mhi/core/pm.c +++ b/drivers/bus/mhi/core/pm.c @@ -504,7 +504,7 @@ static void mhi_pm_disable_transition(struct mhi_controller *mhi_cntrl) wake_up_all(&mhi_cntrl->state_event); dev_dbg(dev, "Reset all active channels and remove MHI devices\n"); - device_for_each_child(mhi_cntrl->cntrl_dev, NULL, mhi_destroy_device); + device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device); mutex_lock(&mhi_cntrl->pm_mutex); @@ -637,7 +637,7 @@ static void mhi_pm_sys_error_transition(struct mhi_controller *mhi_cntrl) wake_up_all(&mhi_cntrl->state_event); dev_dbg(dev, "Reset all active channels and remove MHI devices\n"); - device_for_each_child(mhi_cntrl->cntrl_dev, NULL, mhi_destroy_device); + device_for_each_child(&mhi_cntrl->mhi_dev->dev, NULL, mhi_destroy_device); mutex_lock(&mhi_cntrl->pm_mutex); -- cgit v1.2.3 From 89828f632dec8ab1e19f23771b313c1942e102c0 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 1 Dec 2020 10:02:54 +0300 Subject: bus: mhi: core: Fix error handling in mhi_register_controller() There are a few problems with the error handling in this function. They mostly center around the alloc_ordered_workqueue() allocation. 1) If that allocation fails or if the kcalloc() prior to it fails then it leads to a NULL dereference when we call destroy_workqueue(mhi_cntrl->hiprio_wq). 2) The error code is not set. 3) The "mhi_cntrl->mhi_cmd" allocation is not freed. The error handling was slightly confusing and I re-ordered it to be in the exact mirror/reverse order of how things were allocated. I changed the label names to say what the goto does instead of describing where the goto comes from. Fixes: 8f7039787687 ("bus: mhi: core: Move to using high priority workqueue") Signed-off-by: Dan Carpenter Reviewed-by: Loic Poulain Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/core/init.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/core/init.c b/drivers/bus/mhi/core/init.c index 96cde9c0034c..f0697f433c2f 100644 --- a/drivers/bus/mhi/core/init.c +++ b/drivers/bus/mhi/core/init.c @@ -871,7 +871,7 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, sizeof(*mhi_cntrl->mhi_cmd), GFP_KERNEL); if (!mhi_cntrl->mhi_cmd) { ret = -ENOMEM; - goto error_alloc_cmd; + goto err_free_event; } INIT_LIST_HEAD(&mhi_cntrl->transition_list); @@ -886,7 +886,8 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, ("mhi_hiprio_wq", WQ_MEM_RECLAIM | WQ_HIGHPRI); if (!mhi_cntrl->hiprio_wq) { dev_err(mhi_cntrl->cntrl_dev, "Failed to allocate workqueue\n"); - goto error_alloc_cmd; + ret = -ENOMEM; + goto err_free_cmd; } mhi_cmd = mhi_cntrl->mhi_cmd; @@ -932,7 +933,7 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, ret = mhi_read_reg(mhi_cntrl, mhi_cntrl->regs, SOC_HW_VERSION_OFFS, &soc_info); if (ret) - goto error_alloc_dev; + goto err_destroy_wq; mhi_cntrl->family_number = (soc_info & SOC_HW_VERSION_FAM_NUM_BMSK) >> SOC_HW_VERSION_FAM_NUM_SHFT; @@ -946,7 +947,7 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, mhi_cntrl->index = ida_alloc(&mhi_controller_ida, GFP_KERNEL); if (mhi_cntrl->index < 0) { ret = mhi_cntrl->index; - goto error_ida_alloc; + goto err_destroy_wq; } /* Register controller with MHI bus */ @@ -954,7 +955,7 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, if (IS_ERR(mhi_dev)) { dev_err(mhi_cntrl->cntrl_dev, "Failed to allocate MHI device\n"); ret = PTR_ERR(mhi_dev); - goto error_alloc_dev; + goto err_ida_free; } mhi_dev->dev_type = MHI_DEVICE_CONTROLLER; @@ -967,7 +968,7 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, ret = device_add(&mhi_dev->dev); if (ret) - goto error_add_dev; + goto err_release_dev; mhi_cntrl->mhi_dev = mhi_dev; @@ -975,19 +976,17 @@ int mhi_register_controller(struct mhi_controller *mhi_cntrl, return 0; -error_add_dev: +err_release_dev: put_device(&mhi_dev->dev); - -error_alloc_dev: +err_ida_free: ida_free(&mhi_controller_ida, mhi_cntrl->index); - -error_ida_alloc: +err_destroy_wq: + destroy_workqueue(mhi_cntrl->hiprio_wq); +err_free_cmd: kfree(mhi_cntrl->mhi_cmd); - -error_alloc_cmd: - vfree(mhi_cntrl->mhi_chan); +err_free_event: kfree(mhi_cntrl->mhi_event); - destroy_workqueue(mhi_cntrl->hiprio_wq); + vfree(mhi_cntrl->mhi_chan); return ret; } -- cgit v1.2.3 From 4ea6fa2cb921cb17812501a27c3761037d64a217 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Wed, 2 Dec 2020 09:12:30 +0100 Subject: mhi: pci_generic: Fix implicit conversion warning Fix the following warning with explicit cast: warning: implicit conversion from 'unsigned long long' to 'dma_addr_t' (aka 'unsigned int') mhi_cntrl->iova_stop = DMA_BIT_MASK(info->dma_data_width); Fixes: 855a70c12021 ("bus: mhi: Add MHI PCI support for WWAN modems") Signed-off-by: Loic Poulain Reported-by: kernel test robot Reviewed-by: Nathan Chancellor Reviewed-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam --- drivers/bus/mhi/pci_generic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/bus') diff --git a/drivers/bus/mhi/pci_generic.c b/drivers/bus/mhi/pci_generic.c index e3df838c3c80..f5bee76ea061 100644 --- a/drivers/bus/mhi/pci_generic.c +++ b/drivers/bus/mhi/pci_generic.c @@ -273,7 +273,7 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) mhi_cntrl_config = info->config; mhi_cntrl->cntrl_dev = &pdev->dev; mhi_cntrl->iova_start = 0; - mhi_cntrl->iova_stop = DMA_BIT_MASK(info->dma_data_width); + mhi_cntrl->iova_stop = (dma_addr_t)DMA_BIT_MASK(info->dma_data_width); mhi_cntrl->fw_image = info->fw; mhi_cntrl->edl_image = info->edl; -- cgit v1.2.3