* Re: [PATCH v8 1/4] coresight: cti: Convert trigger usage fields to dynamic bitmaps and arrays
From: Jie Gan @ 2026-04-27 2:47 UTC (permalink / raw)
To: Yingchao Deng, Suzuki K Poulose, Mike Leach, James Clark, Leo Yan,
Alexander Shishkin
Cc: coresight, linux-arm-kernel, linux-kernel, linux-arm-msm,
quic_yingdeng, Jinlong Mao, Tingwei Zhang
In-Reply-To: <1c99a162-475e-4d6c-af85-a16322d31476@oss.qualcomm.com>
On 4/27/2026 9:48 AM, Jie Gan wrote:
>
>
> On 4/26/2026 5:44 PM, Yingchao Deng wrote:
>> Replace the fixed-size u32 fields in the cti_config and cti_trig_grp
>> structure with dynamically allocated bitmaps and arrays. This allows
>> memory to be allocated based on the actual number of triggers during
>> probe
>> time, reducing memory footprint and improving scalability for platforms
>> with varying trigger counts.
>>
>> Signed-off-by: Yingchao Deng <yingchao.deng@oss.qualcomm.com>
>> ---
>> drivers/hwtracing/coresight/coresight-cti-core.c | 59 ++++++++++++
>> +++++-----
>> .../hwtracing/coresight/coresight-cti-platform.c | 26 +++++++---
>> drivers/hwtracing/coresight/coresight-cti-sysfs.c | 14 ++---
>> drivers/hwtracing/coresight/coresight-cti.h | 12 ++---
>> 4 files changed, 76 insertions(+), 35 deletions(-)
>>
>> diff --git a/drivers/hwtracing/coresight/coresight-cti-core.c b/
>> drivers/hwtracing/coresight/coresight-cti-core.c
>> index 2f4c9362709a..4e7d12bd2d3e 100644
>> --- a/drivers/hwtracing/coresight/coresight-cti-core.c
>> +++ b/drivers/hwtracing/coresight/coresight-cti-core.c
>> @@ -161,8 +161,8 @@ void cti_write_intack(struct device *dev, u32 ackval)
>> /* DEVID[19:16] - number of CTM channels */
>> #define CTI_DEVID_CTMCHANNELS(devid_val) ((int) BMVAL(devid_val, 16,
>> 19))
>> -static void cti_set_default_config(struct device *dev,
>> - struct cti_drvdata *drvdata)
>> +static int cti_set_default_config(struct device *dev,
>> + struct cti_drvdata *drvdata)
>> {
>> struct cti_config *config = &drvdata->config;
>> u32 devid;
>> @@ -181,6 +181,26 @@ static void cti_set_default_config(struct device
>> *dev,
>> config->nr_trig_max = CTIINOUTEN_MAX;
>> }
>> + config->trig_in_use = devm_bitmap_zalloc(dev, config-
>> >nr_trig_max, GFP_KERNEL);
>> + if (!config->trig_in_use)
>> + return -ENOMEM;
>> +
>> + config->trig_out_use = devm_bitmap_zalloc(dev, config-
>> >nr_trig_max, GFP_KERNEL);
>> + if (!config->trig_out_use)
>> + return -ENOMEM;
>> +
>> + config->trig_out_filter = devm_bitmap_zalloc(dev, config-
>> >nr_trig_max, GFP_KERNEL);
>> + if (!config->trig_out_filter)
>> + return -ENOMEM;
>> +
>> + config->ctiinen = devm_kcalloc(dev, config->nr_trig_max,
>> sizeof(u32), GFP_KERNEL);
>> + if (!config->ctiinen)
>> + return -ENOMEM;
>> +
>> + config->ctiouten = devm_kcalloc(dev, config->nr_trig_max,
>> sizeof(u32), GFP_KERNEL);
>> + if (!config->ctiouten)
>> + return -ENOMEM;
>> +
>> config->nr_ctm_channels = CTI_DEVID_CTMCHANNELS(devid);
>> /* Most regs default to 0 as zalloc'ed except...*/
>> @@ -189,6 +209,7 @@ static void cti_set_default_config(struct device
>> *dev,
>> config->enable_req_count = 0;
>> config->asicctl_impl = !!FIELD_GET(GENMASK(4, 0), devid);
>> + return 0;
>> }
>> /*
>> @@ -219,8 +240,10 @@ int cti_add_connection_entry(struct device *dev,
>> struct cti_drvdata *drvdata,
>> cti_dev->nr_trig_con++;
>> /* add connection usage bit info to overall info */
>> - drvdata->config.trig_in_use |= tc->con_in->used_mask;
>> - drvdata->config.trig_out_use |= tc->con_out->used_mask;
>> + bitmap_or(drvdata->config.trig_in_use, drvdata->config.trig_in_use,
>> + tc->con_in->used_mask, drvdata->config.nr_trig_max);
>> + bitmap_or(drvdata->config.trig_out_use, drvdata-
>> >config.trig_out_use,
>> + tc->con_out->used_mask, drvdata->config.nr_trig_max);
>> return 0;
>> }
>> @@ -231,6 +254,8 @@ struct cti_trig_con *cti_allocate_trig_con(struct
>> device *dev, int in_sigs,
>> {
>> struct cti_trig_con *tc = NULL;
>> struct cti_trig_grp *in = NULL, *out = NULL;
>> + struct cti_drvdata *drvdata = dev_get_drvdata(dev);
>> + int n_trigs = drvdata->config.nr_trig_max;
>> tc = devm_kzalloc(dev, sizeof(struct cti_trig_con), GFP_KERNEL);
>> if (!tc)
>> @@ -242,12 +267,20 @@ struct cti_trig_con
>> *cti_allocate_trig_con(struct device *dev, int in_sigs,
>> if (!in)
>> return NULL;
>> + in->used_mask = devm_bitmap_zalloc(dev, n_trigs, GFP_KERNEL);
>> + if (!in->used_mask)
>> + return NULL;
>> +
>> out = devm_kzalloc(dev,
>> offsetof(struct cti_trig_grp, sig_types[out_sigs]),
>> GFP_KERNEL);
>> if (!out)
>> return NULL;
>> + out->used_mask = devm_bitmap_zalloc(dev, n_trigs, GFP_KERNEL);
>> + if (!out->used_mask)
>> + return NULL;
>> +
>> tc->con_in = in;
>> tc->con_out = out;
>> tc->con_in->nr_sigs = in_sigs;
>> @@ -263,7 +296,6 @@ int cti_add_default_connection(struct device *dev,
>> struct cti_drvdata *drvdata)
>> {
>> int ret = 0;
>> int n_trigs = drvdata->config.nr_trig_max;
>> - u32 n_trig_mask = GENMASK(n_trigs - 1, 0);
>> struct cti_trig_con *tc = NULL;
>> /*
>> @@ -274,8 +306,8 @@ int cti_add_default_connection(struct device *dev,
>> struct cti_drvdata *drvdata)
>> if (!tc)
>> return -ENOMEM;
>> - tc->con_in->used_mask = n_trig_mask;
>> - tc->con_out->used_mask = n_trig_mask;
>> + bitmap_fill(tc->con_in->used_mask, n_trigs);
>> + bitmap_fill(tc->con_out->used_mask, n_trigs);
>> ret = cti_add_connection_entry(dev, drvdata, tc, NULL, "default");
>> return ret;
>> }
>> @@ -288,7 +320,6 @@ int cti_channel_trig_op(struct device *dev, enum
>> cti_chan_op op,
>> {
>> struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
>> struct cti_config *config = &drvdata->config;
>> - u32 trig_bitmask;
>> u32 chan_bitmask;
>> u32 reg_value;
>> int reg_offset;
>> @@ -298,18 +329,16 @@ int cti_channel_trig_op(struct device *dev, enum
>> cti_chan_op op,
>> (trigger_idx >= config->nr_trig_max))
>> return -EINVAL;
>> - trig_bitmask = BIT(trigger_idx);
>> -
>> /* ensure registered triggers and not out filtered */
>> if (direction == CTI_TRIG_IN) {
>> - if (!(trig_bitmask & config->trig_in_use))
>> + if (!(test_bit(trigger_idx, config->trig_in_use)))
>> return -EINVAL;
>> } else {
>> - if (!(trig_bitmask & config->trig_out_use))
>> + if (!(test_bit(trigger_idx, config->trig_out_use)))
>> return -EINVAL;
>> if ((config->trig_filter_enable) &&
>> - (config->trig_out_filter & trig_bitmask))
>> + test_bit(trigger_idx, config->trig_out_filter))
>> return -EINVAL;
>> }
>> @@ -687,7 +716,9 @@ static int cti_probe(struct amba_device *adev,
>> const struct amba_id *id)
>> raw_spin_lock_init(&drvdata->spinlock);
>> /* initialise CTI driver config values */
>> - cti_set_default_config(dev, drvdata);
>> + ret = cti_set_default_config(dev, drvdata);
>> + if (ret)
>> + return ret;
>> pdata = coresight_cti_get_platform_data(dev);
>> if (IS_ERR(pdata)) {
>> diff --git a/drivers/hwtracing/coresight/coresight-cti-platform.c b/
>> drivers/hwtracing/coresight/coresight-cti-platform.c
>> index 4eff96f48594..557debbc8ca4 100644
>> --- a/drivers/hwtracing/coresight/coresight-cti-platform.c
>> +++ b/drivers/hwtracing/coresight/coresight-cti-platform.c
>> @@ -136,8 +136,8 @@ static int
>> cti_plat_create_v8_etm_connection(struct device *dev,
>> goto create_v8_etm_out;
>> /* build connection data */
>> - tc->con_in->used_mask = 0xF0; /* sigs <4,5,6,7> */
>> - tc->con_out->used_mask = 0xF0; /* sigs <4,5,6,7> */
>> + bitmap_set(tc->con_in->used_mask, 4, 4); /* sigs <4,5,6,7> */
>> + bitmap_set(tc->con_out->used_mask, 4, 4); /* sigs <4,5,6,7> */
>> /*
>> * The EXTOUT type signals from the ETM are connected to a set
>> of input
>> @@ -194,10 +194,10 @@ static int cti_plat_create_v8_connections(struct
>> device *dev,
>> goto of_create_v8_out;
>> /* Set the v8 PE CTI connection data */
>> - tc->con_in->used_mask = 0x3; /* sigs <0 1> */
>> + bitmap_set(tc->con_in->used_mask, 0, 2); /* sigs <0 1> */
>> tc->con_in->sig_types[0] = PE_DBGTRIGGER;
>> tc->con_in->sig_types[1] = PE_PMUIRQ;
>> - tc->con_out->used_mask = 0x7; /* sigs <0 1 2 > */
>> + bitmap_set(tc->con_out->used_mask, 0, 3); /* sigs <0 1 2 > */
>> tc->con_out->sig_types[0] = PE_EDBGREQ;
>> tc->con_out->sig_types[1] = PE_DBGRESTART;
>> tc->con_out->sig_types[2] = PE_CTIIRQ;
>> @@ -213,7 +213,7 @@ static int cti_plat_create_v8_connections(struct
>> device *dev,
>> goto of_create_v8_out;
>> /* filter pe_edbgreq - PE trigout sig <0> */
>> - drvdata->config.trig_out_filter |= 0x1;
>> + set_bit(0, drvdata->config.trig_out_filter);
>> of_create_v8_out:
>> return ret;
>> @@ -257,7 +257,7 @@ static int cti_plat_read_trig_group(struct
>> cti_trig_grp *tgrp,
>> if (!err) {
>> /* set the signal usage mask */
>> for (idx = 0; idx < tgrp->nr_sigs; idx++)
>> - tgrp->used_mask |= BIT(values[idx]);
>> + set_bit(values[idx], tgrp->used_mask);
>> }
>> kfree(values);
>> @@ -316,23 +316,33 @@ static int cti_plat_process_filter_sigs(struct
>> cti_drvdata *drvdata,
>> {
>> struct cti_trig_grp *tg = NULL;
>> int err = 0, nr_filter_sigs;
>> + int nr_trigs = drvdata->config.nr_trig_max;
>> nr_filter_sigs = cti_plat_count_sig_elements(fwnode,
>> CTI_DT_FILTER_OUT_SIGS);
>> if (nr_filter_sigs == 0)
>> return 0;
>> - if (nr_filter_sigs > drvdata->config.nr_trig_max)
>> + if (nr_filter_sigs > nr_trigs)
>> return -EINVAL;
>> tg = kzalloc_obj(*tg);
>> if (!tg)
>> return -ENOMEM;
>> + tg->used_mask = bitmap_zalloc(nr_trigs, GFP_KERNEL);
>> + if (!tg->used_mask) {
>> + kfree(tg);
>> + return -ENOMEM;
>> + }
>> +
>> err = cti_plat_read_trig_group(tg, fwnode, CTI_DT_FILTER_OUT_SIGS);
>> if (!err)
>> - drvdata->config.trig_out_filter |= tg->used_mask;
>> + bitmap_or(drvdata->config.trig_out_filter,
>> + drvdata->config.trig_out_filter,
>> + tg->used_mask, nr_trigs);
>
> The error may be silently ignored when a memory allocation error
> occured. I think it's better to add a log print to tell user what happened.
My fault here, please ignore this comment.
This error will be handled by probe fail process.
Thanks,
Jie
>
> Thanks,
> Jie
>
>> + bitmap_free(tg->used_mask);
>> kfree(tg);
>> return err;
>> }
>> diff --git a/drivers/hwtracing/coresight/coresight-cti-sysfs.c b/
>> drivers/hwtracing/coresight/coresight-cti-sysfs.c
>> index 3fe2c916d228..2bbfa405cb6b 100644
>> --- a/drivers/hwtracing/coresight/coresight-cti-sysfs.c
>> +++ b/drivers/hwtracing/coresight/coresight-cti-sysfs.c
>> @@ -719,12 +719,12 @@ static ssize_t trigout_filtered_show(struct
>> device *dev,
>> struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
>> struct cti_config *cfg = &drvdata->config;
>> int nr_trig_max = cfg->nr_trig_max;
>> - unsigned long mask = cfg->trig_out_filter;
>> + unsigned long *mask = cfg->trig_out_filter;
>> - if (mask == 0)
>> + if (bitmap_empty(mask, nr_trig_max))
>> return 0;
>> - return sysfs_emit(buf, "%*pbl\n", nr_trig_max, &mask);
>> + return sysfs_emit(buf, "%*pbl\n", nr_trig_max, mask);
>> }
>> static DEVICE_ATTR_RO(trigout_filtered);
>> @@ -931,9 +931,9 @@ static ssize_t trigin_sig_show(struct device *dev,
>> struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var;
>> struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
>> struct cti_config *cfg = &drvdata->config;
>> - unsigned long mask = con->con_in->used_mask;
>> + unsigned long *mask = con->con_in->used_mask;
>> - return sysfs_emit(buf, "%*pbl\n", cfg->nr_trig_max, &mask);
>> + return sysfs_emit(buf, "%*pbl\n", cfg->nr_trig_max, mask);
>> }
>> static ssize_t trigout_sig_show(struct device *dev,
>> @@ -945,9 +945,9 @@ static ssize_t trigout_sig_show(struct device *dev,
>> struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var;
>> struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
>> struct cti_config *cfg = &drvdata->config;
>> - unsigned long mask = con->con_out->used_mask;
>> + unsigned long *mask = con->con_out->used_mask;
>> - return sysfs_emit(buf, "%*pbl\n", cfg->nr_trig_max, &mask);
>> + return sysfs_emit(buf, "%*pbl\n", cfg->nr_trig_max, mask);
>> }
>> /* convert a sig type id to a name */
>> diff --git a/drivers/hwtracing/coresight/coresight-cti.h b/drivers/
>> hwtracing/coresight/coresight-cti.h
>> index c5f9e79fabc6..ef079fc18b72 100644
>> --- a/drivers/hwtracing/coresight/coresight-cti.h
>> +++ b/drivers/hwtracing/coresight/coresight-cti.h
>> @@ -68,7 +68,7 @@ struct fwnode_handle;
>> */
>> struct cti_trig_grp {
>> int nr_sigs;
>> - u32 used_mask;
>> + unsigned long *used_mask;
>> int sig_types[];
>> };
>> @@ -145,17 +145,17 @@ struct cti_config {
>> int enable_req_count;
>> /* registered triggers and filtering */
>> - u32 trig_in_use;
>> - u32 trig_out_use;
>> - u32 trig_out_filter;
>> + unsigned long *trig_in_use;
>> + unsigned long *trig_out_use;
>> + unsigned long *trig_out_filter;
>> bool trig_filter_enable;
>> u8 xtrig_rchan_sel;
>> /* cti cross trig programmable regs */
>> u32 ctiappset;
>> u8 ctiinout_sel;
>> - u32 ctiinen[CTIINOUTEN_MAX];
>> - u32 ctiouten[CTIINOUTEN_MAX];
>> + u32 *ctiinen;
>> + u32 *ctiouten;
>> u32 ctigate;
>> u32 asicctl;
>> };
>>
>
^ permalink raw reply
* RE: [PATCH v1 1/3] dt-bindings: PCI: imx6q-pcie: Add intr, aer and pme interrupts
From: Hongxing Zhu @ 2026-04-27 3:17 UTC (permalink / raw)
To: Conor Dooley
Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
bhelgaas@google.com, Frank Li, l.stach@pengutronix.de,
lpieralisi@kernel.org, kwilczynski@kernel.org, mani@kernel.org,
s.hauer@pengutronix.de, kernel@pengutronix.de, festevam@gmail.com,
linux-pci@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
devicetree@vger.kernel.org, imx@lists.linux.dev,
linux-kernel@vger.kernel.org
In-Reply-To: <20260424-sinless-unfiled-d1087a894da5@spud>
> -----Original Message-----
> From: Conor Dooley <conor@kernel.org>
> Sent: Saturday, April 25, 2026 1:06 AM
> To: Hongxing Zhu <hongxing.zhu@nxp.com>
> Cc: robh@kernel.org; krzk+dt@kernel.org; conor+dt@kernel.org;
> bhelgaas@google.com; Frank Li <frank.li@nxp.com>; l.stach@pengutronix.de;
> lpieralisi@kernel.org; kwilczynski@kernel.org; mani@kernel.org;
> s.hauer@pengutronix.de; kernel@pengutronix.de; festevam@gmail.com; linux-
> pci@vger.kernel.org; linux-arm-kernel@lists.infradead.org;
> devicetree@vger.kernel.org; imx@lists.linux.dev; linux-kernel@vger.kernel.org
> Subject: Re: [PATCH v1 1/3] dt-bindings: PCI: imx6q-pcie: Add intr, aer and pme
> interrupts
>
> On Fri, Apr 24, 2026 at 10:57:33AM +0800, Richard Zhu wrote:
> > Add optional 'intr', 'aer', and 'pme' interrupt entries to the i.MX6Q
> > PCIe binding to support PCIe event-based interrupts for general
> > controller events, Advanced Error Reporting, and Power Management
> > Events respectively.
> >
> > Signed-off-by: Richard Zhu <hongxing.zhu@nxp.com>
> > ---
>
> This binding supports lots of devices. Do they all have these additional interrupts?
Currently, only i.MX95 PCIe has these dedicated SPI interrupts. The earlier
SoCs in this binding (i.MX6Q/6SX/7D/8MQ/8MM/8MP, etc.) do not expose these as
separate interrupt lines.
I can constrain these three interrupt entries to be valid only for the i.MX95
variant using conditional schemas. Would that be acceptable?
Best Regards
Richard Zhu
>
> > Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml | 6 ++++++
> > 1 file changed, 6 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml
> > b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml
> > index 9d1349855b422..badc7fcbd556c 100644
> > --- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml
> > +++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml
> > @@ -58,12 +58,18 @@ properties:
> > items:
> > - description: builtin MSI controller.
> > - description: builtin DMA controller.
> > + - description: PCIe event interrupt.
> > + - description: builtin AER SPI standalone interrupter line.
> > + - description: builtin PME SPI standalone interrupter line.
> >
> > interrupt-names:
> > minItems: 1
> > items:
> > - const: msi
> > - const: dma
> > + - const: intr
> > + - const: aer
> > + - const: pme
> >
> > reset-gpio:
> > description: Should specify the GPIO for controlling the PCI bus
> > device
> > --
> > 2.37.1
> >
^ permalink raw reply
* RE: [PATCH v1 1/3] dt-bindings: PCI: imx6q-pcie: Add intr, aer and pme interrupts
From: Hongxing Zhu @ 2026-04-27 3:17 UTC (permalink / raw)
To: Krzysztof Kozlowski, Conor Dooley
Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
bhelgaas@google.com, Frank Li, l.stach@pengutronix.de,
lpieralisi@kernel.org, kwilczynski@kernel.org, mani@kernel.org,
s.hauer@pengutronix.de, kernel@pengutronix.de, festevam@gmail.com,
linux-pci@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
devicetree@vger.kernel.org, imx@lists.linux.dev,
linux-kernel@vger.kernel.org
In-Reply-To: <20260425-agile-impala-of-unity-cadced@quoll>
> -----Original Message-----
> From: Krzysztof Kozlowski <krzk@kernel.org>
> Sent: Saturday, April 25, 2026 6:00 PM
> To: Conor Dooley <conor@kernel.org>
> Cc: Hongxing Zhu <hongxing.zhu@nxp.com>; robh@kernel.org;
> krzk+dt@kernel.org; conor+dt@kernel.org; bhelgaas@google.com; Frank Li
> <frank.li@nxp.com>; l.stach@pengutronix.de; lpieralisi@kernel.org;
> kwilczynski@kernel.org; mani@kernel.org; s.hauer@pengutronix.de;
> kernel@pengutronix.de; festevam@gmail.com; linux-pci@vger.kernel.org; linux-
> arm-kernel@lists.infradead.org; devicetree@vger.kernel.org;
> imx@lists.linux.dev; linux-kernel@vger.kernel.org
> Subject: Re: [PATCH v1 1/3] dt-bindings: PCI: imx6q-pcie: Add intr, aer and pme
> interrupts
>
> On Fri, Apr 24, 2026 at 06:06:18PM +0100, Conor Dooley wrote:
> > On Fri, Apr 24, 2026 at 10:57:33AM +0800, Richard Zhu wrote:
> > > Add optional 'intr', 'aer', and 'pme' interrupt entries to the
> > > i.MX6Q PCIe binding to support PCIe event-based interrupts for
> > > general controller events, Advanced Error Reporting, and Power
> > > Management Events respectively.
> > >
> > > Signed-off-by: Richard Zhu <hongxing.zhu@nxp.com>
> > > ---
> >
> > This binding supports lots of devices. Do they all have these
> > additional interrupts?
>
> Yep. Commit msg says only i.MX6Q...
>
Sorry for the confusion caused by the file name. These standalone SPI
interrupts are specific to i.MX95 PCIe only, not the other devices covered by
this binding.
Best Regards
Richard Zhu
> Best regards,
> Krzysztof
>
^ permalink raw reply
* Re: [PATCH v8 2/4] coresight: cti: encode trigger register index in register offsets
From: Yingchao Deng (Consultant) @ 2026-04-27 3:36 UTC (permalink / raw)
To: Jie Gan, Yingchao Deng, Suzuki K Poulose, Mike Leach, James Clark,
Leo Yan, Alexander Shishkin
Cc: coresight, linux-arm-kernel, linux-kernel, linux-arm-msm,
Jinlong Mao, Tingwei Zhang
In-Reply-To: <11376a1b-923d-4bee-bdc6-fecea43a256d@oss.qualcomm.com>
On 4/27/2026 10:22 AM, Jie Gan wrote:
>
>
> On 4/26/2026 5:44 PM, Yingchao Deng wrote:
>> Introduce a small encoding to carry the register index together with the
>> base offset in a single u32, and use a common helper to compute the
>> final
>> MMIO address. This refactors register access to be based on the encoded
>> (reg, nr) pair, reducing duplicated arithmetic and making it easier to
>> support variants that bank or relocate trigger-indexed registers.
>>
>> Signed-off-by: Yingchao Deng <yingchao.deng@oss.qualcomm.com>
>> ---
>> drivers/hwtracing/coresight/coresight-cti-core.c | 31
>> +++++++++++++++--------
>> drivers/hwtracing/coresight/coresight-cti-sysfs.c | 4 +--
>> drivers/hwtracing/coresight/coresight-cti.h | 16 ++++++++++--
>> 3 files changed, 36 insertions(+), 15 deletions(-)
>>
>> diff --git a/drivers/hwtracing/coresight/coresight-cti-core.c
>> b/drivers/hwtracing/coresight/coresight-cti-core.c
>> index 4e7d12bd2d3e..c4cbeb64365b 100644
>> --- a/drivers/hwtracing/coresight/coresight-cti-core.c
>> +++ b/drivers/hwtracing/coresight/coresight-cti-core.c
>> @@ -42,6 +42,14 @@ static DEFINE_MUTEX(ect_mutex);
>> #define csdev_to_cti_drvdata(csdev) \
>> dev_get_drvdata(csdev->dev.parent)
>> +static void __iomem *cti_reg_addr(struct cti_drvdata *drvdata, int
>> reg)
>
> u32 reg would be better.
>
>> +{
>> + u32 offset = CTI_REG_CLR_NR(reg);
>
> No functional error but a little bit tricky here.
>
> CTI_REG_CLR_NR(reg) will produce a offset for the bits[0:23], but in
> the comment, you mentioned the base register offset ranges from [0:11].
>
> With my understanding, all CTI register offsets fall within the range
> b 0 to 0XFAC, that's why we have bits[0:11]?
>
> Thanks,
> Jie
Thanks for the review.
While current CoreSight components fit within a single 4KB
block, IHI0029 states that a component can occupy up to 64MB (16,384
x 4KB blocks), requiring up to 26 bits for the offset. I will change
CTI_REG_NR_MASK to GENMASK(31, 28) to avoid any potential conflict
with bits[24:25].
Thanks,
Yingchao
>> + u32 nr = CTI_REG_GET_NR(reg);
>> +
>> + return drvdata->base + offset + sizeof(u32) * nr;
>> +}
>> +
>> /* write set of regs to hardware - call with spinlock claimed */
>> void cti_write_all_hw_regs(struct cti_drvdata *drvdata)
>> {
>> @@ -55,16 +63,17 @@ void cti_write_all_hw_regs(struct cti_drvdata
>> *drvdata)
>> /* write the CTI trigger registers */
>> for (i = 0; i < config->nr_trig_max; i++) {
>> - writel_relaxed(config->ctiinen[i], drvdata->base + CTIINEN(i));
>> + writel_relaxed(config->ctiinen[i],
>> + cti_reg_addr(drvdata, CTI_REG_SET_NR(CTIINEN, i)));
>> writel_relaxed(config->ctiouten[i],
>> - drvdata->base + CTIOUTEN(i));
>> + cti_reg_addr(drvdata, CTI_REG_SET_NR(CTIOUTEN, i)));
>> }
>> /* other regs */
>> - writel_relaxed(config->ctigate, drvdata->base + CTIGATE);
>> + writel_relaxed(config->ctigate, cti_reg_addr(drvdata, CTIGATE));
>> if (config->asicctl_impl)
>> - writel_relaxed(config->asicctl, drvdata->base + ASICCTL);
>> - writel_relaxed(config->ctiappset, drvdata->base + CTIAPPSET);
>> + writel_relaxed(config->asicctl, cti_reg_addr(drvdata,
>> ASICCTL));
>> + writel_relaxed(config->ctiappset, cti_reg_addr(drvdata,
>> CTIAPPSET));
>> /* re-enable CTI */
>> writel_relaxed(1, drvdata->base + CTICONTROL);
>> @@ -127,7 +136,7 @@ u32 cti_read_single_reg(struct cti_drvdata
>> *drvdata, int offset)
>> int val;
>> CS_UNLOCK(drvdata->base);
>> - val = readl_relaxed(drvdata->base + offset);
>> + val = readl_relaxed(cti_reg_addr(drvdata, offset));
>> CS_LOCK(drvdata->base);
>> return val;
>> @@ -136,7 +145,7 @@ u32 cti_read_single_reg(struct cti_drvdata
>> *drvdata, int offset)
>> void cti_write_single_reg(struct cti_drvdata *drvdata, int offset,
>> u32 value)
>> {
>> CS_UNLOCK(drvdata->base);
>> - writel_relaxed(value, drvdata->base + offset);
>> + writel_relaxed(value, cti_reg_addr(drvdata, offset));
>> CS_LOCK(drvdata->base);
>> }
>> @@ -344,8 +353,7 @@ int cti_channel_trig_op(struct device *dev,
>> enum cti_chan_op op,
>> /* update the local register values */
>> chan_bitmask = BIT(channel_idx);
>> - reg_offset = (direction == CTI_TRIG_IN ? CTIINEN(trigger_idx) :
>> - CTIOUTEN(trigger_idx));
>> + reg_offset = (direction == CTI_TRIG_IN ? CTIINEN : CTIOUTEN);
>> guard(raw_spinlock_irqsave)(&drvdata->spinlock);
>> @@ -365,8 +373,9 @@ int cti_channel_trig_op(struct device *dev,
>> enum cti_chan_op op,
>> /* write through if enabled */
>> if (cti_is_active(config))
>> - cti_write_single_reg(drvdata, reg_offset, reg_value);
>> -
>> + cti_write_single_reg(drvdata,
>> + CTI_REG_SET_NR(reg_offset, trigger_idx),
>> + reg_value);
>> return 0;
>> }
>> diff --git a/drivers/hwtracing/coresight/coresight-cti-sysfs.c
>> b/drivers/hwtracing/coresight/coresight-cti-sysfs.c
>> index 2bbfa405cb6b..8b70e7e38ea3 100644
>> --- a/drivers/hwtracing/coresight/coresight-cti-sysfs.c
>> +++ b/drivers/hwtracing/coresight/coresight-cti-sysfs.c
>> @@ -386,7 +386,7 @@ static ssize_t inen_store(struct device *dev,
>> /* write through if enabled */
>> if (cti_is_active(config))
>> - cti_write_single_reg(drvdata, CTIINEN(index), val);
>> + cti_write_single_reg(drvdata, CTI_REG_SET_NR(CTIINEN,
>> index), val);
>> return size;
>> }
>> @@ -427,7 +427,7 @@ static ssize_t outen_store(struct device *dev,
>> /* write through if enabled */
>> if (cti_is_active(config))
>> - cti_write_single_reg(drvdata, CTIOUTEN(index), val);
>> + cti_write_single_reg(drvdata, CTI_REG_SET_NR(CTIOUTEN,
>> index), val);
>> return size;
>> }
>> diff --git a/drivers/hwtracing/coresight/coresight-cti.h
>> b/drivers/hwtracing/coresight/coresight-cti.h
>> index ef079fc18b72..dd1ba44518c4 100644
>> --- a/drivers/hwtracing/coresight/coresight-cti.h
>> +++ b/drivers/hwtracing/coresight/coresight-cti.h
>> @@ -7,6 +7,7 @@
>> #ifndef _CORESIGHT_CORESIGHT_CTI_H
>> #define _CORESIGHT_CORESIGHT_CTI_H
>> +#include <linux/bitfield.h>
>> #include <linux/coresight.h>
>> #include <linux/device.h>
>> #include <linux/list.h>
>> @@ -30,8 +31,8 @@ struct fwnode_handle;
>> #define CTIAPPSET 0x014
>> #define CTIAPPCLEAR 0x018
>> #define CTIAPPPULSE 0x01C
>> -#define CTIINEN(n) (0x020 + (4 * n))
>> -#define CTIOUTEN(n) (0x0A0 + (4 * n))
>> +#define CTIINEN 0x020
>> +#define CTIOUTEN 0x0A0
>> #define CTITRIGINSTATUS 0x130
>> #define CTITRIGOUTSTATUS 0x134
>> #define CTICHINSTATUS 0x138
>> @@ -59,6 +60,17 @@ struct fwnode_handle;
>> */
>> #define CTIINOUTEN_MAX 32
>> +/*
>> + * Encode CTI register offset and register index in one u32:
>> + * - bits[0:11] : base register offset (0x000 to 0xFFF)
>> + * - bits[24:31] : register index (nr)
>> + */
>> +#define CTI_REG_NR_MASK GENMASK(31, 24)
>> +#define CTI_REG_GET_NR(reg) FIELD_GET(CTI_REG_NR_MASK, (reg))
>> +#define CTI_REG_SET_NR_CONST(reg, nr) ((reg) |
>> FIELD_PREP_CONST(CTI_REG_NR_MASK, (nr)))
>> +#define CTI_REG_SET_NR(reg, nr) ((reg) |
>> FIELD_PREP(CTI_REG_NR_MASK, (nr)))
>> +#define CTI_REG_CLR_NR(reg) ((reg) & (~CTI_REG_NR_MASK))
>> +
>> /**
>> * Group of related trigger signals
>> *
>>
>
^ permalink raw reply
* [PATCH 0/3] wifi: mt76: remove mediatek,mtd-eeprom
From: Rosen Penev @ 2026-04-27 3:44 UTC (permalink / raw)
To: devicetree
Cc: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Johannes Berg, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Thomas Bogendoerfer, open list:MEDIATEK MT76 WIRELESS LAN DRIVER,
open list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support, open list:MIPS
mediatek,mtd-eeprom predates nvmem which is used everywhere to grab
eeprom and mac-address data from MTD devices.
Transition the one place to nvmem and remove the binding to discourage
use.
Rosen Penev (3):
MIPS: dts: ralink: gardena_smart_gateway_mt7688: use nvmem for EEPROM
dt-bindings: net: wireless: mt76: remove mediatek,mtd-eeprom
wifi: mt76: remove mt76_get_of_data_from_mtd
.../bindings/net/wireless/mediatek,mt76.yaml | 19 +---
.../ralink/gardena_smart_gateway_mt7688.dts | 17 +++-
drivers/net/wireless/mediatek/mt76/eeprom.c | 87 -------------------
drivers/net/wireless/mediatek/mt76/mt76.h | 1 -
.../wireless/mediatek/mt76/mt7915/eeprom.c | 4 -
5 files changed, 17 insertions(+), 111 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH 1/3] MIPS: dts: ralink: gardena_smart_gateway_mt7688: use nvmem for EEPROM
From: Rosen Penev @ 2026-04-27 3:44 UTC (permalink / raw)
To: devicetree
Cc: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Johannes Berg, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Thomas Bogendoerfer, open list:MEDIATEK MT76 WIRELESS LAN DRIVER,
open list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support, open list:MIPS
In-Reply-To: <20260427034427.881389-1-rosenp@gmail.com>
mediatek,mtd-eeprom is a deprecated binding for extracting data on MTD
devices which has been replaced by NVMEM.
The latter is already in wide use with mt76. As this is the only user,
transition to NVMEM.
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
.../dts/ralink/gardena_smart_gateway_mt7688.dts | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/arch/mips/boot/dts/ralink/gardena_smart_gateway_mt7688.dts b/arch/mips/boot/dts/ralink/gardena_smart_gateway_mt7688.dts
index 0bfb1dde9764..a8a8efbaf527 100644
--- a/arch/mips/boot/dts/ralink/gardena_smart_gateway_mt7688.dts
+++ b/arch/mips/boot/dts/ralink/gardena_smart_gateway_mt7688.dts
@@ -155,10 +155,20 @@ partition@b0000 {
reg = <0xb0000 0x10000>;
};
- factory: partition@c0000 {
+ partition@c0000 {
label = "factory";
reg = <0xc0000 0x10000>;
read-only;
+
+ nvmem-layout {
+ compatible = "fixed-layout";
+ #address-cells = <1>;
+ #nvmem-cell-cells = <1>;
+
+ eeprom_factory_0: eeprom@0 {
+ reg = <0x0 0x400>;
+ };
+ };
};
};
};
@@ -201,5 +211,8 @@ &watchdog {
&wmac {
status = "okay";
- mediatek,mtd-eeprom = <&factory 0x0000>;
+
+ nvmem-cells = <&eeprom_factory_0>;
+ nvmem-cell-names = "eeprom"
+
};
--
2.54.0
^ permalink raw reply related
* [PATCH 2/3] dt-bindings: net: wireless: mt76: remove mediatek,mtd-eeprom
From: Rosen Penev @ 2026-04-27 3:44 UTC (permalink / raw)
To: devicetree
Cc: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Johannes Berg, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Thomas Bogendoerfer, open list:MEDIATEK MT76 WIRELESS LAN DRIVER,
open list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support, open list:MIPS
In-Reply-To: <20260427034427.881389-1-rosenp@gmail.com>
mediatek,mtd-eeprom is a widely unused binding that predates and has
been replaced by NVMEM. As there are no users, remove it.
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
.../bindings/net/wireless/mediatek,mt76.yaml | 19 ++-----------------
1 file changed, 2 insertions(+), 17 deletions(-)
diff --git a/Documentation/devicetree/bindings/net/wireless/mediatek,mt76.yaml b/Documentation/devicetree/bindings/net/wireless/mediatek,mt76.yaml
index ae6b97cdc44b..482c22cd6627 100644
--- a/Documentation/devicetree/bindings/net/wireless/mediatek,mt76.yaml
+++ b/Documentation/devicetree/bindings/net/wireless/mediatek,mt76.yaml
@@ -87,21 +87,6 @@ properties:
description:
EEPROM data embedded as array.
- mediatek,mtd-eeprom:
- $ref: /schemas/types.yaml#/definitions/phandle-array
- items:
- - items:
- - description: phandle to MTD partition
- - description: offset containing EEPROM data
- description:
- Phandle to a MTD partition + offset containing EEPROM data
- deprecated: true
-
- big-endian:
- $ref: /schemas/types.yaml#/definitions/flag
- description:
- Specify if the radio eeprom partition is written in big-endian
-
mediatek,eeprom-merge-otp:
type: boolean
description:
@@ -314,8 +299,8 @@ examples:
compatible = "mediatek,mt76";
reg = <0x0000 0 0 0 0>;
ieee80211-freq-limit = <5000000 6000000>;
- mediatek,mtd-eeprom = <&factory 0x8000>;
- big-endian;
+ nvmem-cells = <&eeprom>;
+ nvmem-cell-names = "eeprom";
led {
led-sources = <2>;
--
2.54.0
^ permalink raw reply related
* [PATCH 3/3] wifi: mt76: remove mt76_get_of_data_from_mtd
From: Rosen Penev @ 2026-04-27 3:44 UTC (permalink / raw)
To: devicetree
Cc: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Johannes Berg, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Thomas Bogendoerfer, open list:MEDIATEK MT76 WIRELESS LAN DRIVER,
open list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support, open list:MIPS
In-Reply-To: <20260427034427.881389-1-rosenp@gmail.com>
mt76_get_of_data_from_mtd has been replaced by
mt76_get_of_data_from_nvmem in all usages.
Remove it to prevent people from using the deprecated
mediatek,mtd-eeprom binding.
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/wireless/mediatek/mt76/eeprom.c | 87 -------------------
drivers/net/wireless/mediatek/mt76/mt76.h | 1 -
.../wireless/mediatek/mt76/mt7915/eeprom.c | 4 -
3 files changed, 92 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/eeprom.c b/drivers/net/wireless/mediatek/mt76/eeprom.c
index afdb73661866..092804323d81 100644
--- a/drivers/net/wireless/mediatek/mt76/eeprom.c
+++ b/drivers/net/wireless/mediatek/mt76/eeprom.c
@@ -35,89 +35,6 @@ static int mt76_get_of_eeprom_data(struct mt76_dev *dev, void *eep, int len)
return 0;
}
-int mt76_get_of_data_from_mtd(struct mt76_dev *dev, void *eep, int offset, int len)
-{
-#ifdef CONFIG_MTD
- struct device_node *np = dev->dev->of_node;
- struct mtd_info *mtd;
- const __be32 *list;
- const char *part;
- phandle phandle;
- size_t retlen;
- int size;
- int ret;
-
- list = of_get_property(np, "mediatek,mtd-eeprom", &size);
- if (!list)
- return -ENOENT;
-
- phandle = be32_to_cpup(list++);
- if (!phandle)
- return -ENOENT;
-
- np = of_find_node_by_phandle(phandle);
- if (!np)
- return -EINVAL;
-
- part = of_get_property(np, "label", NULL);
- if (!part)
- part = np->name;
-
- mtd = get_mtd_device_nm(part);
- if (IS_ERR(mtd)) {
- ret = PTR_ERR(mtd);
- goto out_put_node;
- }
-
- if (size <= sizeof(*list)) {
- ret = -EINVAL;
- goto out_put_node;
- }
-
- offset += be32_to_cpup(list);
- ret = mtd_read(mtd, offset, len, &retlen, eep);
- put_mtd_device(mtd);
- if (mtd_is_bitflip(ret))
- ret = 0;
- if (ret) {
- dev_err(dev->dev, "reading EEPROM from mtd %s failed: %i\n",
- part, ret);
- goto out_put_node;
- }
-
- if (retlen < len) {
- ret = -EINVAL;
- goto out_put_node;
- }
-
- if (of_property_read_bool(dev->dev->of_node, "big-endian")) {
- u8 *data = (u8 *)eep;
- int i;
-
- /* convert eeprom data in Little Endian */
- for (i = 0; i < round_down(len, 2); i += 2)
- put_unaligned_le16(get_unaligned_be16(&data[i]),
- &data[i]);
- }
-
-#ifdef CONFIG_NL80211_TESTMODE
- dev->test_mtd.name = devm_kstrdup(dev->dev, part, GFP_KERNEL);
- if (!dev->test_mtd.name) {
- ret = -ENOMEM;
- goto out_put_node;
- }
- dev->test_mtd.offset = offset;
-#endif
-
-out_put_node:
- of_node_put(np);
- return ret;
-#else
- return -ENOENT;
-#endif
-}
-EXPORT_SYMBOL_GPL(mt76_get_of_data_from_mtd);
-
int mt76_get_of_data_from_nvmem(struct mt76_dev *dev, void *eep,
const char *cell_name, int len)
{
@@ -163,10 +80,6 @@ static int mt76_get_of_eeprom(struct mt76_dev *dev, void *eep, int len)
if (!ret)
return 0;
- ret = mt76_get_of_data_from_mtd(dev, eep, 0, len);
- if (!ret)
- return 0;
-
return mt76_get_of_data_from_nvmem(dev, eep, "eeprom", len);
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 527bef97e122..f447ecac664d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -1339,7 +1339,6 @@ void mt76_seq_puts_array(struct seq_file *file, const char *str,
int mt76_eeprom_init(struct mt76_dev *dev, int len);
int mt76_eeprom_override(struct mt76_phy *phy);
-int mt76_get_of_data_from_mtd(struct mt76_dev *dev, void *eep, int offset, int len);
int mt76_get_of_data_from_nvmem(struct mt76_dev *dev, void *eep,
const char *cell_name, int len);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c
index eb92cbf1a284..c24e1276700b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/eeprom.c
@@ -29,10 +29,6 @@ static int mt7915_eeprom_load_precal(struct mt7915_dev *dev)
offs = is_mt7915(&dev->mt76) ? MT_EE_PRECAL : MT_EE_PRECAL_V2;
- ret = mt76_get_of_data_from_mtd(mdev, dev->cal, offs, size);
- if (!ret)
- return ret;
-
ret = mt76_get_of_data_from_nvmem(mdev, dev->cal, "precal", size);
if (!ret)
return ret;
--
2.54.0
^ permalink raw reply related
* [PATCH 1/1] scsi: ufs: remove ucd_rsp_dma_addr and ucd_prdt_dma_addr from ufshcd_lrb
From: ed.tsai @ 2026-04-27 3:58 UTC (permalink / raw)
To: bvanassche, Alim Akhtar, Avri Altman, James E.J. Bottomley,
Martin K. Petersen, Matthias Brugger, AngeloGioacchino Del Regno
Cc: linux-kernel, linux-arm-kernel, linux-mediatek, wsd_upstream,
peter.wang, alice.chao, naomi.chu, chun-hung.wu, Ed Tsai, stable,
linux-scsi
From: Ed Tsai <ed.tsai@mediatek.com>
The offsets stored in utp_transfer_req_desc are in double words on
hosts without UFSHCD_QUIRK_PRDT_BYTE_GRAN, using them directly to
compute ucd_rsp_dma_addr and ucd_prdt_dma_addr results in incorrect
DMA addresses.
Since these fields are only used for error logging, remove them from
struct ufshcd_lrb and compute directly in ufshcd_print_tr() using
offsetof(struct utp_transfer_cmd_desc, ...) instead.
Fixes: d5130c5a0932 ("scsi: ufs: Use pre-calculated offsets in ufshcd_init_lrb()")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/all/20260424063603.382328-2-ed.tsai@mediatek.com/
Signed-off-by: Ed Tsai <ed.tsai@mediatek.com>
---
drivers/ufs/core/ufshcd.c | 10 ++++------
include/ufs/ufshcd.h | 4 ----
2 files changed, 4 insertions(+), 10 deletions(-)
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index 4805e40ed4d7..02fa61322e77 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -621,7 +621,8 @@ static void ufshcd_print_tr(struct ufs_hba *hba, struct scsi_cmnd *cmd,
ufshcd_hex_dump("UPIU REQ: ", lrbp->ucd_req_ptr,
sizeof(struct utp_upiu_req));
dev_err(hba->dev, "UPIU[%d] - Response UPIU phys@0x%llx\n", tag,
- (u64)lrbp->ucd_rsp_dma_addr);
+ (u64)(lrbp->ucd_req_dma_addr +
+ offsetof(struct utp_transfer_cmd_desc, response_upiu)));
ufshcd_hex_dump("UPIU RSP: ", lrbp->ucd_rsp_ptr,
sizeof(struct utp_upiu_rsp));
@@ -633,7 +634,8 @@ static void ufshcd_print_tr(struct ufs_hba *hba, struct scsi_cmnd *cmd,
dev_err(hba->dev,
"UPIU[%d] - PRDT - %d entries phys@0x%llx\n",
tag, prdt_length,
- (u64)lrbp->ucd_prdt_dma_addr);
+ (u64)(lrbp->ucd_req_dma_addr +
+ offsetof(struct utp_transfer_cmd_desc, prd_table)));
if (pr_prdt)
ufshcd_hex_dump("UPIU PRDT: ", lrbp->ucd_prdt_ptr,
@@ -2971,8 +2973,6 @@ static void ufshcd_init_lrb(struct ufs_hba *hba, struct scsi_cmnd *cmd)
struct utp_transfer_req_desc *utrdlp = hba->utrdl_base_addr;
dma_addr_t cmd_desc_element_addr =
hba->ucdl_dma_addr + i * ufshcd_get_ucd_size(hba);
- u16 response_offset = le16_to_cpu(utrdlp[i].response_upiu_offset);
- u16 prdt_offset = le16_to_cpu(utrdlp[i].prd_table_offset);
struct ufshcd_lrb *lrb = scsi_cmd_priv(cmd);
lrb->utr_descriptor_ptr = utrdlp + i;
@@ -2981,9 +2981,7 @@ static void ufshcd_init_lrb(struct ufs_hba *hba, struct scsi_cmnd *cmd)
lrb->ucd_req_ptr = (struct utp_upiu_req *)cmd_descp->command_upiu;
lrb->ucd_req_dma_addr = cmd_desc_element_addr;
lrb->ucd_rsp_ptr = (struct utp_upiu_rsp *)cmd_descp->response_upiu;
- lrb->ucd_rsp_dma_addr = cmd_desc_element_addr + response_offset;
lrb->ucd_prdt_ptr = (struct ufshcd_sg_entry *)cmd_descp->prd_table;
- lrb->ucd_prdt_dma_addr = cmd_desc_element_addr + prdt_offset;
}
static void __ufshcd_setup_cmd(struct ufs_hba *hba, struct scsi_cmnd *cmd,
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index cfbc75d8df83..8cb845534e63 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -158,8 +158,6 @@ struct ufs_pm_lvl_states {
* @ucd_rsp_ptr: Response UPIU address for this command
* @ucd_prdt_ptr: PRDT address of the command
* @utrd_dma_addr: UTRD dma address for debug
- * @ucd_prdt_dma_addr: PRDT dma address for debug
- * @ucd_rsp_dma_addr: UPIU response dma address for debug
* @ucd_req_dma_addr: UPIU request dma address for debug
* @scsi_status: SCSI status of the command
* @command_type: SCSI, UFS, Query.
@@ -182,8 +180,6 @@ struct ufshcd_lrb {
dma_addr_t utrd_dma_addr;
dma_addr_t ucd_req_dma_addr;
- dma_addr_t ucd_rsp_dma_addr;
- dma_addr_t ucd_prdt_dma_addr;
int scsi_status;
--
2.45.2
^ permalink raw reply related
* [PATCH v3] arm64: defconfig: Enable J721E and Keystone PCIe drivers for TI SoCs
From: Aksh Garg @ 2026-04-27 4:44 UTC (permalink / raw)
To: krzysztof.kozlowski, bjorn.andersson, geert, dmitry.baryshkov,
arnd, ebiggers, michal.simek, luca.weiss, sven,
kuninori.morimoto.gx, shijie, linux-arm-kernel
Cc: linux-kernel, s-vadapalli, danishanwar
Enable the J721E PCIe endpoint driver used by TI's J721E, J7200, J721S2,
J722S, J742S2, J784S4, AM64, AM68, and AM69 SoCs.
Enable the Keystone PCIe driver for host and endpoint mode used by TI's
AM65 SoC.
Signed-off-by: Aksh Garg <a-garg7@ti.com>
Reviewed-by: Siddharth Vadapalli <s-vadapalli@ti.com>
---
Changes from v2 to v3:
- Rebased on 7.1-rc1.
Changes from v1 to v2:
- Squashed the individial patches [1] and [2].
Link to v2: https://lore.kernel.org/all/20260317063902.262392-1-a-garg7@ti.com/
[1] - https://lore.kernel.org/all/20260223104609.876613-1-a-garg7@ti.com/
[2] - https://lore.kernel.org/all/20260223104650.876632-1-a-garg7@ti.com/
arch/arm64/configs/defconfig | 3 +++
1 file changed, 3 insertions(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index d905a0777f93..d22a6bcbdaaf 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -230,6 +230,8 @@ CONFIG_PCIE_BRCMSTB=m
CONFIG_PCI_HOST_THUNDER_PEM=y
CONFIG_PCI_HOST_THUNDER_ECAM=y
CONFIG_PCI_HOST_GENERIC=y
+CONFIG_PCI_KEYSTONE_HOST=m
+CONFIG_PCI_KEYSTONE_EP=m
CONFIG_PCIE_MEDIATEK_GEN3=m
CONFIG_PCI_TEGRA=y
CONFIG_PCIE_RCAR_HOST=y
@@ -242,6 +244,7 @@ CONFIG_PCIE_XILINX_DMA_PL=y
CONFIG_PCIE_XILINX_NWL=y
CONFIG_PCIE_XILINX_CPM=y
CONFIG_PCI_J721E_HOST=m
+CONFIG_PCI_J721E_EP=m
CONFIG_PCI_IMX6_HOST=y
CONFIG_PCI_LAYERSCAPE=y
CONFIG_PCI_HISI=y
--
2.34.1
^ permalink raw reply related
* [PATCH v3 0/4] PCI: Add DOE support for endpoint
From: Aksh Garg @ 2026-04-27 5:17 UTC (permalink / raw)
To: linux-pci, linux-doc, mani, kwilczynski, bhelgaas, corbet, kishon,
skhan, lukas, cassel, alistair
Cc: linux-arm-kernel, linux-kernel, s-vadapalli, danishanwar, srk,
a-garg7
This patch series introduces the framework for supporting the Data
Object Exchange (DOE) feature for PCIe endpoint devices. Please refer
to the documentation added in patch 4 for details on the feature and
implementation architecture.
The implementation provides a common framework for all PCIe endpoint
controllers, not specific to any particular SoC vendor.
This patch series is the non-RFC version of the RFC series at
https://lore.kernel.org/all/20260213123603.420941-1-a-garg7@ti.com/
The changes since v1 are documented in the respective patch description.
Changes from v2 to v3:
- Rebased on 7.1-rc1.
v2: https://lore.kernel.org/all/20260401073022.215805-1-a-garg7@ti.com/
Aksh Garg (4):
PCI/DOE: Move common definitions to the header file
PCI: endpoint: Add DOE mailbox support for endpoint functions
PCI: endpoint: Add API for DOE initialization and setup in EPC core
Documentation: PCI: Add documentation for DOE endpoint support
Documentation/PCI/endpoint/index.rst | 1 +
.../PCI/endpoint/pci-endpoint-doe.rst | 318 ++++++++++
drivers/pci/doe.c | 11 -
drivers/pci/endpoint/Kconfig | 14 +
drivers/pci/endpoint/Makefile | 1 +
drivers/pci/endpoint/pci-ep-doe.c | 552 ++++++++++++++++++
drivers/pci/endpoint/pci-epc-core.c | 71 +++
drivers/pci/pci.h | 47 ++
include/linux/pci-doe.h | 8 +
include/linux/pci-epc.h | 24 +
10 files changed, 1036 insertions(+), 11 deletions(-)
create mode 100644 Documentation/PCI/endpoint/pci-endpoint-doe.rst
create mode 100644 drivers/pci/endpoint/pci-ep-doe.c
--
2.34.1
^ permalink raw reply
* [PATCH v3 1/4] PCI/DOE: Move common definitions to the header file
From: Aksh Garg @ 2026-04-27 5:17 UTC (permalink / raw)
To: linux-pci, linux-doc, mani, kwilczynski, bhelgaas, corbet, kishon,
skhan, lukas, cassel, alistair
Cc: linux-arm-kernel, linux-kernel, s-vadapalli, danishanwar, srk,
a-garg7
In-Reply-To: <20260427051725.223704-1-a-garg7@ti.com>
Move common macros and structures from drivers/pci/doe.c to
drivers/pci/pci.h to allow reuse across root complex and
endpoint DOE implementations.
PCI_DOE_MAX_LENGTH macro can be used outside the PCI core as well,
hence move the macro to include/linux/pci-doe.h.
These changes prepare the groundwork for the DOE endpoint implementation
that will reuse these common definitions.
Co-developed-by: Siddharth Vadapalli <s-vadapalli@ti.com>
Signed-off-by: Siddharth Vadapalli <s-vadapalli@ti.com>
Signed-off-by: Aksh Garg <a-garg7@ti.com>
---
Changes from v2 to v3:
- Rebased on 7.1-rc1.
Changes since v1:
- Moved the common macros that need not be visible outside the PCI core
to drivers/pci/pci.h instead to include/linux/pci-doe.h as suggested
by Lukas Wunner
- Removed the redundant empty inlines guarded with CONFIG_PCI_DOE in
include/linux/pci-doe.h.
v2: https://lore.kernel.org/all/20260401073022.215805-2-a-garg7@ti.com/
v1: https://lore.kernel.org/all/20260213123603.420941-3-a-garg7@ti.com/
drivers/pci/doe.c | 11 -----------
drivers/pci/pci.h | 9 +++++++++
include/linux/pci-doe.h | 3 +++
3 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c
index 7b41da4ec11a..e8d9e95644b3 100644
--- a/drivers/pci/doe.c
+++ b/drivers/pci/doe.c
@@ -28,12 +28,6 @@
#define PCI_DOE_TIMEOUT HZ
#define PCI_DOE_POLL_INTERVAL (PCI_DOE_TIMEOUT / 128)
-#define PCI_DOE_FLAG_CANCEL 0
-#define PCI_DOE_FLAG_DEAD 1
-
-/* Max data object length is 2^18 dwords */
-#define PCI_DOE_MAX_LENGTH (1 << 18)
-
/**
* struct pci_doe_mb - State for a single DOE mailbox
*
@@ -63,11 +57,6 @@ struct pci_doe_mb {
#endif
};
-struct pci_doe_feature {
- u16 vid;
- u8 type;
-};
-
/**
* struct pci_doe_task - represents a single query/response
*
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 4a14f88e543a..5844deee2b5f 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -683,6 +683,15 @@ struct pci_sriov {
bool drivers_autoprobe; /* Auto probing of VFs by driver */
};
+/* DOE Mailbox state flags */
+#define PCI_DOE_FLAG_CANCEL 0
+#define PCI_DOE_FLAG_DEAD 1
+
+struct pci_doe_feature {
+ u16 vid;
+ u8 type;
+};
+
#ifdef CONFIG_PCI_DOE
void pci_doe_init(struct pci_dev *pdev);
void pci_doe_destroy(struct pci_dev *pdev);
diff --git a/include/linux/pci-doe.h b/include/linux/pci-doe.h
index bd4346a7c4e7..abb9b7ae8029 100644
--- a/include/linux/pci-doe.h
+++ b/include/linux/pci-doe.h
@@ -19,6 +19,9 @@ struct pci_doe_mb;
#define PCI_DOE_FEATURE_CMA 1
#define PCI_DOE_FEATURE_SSESSION 2
+/* Max data object length is 2^18 dwords */
+#define PCI_DOE_MAX_LENGTH (1 << 18)
+
struct pci_doe_mb *pci_find_doe_mailbox(struct pci_dev *pdev, u16 vendor,
u8 type);
--
2.34.1
^ permalink raw reply related
* [PATCH v3 2/4] PCI: endpoint: Add DOE mailbox support for endpoint functions
From: Aksh Garg @ 2026-04-27 5:17 UTC (permalink / raw)
To: linux-pci, linux-doc, mani, kwilczynski, bhelgaas, corbet, kishon,
skhan, lukas, cassel, alistair
Cc: linux-arm-kernel, linux-kernel, s-vadapalli, danishanwar, srk,
a-garg7
In-Reply-To: <20260427051725.223704-1-a-garg7@ti.com>
DOE (Data Object Exchange) is a standard PCIe extended capability
feature introduced in the Data Object Exchange (DOE) ECN for
PCIe r5.0. It provides a communication mechanism primarily used for
implementing PCIe security features such as device authentication, and
secure link establishment. Think of DOE as a sophisticated mailbox
system built into PCIe. The root complex can send structured requests
to the endpoint device through DOE mailboxes, and the endpoint device
responds with appropriate data.
Add the DOE support for PCIe endpoint devices, enabling endpoint
functions to process the DOE requests from the host. The implementation
provides framework APIs for EPC core driver and controller drivers to
register mailboxes, and request processing with workqueues ensuring
sequential handling per mailbox, and parallel handling across mailboxes.
The Discovery protocol is handled internally by the DOE core.
This implementation complements the existing DOE implementation for
root complex in drivers/pci/doe.c.
Co-developed-by: Siddharth Vadapalli <s-vadapalli@ti.com>
Signed-off-by: Siddharth Vadapalli <s-vadapalli@ti.com>
Signed-off-by: Aksh Garg <a-garg7@ti.com>
---
Changes from v2 to v3:
- Rebased on 7.1-rc1.
Changes since v1:
- Moved the DOE-EP core file to drivers/pci/endpoint/pci-ep-doe.c, and
corresponding Kconfig and Makefile to match the existing naming scheme,
as suggested by Niklas Cassel.
- Renamed the config from PCI_DOE_EP to PCI_ENDPOINT_DOE
- Moved the function declarations that need not be visible outside the
PCI core to drivers/pci/pci.h instead to include/linux/pci-doe.h as
suggested by Lukas Wunner
- Converted from synchronous to asynchronous request processing:
* Removed wait_for_completion() from pci_ep_doe_process_request()
* Function returns immediately after queuing to workqueue, hence
removed private data for completion in the task structure
* Added completion callback as an additional argument to
pci_ep_doe_process_request(), which takes the response and status
parameters as arguments (along with other required arguments), hence
removed task_status in the task structure
* Created a typedef pci_ep_doe_complete_t for completion callback
* Removed the pci_ep_doe_task_complete() function, as it would not be
required anymore with these changes
* Moved from INIT_WORK_ONSTACK() to INIT_WORK(), to initialize the work
on heap instead of stack
* signal_task_complete() now invokes the completion callback, once the
protocol handler completes its task
- Changed from dynamic xarray-based protocol registration to static array:
* Removed the register/unregister protocol APIs
* Replaced the dynamic xarray with static array of struct pci_doe_protocol
* Added discovery protocol to static array, instead of treating it specially,
hence removed the special handling for Discovery protocol in
doe_ep_task_work()
* Updated pci_ep_doe_handle_discovery() and pci_ep_doe_find_protocol()
accordingly.
- Memory Management:
* DOE core frees request buffer in signal_task_complete()
or during error handling
* pci_ep_doe_process_request() defines response_pl and response_pl_sz
as NULL and 0 respectively, whose pointer is passed to the protocol
handler, hence removed the arguments void **response, size_t *response_sz
to this function.
- Task structure refactoring:
* Response buffer: void **response_pl to void *response_pl
* Response size: size_t *response_pl_sz to size_t response_pl_sz
* Changed the completion callback to type pci_ep_doe_complete_t
* Removed void *private and int task_status
- Updated documentation comments of the functions according to the changes
v2: https://lore.kernel.org/all/20260401073022.215805-3-a-garg7@ti.com/
v1: https://lore.kernel.org/all/20260213123603.420941-4-a-garg7@ti.com/
drivers/pci/endpoint/Kconfig | 14 +
drivers/pci/endpoint/Makefile | 1 +
drivers/pci/endpoint/pci-ep-doe.c | 552 ++++++++++++++++++++++++++++++
drivers/pci/pci.h | 38 ++
include/linux/pci-doe.h | 5 +
include/linux/pci-epc.h | 3 +
6 files changed, 613 insertions(+)
create mode 100644 drivers/pci/endpoint/pci-ep-doe.c
diff --git a/drivers/pci/endpoint/Kconfig b/drivers/pci/endpoint/Kconfig
index 8dad291be8b8..15ae16aaa58f 100644
--- a/drivers/pci/endpoint/Kconfig
+++ b/drivers/pci/endpoint/Kconfig
@@ -36,6 +36,20 @@ config PCI_ENDPOINT_MSI_DOORBELL
doorbell. The RC can trigger doorbell in EP by writing data to a
dedicated BAR, which the EP maps to the controller's message address.
+config PCI_ENDPOINT_DOE
+ bool "PCI Endpoint Data Object Exchange (DOE) support"
+ depends on PCI_ENDPOINT
+ help
+ This enables support for Data Object Exchange (DOE) protocol
+ on PCI Endpoint controllers. It provides a communication
+ mechanism through mailboxes, primarily used for PCIe security
+ features.
+
+ Say Y here if you want be able to communicate using PCIe DOE
+ mailboxes.
+
+ If unsure, say N.
+
source "drivers/pci/endpoint/functions/Kconfig"
endmenu
diff --git a/drivers/pci/endpoint/Makefile b/drivers/pci/endpoint/Makefile
index b4869d52053a..1fa176b6792b 100644
--- a/drivers/pci/endpoint/Makefile
+++ b/drivers/pci/endpoint/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_PCI_ENDPOINT_CONFIGFS) += pci-ep-cfs.o
obj-$(CONFIG_PCI_ENDPOINT) += pci-epc-core.o pci-epf-core.o\
pci-epc-mem.o functions/
obj-$(CONFIG_PCI_ENDPOINT_MSI_DOORBELL) += pci-ep-msi.o
+obj-$(CONFIG_PCI_ENDPOINT_DOE) += pci-ep-doe.o
diff --git a/drivers/pci/endpoint/pci-ep-doe.c b/drivers/pci/endpoint/pci-ep-doe.c
new file mode 100644
index 000000000000..ded0290b15ed
--- /dev/null
+++ b/drivers/pci/endpoint/pci-ep-doe.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0-only or MIT
+/*
+ * Data Object Exchange for PCIe Endpoint
+ * PCIe r7.0, sec 6.30 DOE
+ *
+ * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com
+ * Aksh Garg <a-garg7@ti.com>
+ * Siddharth Vadapalli <s-vadapalli@ti.com>
+ */
+
+#define dev_fmt(fmt) "DOE EP: " fmt
+
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/pci.h>
+#include <linux/pci-epc.h>
+#include <linux/pci-doe.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/xarray.h>
+
+#include "../pci.h"
+
+/* Forward declaration of discovery protocol handler */
+static int pci_ep_doe_handle_discovery(const void *request, size_t request_sz,
+ void **response, size_t *response_sz);
+
+/**
+ * struct pci_doe_protocol - DOE protocol handler entry
+ * @vid: Vendor ID
+ * @type: Protocol type
+ * @handler: Handler function pointer
+ */
+struct pci_doe_protocol {
+ u16 vid;
+ u8 type;
+ pci_doe_protocol_handler_t handler;
+};
+
+/**
+ * struct pci_ep_doe_mb - State for a single DOE mailbox on EP
+ *
+ * This state is used to manage a single DOE mailbox capability on the
+ * endpoint side.
+ *
+ * @epc: PCI endpoint controller this mailbox belongs to
+ * @func_no: Physical function number of the function this mailbox belongs to
+ * @cap_offset: Capability offset
+ * @work_queue: Queue of work items
+ * @flags: Bit array of PCI_DOE_FLAG_* flags
+ */
+struct pci_ep_doe_mb {
+ struct pci_epc *epc;
+ u8 func_no;
+ u16 cap_offset;
+ struct workqueue_struct *work_queue;
+ unsigned long flags;
+};
+
+/**
+ * struct pci_ep_doe_task - Represents a single DOE request/response task
+ *
+ * @feat: DOE feature (vendor ID and type)
+ * @request_pl: Request payload
+ * @request_pl_sz: Size of request payload in bytes
+ * @response_pl: Response buffer
+ * @response_pl_sz: Size of response buffer in bytes
+ * @complete: Completion callback
+ * @work: Work structure for workqueue
+ * @doe_mb: DOE mailbox handling this task
+ */
+struct pci_ep_doe_task {
+ struct pci_doe_feature feat;
+ const void *request_pl;
+ size_t request_pl_sz;
+ void *response_pl;
+ size_t response_pl_sz;
+ pci_ep_doe_complete_t complete;
+
+ /* Initialized by pci_ep_doe_submit_task() */
+ struct work_struct work;
+ struct pci_ep_doe_mb *doe_mb;
+};
+
+/*
+ * Global registry of protocol handlers.
+ * When a new DOE protocol, library is added, add an entry to this array.
+ */
+static const struct pci_doe_protocol pci_doe_protocols[] = {
+ {
+ .vid = PCI_VENDOR_ID_PCI_SIG,
+ .type = PCI_DOE_FEATURE_DISCOVERY,
+ .handler = pci_ep_doe_handle_discovery,
+ },
+};
+
+/*
+ * Combines function number and capability offset into a unique lookup key
+ * for storing/retrieving DOE mailboxes in an xarray.
+ */
+#define PCI_DOE_MB_KEY(func, offset) \
+ (((unsigned long)(func) << 16) | (offset))
+#define PCI_DOE_PROTOCOL_COUNT ARRAY_SIZE(pci_doe_protocols)
+
+/**
+ * pci_ep_doe_init() - Initialize the DOE framework for a controller in EP mode
+ * @epc: PCI endpoint controller
+ *
+ * Initialize the DOE framework data structures. This only initializes
+ * the xarray that will hold the mailboxes.
+ *
+ * RETURNS: 0 on success, -errno on failure
+ */
+int pci_ep_doe_init(struct pci_epc *epc)
+{
+ if (!epc)
+ return -EINVAL;
+
+ xa_init(&epc->doe_mbs);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_ep_doe_init);
+
+/**
+ * pci_ep_doe_add_mailbox() - Add a DOE mailbox for a physical function
+ * @epc: PCI endpoint controller
+ * @func_no: Physical function number
+ * @cap_offset: Offset of the DOE capability
+ *
+ * Create and register a DOE mailbox for the specified physical function
+ * and capability offset.
+ *
+ * EPC core driver calls this for each DOE capability discovered in the config
+ * space of each endpoint function through an API. The API is invoked by the
+ * controller driver during initialization if DOE support is available.
+ *
+ * RETURNS: 0 on success, -errno on failure
+ */
+int pci_ep_doe_add_mailbox(struct pci_epc *epc, u8 func_no, u16 cap_offset)
+{
+ struct pci_ep_doe_mb *doe_mb;
+ unsigned long key;
+ int ret;
+
+ if (!epc)
+ return -EINVAL;
+
+ doe_mb = kzalloc_obj(*doe_mb, GFP_KERNEL);
+ if (!doe_mb)
+ return -ENOMEM;
+
+ doe_mb->epc = epc;
+ doe_mb->func_no = func_no;
+ doe_mb->cap_offset = cap_offset;
+
+ doe_mb->work_queue = alloc_ordered_workqueue("pci_ep_doe[%s:pf%d:offset%x]", 0,
+ dev_name(&epc->dev),
+ func_no, cap_offset);
+ if (!doe_mb->work_queue) {
+ dev_err(epc->dev.parent,
+ "[pf%d:offset%x] failed to allocate work queue\n",
+ func_no, cap_offset);
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ /* Add to xarray with composite key */
+ key = PCI_DOE_MB_KEY(func_no, cap_offset);
+ ret = xa_insert(&epc->doe_mbs, key, doe_mb, GFP_KERNEL);
+ if (ret) {
+ dev_err(epc->dev.parent,
+ "[pf%d:offset%x] failed to insert mailbox: %d\n",
+ func_no, cap_offset, ret);
+ goto err_destroy;
+ }
+
+ dev_dbg(epc->dev.parent,
+ "DOE mailbox added: pf%d offset 0x%x\n",
+ func_no, cap_offset);
+
+ return 0;
+
+err_destroy:
+ destroy_workqueue(doe_mb->work_queue);
+err_free:
+ kfree(doe_mb);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pci_ep_doe_add_mailbox);
+
+/**
+ * pci_ep_doe_cancel_tasks() - Cancel all pending tasks
+ * @doe_mb: DOE mailbox
+ *
+ * Cancel all pending tasks in the mailbox. Mark the mailbox as dead
+ * so no new tasks can be submitted.
+ */
+static void pci_ep_doe_cancel_tasks(struct pci_ep_doe_mb *doe_mb)
+{
+ if (!doe_mb)
+ return;
+
+ /* Mark the mailbox as dead */
+ set_bit(PCI_DOE_FLAG_DEAD, &doe_mb->flags);
+
+ /* Stop all pending work items from starting */
+ set_bit(PCI_DOE_FLAG_CANCEL, &doe_mb->flags);
+}
+
+/**
+ * pci_ep_doe_get_mailbox() - Get DOE mailbox by function and offset
+ * @epc: PCI endpoint controller
+ * @func_no: Physical function number
+ * @cap_offset: Offset of the DOE capability
+ *
+ * Internal helper to look up a DOE mailbox by its function number and
+ * capability offset.
+ *
+ * RETURNS: Pointer to the mailbox or NULL if not found
+ */
+static struct pci_ep_doe_mb *pci_ep_doe_get_mailbox(struct pci_epc *epc,
+ u8 func_no, u16 cap_offset)
+{
+ unsigned long key;
+
+ if (!epc)
+ return NULL;
+
+ key = PCI_DOE_MB_KEY(func_no, cap_offset);
+ return xa_load(&epc->doe_mbs, key);
+}
+
+/**
+ * pci_ep_doe_find_protocol() - Find protocol handler in static array
+ * @vendor: Vendor ID
+ * @type: Protocol type
+ *
+ * Look up a protocol handler in the static protocol array by matching vendor ID
+ * and protocol type.
+ *
+ * RETURNS: Handler function pointer or NULL if not found
+ */
+static pci_doe_protocol_handler_t pci_ep_doe_find_protocol(u16 vendor, u8 type)
+{
+ int i;
+
+ /* Search static protocol array */
+ for (i = 0; i < PCI_DOE_PROTOCOL_COUNT; i++) {
+ if (pci_doe_protocols[i].vid == vendor &&
+ pci_doe_protocols[i].type == type)
+ return pci_doe_protocols[i].handler;
+ }
+
+ return NULL;
+}
+
+/**
+ * pci_ep_doe_handle_discovery() - Handle Discovery protocol request
+ * @request: Request payload
+ * @request_sz: Request size
+ * @response: Output pointer for response buffer
+ * @response_sz: Output pointer for response size
+ *
+ * Handle the DOE Discovery protocol. The request contains an index specifying
+ * which protocol to query. This function creates a response containing the
+ * vendor ID and protocol type for the requested index, along with the next
+ * index value for further discovery:
+ *
+ * - next_index = 0: Signals this is the last protocol supported
+ * - next_index = n (non-zero): Signals more protocols available,
+ * query index n next
+ *
+ * RETURNS: 0 on success, -errno on failure
+ */
+static int pci_ep_doe_handle_discovery(const void *request, size_t request_sz,
+ void **response, size_t *response_sz)
+{
+ struct pci_doe_protocol protocol;
+ u8 requested_index, next_index;
+ u32 *response_pl;
+ u32 request_pl;
+ u16 vendor;
+ u8 type;
+
+ if (request_sz != sizeof(u32))
+ return -EINVAL;
+
+ request_pl = *(u32 *)request;
+ requested_index = FIELD_GET(PCI_DOE_DATA_OBJECT_DISC_REQ_3_INDEX, request_pl);
+
+ if (requested_index >= PCI_DOE_PROTOCOL_COUNT)
+ return -EINVAL;
+
+ /* Get protocol from array at requested_index */
+ protocol = pci_doe_protocols[requested_index];
+ vendor = protocol.vid;
+ type = protocol.type;
+
+ /* Calculate next index */
+ next_index = (requested_index + 1 < PCI_DOE_PROTOCOL_COUNT) ? requested_index + 1 : 0;
+
+ response_pl = kzalloc_obj(*response_pl, GFP_KERNEL);
+ if (!response_pl)
+ return -ENOMEM;
+
+ /* Build response */
+ *response_pl = FIELD_PREP(PCI_DOE_DATA_OBJECT_DISC_RSP_3_VID, vendor) |
+ FIELD_PREP(PCI_DOE_DATA_OBJECT_DISC_RSP_3_TYPE, type) |
+ FIELD_PREP(PCI_DOE_DATA_OBJECT_DISC_RSP_3_NEXT_INDEX, next_index);
+
+ *response = response_pl;
+ *response_sz = sizeof(*response_pl);
+
+ return 0;
+}
+
+static void signal_task_complete(struct pci_ep_doe_task *task, int status)
+{
+ kfree(task->request_pl);
+ task->complete(task->doe_mb->func_no, task->doe_mb->cap_offset, status,
+ task->feat.vid, task->feat.type,
+ task->response_pl, task->response_pl_sz);
+ kfree(task);
+}
+
+/**
+ * doe_ep_task_work() - Work function for processing DOE EP tasks
+ * @work: Work structure
+ *
+ * Process a DOE request by calling the appropriate protocol handler.
+ */
+static void doe_ep_task_work(struct work_struct *work)
+{
+ struct pci_ep_doe_task *task = container_of(work, struct pci_ep_doe_task,
+ work);
+ struct pci_ep_doe_mb *doe_mb = task->doe_mb;
+ pci_doe_protocol_handler_t handler;
+ int rc;
+
+ if (test_bit(PCI_DOE_FLAG_DEAD, &doe_mb->flags)) {
+ signal_task_complete(task, -EIO);
+ return;
+ }
+
+ /* Check if request was aborted */
+ if (test_bit(PCI_DOE_FLAG_CANCEL, &doe_mb->flags)) {
+ signal_task_complete(task, -ECANCELED);
+ return;
+ }
+
+ /* Find protocol handler in the array */
+ handler = pci_ep_doe_find_protocol(task->feat.vid, task->feat.type);
+ if (!handler) {
+ dev_warn(doe_mb->epc->dev.parent,
+ "[%d:%x] Unsupported protocol VID=%04x TYPE=%02x\n",
+ doe_mb->func_no, doe_mb->cap_offset,
+ task->feat.vid, task->feat.type);
+ signal_task_complete(task, -EOPNOTSUPP);
+ return;
+ }
+
+ /* Call protocol handler */
+ rc = handler(task->request_pl, task->request_pl_sz,
+ &task->response_pl, &task->response_pl_sz);
+
+ signal_task_complete(task, rc);
+}
+
+/**
+ * pci_ep_doe_submit_task() - Submit a task to be processed
+ * @doe_mb: DOE mailbox
+ * @task: Task to submit
+ *
+ * Submit a DOE task to the workqueue for asynchronous processing.
+ *
+ * RETURNS: 0 on success, -errno on failure
+ */
+static int pci_ep_doe_submit_task(struct pci_ep_doe_mb *doe_mb,
+ struct pci_ep_doe_task *task)
+{
+ if (test_bit(PCI_DOE_FLAG_DEAD, &doe_mb->flags))
+ return -EIO;
+
+ task->doe_mb = doe_mb;
+ INIT_WORK(&task->work, doe_ep_task_work);
+ queue_work(doe_mb->work_queue, &task->work);
+ return 0;
+}
+
+/**
+ * pci_ep_doe_process_request() - Process DOE request on endpoint
+ * @epc: PCI endpoint controller
+ * @func_no: Physical function number
+ * @cap_offset: DOE capability offset
+ * @vendor: Vendor ID from request header
+ * @type: Protocol type from request header
+ * @request: Request payload in CPU-native format
+ * @request_sz: Size of request payload (bytes)
+ * @complete: Callback to invoke upon completion
+ *
+ * Asynchronously process a DOE request received on the endpoint. The request
+ * payload should not include the DOE header (vendor/type/length). The protocol
+ * handler will allocate the response buffer, which the caller (controller driver)
+ * must free after use.
+ *
+ * This function returns immediately after queuing the request. The completion
+ * callback will be invoked asynchronously from workqueue context once the
+ * request is processed. The callback receives the function number and capability
+ * offset to identify the mailbox, along with a status code (0 on success, -errno
+ * on failure), and other required arguments.
+ *
+ * As per DOE specification, a mailbox processes one request at a time.
+ * Therefore, this function will never be called concurrently for the same
+ * mailbox by different callers.
+ *
+ * The caller is responsible for the conversion of the received DOE request
+ * with le32_to_cpu() before calling this function.
+ * Similarly, it is responsible for converting the response payload with
+ * cpu_to_le32() before sending it back over the DOE mailbox.
+ *
+ * The caller is also responsible for ensuring that the request size
+ * is within the limits defined by PCI_DOE_MAX_LENGTH.
+ *
+ * RETURNS: 0 if the request was successfully queued, -errno on failure
+ */
+int pci_ep_doe_process_request(struct pci_epc *epc, u8 func_no, u16 cap_offset,
+ u16 vendor, u8 type, const void *request, size_t request_sz,
+ pci_ep_doe_complete_t complete)
+{
+ struct pci_ep_doe_mb *doe_mb;
+ struct pci_ep_doe_task *task;
+ int rc;
+
+ doe_mb = pci_ep_doe_get_mailbox(epc, func_no, cap_offset);
+ if (!doe_mb) {
+ kfree(request);
+ return -ENODEV;
+ }
+
+ task = kzalloc_obj(*task, GFP_KERNEL);
+ if (!task) {
+ kfree(request);
+ return -ENOMEM;
+ }
+
+ task->feat.vid = vendor;
+ task->feat.type = type;
+ task->request_pl = request;
+ task->request_pl_sz = request_sz;
+ task->response_pl = NULL;
+ task->response_pl_sz = 0;
+ task->complete = complete;
+
+ rc = pci_ep_doe_submit_task(doe_mb, task);
+ if (rc) {
+ kfree(request);
+ kfree(task);
+ return rc;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_ep_doe_process_request);
+
+/**
+ * pci_ep_doe_abort() - Abort DOE operations on a mailbox
+ * @epc: PCI endpoint controller
+ * @func_no: Physical function number
+ * @cap_offset: DOE capability offset
+ *
+ * Abort all queued and wait for in-flight DOE operations to complete for the
+ * specified mailbox. This function is called by the EP controller driver
+ * when the RC sets the ABORT bit in the DOE Control register.
+ *
+ * The function will:
+ *
+ * - Set CANCEL flag to prevent new requests in the queue from starting
+ * - Wait for the currently executing handler to complete (cannot interrupt)
+ * - Flush the workqueue to wait for all requests to be handled appropriately
+ * - Clear CANCEL flag to prepare for new requests
+ *
+ * RETURNS: 0 on success, -errno on failure
+ */
+int pci_ep_doe_abort(struct pci_epc *epc, u8 func_no, u16 cap_offset)
+{
+ struct pci_ep_doe_mb *doe_mb;
+
+ if (!epc)
+ return -EINVAL;
+
+ doe_mb = pci_ep_doe_get_mailbox(epc, func_no, cap_offset);
+ if (!doe_mb)
+ return -ENODEV;
+
+ /* Set CANCEL flag - worker will abort queued requests */
+ set_bit(PCI_DOE_FLAG_CANCEL, &doe_mb->flags);
+ flush_workqueue(doe_mb->work_queue);
+
+ /* Clear CANCEL flag - mailbox ready for new requests */
+ clear_bit(PCI_DOE_FLAG_CANCEL, &doe_mb->flags);
+
+ dev_dbg(epc->dev.parent,
+ "DOE mailbox aborted: PF%d offset 0x%x\n",
+ func_no, cap_offset);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_ep_doe_abort);
+
+/**
+ * pci_ep_doe_destroy_mb() - Destroy a single DOE mailbox
+ * @doe_mb: DOE mailbox to destroy
+ *
+ * Internal function to destroy a mailbox and free its resources.
+ */
+static void pci_ep_doe_destroy_mb(struct pci_ep_doe_mb *doe_mb)
+{
+ if (!doe_mb)
+ return;
+
+ pci_ep_doe_cancel_tasks(doe_mb);
+
+ if (doe_mb->work_queue)
+ destroy_workqueue(doe_mb->work_queue);
+
+ kfree(doe_mb);
+}
+
+/**
+ * pci_ep_doe_destroy() - Destroy all DOE mailboxes
+ * @epc: PCI endpoint controller
+ *
+ * Destroy all DOE mailboxes and free associated resources.
+ *
+ * The EPC core driver calls this through an API, invoked by the controller
+ * driver during controller cleanup to free all DOE resources,
+ * if DOE support is available.
+ */
+void pci_ep_doe_destroy(struct pci_epc *epc)
+{
+ struct pci_ep_doe_mb *doe_mb;
+ unsigned long index;
+
+ if (!epc)
+ return;
+
+ xa_for_each(&epc->doe_mbs, index, doe_mb)
+ pci_ep_doe_destroy_mb(doe_mb);
+
+ xa_destroy(&epc->doe_mbs);
+}
+EXPORT_SYMBOL_GPL(pci_ep_doe_destroy);
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 5844deee2b5f..f7766cbedbaf 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -692,6 +692,12 @@ struct pci_doe_feature {
u8 type;
};
+struct pci_epc;
+
+typedef void (*pci_ep_doe_complete_t)(u8 func_no, u16 cap_offset, int status,
+ u16 vendor, u8 type, void *response_pl,
+ size_t response_pl_sz);
+
#ifdef CONFIG_PCI_DOE
void pci_doe_init(struct pci_dev *pdev);
void pci_doe_destroy(struct pci_dev *pdev);
@@ -702,6 +708,38 @@ static inline void pci_doe_destroy(struct pci_dev *pdev) { }
static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#endif
+#ifdef CONFIG_PCI_ENDPOINT_DOE
+int pci_ep_doe_init(struct pci_epc *epc);
+int pci_ep_doe_add_mailbox(struct pci_epc *epc, u8 func_no, u16 cap_offset);
+int pci_ep_doe_process_request(struct pci_epc *epc, u8 func_no, u16 cap_offset,
+ u16 vendor, u8 type, const void *request,
+ size_t request_sz, pci_ep_doe_complete_t complete);
+int pci_ep_doe_abort(struct pci_epc *epc, u8 func_no, u16 cap_offset);
+void pci_ep_doe_destroy(struct pci_epc *epc);
+#else
+static inline int pci_ep_doe_init(struct pci_epc *epc) { return -EOPNOTSUPP; }
+static inline int pci_ep_doe_add_mailbox(struct pci_epc *epc, u8 func_no,
+ u16 cap_offset)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int pci_ep_doe_process_request(struct pci_epc *epc, u8 func_no,
+ u16 cap_offset, u16 vendor, u8 type,
+ const void *request, size_t request_sz,
+ pci_ep_doe_complete_t complete)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int pci_ep_doe_abort(struct pci_epc *epc, u8 func_no, u16 cap_offset)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void pci_ep_doe_destroy(struct pci_epc *epc) { }
+#endif
+
#ifdef CONFIG_PCI_NPEM
void pci_npem_create(struct pci_dev *dev);
void pci_npem_remove(struct pci_dev *dev);
diff --git a/include/linux/pci-doe.h b/include/linux/pci-doe.h
index abb9b7ae8029..c46e42f3ce78 100644
--- a/include/linux/pci-doe.h
+++ b/include/linux/pci-doe.h
@@ -22,6 +22,11 @@ struct pci_doe_mb;
/* Max data object length is 2^18 dwords */
#define PCI_DOE_MAX_LENGTH (1 << 18)
+typedef int (*pci_doe_protocol_handler_t)(const void *request,
+ size_t request_sz,
+ void **response,
+ size_t *response_sz);
+
struct pci_doe_mb *pci_find_doe_mailbox(struct pci_dev *pdev, u16 vendor,
u8 type);
diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h
index 1eca1264815b..dd26294c8175 100644
--- a/include/linux/pci-epc.h
+++ b/include/linux/pci-epc.h
@@ -182,6 +182,9 @@ struct pci_epc {
unsigned long function_num_map;
int domain_nr;
bool init_complete;
+#ifdef CONFIG_PCI_ENDPOINT_DOE
+ struct xarray doe_mbs;
+#endif
};
/**
--
2.34.1
^ permalink raw reply related
* [PATCH v3 4/4] Documentation: PCI: Add documentation for DOE endpoint support
From: Aksh Garg @ 2026-04-27 5:17 UTC (permalink / raw)
To: linux-pci, linux-doc, mani, kwilczynski, bhelgaas, corbet, kishon,
skhan, lukas, cassel, alistair
Cc: linux-arm-kernel, linux-kernel, s-vadapalli, danishanwar, srk,
a-garg7
In-Reply-To: <20260427051725.223704-1-a-garg7@ti.com>
Document the architecture and implementation details for the Data Object
Exchange (DOE) framework for PCIe Endpoint devices.
Co-developed-by: Siddharth Vadapalli <s-vadapalli@ti.com>
Signed-off-by: Siddharth Vadapalli <s-vadapalli@ti.com>
Signed-off-by: Aksh Garg <a-garg7@ti.com>
---
Changes from v2 to v3:
- Rebased on 7.1-rc1.
Changes since v1:
- Squashed the patches [1] and [2], and moved the documentation file
to Documentation/PCI/endpoint/pci-endpoint-doe.rst to match the existing
naming scheme, as suggested by Niklas Cassel
- Updated the documentation as per the design and implementaion changes
made to previous patches in this series:
* Updated for static protocol array instead of dynamic registration
* Documented asynchronous callback model
* Updated request/response flow with new callback signature
* Updated memory ownership: DOE core frees request, driver frees response
* Updated initialization and cleanup sections for new APIs
v2: https://lore.kernel.org/all/20260401073022.215805-5-a-garg7@ti.com/
v1: [1] https://lore.kernel.org/all/20260213123603.420941-2-a-garg7@ti.com/
[2] https://lore.kernel.org/all/20260213123603.420941-5-a-garg7@ti.com/
Documentation/PCI/endpoint/index.rst | 1 +
.../PCI/endpoint/pci-endpoint-doe.rst | 318 ++++++++++++++++++
2 files changed, 319 insertions(+)
create mode 100644 Documentation/PCI/endpoint/pci-endpoint-doe.rst
diff --git a/Documentation/PCI/endpoint/index.rst b/Documentation/PCI/endpoint/index.rst
index dd1f62e731c9..7c03d5abd2ef 100644
--- a/Documentation/PCI/endpoint/index.rst
+++ b/Documentation/PCI/endpoint/index.rst
@@ -9,6 +9,7 @@ PCI Endpoint Framework
pci-endpoint
pci-endpoint-cfs
+ pci-endpoint-doe
pci-test-function
pci-test-howto
pci-ntb-function
diff --git a/Documentation/PCI/endpoint/pci-endpoint-doe.rst b/Documentation/PCI/endpoint/pci-endpoint-doe.rst
new file mode 100644
index 000000000000..03b7a69516f3
--- /dev/null
+++ b/Documentation/PCI/endpoint/pci-endpoint-doe.rst
@@ -0,0 +1,318 @@
+.. SPDX-License-Identifier: GPL-2.0-only or MIT
+
+.. include:: <isonum.txt>
+
+=============================================
+Data Object Exchange (DOE) for PCIe Endpoint
+=============================================
+
+:Copyright: |copy| 2026 Texas Instruments Incorporated
+:Author: Aksh Garg <a-garg7@ti.com>
+:Co-Author: Siddharth Vadapalli <s-vadapalli@ti.com>
+
+Overview
+========
+
+DOE (Data Object Exchange) is a standard PCIe extended capability feature
+introduced in the Data Object Exchange (DOE) ECN for PCIe r5.0. It is an optional
+mechanism for system firmware/software running on root complex (host) to perform
+:ref:`data object <data-object-term>` exchanges with an endpoint function. Each
+data object is uniquely identified by the Vendor ID of the vendor publishing the
+data object definition and a Data Object Type value assigned by that vendor.
+
+Think of DOE as a sophisticated mailbox system built into PCIe. The root complex
+can send structured requests to the endpoint device through DOE mailboxes, and
+the endpoint device responds with appropriate data. DOE mailboxes are implemented
+as PCIe Extended Capabilities in endpoint devices, allowing multiple mailboxes
+per function, each potentially supporting different data object protocols.
+
+The DOE support for root complex devices has already been implemented in
+``drivers/pci/doe.c``.
+
+How DOE Works
+=============
+
+The DOE mailbox operates through a simple request-response model:
+
+1. **Host sends request**: The root complex writes a data object (vendor ID, type,
+ and payload) to the DOE write mailbox register (one DWORD at a time) of the
+ endpoint function's config space and sets the GO bit in the DOE Status register
+ to indicate that a request is ready for processing.
+2. **Endpoint processes**: The endpoint function reads the request from DOE write
+ mailbox register, sets the BUSY bit in the DOE Status register, identifies the
+ protocol of the data object, and executes the appropriate handler.
+3. **Endpoint responds**: The endpoint function writes the response data object to the
+ DOE read mailbox register (one DWORD at a time), and sets the READY bit in the DOE
+ Status register to indicate that the response is ready. If an error occurs during
+ request processing (such as unsupported protocol or handler failure), the endpoint
+ sets the ERROR bit in the DOE Status register instead of the READY bit.
+4. **Host reads response**: The root complex retrieves the response data from the DOE read
+ mailbox register once the READY bit is set in the DOE Status register, and then writes
+ any value to this register to indicate a successful read. If the ERROR bit was set,
+ the root complex discards the response and performs error handling as needed.
+
+Each mailbox operates independently and can handle one transaction at a time. The
+DOE specification supports data objects of size up to 256KB (2\ :sup:`18` dwords).
+
+For complete DOE capability details, refer to `PCI Express Base Specification Revision 7.0,
+Section 6.30 - Data Object Exchange (DOE)`.
+
+Key Terminologies
+=================
+
+.. _data-object-term:
+
+**Data Object**
+ A structured, vendor-defined, or standard-defined message exchanged between
+ root complex and endpoint function via DOE capability registers in configuration
+ space of the function.
+
+**Mailbox**
+ A DOE capability on the endpoint device, where each physical function can have
+ multiple mailboxes.
+
+**Protocol**
+ A specific type of DOE communication data object identified by a Vendor ID and Type.
+
+**Handler**
+ A function that processes DOE requests of a specific protocol and generates responses.
+
+Architecture of DOE Implementation for Endpoint
+===============================================
+
+.. code-block:: text
+
+ +------------------+
+ | |
+ | Root Complex |
+ | |
+ +--------^---------+
+ |
+ | Config space access
+ | over PCIe link
+ |
+ +----------v-----------+
+ | |
+ | PCIe Controller |
+ | as Endpoint |
+ | |
+ | +-----------------+ |
+ | | DOE Mailbox | |
+ | +-------^---------+ |
+ +----------|-----------+
+ +-----------|---------------------------------------------------------------+
+ | | +--------------------+ |
+ | +---------v--------+ Allocate | +--------------+ | |
+ | | |-------------------------------->| Request | | |
+ | | EP Controller | +--->| Buffer | | |
+ | | Driver | Free | | +--------------+ | |
+ | | |--------------------------+ | | | |
+ | +--------^---------+ | | | | |
+ | | | | | | |
+ | | | | | | |
+ | | pci_ep_doe_process_request() | | | | |
+ | | | | | | |
+ | +--------v---------+ Free | | | | |
+ | | |----------------------------+ | DDR | |
+ | | DOE EP Core |<----+ | | | |
+ | | (doe-ep.c) | | Discovery | | | |
+ | | |-----+ Protocol Handler | | | |
+ | +--------^---------+ | | | |
+ | | | | | |
+ | | protocol_handler() | | | |
+ | | | | | |
+ | +--------v---------+ | | | |
+ | | | | | +--------------+ | |
+ | | Protocol Handler | +----->| Response | | |
+ | | Module |-------------------------------->| Buffer | | |
+ | | (CMA/SPDM/Other) | Allocate | +--------------+ | |
+ | | | | | |
+ | +------------------+ | | |
+ | +--------------------+ |
+ +---------------------------------------------------------------------------+
+
+Initialization and Cleanup
+--------------------------
+
+**Framework Initialization and DOE Setup**
+
+The EPC core provides the ``pci_epc_doe_setup(epc)`` API for centralized DOE
+mailbox discovery and registration. The controller driver calls this API during
+its probe sequence if DOE is supported.
+
+This API performs the following steps:
+
+1. Calls ``pci_ep_doe_init(epc)``, which initializes the xarray data structure
+ (a resizable array data structure defined in linux) named ``doe_mbs`` that
+ stores metadata of DOE mailboxes for the controller in ``struct pci_epc``.
+2. Discovers all DOE capabilities in the endpoint function's configuration space
+ for each function. For each discovered DOE capability, calls
+ ``pci_ep_doe_add_mailbox(epc, func_no, cap_offset)`` to register the mailbox.
+
+Each DOE mailbox structure created by ``pci_ep_doe_add_mailbox()`` gets an
+ordered workqueue allocated for processing DOE requests sequentially for that
+mailbox, enabling concurrent request handling across different mailboxes. Each
+mailbox is uniquely identified by the combination of physical function number
+and capability offset for that controller.
+
+**Cleanup**
+
+The EPC core provides the ``pci_epc_doe_destroy(epc)`` API for centralized DOE
+cleanup. The controller driver calls this API during its remove sequence
+if DOE is supported.
+
+This API calls ``pci_ep_doe_destroy(epc)``, which destroys all registered
+mailboxes, cancels any pending tasks, flushes and destroys the workqueues,
+and frees all memory allocated to the mailboxes.
+
+Protocol Handler Support
+------------------------
+
+Protocol implementations (such as CMA, SPDM, or vendor-specific protocols) are
+supported through a static array of protocol handlers.
+
+When a new DOE protocol library is introduced, its handler function is added to
+the static ``pci_doe_protocols`` array in ``drivers/pci/endpoint/pci-ep-doe.c``.
+The discovery protocol (VID = 0x0001 (PCI-SIG vendor ID), Type = 0x00 (discovery
+protocol)) is included in this static array and handled internally by the
+DOE EP core.
+
+Request Handling
+----------------
+
+The complete flow of a DOE request from the root complex to the response:
+
+**Step 1: Root Complex → EP Controller Driver**
+
+The root complex writes a DOE request (Vendor ID, Type, and Payload) to the
+DOE write mailbox register in the endpoint function's configuration space and sets
+the GO bit in the DOE Control register, indicating that the request is ready for
+processing.
+
+**Step 2: EP Controller Driver → DOE EP Core**
+
+The controller driver reads the request header to determine the data object
+length. Based on this length field, it allocates a request buffer in memory
+(DDR) of the appropriate size. The driver then reads the complete request
+payload from the DOE write mailbox register and converts the data from
+little-endian format (the format followed in the PCIe transactions over the
+link) to CPU-native format using ``le32_to_cpu()``. The driver defines a
+completion callback function with signature ``void (*complete)(u8 func_no,
+u16 cap_offset, int status, u16 vendor, u8 type, void *response_pl,
+size_t response_pl_sz)`` to be invoked when the request processing completes.
+The driver then calls ``pci_ep_doe_process_request(epc, func_no, cap_offset,
+vendor, type, request, request_sz, complete)`` to hand off the request to the
+DOE EP core. This function returns immediately after queuing the work
+(without blocking), and the driver sets the BUSY bit in the DOE Status register.
+
+**Step 3: DOE EP Core Processing**
+
+The DOE EP core creates a task structure and submits it to the mailbox's ordered
+workqueue. This ensures that requests for each mailbox are processed
+sequentially, one at a time, as required by the DOE specification. It looks up
+the protocol handler based on the Vendor ID and Type from the request header,
+and executes the handler function.
+
+**Step 4: Protocol Handler Execution**
+
+The workqueue executes the task by calling the registered protocol handler:
+``handler(request, request_sz, &response, &response_sz)``. The handler processes
+the request, allocates a response buffer in memory (DDR), builds the response
+data, and returns the response pointer and size. For the discovery protocol,
+the DOE EP core handles this directly without invoking an external handler.
+
+**Step 5: DOE EP Core → EP Controller Driver**
+
+After the protocol handler completes, the DOE EP core frees the request buffer,
+and invokes the completion callback provided by the controller driver asynchronously.
+The callback receives the function number, capability offset (to identify the mailbox),
+status code indicating the result of request processing, vendor ID and type of the data
+object, the response buffer, and its size.
+
+**Step 6: EP Controller Driver → Root Complex**
+
+The controller driver converts the response from CPU-native format to
+little-endian format using ``cpu_to_le32()``, writes the response to DOE read
+mailbox register, and sets the READY bit in the DOE Status register. The root
+complex then reads the response from the read mailbox register. Finally, the controller
+driver frees the response buffer (which the handler allocated).
+
+Asynchronous Request Processing
+-------------------------------
+
+The DOE-EP framework implements asynchronous request processing because an
+endpoint function can have multiple instances of DOE mailboxes, and requests may
+be interleaved across these mailboxes. Request processing of one mailbox should
+not result in blocking request processing of other mailboxes. Hence, requests
+on each mailbox need to be handled in parallel for optimization.
+
+For the EP controller driver to handle requests on multiple mailboxes in
+parallel, ``pci_ep_doe_process_request()`` must be asynchronous. The function
+returns immediately after submitting the request to the mailbox's workqueue,
+without waiting for the request to complete. A completion callback provided by
+the controller driver is invoked asynchronously when request processing
+finishes. This asynchronous design enables concurrent processing of requests
+across different mailboxes.
+
+Abort Handling
+--------------
+
+The DOE specification allows the root complex to abort ongoing DOE operations
+by setting the ABORT bit in the DOE Control register.
+
+**Trigger**
+
+When the root complex sets the ABORT bit, the EP controller driver detects this
+condition (typically in an interrupt handler or register polling routine). The
+action taken depends on the timing of the abort:
+
+- **ABORT during request transfer**: If the ABORT bit is set while the root complex
+ is still transferring the request to the mailbox registers, the controller driver
+ discards the request and no call to ``pci_ep_doe_abort()`` is needed.
+
+- **ABORT after request submission**: If the ABORT bit is set after the request
+ has been fully received and submitted to the DOE EP core via
+ ``pci_ep_doe_process_request()``, the controller driver must call
+ ``pci_ep_doe_abort(epc, func_no, cap_offset)`` for the affected mailbox to
+ perform abort sequence in the DOE EP core.
+
+**Abort Sequence**
+
+The abort function performs the following actions:
+
+1. Sets the CANCEL flag on the mailbox to prevent queued requests from starting
+2. Flushes the workqueue to wait for any currently executing handler to complete
+ (handlers cannot be interrupted mid-execution)
+3. Clears the CANCEL flag to allow the mailbox to accept new requests
+
+Queued requests that have not started execution will be aborted with an error
+status. The currently executing request will complete normally, and the controller
+will reject the response if it arrives after the abort sequence has been triggered.
+
+.. note::
+ Independent of when the ABORT bit is triggered, the controller driver must
+ clear the ERROR, BUSY, and READY bits in the DOE Status register after
+ completing the abort operation to reset the mailbox to an idle state.
+
+Error Handling
+--------------
+
+Errors can occur during DOE request processing for various reasons, such as
+unsupported protocols, handler failures, or memory allocation failures.
+
+**Error Detection**
+
+When an error occurs during DOE request processing, the DOE EP core propagates this error
+back to the controller driver either through the ``pci_ep_doe_process_request()`` return value,
+or the status code passed to the completion callback.
+
+**Error Response**
+
+When the controller driver receives an error code, it sets the ERROR bit in the DOE Status
+register instead of writing a response to the read mailbox register, and frees the buffers.
+
+API Reference
+=============
+
+.. kernel-doc:: drivers/pci/endpoint/pci-ep-doe.c
+ :export:
--
2.34.1
^ permalink raw reply related
* [PATCH v3 3/4] PCI: endpoint: Add API for DOE initialization and setup in EPC core
From: Aksh Garg @ 2026-04-27 5:17 UTC (permalink / raw)
To: linux-pci, linux-doc, mani, kwilczynski, bhelgaas, corbet, kishon,
skhan, lukas, cassel, alistair
Cc: linux-arm-kernel, linux-kernel, s-vadapalli, danishanwar, srk,
a-garg7
In-Reply-To: <20260427051725.223704-1-a-garg7@ti.com>
Add pci_epc_setup_doe() API in EPC core driver to initialize and setup
the DOE framework for an endpoint controller. The API discovers the DOE
capabilities (extended capability ID 0x2E), and registers each discovered
DOE mailbox for all the functions in the endpoint controller. This API
should be invoked by the controller driver during probe based on the
doe_capable feature.
Add pci_epc_destroy_doe() API in EPC core driver for cleanup of DOE
resources, which should be invoked by the controller driver during
controller cleanup based on the doe_capable feature.
Co-developed-by: Siddharth Vadapalli <s-vadapalli@ti.com>
Signed-off-by: Siddharth Vadapalli <s-vadapalli@ti.com>
Signed-off-by: Aksh Garg <a-garg7@ti.com>
---
Changes from v2 to v3:
- Rebased on 7.1-rc1.
Changes since v1:
- New patch added to v2 (not present in v1)
v2: https://lore.kernel.org/all/20260401073022.215805-4-a-garg7@ti.com/
This patch is introduced based on the feedback provided by Manivannan
Sadhasivam at [1].
[1]: https://lore.kernel.org/all/p57x6jleaim5w7t2k3v7tioujnaxuovfpj5euop5ogefvw23se@y5fw3che5p5d/
drivers/pci/endpoint/pci-epc-core.c | 71 +++++++++++++++++++++++++++++
include/linux/pci-epc.h | 21 +++++++++
2 files changed, 92 insertions(+)
diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c
index 6c3c58185fc5..5a95a07b7d3a 100644
--- a/drivers/pci/endpoint/pci-epc-core.c
+++ b/drivers/pci/endpoint/pci-epc-core.c
@@ -14,6 +14,8 @@
#include <linux/pci-epf.h>
#include <linux/pci-ep-cfs.h>
+#include "../pci.h"
+
static const struct class pci_epc_class = {
.name = "pci_epc",
};
@@ -548,6 +550,75 @@ void pci_epc_mem_unmap(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
}
EXPORT_SYMBOL_GPL(pci_epc_mem_unmap);
+/**
+ * pci_epc_doe_setup() - Setup and discover DOE mailboxes for all functions
+ * @epc: the EPC device on which DOE mailboxes has to be setup
+ *
+ * Discover DOE (Data Object Exchange) capabilities for all physical functions
+ * in the endpoint controller and register DOE mailboxes.
+ *
+ * This API should be called by the controller driver during initialization
+ * if DOE support is available (indicated by doe_capable in pci_epc_features).
+ *
+ * RETURNS: 0 on success, -errno on failure
+ */
+int pci_epc_doe_setup(struct pci_epc *epc)
+{
+ u16 cap_offset = 0;
+ u8 func_no;
+ int ret;
+
+ if (!epc || !epc->ops || !epc->ops->find_ext_capability)
+ return -EINVAL;
+
+ /* Initialize DOE framework for this controller */
+ ret = pci_ep_doe_init(epc);
+ if (ret)
+ return ret;
+
+ /* Discover DOE capabilities for all functions */
+ for (func_no = 0; func_no < epc->max_functions; func_no++) {
+ while ((cap_offset = epc->ops->find_ext_capability(epc, func_no, 0,
+ cap_offset,
+ PCI_EXT_CAP_ID_DOE))) {
+ /* Register this DOE mailbox */
+ ret = pci_ep_doe_add_mailbox(epc, func_no, cap_offset);
+ if (ret) {
+ dev_err(&epc->dev,
+ "[pf%d:offset %x] failed to add DOE mailbox\n",
+ func_no, cap_offset);
+ }
+ }
+ }
+
+ dev_dbg(&epc->dev, "DOE mailboxes setup complete\n");
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_epc_doe_setup);
+
+/**
+ * pci_epc_doe_destroy() - Destroy and cleanup DOE mailboxes
+ * @epc: the EPC device on which DOE mailboxes has to be destroyed
+ *
+ * Destroy all DOE mailboxes registered on this endpoint controller and
+ * free associated resources.
+ *
+ * This API should be called by the controller driver during controller cleanup
+ * if DOE support is available (indicated by doe_capable in pci_epc_features).
+ *
+ * RETURNS: 0 on success, -errno on failure
+ */
+int pci_epc_doe_destroy(struct pci_epc *epc)
+{
+ if (!epc)
+ return -EINVAL;
+
+ pci_ep_doe_destroy(epc);
+ dev_dbg(&epc->dev, "DOE mailboxes destroyed\n");
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_epc_doe_destroy);
+
/**
* pci_epc_clear_bar() - reset the BAR
* @epc: the EPC device for which the BAR has to be cleared
diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h
index dd26294c8175..7b0f258ef330 100644
--- a/include/linux/pci-epc.h
+++ b/include/linux/pci-epc.h
@@ -84,6 +84,8 @@ struct pci_epc_map {
* @start: ops to start the PCI link
* @stop: ops to stop the PCI link
* @get_features: ops to get the features supported by the EPC
+ * @find_ext_capability: ops to find extended capability offset for a function
+ * in endpoint controller
* @owner: the module owner containing the ops
*/
struct pci_epc_ops {
@@ -115,6 +117,8 @@ struct pci_epc_ops {
void (*stop)(struct pci_epc *epc);
const struct pci_epc_features* (*get_features)(struct pci_epc *epc,
u8 func_no, u8 vfunc_no);
+ u16 (*find_ext_capability)(struct pci_epc *epc, u8 func_no,
+ u8 vfunc_no, u16 start, u8 cap);
struct module *owner;
};
@@ -270,6 +274,7 @@ struct pci_epc_bar_desc {
* @msi_capable: indicate if the endpoint function has MSI capability
* @msix_capable: indicate if the endpoint function has MSI-X capability
* @intx_capable: indicate if the endpoint can raise INTx interrupts
+ * @doe_capable: indicate if the endpoint function has DOE capability
* @bar: array specifying the hardware description for each BAR
* @align: alignment size required for BAR buffer allocation
*/
@@ -280,6 +285,7 @@ struct pci_epc_features {
unsigned int msi_capable : 1;
unsigned int msix_capable : 1;
unsigned int intx_capable : 1;
+ unsigned int doe_capable : 1;
struct pci_epc_bar_desc bar[PCI_STD_NUM_BARS];
size_t align;
};
@@ -368,6 +374,21 @@ int pci_epc_mem_map(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
void pci_epc_mem_unmap(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
struct pci_epc_map *map);
+#ifdef CONFIG_PCI_ENDPOINT_DOE
+int pci_epc_doe_setup(struct pci_epc *epc);
+int pci_epc_doe_destroy(struct pci_epc *epc);
+#else
+static inline int pci_epc_doe_setup(struct pci_epc *epc)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int pci_epc_doe_destroy(struct pci_epc *epc)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
#else
static inline void pci_epc_init_notify(struct pci_epc *epc)
{
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next] wifi: mt76: fix of_get_mac_address error handling
From: Rosen Penev @ 2026-04-27 5:17 UTC (permalink / raw)
To: linux-wireless
Cc: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
open list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support,
moderated list:ARM/Mediatek SoC support
Check return value instead of is_valid_ether_addr. The latter is handled
by the former.
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/wireless/mediatek/mt76/eeprom.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/eeprom.c b/drivers/net/wireless/mediatek/mt76/eeprom.c
index 93d91264687f..0f6ccf6ed53d 100644
--- a/drivers/net/wireless/mediatek/mt76/eeprom.c
+++ b/drivers/net/wireless/mediatek/mt76/eeprom.c
@@ -93,7 +93,7 @@ mt76_eeprom_override(struct mt76_phy *phy)
if (err == -EPROBE_DEFER)
return err;
- if (!is_valid_ether_addr(phy->macaddr)) {
+ if (err) {
eth_random_addr(phy->macaddr);
dev_info(dev->dev,
"Invalid MAC address, using random address %pM\n",
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] KVM: arm64: Wake-up from WFI when iqrchip is in userspace
From: Yao Yuan @ 2026-04-27 5:31 UTC (permalink / raw)
To: Marc Zyngier
Cc: kvmarm, kvm, linux-arm-kernel, Joey Gouly, Suzuki K Poulose,
Oliver Upton, Zenghui Yu
In-Reply-To: <87jytwbz2d.wl-maz@kernel.org>
On Fri, Apr 24, 2026 at 08:24:42AM +0800, Marc Zyngier wrote:
> On Fri, 24 Apr 2026 07:33:02 +0100,
> Yao Yuan <yaoyuan@linux.alibaba.com> wrote:
> >
> > On Thu, Apr 23, 2026 at 05:36:07PM +0800, Marc Zyngier wrote:
> > > It appears that there is nothing in the wake-up path that
> > > evaluates whether the in-kernel interrupts are pending unless
> > > we have a vgic.
> > >
> > > This means that the userspace irqchip support has been broken for
> > > about four years, and nobody noticed. It was also broken before
> > > as we wouldn't wake-up on a PMU interrupt, but hey, who cares...
> > >
> > > It is probably time to remove the feature altogether, because it
> > > was a terrible idea 10 years ago, and it still is.
> > >
> > > Fixes: b57de4ffd7c6d ("KVM: arm64: Simplify kvm_cpu_has_pending_timer()")
> > > Signed-off-by: Marc Zyngier <maz@kernel.org>
> > > ---
> > > arch/arm64/kvm/arm.c | 4 ++++
> > > 1 file changed, 4 insertions(+)
> > >
> > > diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> > > index 176cbe8baad30..8bb2c7422cc8b 100644
> > > --- a/arch/arm64/kvm/arm.c
> > > +++ b/arch/arm64/kvm/arm.c
> > > @@ -824,6 +824,10 @@ int kvm_arch_vcpu_runnable(struct kvm_vcpu *v)
> > > {
> > > bool irq_lines = *vcpu_hcr(v) & (HCR_VI | HCR_VF | HCR_VSE);
> > >
> >
> > Hi Marc,
> >
> > > + irq_lines |= (!irqchip_in_kernel(v->kvm) &&
> > > + (kvm_timer_should_notify_user(v) ||
> > > + kvm_pmu_should_notify_user(v)));
> >
> > How about a new helper like 'kvm_should_notify_us_irqchip()' ?
> > We can replace the same part at beginning of kvm_vcpu_exit_request() and
> > here w/ unlikely().
>
> I'd rather not introduce a helper, for two reasons:
>
> - this needs to be backported all the way to 5.19, because that's how
> far it has been broken. So keeping it small and localised is far
> better than introducing a helper that will make the backport less
> obvious.
Agree on this point very much!
>
> - I have patches to remove the other calls to kvm_*_notify_user() as a
> simplification of this utterly stupid feature.
OK.
>
> Finally, and while I agree that this could take an unlikely()
> qualifier, a much better course of action would be to have a separate
> patch that moves the qualifier to the predicate itself.
I got it, thanks for your such detail explanation!
>
> Thanks,
>
> M.
>
> --
> Jazz isn't dead. It just smells funny.
^ permalink raw reply
* RE: [PATCH v2 02/15] Drivers: hv: Move hv_vp_assist_page to common files
From: Michael Kelley @ 2026-04-27 5:37 UTC (permalink / raw)
To: Naman Jain, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Catalin Marinas, Will Deacon,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
x86@kernel.org, H . Peter Anvin, Arnd Bergmann, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Kelley
Cc: Marc Zyngier, Timothy Hayes, Lorenzo Pieralisi, Sascha Bischoff,
mrigendrachaubey, linux-hyperv@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-arch@vger.kernel.org,
linux-riscv@lists.infradead.org, vdso@mailbox.org,
ssengar@linux.microsoft.com
In-Reply-To: <20260423124206.2410879-3-namjain@linux.microsoft.com>
From: Naman Jain <namjain@linux.microsoft.com> Sent: Thursday, April 23, 2026 5:42 AM
>
> Move the logic to initialize and export hv_vp_assist_page from x86
> architecture code to Hyper-V common code to allow it to be used for
> upcoming arm64 support in MSHV_VTL driver.
> Note: This change also improves error handling - if VP assist page
> allocation fails, hyperv_init() now returns early instead of
> continuing with partial initialization.
>
> Signed-off-by: Roman Kisel <romank@linux.microsoft.com>
> Reviewed-by: Roman Kisel <vdso@mailbox.org>
> Signed-off-by: Naman Jain <namjain@linux.microsoft.com>
> ---
> arch/x86/hyperv/hv_init.c | 88 +-----------------------------
> arch/x86/include/asm/mshyperv.h | 14 -----
> drivers/hv/hv_common.c | 94 ++++++++++++++++++++++++++++++++-
> include/asm-generic/mshyperv.h | 16 ++++++
> include/hyperv/hvgdk_mini.h | 6 ++-
> 5 files changed, 115 insertions(+), 103 deletions(-)
>
> diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c
> index 323adc93f2dc..75a98b5e451b 100644
> --- a/arch/x86/hyperv/hv_init.c
> +++ b/arch/x86/hyperv/hv_init.c
> @@ -81,9 +81,6 @@ union hv_ghcb * __percpu *hv_ghcb_pg;
> /* Storage to save the hypercall page temporarily for hibernation */
> static void *hv_hypercall_pg_saved;
>
> -struct hv_vp_assist_page **hv_vp_assist_page;
> -EXPORT_SYMBOL_GPL(hv_vp_assist_page);
> -
> static int hyperv_init_ghcb(void)
> {
> u64 ghcb_gpa;
> @@ -117,59 +114,12 @@ static int hyperv_init_ghcb(void)
>
> static int hv_cpu_init(unsigned int cpu)
> {
> - union hv_vp_assist_msr_contents msr = { 0 };
> - struct hv_vp_assist_page **hvp;
> int ret;
>
> ret = hv_common_cpu_init(cpu);
> if (ret)
> return ret;
>
> - if (!hv_vp_assist_page)
> - return 0;
> -
> - hvp = &hv_vp_assist_page[cpu];
> - if (hv_root_partition()) {
> - /*
> - * For root partition we get the hypervisor provided VP assist
> - * page, instead of allocating a new page.
> - */
> - rdmsrq(HV_X64_MSR_VP_ASSIST_PAGE, msr.as_uint64);
> - *hvp = memremap(msr.pfn << HV_X64_MSR_VP_ASSIST_PAGE_ADDRESS_SHIFT,
> - PAGE_SIZE, MEMREMAP_WB);
> - } else {
> - /*
> - * The VP assist page is an "overlay" page (see Hyper-V TLFS's
> - * Section 5.2.1 "GPA Overlay Pages"). Here it must be zeroed
> - * out to make sure we always write the EOI MSR in
> - * hv_apic_eoi_write() *after* the EOI optimization is disabled
> - * in hv_cpu_die(), otherwise a CPU may not be stopped in the
> - * case of CPU offlining and the VM will hang.
> - */
> - if (!*hvp) {
> - *hvp = __vmalloc(PAGE_SIZE, GFP_KERNEL | __GFP_ZERO);
> -
> - /*
> - * Hyper-V should never specify a VM that is a Confidential
> - * VM and also running in the root partition. Root partition
> - * is blocked to run in Confidential VM. So only decrypt assist
> - * page in non-root partition here.
> - */
> - if (*hvp && !ms_hyperv.paravisor_present && hv_isolation_type_snp()) {
> - WARN_ON_ONCE(set_memory_decrypted((unsigned long)(*hvp), 1));
> - memset(*hvp, 0, PAGE_SIZE);
> - }
> - }
> -
> - if (*hvp)
> - msr.pfn = vmalloc_to_pfn(*hvp);
> -
> - }
> - if (!WARN_ON(!(*hvp))) {
> - msr.enable = 1;
> - wrmsrq(HV_X64_MSR_VP_ASSIST_PAGE, msr.as_uint64);
> - }
> -
> /* Allow Hyper-V stimer vector to be injected from Hypervisor. */
> if (ms_hyperv.misc_features & HV_STIMER_DIRECT_MODE_AVAILABLE)
> apic_update_vector(cpu, HYPERV_STIMER0_VECTOR, true);
> @@ -286,23 +236,6 @@ static int hv_cpu_die(unsigned int cpu)
>
> hv_common_cpu_die(cpu);
>
> - if (hv_vp_assist_page && hv_vp_assist_page[cpu]) {
> - union hv_vp_assist_msr_contents msr = { 0 };
> - if (hv_root_partition()) {
> - /*
> - * For root partition the VP assist page is mapped to
> - * hypervisor provided page, and thus we unmap the
> - * page here and nullify it, so that in future we have
> - * correct page address mapped in hv_cpu_init.
> - */
> - memunmap(hv_vp_assist_page[cpu]);
> - hv_vp_assist_page[cpu] = NULL;
> - rdmsrq(HV_X64_MSR_VP_ASSIST_PAGE, msr.as_uint64);
> - msr.enable = 0;
> - }
> - wrmsrq(HV_X64_MSR_VP_ASSIST_PAGE, msr.as_uint64);
> - }
> -
> if (hv_reenlightenment_cb == NULL)
> return 0;
>
> @@ -460,21 +393,6 @@ void __init hyperv_init(void)
> if (hv_common_init())
> return;
>
> - /*
> - * The VP assist page is useless to a TDX guest: the only use we
> - * would have for it is lazy EOI, which can not be used with TDX.
> - */
> - if (hv_isolation_type_tdx())
> - hv_vp_assist_page = NULL;
> - else
> - hv_vp_assist_page = kzalloc_objs(*hv_vp_assist_page, nr_cpu_ids);
> - if (!hv_vp_assist_page) {
> - ms_hyperv.hints &= ~HV_X64_ENLIGHTENED_VMCS_RECOMMENDED;
> -
> - if (!hv_isolation_type_tdx())
> - goto common_free;
> - }
> -
> if (ms_hyperv.paravisor_present && hv_isolation_type_snp()) {
> /* Negotiate GHCB Version. */
> if (!hv_ghcb_negotiate_protocol())
> @@ -483,7 +401,7 @@ void __init hyperv_init(void)
>
> hv_ghcb_pg = alloc_percpu(union hv_ghcb *);
> if (!hv_ghcb_pg)
> - goto free_vp_assist_page;
> + goto free_ghcb_page;
Seems like this should be "goto common_free". The allocation of
hv_ghcb_pg has failed, so going to a label where hv_ghcb_pg is
freed seems redundant. It works since free_percpu() checks for
a NULL argument, but it's a bit unexpected since the common_free
label is already there.
> }
>
> cpuhp = cpuhp_setup_state(CPUHP_AP_HYPERV_ONLINE, "x86/hyperv_init:online",
> @@ -613,10 +531,6 @@ void __init hyperv_init(void)
> cpuhp_remove_state(CPUHP_AP_HYPERV_ONLINE);
> free_ghcb_page:
> free_percpu(hv_ghcb_pg);
> -free_vp_assist_page:
> - kfree(hv_vp_assist_page);
> - hv_vp_assist_page = NULL;
> -common_free:
> hv_common_free();
> }
>
> diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
> index f64393e853ee..95b452387969 100644
> --- a/arch/x86/include/asm/mshyperv.h
> +++ b/arch/x86/include/asm/mshyperv.h
> @@ -155,16 +155,6 @@ static inline u64 hv_do_fast_hypercall16(u16 code, u64 input1, u64 input2)
> return _hv_do_fast_hypercall16(control, input1, input2);
> }
>
> -extern struct hv_vp_assist_page **hv_vp_assist_page;
> -
> -static inline struct hv_vp_assist_page *hv_get_vp_assist_page(unsigned int cpu)
> -{
> - if (!hv_vp_assist_page)
> - return NULL;
> -
> - return hv_vp_assist_page[cpu];
> -}
> -
> void __init hyperv_init(void);
> void hyperv_setup_mmu_ops(void);
> void set_hv_tscchange_cb(void (*cb)(void));
> @@ -254,10 +244,6 @@ static inline void hyperv_setup_mmu_ops(void) {}
> static inline void set_hv_tscchange_cb(void (*cb)(void)) {}
> static inline void clear_hv_tscchange_cb(void) {}
> static inline void hyperv_stop_tsc_emulation(void) {};
> -static inline struct hv_vp_assist_page *hv_get_vp_assist_page(unsigned int cpu)
> -{
> - return NULL;
> -}
> static inline int hyperv_flush_guest_mapping(u64 as) { return -1; }
> static inline int hyperv_flush_guest_mapping_range(u64 as,
> hyperv_fill_flush_list_func fill_func, void *data)
> diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
> index 6b67ac616789..e8633bc51d56 100644
> --- a/drivers/hv/hv_common.c
> +++ b/drivers/hv/hv_common.c
> @@ -28,7 +28,11 @@
> #include <linux/slab.h>
> #include <linux/dma-map-ops.h>
> #include <linux/set_memory.h>
> +#include <linux/vmalloc.h>
> +#include <linux/io.h>
> +#include <linux/hyperv.h>
> #include <hyperv/hvhdk.h>
> +#include <hyperv/hvgdk.h>
> #include <asm/mshyperv.h>
>
> u64 hv_current_partition_id = HV_PARTITION_ID_SELF;
> @@ -78,6 +82,8 @@ static struct ctl_table_header *hv_ctl_table_hdr;
> u8 * __percpu *hv_synic_eventring_tail;
> EXPORT_SYMBOL_GPL(hv_synic_eventring_tail);
>
> +struct hv_vp_assist_page **hv_vp_assist_page;
> +EXPORT_SYMBOL_GPL(hv_vp_assist_page);
> /*
> * Hyper-V specific initialization and shutdown code that is
> * common across all architectures. Called from architecture
> @@ -92,6 +98,9 @@ void __init hv_common_free(void)
> if (ms_hyperv.misc_features & HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE)
> hv_kmsg_dump_unregister();
>
> + kfree(hv_vp_assist_page);
> + hv_vp_assist_page = NULL;
> +
> kfree(hv_vp_index);
> hv_vp_index = NULL;
>
> @@ -394,6 +403,23 @@ int __init hv_common_init(void)
> for (i = 0; i < nr_cpu_ids; i++)
> hv_vp_index[i] = VP_INVAL;
>
> + /*
> + * The VP assist page is useless to a TDX guest: the only use we
> + * would have for it is lazy EOI, which can not be used with TDX.
> + */
> + if (hv_isolation_type_tdx()) {
> + hv_vp_assist_page = NULL;
> +#ifdef CONFIG_X86_64
> + ms_hyperv.hints &= ~HV_X64_ENLIGHTENED_VMCS_RECOMMENDED;
> +#endif
I realize that this #ifdef went away for the reason I flagged in v1 of
this patch set, but it's back again for a different reason.
Let me suggest another approach. hv_common_init() is called from
both the x86/64 and arm64 hyperv_init() functions. Immediately after
the call to hv_common_init() in the x86/64 hyperv_init(), test
hv_vp_assist_page for NULL and clear
HV_X64_ENLIGHTENED_VMCS_RECOMMENDED if it is. No #ifdef is
needed, and x86/64 specific hackery stays under arch/x86 instead of
being in common code.
> + } else {
> + hv_vp_assist_page = kzalloc_objs(*hv_vp_assist_page, nr_cpu_ids);
> + if (!hv_vp_assist_page) {
> + hv_common_free();
> + return -ENOMEM;
> + }
> + }
> +
> return 0;
> }
>
> @@ -471,6 +497,8 @@ void __init ms_hyperv_late_init(void)
>
> int hv_common_cpu_init(unsigned int cpu)
> {
> + union hv_vp_assist_msr_contents msr = { 0 };
> + struct hv_vp_assist_page **hvp;
> void **inputarg, **outputarg;
> u8 **synic_eventring_tail;
> u64 msr_vp_index;
> @@ -539,7 +567,53 @@ int hv_common_cpu_init(unsigned int cpu)
> sizeof(u8), flags);
> /* No need to unwind any of the above on failure here */
> if (unlikely(!*synic_eventring_tail))
> - ret = -ENOMEM;
> + return -ENOMEM;
> + }
> +
> + if (!hv_vp_assist_page)
> + return ret;
> +
> + hvp = &hv_vp_assist_page[cpu];
> + if (hv_root_partition()) {
> + /*
> + * For root partition we get the hypervisor provided VP assist
> + * page, instead of allocating a new page.
> + */
> + msr.as_uint64 = hv_get_msr(HV_MSR_VP_ASSIST_PAGE);
> + *hvp = memremap(msr.pfn << HV_VP_ASSIST_PAGE_ADDRESS_SHIFT,
> + HV_HYP_PAGE_SIZE, MEMREMAP_WB);
> + } else {
> + /*
> + * The VP assist page is an "overlay" page (see Hyper-V TLFS's
> + * Section 5.2.1 "GPA Overlay Pages"). Here it must be zeroed
> + * out to make sure that on x86/x64, we always write the EOI MSR in
> + * hv_apic_eoi_write() *after* the EOI optimization is disabled
> + * in hv_cpu_die(), otherwise a CPU may not be stopped in the
> + * case of CPU offlining and the VM will hang.
> + */
> + if (!*hvp) {
> + *hvp = __vmalloc(HV_HYP_PAGE_SIZE, flags | __GFP_ZERO);
> +
> + /*
> + * Hyper-V should never specify a VM that is a Confidential
> + * VM and also running in the root partition. Root partition
> + * is blocked to run in Confidential VM. So only decrypt assist
> + * page in non-root partition here.
> + */
> + if (*hvp &&
> + !ms_hyperv.paravisor_present &&
> + hv_isolation_type_snp()) {
> + WARN_ON_ONCE(set_memory_decrypted((unsigned long)(*hvp), 1));
> + memset(*hvp, 0, HV_HYP_PAGE_SIZE);
> + }
> + }
> +
> + if (*hvp)
> + msr.pfn = page_to_hvpfn(vmalloc_to_page(*hvp));
Your Patch 0 changelog mentions adding a comment about vmalloc_to_pfn(), which
I didn't see anywhere. I'm not sure what that comment would say, so maybe it
became unnecessary.
> + }
> + if (!WARN_ON(!(*hvp))) {
> + msr.enable = 1;
> + hv_set_msr(HV_MSR_VP_ASSIST_PAGE, msr.as_uint64);
> }
>
> return ret;
> @@ -566,6 +640,24 @@ int hv_common_cpu_die(unsigned int cpu)
> *synic_eventring_tail = NULL;
> }
>
> + if (hv_vp_assist_page && hv_vp_assist_page[cpu]) {
> + union hv_vp_assist_msr_contents msr = { 0 };
> +
> + if (hv_root_partition()) {
> + /*
> + * For root partition the VP assist page is mapped to
> + * hypervisor provided page, and thus we unmap the
> + * page here and nullify it, so that in future we have
> + * correct page address mapped in hv_cpu_init.
> + */
> + memunmap(hv_vp_assist_page[cpu]);
> + hv_vp_assist_page[cpu] = NULL;
> + msr.as_uint64 = hv_get_msr(HV_MSR_VP_ASSIST_PAGE);
> + msr.enable = 0;
> + }
> + hv_set_msr(HV_MSR_VP_ASSIST_PAGE, msr.as_uint64);
> + }
> +
> return 0;
> }
>
> diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
> index d37b68238c97..2810aa05dc73 100644
> --- a/include/asm-generic/mshyperv.h
> +++ b/include/asm-generic/mshyperv.h
> @@ -25,6 +25,7 @@
> #include <linux/nmi.h>
> #include <asm/ptrace.h>
> #include <hyperv/hvhdk.h>
> +#include <hyperv/hvgdk.h>
>
> #define VTPM_BASE_ADDRESS 0xfed40000
>
> @@ -299,6 +300,16 @@ do { \
> #define hv_status_debug(status, fmt, ...) \
> hv_status_printk(debug, status, fmt, ##__VA_ARGS__)
>
> +extern struct hv_vp_assist_page **hv_vp_assist_page;
> +
> +static inline struct hv_vp_assist_page *hv_get_vp_assist_page(unsigned int cpu)
> +{
> + if (!hv_vp_assist_page)
> + return NULL;
> +
> + return hv_vp_assist_page[cpu];
> +}
> +
> const char *hv_result_to_string(u64 hv_status);
> int hv_result_to_errno(u64 status);
> void hyperv_report_panic(struct pt_regs *regs, long err, bool in_die);
> @@ -327,6 +338,11 @@ static inline enum hv_isolation_type hv_get_isolation_type(void)
> {
> return HV_ISOLATION_TYPE_NONE;
> }
> +
> +static inline struct hv_vp_assist_page *hv_get_vp_assist_page(unsigned int cpu)
> +{
> + return NULL;
> +}
> #endif /* CONFIG_HYPERV */
>
> #if IS_ENABLED(CONFIG_MSHV_ROOT)
> diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h
> index 056ef7b6b360..c72d04cd5ae4 100644
> --- a/include/hyperv/hvgdk_mini.h
> +++ b/include/hyperv/hvgdk_mini.h
> @@ -149,6 +149,7 @@ struct hv_u128 {
> #define HV_X64_MSR_VP_ASSIST_PAGE_ADDRESS_SHIFT 12
Can this X64 specific definition of the shift be eliminated entirely,
and a single common definition for x86/64 and arm64 be used?
As I understand it, the MSR layout is the same on both architectures.
The one gotcha is that kvm_hv_set_msr() would need to be updated.
HV_X64_MSR_VP_ASSIST_PAGE_ADDRESS_MASK defined below isn't
used anywhere, so it could go away too. (The KVM selftest usage has
its own definition.)
I realize these are changes to a source code file that is derived from
Windows, and I'm not sure of the guidelines for such changes. So maybe
these suggestions have to be ignored ....
> #define HV_X64_MSR_VP_ASSIST_PAGE_ADDRESS_MASK \
> (~((1ull << HV_X64_MSR_VP_ASSIST_PAGE_ADDRESS_SHIFT) - 1))
> +#define HV_MSR_VP_ASSIST_PAGE (HV_X64_MSR_VP_ASSIST_PAGE)
This is the correct file for this #define, but it should be placed down around
line 1148 or so with the other HV_MSR_* definitions in terms of HV_X64_MSR_*
>
> /* Hyper-V Enlightened VMCS version mask in nested features CPUID */
> #define HV_X64_ENLIGHTENED_VMCS_VERSION 0xff
> @@ -410,6 +411,7 @@ union hv_x64_msr_hypercall_contents {
> #if defined(CONFIG_ARM64)
> #define HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE BIT(8)
> #define HV_STIMER_DIRECT_MODE_AVAILABLE BIT(13)
> +#define HV_VP_ASSIST_PAGE_ADDRESS_SHIFT 12
> #endif /* CONFIG_ARM64 */
>
> #if defined(CONFIG_X86)
> @@ -1163,6 +1165,8 @@ enum hv_register_name {
> #define HV_MSR_STIMER0_CONFIG (HV_X64_MSR_STIMER0_CONFIG)
> #define HV_MSR_STIMER0_COUNT (HV_X64_MSR_STIMER0_COUNT)
>
> +#define HV_VP_ASSIST_PAGE_ADDRESS_SHIFT HV_X64_MSR_VP_ASSIST_PAGE_ADDRESS_SHIFT
> +
> #elif defined(CONFIG_ARM64) /* CONFIG_X86 */
>
> #define HV_MSR_CRASH_P0 (HV_REGISTER_GUEST_CRASH_P0)
> @@ -1185,7 +1189,7 @@ enum hv_register_name {
>
> #define HV_MSR_STIMER0_CONFIG (HV_REGISTER_STIMER0_CONFIG)
> #define HV_MSR_STIMER0_COUNT (HV_REGISTER_STIMER0_COUNT)
> -
> +#define HV_MSR_VP_ASSIST_PAGE (HV_REGISTER_VP_ASSIST_PAGE)
Nit: This definition is slightly mis-aligned. It has spaces where there
should be a tab to match the similar definitions above it.
> #endif /* CONFIG_ARM64 */
>
> union hv_explicit_suspend_register {
> --
> 2.43.0
>
^ permalink raw reply
* RE: [PATCH v2 03/15] Drivers: hv: Move vmbus_handler to common code
From: Michael Kelley @ 2026-04-27 5:38 UTC (permalink / raw)
To: Naman Jain, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Catalin Marinas, Will Deacon,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
x86@kernel.org, H . Peter Anvin, Arnd Bergmann, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Kelley
Cc: Marc Zyngier, Timothy Hayes, Lorenzo Pieralisi, Sascha Bischoff,
mrigendrachaubey, linux-hyperv@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-arch@vger.kernel.org,
linux-riscv@lists.infradead.org, vdso@mailbox.org,
ssengar@linux.microsoft.com
In-Reply-To: <20260423124206.2410879-4-namjain@linux.microsoft.com>
From: Naman Jain <namjain@linux.microsoft.com> Sent: Thursday, April 23, 2026 5:42 AM
>
> Move the vmbus_handler global variable and hv_setup_vmbus_handler()/
> hv_remove_vmbus_handler() from arch/x86 to drivers/hv/hv_common.c.
>
> hv_setup_vmbus_handler() is called unconditionally in vmbus_bus_init()
> and works for both x86 (sysvec handler) and arm64 (vmbus_percpu_isr).
>
> This eliminates the need for separate percpu vmbus handler setup
> functions and __weak stubs, that are needed for adding ARM64 support
> in MSHV_VTL driver where we need to set a custom per-cpu vmbus handler.
>
> Signed-off-by: Naman Jain <namjain@linux.microsoft.com>
> ---
> arch/x86/kernel/cpu/mshyperv.c | 12 ------------
> drivers/hv/hv_common.c | 9 +++++++--
> drivers/hv/vmbus_drv.c | 17 +++++++++--------
> include/asm-generic/mshyperv.h | 1 +
> 4 files changed, 17 insertions(+), 22 deletions(-)
>
> diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c
> index 89a2eb8a0722..68706ff5880e 100644
> --- a/arch/x86/kernel/cpu/mshyperv.c
> +++ b/arch/x86/kernel/cpu/mshyperv.c
> @@ -145,7 +145,6 @@ void hv_set_msr(unsigned int reg, u64 value)
> EXPORT_SYMBOL_GPL(hv_set_msr);
>
> static void (*mshv_handler)(void);
> -static void (*vmbus_handler)(void);
> static void (*hv_stimer0_handler)(void);
> static void (*hv_kexec_handler)(void);
> static void (*hv_crash_handler)(struct pt_regs *regs);
> @@ -172,17 +171,6 @@ void hv_setup_mshv_handler(void (*handler)(void))
> mshv_handler = handler;
> }
>
> -void hv_setup_vmbus_handler(void (*handler)(void))
> -{
> - vmbus_handler = handler;
> -}
> -
> -void hv_remove_vmbus_handler(void)
> -{
> - /* We have no way to deallocate the interrupt gate */
> - vmbus_handler = NULL;
> -}
> -
> /*
> * Routines to do per-architecture handling of stimer0
> * interrupts when in Direct Mode
> diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c
> index e8633bc51d56..eb7b0028b45d 100644
> --- a/drivers/hv/hv_common.c
> +++ b/drivers/hv/hv_common.c
> @@ -758,13 +758,18 @@ bool __weak hv_isolation_type_tdx(void)
> }
> EXPORT_SYMBOL_GPL(hv_isolation_type_tdx);
>
> -void __weak hv_setup_vmbus_handler(void (*handler)(void))
> +void (*vmbus_handler)(void);
> +EXPORT_SYMBOL_GPL(vmbus_handler);
> +
> +void hv_setup_vmbus_handler(void (*handler)(void))
> {
> + vmbus_handler = handler;
> }
> EXPORT_SYMBOL_GPL(hv_setup_vmbus_handler);
>
> -void __weak hv_remove_vmbus_handler(void)
> +void hv_remove_vmbus_handler(void)
> {
> + vmbus_handler = NULL;
> }
> EXPORT_SYMBOL_GPL(hv_remove_vmbus_handler);
I'd suggest moving hv_setup_vmbus_handler() and
hv_remove_vmbus_handler() above or below the group
of __weak stubs in this source code file. There's a comment
describing the purpose of these __weak functions, and
intermixing these two functions that are no longer __weak
produces something of a jumble.
>
> diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
> index bc4fc1951ae1..052ca8b11cee 100644
> --- a/drivers/hv/vmbus_drv.c
> +++ b/drivers/hv/vmbus_drv.c
> @@ -1415,7 +1415,8 @@ EXPORT_SYMBOL_FOR_MODULES(vmbus_isr, "mshv_vtl");
>
> static irqreturn_t vmbus_percpu_isr(int irq, void *dev_id)
> {
> - vmbus_isr();
> + if (vmbus_handler)
> + vmbus_handler();
Is it necessary to test vmbus_handler first? From what I can
see, it is always set before the per-cpu interrupt is setup.
> return IRQ_HANDLED;
> }
>
> @@ -1517,8 +1518,10 @@ static int vmbus_bus_init(void)
> vmbus_irq_initialized = true;
> }
>
> + hv_setup_vmbus_handler(vmbus_isr);
> +
> if (vmbus_irq == -1) {
> - hv_setup_vmbus_handler(vmbus_isr);
> + /* x86: sysvec handler uses vmbus_handler directly */
> } else {
> ret = request_percpu_irq(vmbus_irq, vmbus_percpu_isr,
> "Hyper-V VMbus", &vmbus_evt);
> @@ -1553,9 +1556,8 @@ static int vmbus_bus_init(void)
> return 0;
>
> err_connect:
> - if (vmbus_irq == -1)
> - hv_remove_vmbus_handler();
> - else
> + hv_remove_vmbus_handler();
> + if (vmbus_irq != -1)
> free_percpu_irq(vmbus_irq, &vmbus_evt);
These operations should be reordered so they are the inverse
of how they are setup. I.e., free_percpu_irq() first, then remove
the VMBus handler. That's just good standard practice unless
there's a specific reason to do the cleanup ordering differently. In
fact, hv_remove_vmbus_handler() needs to be moved down
to the err_setup label so it's done if request_percpu_irq()
fails.
> err_setup:
> if (IS_ENABLED(CONFIG_PREEMPT_RT) && vmbus_irq_initialized) {
> @@ -3026,9 +3028,8 @@ static void __exit vmbus_exit(void)
> vmbus_connection.conn_state = DISCONNECTED;
> hv_stimer_global_cleanup();
> vmbus_disconnect();
> - if (vmbus_irq == -1)
> - hv_remove_vmbus_handler();
> - else
> + hv_remove_vmbus_handler();
> + if (vmbus_irq != -1)
> free_percpu_irq(vmbus_irq, &vmbus_evt);
Ordering should be changed here as well so it is the inverse
of how things are set up.
> if (IS_ENABLED(CONFIG_PREEMPT_RT) && vmbus_irq_initialized) {
> smpboot_unregister_percpu_thread(&vmbus_irq_threads);
> diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
> index 2810aa05dc73..db183c8cfb95 100644
> --- a/include/asm-generic/mshyperv.h
> +++ b/include/asm-generic/mshyperv.h
> @@ -179,6 +179,7 @@ static inline u64 hv_generate_guest_id(u64 kernel_version)
>
> int hv_get_hypervisor_version(union hv_hypervisor_version_info *info);
>
> +extern void (*vmbus_handler)(void);
> void hv_setup_vmbus_handler(void (*handler)(void));
> void hv_remove_vmbus_handler(void);
> void hv_setup_stimer0_handler(void (*handler)(void));
> --
> 2.43.0
>
^ permalink raw reply
* RE: [PATCH v2 07/15] arm64: hyperv: Add support for mshv_vtl_return_call
From: Michael Kelley @ 2026-04-27 5:38 UTC (permalink / raw)
To: Naman Jain, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Catalin Marinas, Will Deacon,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
x86@kernel.org, H . Peter Anvin, Arnd Bergmann, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Kelley
Cc: Marc Zyngier, Timothy Hayes, Lorenzo Pieralisi, Sascha Bischoff,
mrigendrachaubey, linux-hyperv@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-arch@vger.kernel.org,
linux-riscv@lists.infradead.org, vdso@mailbox.org,
ssengar@linux.microsoft.com
In-Reply-To: <20260423124206.2410879-8-namjain@linux.microsoft.com>
From: Naman Jain <namjain@linux.microsoft.com> Sent: Thursday, April 23, 2026 5:42 AM
>
> Add the arm64 variant of mshv_vtl_return_call() to support the MSHV_VTL
> driver on arm64. This function enables the transition between Virtual
> Trust Levels (VTLs) in MSHV_VTL when the kernel acts as a paravisor.
>
> Signed-off-by: Roman Kisel <romank@linux.microsoft.com>
> Reviewed-by: Roman Kisel <vdso@mailbox.org>
> Signed-off-by: Naman Jain <namjain@linux.microsoft.com>
> ---
> arch/arm64/hyperv/Makefile | 1 +
> arch/arm64/hyperv/hv_vtl.c | 158 ++++++++++++++++++++++++++++++
> arch/arm64/include/asm/mshyperv.h | 13 +++
> arch/x86/include/asm/mshyperv.h | 2 -
> drivers/hv/mshv_vtl.h | 3 +
> include/asm-generic/mshyperv.h | 2 +
> 6 files changed, 177 insertions(+), 2 deletions(-)
> create mode 100644 arch/arm64/hyperv/hv_vtl.c
>
[snip]
> diff --git a/arch/arm64/include/asm/mshyperv.h b/arch/arm64/include/asm/mshyperv.h
> index 585b23a26f1b..9eb0e5999f29 100644
> --- a/arch/arm64/include/asm/mshyperv.h
> +++ b/arch/arm64/include/asm/mshyperv.h
> @@ -60,6 +60,18 @@ static inline u64 hv_get_non_nested_msr(unsigned int reg)
> ARM_SMCCC_SMC_64, \
> ARM_SMCCC_OWNER_VENDOR_HYP, \
> HV_SMCCC_FUNC_NUMBER)
> +
> +struct mshv_vtl_cpu_context {
> +/*
> + * x18 is managed by the hypervisor. It won't be reloaded from this array.
> + * It is included here for convenience in array indexing.
> + * 'rsvd' field serves as alignment padding so q[] starts at offset 32*8=256.
> + */
> + __u64 x[31];
> + __u64 rsvd;
> + __uint128_t q[32];
> +};
> +
> #ifdef CONFIG_HYPERV_VTL_MODE
> /*
> * Get/Set the register. If the function returns `1`, that must be done via
> @@ -69,6 +81,7 @@ static inline int hv_vtl_get_set_reg(struct hv_register_assoc *regs,
> bool set, b
> {
> return 1;
> }
> +
This appears to be a spurious blank line being added since there
are no other changes in the vicinity.
> #endif
>
> #include <asm-generic/mshyperv.h>
> diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
> index 08278547b84c..b4d80c9a673a 100644
> --- a/arch/x86/include/asm/mshyperv.h
> +++ b/arch/x86/include/asm/mshyperv.h
> @@ -286,7 +286,6 @@ struct mshv_vtl_cpu_context {
> #ifdef CONFIG_HYPERV_VTL_MODE
> void __init hv_vtl_init_platform(void);
> int __init hv_vtl_early_init(void);
> -void mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0);
> void mshv_vtl_return_call_init(u64 vtl_return_offset);
> void mshv_vtl_return_hypercall(void);
> void __mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0);
> @@ -294,7 +293,6 @@ int hv_vtl_get_set_reg(struct hv_register_assoc *regs, bool set,
> bool shared);
> #else
> static inline void __init hv_vtl_init_platform(void) {}
> static inline int __init hv_vtl_early_init(void) { return 0; }
> -static inline void mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) {}
> static inline void mshv_vtl_return_call_init(u64 vtl_return_offset) {}
> static inline void mshv_vtl_return_hypercall(void) {}
> static inline void __mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) {}
> diff --git a/drivers/hv/mshv_vtl.h b/drivers/hv/mshv_vtl.h
> index a6eea52f7aa2..103f07371f3f 100644
> --- a/drivers/hv/mshv_vtl.h
> +++ b/drivers/hv/mshv_vtl.h
> @@ -22,4 +22,7 @@ struct mshv_vtl_run {
> char vtl_ret_actions[MSHV_MAX_RUN_MSG_SIZE];
> };
>
> +static_assert(sizeof(struct mshv_vtl_cpu_context) <= 1024,
> + "struct mshv_vtl_cpu_context exceeds reserved space in struct
> mshv_vtl_run");
> +
> #endif /* _MSHV_VTL_H */
> diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
> index db183c8cfb95..8cdf2a9fbdfb 100644
> --- a/include/asm-generic/mshyperv.h
> +++ b/include/asm-generic/mshyperv.h
> @@ -396,8 +396,10 @@ static inline int hv_deposit_memory(u64 partition_id, u64 status)
>
> #if IS_ENABLED(CONFIG_HYPERV_VTL_MODE)
> u8 __init get_vtl(void);
> +void mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0);
> #else
> static inline u8 get_vtl(void) { return 0; }
> +static inline void mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) {}
Is this stub needed? Maybe I missed something, but it looks to me like none
of the code that calls this gets built unless CONFIG_HYPERV_VTL_MODE is set.
See further comments about stubs in Patch 8 of this series.
> #endif
>
> #endif
> --
> 2.43.0
>
^ permalink raw reply
* RE: [PATCH v2 08/15] Drivers: hv: Move hv_call_(get|set)_vp_registers() declarations
From: Michael Kelley @ 2026-04-27 5:39 UTC (permalink / raw)
To: Naman Jain, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Catalin Marinas, Will Deacon,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
x86@kernel.org, H . Peter Anvin, Arnd Bergmann, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Kelley
Cc: Marc Zyngier, Timothy Hayes, Lorenzo Pieralisi, Sascha Bischoff,
mrigendrachaubey, linux-hyperv@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-arch@vger.kernel.org,
linux-riscv@lists.infradead.org, vdso@mailbox.org,
ssengar@linux.microsoft.com
In-Reply-To: <20260423124206.2410879-9-namjain@linux.microsoft.com>
From: Naman Jain <namjain@linux.microsoft.com> Sent: Thursday, April 23, 2026 5:42 AM
>
> Move hv_call_get_vp_registers() and hv_call_set_vp_registers()
> declarations from drivers/hv/mshv.h to include/asm-generic/mshyperv.h.
>
> These functions are defined in mshv_common.c and are going to be called
> from both drivers/hv/ and arch/x86/hyperv/hv_vtl.c. The latter never
> included mshv.h, relying on implicit declaration visibility. Moving the
> declarations to the arch-generic Hyper-V header makes them properly
> visible to all architecture-specific callers.
>
> Provide static inline stubs returning -EOPNOTSUPP when neither
> CONFIG_MSHV_ROOT nor CONFIG_MSHV_VTL is enabled.
Looking at the drivers/hv/Kconfig, it's possible to build with
CONFIG_HYPERV_VTL_MODE=y, but not CONFIG_MSHV_VTL. In such a
case, mshv_common.o doesn't get built, which is why the stubs are
needed. Is such a configuration desirable for some scenarios?
I wonder if having CONFIG_HYPERV_VTL_MODE force the building of
mshv_common.o would be a better approach. Then the stubs wouldn't
be needed. The "ifneq" statement in drivers/hv/Makefile could use
CONFIG_HYPERV_VTL_MODE instead of CONFIG_MSHV_VTL, and
everything would be good since CONFIG_MSHV_VTL depends on
CONFIG_HYPERV_VTL_MODE.
>
> Signed-off-by: Naman Jain <namjain@linux.microsoft.com>
> ---
> drivers/hv/mshv.h | 8 --------
> include/asm-generic/mshyperv.h | 26 ++++++++++++++++++++++++++
> 2 files changed, 26 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/hv/mshv.h b/drivers/hv/mshv.h
> index d4813df92b9c..0fcb7f9ba6a9 100644
> --- a/drivers/hv/mshv.h
> +++ b/drivers/hv/mshv.h
> @@ -14,14 +14,6 @@
> memchr_inv(&((STRUCT).MEMBER), \
> 0, sizeof_field(typeof(STRUCT), MEMBER))
>
> -int hv_call_get_vp_registers(u32 vp_index, u64 partition_id, u16 count,
> - union hv_input_vtl input_vtl,
> - struct hv_register_assoc *registers);
> -
> -int hv_call_set_vp_registers(u32 vp_index, u64 partition_id, u16 count,
> - union hv_input_vtl input_vtl,
> - struct hv_register_assoc *registers);
> -
> int hv_call_get_partition_property(u64 partition_id, u64 property_code,
> u64 *property_value);
>
> diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
> index 8cdf2a9fbdfb..ef0b9466808c 100644
> --- a/include/asm-generic/mshyperv.h
> +++ b/include/asm-generic/mshyperv.h
> @@ -394,6 +394,32 @@ static inline int hv_deposit_memory(u64 partition_id, u64
> status)
> return hv_deposit_memory_node(NUMA_NO_NODE, partition_id, status);
> }
>
> +#if IS_ENABLED(CONFIG_MSHV_ROOT) || IS_ENABLED(CONFIG_MSHV_VTL)
> +int hv_call_get_vp_registers(u32 vp_index, u64 partition_id, u16 count,
> + union hv_input_vtl input_vtl,
> + struct hv_register_assoc *registers);
> +
> +int hv_call_set_vp_registers(u32 vp_index, u64 partition_id, u16 count,
> + union hv_input_vtl input_vtl,
> + struct hv_register_assoc *registers);
> +#else
> +static inline int hv_call_get_vp_registers(u32 vp_index, u64 partition_id,
> + u16 count,
> + union hv_input_vtl input_vtl,
> + struct hv_register_assoc *registers)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +static inline int hv_call_set_vp_registers(u32 vp_index, u64 partition_id,
> + u16 count,
> + union hv_input_vtl input_vtl,
> + struct hv_register_assoc *registers)
> +{
> + return -EOPNOTSUPP;
> +}
> +#endif /* CONFIG_MSHV_ROOT || CONFIG_MSHV_VTL */
> +
> #if IS_ENABLED(CONFIG_HYPERV_VTL_MODE)
> u8 __init get_vtl(void);
> void mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0);
> --
> 2.43.0
>
^ permalink raw reply
* RE: [PATCH v2 09/15] Drivers: hv: mshv_vtl: Move hv_vtl_configure_reg_page() to x86
From: Michael Kelley @ 2026-04-27 5:40 UTC (permalink / raw)
To: Naman Jain, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Catalin Marinas, Will Deacon,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
x86@kernel.org, H . Peter Anvin, Arnd Bergmann, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Kelley
Cc: Marc Zyngier, Timothy Hayes, Lorenzo Pieralisi, Sascha Bischoff,
mrigendrachaubey, linux-hyperv@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-arch@vger.kernel.org,
linux-riscv@lists.infradead.org, vdso@mailbox.org,
ssengar@linux.microsoft.com
In-Reply-To: <20260423124206.2410879-10-namjain@linux.microsoft.com>
From: Naman Jain <namjain@linux.microsoft.com> Sent: Thursday, April 23, 2026 5:42 AM
>
> Move hv_vtl_configure_reg_page() from drivers/hv/mshv_vtl_main.c to
> arch/x86/hyperv/hv_vtl.c. The register page overlay is an x86-specific
> feature that uses HV_X64_REGISTER_REG_PAGE, so its configuration belongs
> in architecture-specific code.
>
> Move struct mshv_vtl_per_cpu and union hv_synic_overlay_page_msr to
> include/asm-generic/mshyperv.h so they are visible to both arch and
> driver code.
>
> Change the return type from void to bool so the caller can determine
> whether the register page was successfully configured and set
> mshv_has_reg_page accordingly.
>
> Signed-off-by: Naman Jain <namjain@linux.microsoft.com>
> ---
> arch/x86/hyperv/hv_vtl.c | 32 ++++++++++++++++++++++
> drivers/hv/mshv_vtl_main.c | 49 +++-------------------------------
> include/asm-generic/mshyperv.h | 17 ++++++++++++
> 3 files changed, 53 insertions(+), 45 deletions(-)
>
> diff --git a/arch/x86/hyperv/hv_vtl.c b/arch/x86/hyperv/hv_vtl.c
> index 09d81f9b853c..f3ffb6a7cb2d 100644
> --- a/arch/x86/hyperv/hv_vtl.c
> +++ b/arch/x86/hyperv/hv_vtl.c
> @@ -20,6 +20,7 @@
> #include <uapi/asm/mtrr.h>
> #include <asm/debugreg.h>
> #include <linux/export.h>
> +#include <linux/hyperv.h>
> #include <../kernel/smpboot.h>
> #include "../../kernel/fpu/legacy.h"
>
> @@ -259,6 +260,37 @@ int __init hv_vtl_early_init(void)
> return 0;
> }
>
> +static const union hv_input_vtl input_vtl_zero;
> +
> +bool hv_vtl_configure_reg_page(struct mshv_vtl_per_cpu *per_cpu)
> +{
> + struct hv_register_assoc reg_assoc = {};
> + union hv_synic_overlay_page_msr overlay = {};
> + struct page *reg_page;
> +
> + reg_page = alloc_page(GFP_KERNEL | __GFP_ZERO | __GFP_RETRY_MAYFAIL);
> + if (!reg_page) {
> + WARN(1, "failed to allocate register page\n");
> + return false;
> + }
> +
> + overlay.enabled = 1;
> + overlay.pfn = page_to_hvpfn(reg_page);
> + reg_assoc.name = HV_X64_REGISTER_REG_PAGE;
> + reg_assoc.value.reg64 = overlay.as_uint64;
> +
> + if (hv_call_set_vp_registers(HV_VP_INDEX_SELF, HV_PARTITION_ID_SELF,
> + 1, input_vtl_zero, ®_assoc)) {
> + WARN(1, "failed to setup register page\n");
> + __free_page(reg_page);
> + return false;
> + }
> +
> + per_cpu->reg_page = reg_page;
> + return true;
> +}
> +EXPORT_SYMBOL_GPL(hv_vtl_configure_reg_page);
> +
> DEFINE_STATIC_CALL_NULL(__mshv_vtl_return_hypercall, void (*)(void));
>
> void mshv_vtl_return_call_init(u64 vtl_return_offset)
> diff --git a/drivers/hv/mshv_vtl_main.c b/drivers/hv/mshv_vtl_main.c
> index 91517b45d526..c79d24317b8e 100644
> --- a/drivers/hv/mshv_vtl_main.c
> +++ b/drivers/hv/mshv_vtl_main.c
> @@ -78,21 +78,6 @@ struct mshv_vtl {
> u64 id;
> };
>
> -struct mshv_vtl_per_cpu {
> - struct mshv_vtl_run *run;
> - struct page *reg_page;
> -};
> -
> -/* SYNIC_OVERLAY_PAGE_MSR - internal, identical to hv_synic_simp */
> -union hv_synic_overlay_page_msr {
> - u64 as_uint64;
> - struct {
> - u64 enabled: 1;
> - u64 reserved: 11;
> - u64 pfn: 52;
> - } __packed;
> -};
> -
> static struct mutex mshv_vtl_poll_file_lock;
> static union hv_register_vsm_page_offsets mshv_vsm_page_offsets;
> static union hv_register_vsm_capabilities mshv_vsm_capabilities;
> @@ -201,34 +186,6 @@ static struct page *mshv_vtl_cpu_reg_page(int cpu)
> return *per_cpu_ptr(&mshv_vtl_per_cpu.reg_page, cpu);
> }
>
> -static void mshv_vtl_configure_reg_page(struct mshv_vtl_per_cpu *per_cpu)
> -{
> - struct hv_register_assoc reg_assoc = {};
> - union hv_synic_overlay_page_msr overlay = {};
> - struct page *reg_page;
> -
> - reg_page = alloc_page(GFP_KERNEL | __GFP_ZERO | __GFP_RETRY_MAYFAIL);
> - if (!reg_page) {
> - WARN(1, "failed to allocate register page\n");
> - return;
> - }
> -
> - overlay.enabled = 1;
> - overlay.pfn = page_to_hvpfn(reg_page);
> - reg_assoc.name = HV_X64_REGISTER_REG_PAGE;
> - reg_assoc.value.reg64 = overlay.as_uint64;
> -
> - if (hv_call_set_vp_registers(HV_VP_INDEX_SELF, HV_PARTITION_ID_SELF,
> - 1, input_vtl_zero, ®_assoc)) {
> - WARN(1, "failed to setup register page\n");
> - __free_page(reg_page);
> - return;
> - }
> -
> - per_cpu->reg_page = reg_page;
> - mshv_has_reg_page = true;
> -}
> -
> static void mshv_vtl_synic_enable_regs(unsigned int cpu)
> {
> union hv_synic_sint sint;
> @@ -329,8 +286,10 @@ static int mshv_vtl_alloc_context(unsigned int cpu)
> if (!per_cpu->run)
> return -ENOMEM;
>
> - if (mshv_vsm_capabilities.intercept_page_available)
> - mshv_vtl_configure_reg_page(per_cpu);
> + if (mshv_vsm_capabilities.intercept_page_available) {
> + if (hv_vtl_configure_reg_page(per_cpu))
> + mshv_has_reg_page = true;
> + }
>
> mshv_vtl_synic_enable_regs(cpu);
>
> diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h
> index ef0b9466808c..9e86178c182e 100644
> --- a/include/asm-generic/mshyperv.h
> +++ b/include/asm-generic/mshyperv.h
> @@ -420,12 +420,29 @@ static inline int hv_call_set_vp_registers(u32 vp_index, u64
> partition_id,
> }
> #endif /* CONFIG_MSHV_ROOT || CONFIG_MSHV_VTL */
>
> +struct mshv_vtl_per_cpu {
> + struct mshv_vtl_run *run;
> + struct page *reg_page;
> +};
> +
> #if IS_ENABLED(CONFIG_HYPERV_VTL_MODE)
> +/* SYNIC_OVERLAY_PAGE_MSR - internal, identical to hv_synic_simp */
This comment pre-dates your patch, but I don't understand the point
it is trying to make. The comment is factually true, but I don't know
why calling that out is relevant. The REG_PAGE MSR seems to be
conceptually separate and distinct from the SIMP MSR, so the fact
that the layouts are the same is just a coincidence. Or is there some
relationship between the two MSRs that I'm not aware of, and the
comment is trying (and failing?) to point out?
> +union hv_synic_overlay_page_msr {
> + u64 as_uint64;
> + struct {
> + u64 enabled: 1;
> + u64 reserved: 11;
> + u64 pfn: 52;
> + } __packed;
> +};
> +
> u8 __init get_vtl(void);
> void mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0);
> +bool hv_vtl_configure_reg_page(struct mshv_vtl_per_cpu *per_cpu);
> #else
> static inline u8 get_vtl(void) { return 0; }
> static inline void mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) {}
> +static inline bool hv_vtl_configure_reg_page(struct mshv_vtl_per_cpu *per_cpu) { return false; }
As with Patch 8, if CONFIG_HYPERV_VTL_MODE caused mshv_common.o
to be built, this stub wouldn't be needed.
> #endif
>
> #endif
> --
> 2.43.0
>
^ permalink raw reply
* RE: [PATCH v2 12/15] mshv_vtl: Move VSM code page offset logic to x86 files
From: Michael Kelley @ 2026-04-27 5:40 UTC (permalink / raw)
To: Naman Jain, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Catalin Marinas, Will Deacon,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
x86@kernel.org, H . Peter Anvin, Arnd Bergmann, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Kelley
Cc: Marc Zyngier, Timothy Hayes, Lorenzo Pieralisi, Sascha Bischoff,
mrigendrachaubey, linux-hyperv@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-arch@vger.kernel.org,
linux-riscv@lists.infradead.org, vdso@mailbox.org,
ssengar@linux.microsoft.com
In-Reply-To: <20260423124206.2410879-13-namjain@linux.microsoft.com>
From: Naman Jain <namjain@linux.microsoft.com> Sent: Thursday, April 23, 2026 5:42 AM
>
> The VSM code page offset register (HV_REGISTER_VSM_CODE_PAGE_OFFSETS)
> is x86 specific, its value configures the static call used to return
> to VTL0 via the hypercall page. Move the register read from the common
> mshv_vtl_get_vsm_regs() into the x86 mshv_vtl_return_call_init(),
> which is the sole consumer of the offset.
>
> Change mshv_vtl_return_call_init() from taking a u64 parameter
> to taking no arguments, and rename mshv_vtl_get_vsm_regs() to
> mshv_vtl_get_vsm_cap_reg() since it now only fetches
> HV_REGISTER_VSM_CAPABILITIES.
>
> No functional change on x86. This prepares the common driver code for
> ARM64 where VSM code page offsets do not apply.
>
> Signed-off-by: Naman Jain <namjain@linux.microsoft.com>
> ---
> arch/x86/hyperv/hv_vtl.c | 19 +++++++++++++++++--
> arch/x86/include/asm/mshyperv.h | 4 ++--
> drivers/hv/mshv_vtl_main.c | 24 +++++++++++++-----------
> 3 files changed, 32 insertions(+), 15 deletions(-)
>
> diff --git a/arch/x86/hyperv/hv_vtl.c b/arch/x86/hyperv/hv_vtl.c
> index f3ffb6a7cb2d..7c10b34cf8a4 100644
> --- a/arch/x86/hyperv/hv_vtl.c
> +++ b/arch/x86/hyperv/hv_vtl.c
> @@ -293,10 +293,25 @@ EXPORT_SYMBOL_GPL(hv_vtl_configure_reg_page);
>
> DEFINE_STATIC_CALL_NULL(__mshv_vtl_return_hypercall, void (*)(void));
>
> -void mshv_vtl_return_call_init(u64 vtl_return_offset)
> +int mshv_vtl_return_call_init(void)
> {
> + struct hv_register_assoc vsm_pg_offset_reg;
> + union hv_register_vsm_page_offsets offsets;
> + int ret;
> +
> + vsm_pg_offset_reg.name = HV_REGISTER_VSM_CODE_PAGE_OFFSETS;
> +
> + ret = hv_call_get_vp_registers(HV_VP_INDEX_SELF, HV_PARTITION_ID_SELF,
> + 1, input_vtl_zero, &vsm_pg_offset_reg);
> + if (ret)
> + return ret;
> +
> + offsets.as_uint64 = vsm_pg_offset_reg.value.reg64;
> +
> static_call_update(__mshv_vtl_return_hypercall,
> - (void *)((u8 *)hv_hypercall_pg + vtl_return_offset));
> + (void *)((u8 *)hv_hypercall_pg + offsets.vtl_return_offset));
> +
> + return 0;
> }
> EXPORT_SYMBOL(mshv_vtl_return_call_init);
>
> diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
> index b4d80c9a673a..b48f115c1292 100644
> --- a/arch/x86/include/asm/mshyperv.h
> +++ b/arch/x86/include/asm/mshyperv.h
> @@ -286,14 +286,14 @@ struct mshv_vtl_cpu_context {
> #ifdef CONFIG_HYPERV_VTL_MODE
> void __init hv_vtl_init_platform(void);
> int __init hv_vtl_early_init(void);
> -void mshv_vtl_return_call_init(u64 vtl_return_offset);
> +int mshv_vtl_return_call_init(void);
> void mshv_vtl_return_hypercall(void);
> void __mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0);
> int hv_vtl_get_set_reg(struct hv_register_assoc *regs, bool set, bool shared);
> #else
> static inline void __init hv_vtl_init_platform(void) {}
> static inline int __init hv_vtl_early_init(void) { return 0; }
> -static inline void mshv_vtl_return_call_init(u64 vtl_return_offset) {}
> +static inline int mshv_vtl_return_call_init(void) { return 0; }
> static inline void mshv_vtl_return_hypercall(void) {}
> static inline void __mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) {}
> #endif
> diff --git a/drivers/hv/mshv_vtl_main.c b/drivers/hv/mshv_vtl_main.c
> index 4c9ae65ad3e8..be498c9234fd 100644
> --- a/drivers/hv/mshv_vtl_main.c
> +++ b/drivers/hv/mshv_vtl_main.c
> @@ -79,7 +79,6 @@ struct mshv_vtl {
> };
>
> static struct mutex mshv_vtl_poll_file_lock;
> -static union hv_register_vsm_page_offsets mshv_vsm_page_offsets;
> static union hv_register_vsm_capabilities mshv_vsm_capabilities;
>
> static DEFINE_PER_CPU(struct mshv_vtl_poll_file, mshv_vtl_poll_file);
> @@ -203,21 +202,19 @@ static void mshv_vtl_synic_enable_regs(unsigned int cpu)
> /* VTL2 Host VSP SINT is (un)masked when the user mode requests that */
> }
>
> -static int mshv_vtl_get_vsm_regs(void)
> +static int mshv_vtl_get_vsm_cap_reg(void)
> {
> - struct hv_register_assoc registers[2];
> - int ret, count = 2;
> + struct hv_register_assoc vsm_capability_reg;
> + int ret;
>
> - registers[0].name = HV_REGISTER_VSM_CODE_PAGE_OFFSETS;
> - registers[1].name = HV_REGISTER_VSM_CAPABILITIES;
> + vsm_capability_reg.name = HV_REGISTER_VSM_CAPABILITIES;
>
> ret = hv_call_get_vp_registers(HV_VP_INDEX_SELF, HV_PARTITION_ID_SELF,
> - count, input_vtl_zero, registers);
> + 1, input_vtl_zero, &vsm_capability_reg);
> if (ret)
> return ret;
>
> - mshv_vsm_page_offsets.as_uint64 = registers[0].value.reg64;
> - mshv_vsm_capabilities.as_uint64 = registers[1].value.reg64;
> + mshv_vsm_capabilities.as_uint64 = vsm_capability_reg.value.reg64;
>
> return ret;
Nit: This could be just "return 0".
> }
> @@ -1139,13 +1136,18 @@ static int __init mshv_vtl_init(void)
> tasklet_init(&msg_dpc, mshv_vtl_sint_on_msg_dpc, 0);
> init_waitqueue_head(&fd_wait_queue);
>
> - if (mshv_vtl_get_vsm_regs()) {
> + if (mshv_vtl_get_vsm_cap_reg()) {
> dev_emerg(dev, "Unable to get VSM capabilities !!\n");
Why is this failure an emergency message, while the other failures
here in mshv_vtl_init() are just error messages? When there's lack
of consistency, I always wonder if there is a reason ..... :-)
> ret = -ENODEV;
> goto free_dev;
> }
>
> - mshv_vtl_return_call_init(mshv_vsm_page_offsets.vtl_return_offset);
> + ret = mshv_vtl_return_call_init();
> + if (ret) {
> + dev_err(dev, "mshv_vtl_return_call_init failed: %d\n", ret);
> + goto free_dev;
> + }
> +
> ret = hv_vtl_setup_synic();
> if (ret)
> goto free_dev;
> --
> 2.43.0
>
^ permalink raw reply
* [PATCH v4 0/3] Allow ATS to be always on for certain ATS-capable devices
From: Nicolin Chen @ 2026-04-27 5:53 UTC (permalink / raw)
To: jgg, will, robin.murphy, bhelgaas
Cc: joro, praan, baolu.lu, kevin.tian, miko.lenczewski,
linux-arm-kernel, iommu, linux-kernel, linux-pci, dan.j.williams,
jonathan.cameron, vsethi, linux-cxl, nirmoyd
PCI ATS function is controlled by the IOMMU driver calling pci_enable_ats()
and pci_disable_ats() helpers. In general, IOMMU driver only enables ATS
when a translation channel is enabled on a PASID, typically for an SVA use
case. When a device's RID is IOMMU bypassed and its PASIDs are not running
SVA use case, ATS is always disabled.
However, certain PCIe devices require non-PASID ATS on the RID, even if the
RID is IOMMU bypassed. E.g. CXL.cache capability requires ATS to access the
physical memory; some pre-CXL NVIDIA GPUs also require the ATS to be always
on even when their RIDs are IOMMU bypassed.
Provide a helper function to detect CXL.cache capability and scan through a
pre-CXL device ID list.
As the initial use case, call the helper in ARM SMMUv3 driver and adapt the
driver accordingly with a per-device ats_always_on flag.
This is on Github:
https://github.com/nicolinc/iommufd/commits/pci_ats_always_on-v4/
Changelog
v4
* Rebase on v7.1-rc1
* Added Reviewed/Tested/Acked-by lines
* Update commit messages and inline comments
* [pci-quirks] Add range-based scan for NVIDIA GPUs
* [smmu] Add missing arm_smmu_remove_master() in error path
* [pci-ats] Don't init "cap=0"; check pci_read_config_word error
v3
https://lore.kernel.org/all/cover.1772833963.git.nicolinc@nvidia.com/
* Add Reviewed-by from Jonathan
* Update function kdocs of PCI APIs
* Simplify boolean return/variable computations
v2
https://lore.kernel.org/all/cover.1771886695.git.nicolinc@nvidia.com/
* s/non-CXL/pre-CXL
* Rebase on v7.0-rc1
* Update inline comments and commit message
* Add WARN_ON back at !ptr in arm_smmu_clear_cd()
* Add NVIDIA CX10 Family NVlink-C2C to the pre-CXL list
* Do not add boolean parameter to arm_smmu_attach_dev_ste()
v1
https://lore.kernel.org/all/cover.1768624180.git.nicolinc@nvidia.com/
Nicolin Chen (3):
PCI: Allow ATS to be always on for CXL.cache capable devices
PCI: Allow ATS to be always on for pre-CXL devices
iommu/arm-smmu-v3: Allow ATS to be always on
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 +
drivers/pci/pci.h | 9 +++
include/linux/pci-ats.h | 3 +
include/uapi/linux/pci_regs.h | 1 +
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 75 ++++++++++++++++++---
drivers/pci/ats.c | 44 ++++++++++++
drivers/pci/quirks.c | 38 +++++++++++
7 files changed, 163 insertions(+), 8 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH v4 1/3] PCI: Allow ATS to be always on for CXL.cache capable devices
From: Nicolin Chen @ 2026-04-27 5:54 UTC (permalink / raw)
To: jgg, will, robin.murphy, bhelgaas
Cc: joro, praan, baolu.lu, kevin.tian, miko.lenczewski,
linux-arm-kernel, iommu, linux-kernel, linux-pci, dan.j.williams,
jonathan.cameron, vsethi, linux-cxl, nirmoyd
In-Reply-To: <cover.1777269009.git.nicolinc@nvidia.com>
Controlled by the IOMMU driver, ATS is usually enabled "on demand" when a
given PASID on a device is attached to an I/O page table. This is working
even when a device has no translation on its RID (i.e., the RID is IOMMU
bypassed).
However, certain PCIe devices require non-PASID ATS on their RID even when
the RID is IOMMU bypassed. Call this "always on".
For example, CXL spec r4.0 notes in sec 3.2.5.13 Memory Type on CXL.cache:
"To source requests on CXL.cache, devices need to get the Host Physical
Address (HPA) from the Host by means of an ATS request on CXL.io."
In other words, the CXL.cache capability requires ATS; otherwise, it can't
access host physical memory.
Introduce a new pci_ats_always_on() helper for the IOMMU driver to scan a
PCI device and shift ATS policies between "on demand" and "always on".
Add the support for CXL.cache devices first. Pre-CXL devices will be added
in quirks.c file.
Note that pci_ats_always_on() validates against pci_ats_supported(), so we
ensure that untrusted devices (e.g. external ports) will not be always on.
This maintains the existing ATS security policy regarding potential side-
channel attacks via ATS.
Cc: linux-cxl@vger.kernel.org
Suggested-by: Vikram Sethi <vsethi@nvidia.com>
Suggested-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
Tested-by: Nirmoy Das <nirmoyd@nvidia.com>
Acked-by: Nirmoy Das <nirmoyd@nvidia.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
include/linux/pci-ats.h | 3 +++
include/uapi/linux/pci_regs.h | 1 +
drivers/pci/ats.c | 43 +++++++++++++++++++++++++++++++++++
3 files changed, 47 insertions(+)
diff --git a/include/linux/pci-ats.h b/include/linux/pci-ats.h
index 75c6c86cf09dc..d14ba727d38b3 100644
--- a/include/linux/pci-ats.h
+++ b/include/linux/pci-ats.h
@@ -12,6 +12,7 @@ int pci_prepare_ats(struct pci_dev *dev, int ps);
void pci_disable_ats(struct pci_dev *dev);
int pci_ats_queue_depth(struct pci_dev *dev);
int pci_ats_page_aligned(struct pci_dev *dev);
+bool pci_ats_always_on(struct pci_dev *dev);
#else /* CONFIG_PCI_ATS */
static inline bool pci_ats_supported(struct pci_dev *d)
{ return false; }
@@ -24,6 +25,8 @@ static inline int pci_ats_queue_depth(struct pci_dev *d)
{ return -ENODEV; }
static inline int pci_ats_page_aligned(struct pci_dev *dev)
{ return 0; }
+static inline bool pci_ats_always_on(struct pci_dev *dev)
+{ return false; }
#endif /* CONFIG_PCI_ATS */
#ifdef CONFIG_PCI_PRI
diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h
index 14f634ab9350d..6ac45be1008b8 100644
--- a/include/uapi/linux/pci_regs.h
+++ b/include/uapi/linux/pci_regs.h
@@ -1349,6 +1349,7 @@
/* CXL r4.0, 8.1.3: PCIe DVSEC for CXL Device */
#define PCI_DVSEC_CXL_DEVICE 0
#define PCI_DVSEC_CXL_CAP 0xA
+#define PCI_DVSEC_CXL_CACHE_CAPABLE _BITUL(0)
#define PCI_DVSEC_CXL_MEM_CAPABLE _BITUL(2)
#define PCI_DVSEC_CXL_HDM_COUNT __GENMASK(5, 4)
#define PCI_DVSEC_CXL_CTRL 0xC
diff --git a/drivers/pci/ats.c b/drivers/pci/ats.c
index ec6c8dbdc5e9c..fc871858b65bc 100644
--- a/drivers/pci/ats.c
+++ b/drivers/pci/ats.c
@@ -205,6 +205,49 @@ int pci_ats_page_aligned(struct pci_dev *pdev)
return 0;
}
+/*
+ * CXL r4.0, sec 3.2.5.13 Memory Type on CXL.cache notes: to source requests on
+ * CXL.cache, devices need to get the Host Physical Address (HPA) from the Host
+ * by means of an ATS request on CXL.io.
+ *
+ * In other words, CXL.cache devices cannot access host physical memory without
+ * ATS.
+ */
+static bool pci_cxl_ats_always_on(struct pci_dev *pdev)
+{
+ int offset;
+ u16 cap;
+
+ offset = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
+ PCI_DVSEC_CXL_DEVICE);
+ if (!offset)
+ return false;
+
+ if (pci_read_config_word(pdev, offset + PCI_DVSEC_CXL_CAP, &cap))
+ return false;
+
+ return cap & PCI_DVSEC_CXL_CACHE_CAPABLE;
+}
+
+/**
+ * pci_ats_always_on - Whether the PCI device requires ATS to be always enabled
+ * @pdev: the PCI device
+ *
+ * Returns true, if the PCI device requires ATS for basic functional operation.
+ */
+bool pci_ats_always_on(struct pci_dev *pdev)
+{
+ if (pci_ats_disabled() || !pci_ats_supported(pdev))
+ return false;
+
+ /* A VF inherits its PF's requirement for ATS function */
+ if (pdev->is_virtfn)
+ pdev = pci_physfn(pdev);
+
+ return pci_cxl_ats_always_on(pdev);
+}
+EXPORT_SYMBOL_GPL(pci_ats_always_on);
+
#ifdef CONFIG_PCI_PRI
void pci_pri_init(struct pci_dev *pdev)
{
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox