Re: [PATCH] pci: Add functions to match pci dev or driver against OF DT node

From: Bjorn Helgaas
Date: Tue Jan 10 2023 - 10:24:47 EST


[+cc Rob]

On Sat, Jan 07, 2023 at 11:14:14PM +0800, Mad Horse wrote:
> Currently, there seems no mechanism to check whether a compatibility
> string within an OF DT node for a PCI device (whose spec is at
> https://www.devicetree.org/open-firmware/bindings/pci/ ) matches the
> vendor and device id of either the PCI device installed on the
> corresponding location or the driver suggested by the compatibility
> string, which can cause, for example, that an nvram cell defined
> within an OF DT node for a driver may be erroneously applied to a
> replaced device which should be powered with another driver capable to
> accept an nvram cell with the same name, causing the driver
> malfunctional or even kernel panic.
>
> In order to resolve such issue, this patch introduces a function to
> decode a compatibility string into a struct pci_device_id, which could
> further be matched against PCI devices or drivers, as well as
> functions to match a compatibility string or OF DT node against PCI
> devices or drivers.
>
> PCI drivers can then call these functions to check whether an OF DT
> node matches themselves during driver enumeration, in order to accept
> additional data provided by the node or ignore them when
> incompatible.

Thanks for your patch! Hints for v2:

- CC Rob Herring <robh@xxxxxxxxxx> for of.c changes (maybe we
should update MAINTAINERS to suggest this?)

- Follow existing style of subject lines and commit logs; use "git
log drivers/pci", "git log --oneline drivers/pci", see
https://chris.beams.io/posts/git-commit/

- Post this patch along with users of the new functions. We don't
normally add new code until there are actual users of it.

- The patch itself looks whitespace-damaged and doesn't apply
cleanly to v6.2-rc1:
$ git am m/20230107_equu_pci_add_functions_to_match_pci_dev_or_driver_against_of_dt_node.mbx
Applying: pci: Add functions to match pci dev or driver against OF DT node
error: corrupt patch at line 14

A few more comments below.

Bjorn

> Signed-off-by: Edward Chow <equu@xxxxxxxxxxx>
> ---
> drivers/pci/of.c | 304 +++++++++++++++++++++++++++++++++++++++
> drivers/pci/pci-driver.c | 5 -
> drivers/pci/pci.h | 56 ++++++++
> include/linux/of_pci.h | 25 ++++
> include/linux/pci.h | 6 +
> 5 files changed, 391 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/pci/of.c b/drivers/pci/of.c
> index 196834ed44fe..5d69bef67ef1 100644
> --- a/drivers/pci/of.c
> +++ b/drivers/pci/of.c
> @@ -13,6 +13,8 @@
> #include <linux/of_irq.h>
> #include <linux/of_address.h>
> #include <linux/of_pci.h>
> +#include <linux/string.h>
> +#include <linux/kstrtox.h>
> #include "pci.h"
> #ifdef CONFIG_PCI
> @@ -251,6 +253,308 @@ void of_pci_check_probe_only(void)
> }
> EXPORT_SYMBOL_GPL(of_pci_check_probe_only);
> +/**
> + * of_pci_compat_to_device_id() - Decode an OF compatibility string into a
> + * pci_device_id structure.
> + * @compat: the compatibility string to decode, could be NULL
> + * @id: pointer to a struct pci_device_id, to store the result
> + * @rev: pointer to output revision info, PCI_ANY_ID if no revision in
> @compat
> + * @req_pcie: pointer to output whether @compat mandates PCI-E
> compatibility

"PCIe" is the conventional spelling.

> + * returns 0 when success, -EINVAL when failed.
> + */
> +int of_pci_compat_to_device_id(const char *compat, struct pci_device_id
> *id,
> + __u32 *rev, __u32 *req_pcie)
> +{
> + union {
> + __u8 u8;
> + __u16 u16;
> + __u32 u32;

Use "u8", "u16", "u32" typedefs instead of "__u8" etc.; you can see
this by looking at other code in of.c.

> + } res = {0};
> + *req_pcie = 0;
> + *rev = PCI_ANY_ID;
> + if (!compat || strncasecmp(compat, "pci", 3) != 0)
> + goto failed;

No need to "goto failed" or "goto done". Those labels don't do any
cleanup, so you can simply return here directly.

> + compat += 3;
> +
> + if (strncasecmp(compat, "class,", 6) == 0) {
> + /* pciclass,CCSSPP */
> + compat += 6;
> + if ((strlen(compat) < 4)
> + || kstrtouint(compat, 16, &id->class))
> + goto failed;
> + if (id->class < 0x10000) {
> + id->class <<= 8;
> + id->class_mask = 0xFFFF00;
> + } else {
> + id->class_mask = PCI_ANY_ID;
> + }
> + id->vendor = PCI_ANY_ID;
> + id->device = PCI_ANY_ID;
> + id->subvendor = PCI_ANY_ID;
> + id->subdevice = PCI_ANY_ID;
> + goto done;
> + }
> +
> + if (strncasecmp(compat, "ex", 2) == 0) {
> + /* pciex... */
> + *req_pcie = 1;
> + compat += 2;
> + }
> + if (kstrtou16(compat, 16, &res.u16))
> + goto failed;
> + id->vendor = res.u16;
> + compat = strchr(compat, ',');
> + if (!compat)
> + goto failed;
> + compat++;
> + if (kstrtou16(compat, 16, &res.u16))
> + goto failed;
> + id->device = res.u16;
> + compat = strchr(compat, '.');
> + if (compat == NULL) {
> + /* pciVVVV,DDDD */
> + id->subvendor = PCI_ANY_ID;
> + id->subdevice = PCI_ANY_ID;
> + goto done;
> + }
> +
> + compat++;
> + if (strlen(compat) == 2) {
> + /* pciVVVV,DDDD.RR */
> + if (kstrtou8(compat, 16, &res.u8))
> + goto failed;
> + *rev = res.u8;
> + id->subvendor = PCI_ANY_ID;
> + id->subdevice = PCI_ANY_ID;
> + goto done;
> + }
> +
> + if (kstrtou16(compat, 16, &res.u16))
> + goto failed;
> + id->subvendor = res.u16;
> + compat = strchr(compat, '.');
> + if (!compat)
> + goto failed;
> + compat++;
> + if (kstrtou16(compat, 16, &res.u16))
> + goto failed;
> + id->subdevice = res.u16;
> + compat = strchr(compat, '.');
> + if (compat == NULL)
> + /* pciVVVV,DDDD.SSSS.ssss */
> + goto done;
> +
> + compat++;
> + if (strlen(compat) == 2) {
> + /* pciVVVV,DDDD.SSSS.ssss.RR */
> + if (kstrtou8(compat, 16, &res.u8))
> + goto failed;
> + *rev = res.u8;
> + goto done;
> + }
> +
> + failed:
> + return -EINVAL;
> + done:
> + return 0;
> +}
> +
> +/**
> + * of_pci_compat_match_device() - Tell whether a PCI device structure
> matches
> + * a given OF compatibility string
> + * @compat: single OF compatibility string to match, could be NULL
> + * @dev the PCI device structure to match against
> + *
> + * Returns whether they match.
> + */
> +bool of_pci_compat_match_device(const char *compat, const struct pci_dev
> *dev)
> +{
> + __u32 rev = PCI_ANY_ID;
> + __u32 req_pcie = 0;
> + struct pci_device_id id = {0};
> +
> + if (of_pci_compat_to_device_id(compat, &id, &rev, &req_pcie))
> + return false;
> + return pci_match_one_device(&id, dev) &&
> + (rev == PCI_ANY_ID || rev == dev->revision) &&
> + req_pcie ? dev->pcie_cap : true;
> +}
> +
> +/**
> + * of_pci_node_match_device() - Tell whether an OF device tree node
> + * matches the given pci device
> + * @node: single OF device tree node to match, could be NULL
> + * @dev: the PCI device structure to match against, could be NULL
> + *
> + * Returns whether they match.
> + */
> +bool of_pci_node_match_device(const struct device_node *node,
> + const struct pci_dev *dev)
> +{
> + struct property *prop;
> + const char *cp;
> +
> + if (!node || !dev)
> + return false;
> + prop = of_find_property(node, "compatible", NULL);
> + for (cp = of_prop_next_string(prop, NULL); cp;
> + cp = of_prop_next_string(prop, cp)) {
> + if (of_pci_compat_match_device(cp, dev))
> + return true;
> + }
> + return false;
> +}
> +EXPORT_SYMBOL_GPL(of_pci_node_match_device);
> +
> +/**
> + * of_pci_compat_match_one_id() - Tell whether a PCI device id structure

"ID" in English text since "id" is not really a word (well, I guess it
*is*, but not in the sense used here).

> matches
> + * a given OF compatibility string, note that there is no revision nor PCIe
> + * capability info in PCI device id structures
> + *
> + * @compat: single OF compatibility string to match, could be NULL
> + * @id the PCI device id structure to match against, could be NULL
> + *
> + * Returns the matching pci_device_id structure pointed by id
> + * or %NULL if there is no match.
> + */
> +const struct pci_device_id *
> +of_pci_compat_match_one_id(const char *compat, const struct pci_device_id
> *id)
> +{
> + __u32 rev = PCI_ANY_ID;
> + __u32 req_pcie = 0;
> + struct pci_device_id pr = {0};
> +
> + if (!compat || !id ||
> + of_pci_compat_to_device_id(compat, &pr, &rev, &req_pcie))
> + return NULL;
> + return pci_match_one_id(id, &pr);
> +}
> +
> +/**
> + * of_pci_compat_match_id_table() - Tell whether a given OF compatibility
> string
> + * matches a given pci_id table
> + *
> + * @compat: single OF compatibility string to match, could be NULL
> + * @table the PCI device id table to match against, could be NULL
> + *
> + * Returns the matching pci_device_id structure or %NULL if there is no
> match.
> + */
> +const struct pci_device_id *
> +of_pci_compat_match_id_table(const char *compat, const struct pci_device_id
> *table)
> +{
> + if (compat && table) {
> + while (table->vendor || table->subvendor || table->class_mask) {
> + if (of_pci_compat_match_one_id(compat, table))
> + return table;
> + table++;
> + }
> + }
> + return NULL;
> +}
> +
> +/**
> + * of_pci_node_match_id_table() - Tell whether an OF device tree node
> + * matches the given pci_id table
> + * @node: single OF device tree node to match, could be NULL
> + * @table: the PCI device id table to match against, could be NULL
> + *
> + * Returns the matching pci_device_id structure
> + * or %NULL if there is no match.
> + */
> +const struct pci_device_id *
> +of_pci_node_match_id_table(const struct device_node *node,
> + const struct pci_device_id *table)
> +{
> + struct property *prop;
> + const char *cp;
> + const struct pci_device_id *id;
> +
> + if (!node || !table)
> + return NULL;
> + prop = of_find_property(node, "compatible", NULL);
> + for (cp = of_prop_next_string(prop, NULL); cp;
> + cp = of_prop_next_string(prop, cp)) {
> + id = of_pci_compat_match_id_table(cp, table);
> + if (id)
> + return id;
> + }
> + return NULL;
> +}
> +EXPORT_SYMBOL_GPL(of_pci_node_match_id_table);
> +
> +/**
> + * of_pci_compat_match_driver - See if a given OF compatibility string
> matches
> + * a driver's list of IDs
> + * @compat: single OF compatibility string to match, could be NULL
> + * @drv: the PCI driver to match against, could be NULL
> + *
> + * Used by a driver to check whether an OF compatibility string matches one
> of
> + * (dynamically) supported devices, which may have been augmented
> + * via the sysfs "new_id" file. Returns the matching pci_device_id
> + * structure or %NULL if there is no match.
> + */
> +const struct pci_device_id *
> +of_pci_compat_match_driver(const char *compat, struct pci_driver *drv)
> +{
> + struct pci_dynid *dynid;
> + const struct pci_device_id *found_id = NULL, *ids;
> +
> + if (!compat || !drv)
> + return NULL;
> + /* Look at the dynamic ids first, before the static ones */
> + spin_lock(&drv->dynids.lock);
> + list_for_each_entry(dynid, &drv->dynids.list, node) {
> + if (of_pci_compat_match_one_id(compat, &dynid->id)) {
> + found_id = &dynid->id;
> + break;
> + }
> + }
> + spin_unlock(&drv->dynids.lock);
> +
> + if (found_id)
> + return found_id;
> +
> + for (ids = drv->id_table; (found_id = of_pci_compat_match_one_id(compat,
> ids));
> + ids = found_id + 1) {
> + /* exclude ids in id_table with override_only */
> + if (!found_id->override_only)
> + return found_id;
> + }
> +
> + return NULL;
> +}
> +
> +/**
> + * of_pci_node_match_driver() - Tell whether an OF device tree node
> + * matches the given pci driver
> + * @node: single OF device tree node to match, could be NULL
> + * @drv: the PCI driver structure to match against, could be NULL
> + *
> + * Returns the matching pci_device_id structure
> + * or %NULL if there is no match.
> + */
> +const struct pci_device_id *
> +of_pci_node_match_driver(const struct device_node *node,
> + struct pci_driver *drv)
> +{
> + struct property *prop;
> + const char *cp;
> + const struct pci_device_id *id;
> +
> + if (!node || !drv)
> + return NULL;
> + prop = of_find_property(node, "compatible", NULL);
> + for (cp = of_prop_next_string(prop, NULL); cp;
> + cp = of_prop_next_string(prop, cp)) {
> + id = of_pci_compat_match_driver(cp, drv);
> + if (id)
> + return id;
> + }
> + return NULL;
> +}
> +EXPORT_SYMBOL_GPL(of_pci_node_match_driver);
> +
> /**
> * devm_of_pci_get_host_bridge_resources() - Resource-managed parsing of PCI
> * host bridge resources from DT
> diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
> index a2ceeacc33eb..aa212d12353f 100644
> --- a/drivers/pci/pci-driver.c
> +++ b/drivers/pci/pci-driver.c
> @@ -24,11 +24,6 @@
> #include "pci.h"
> #include "pcie/portdrv.h"
> -struct pci_dynid {
> - struct list_head node;
> - struct pci_device_id id;
> -};
> -
> /**
> * pci_add_dynid - add a new PCI device ID to this driver and re-probe
> devices
> * @drv: target pci driver
> diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
> index 9ed3b5550043..e30652021a63 100644
> --- a/drivers/pci/pci.h
> +++ b/drivers/pci/pci.h
> @@ -204,6 +204,29 @@ pci_match_one_device(const struct pci_device_id *id,
> const struct pci_dev *dev)
> return NULL;
> }
> +/**
> + * pci_match_one_id - Tell if a PCI device id structure matches another
> + * PCI device id structure
> + * @id: single PCI device id structure to match, usually in a list or array
> + * @pr: the probing PCI device id structure to match against, usually
> converted from
> + * other format
> + *
> + * Returns the matching pci_device_id structure pointed by id
> + * or %NULL if there is no match.
> + */
> +static inline const struct pci_device_id *
> +pci_match_one_id(const struct pci_device_id *id, const struct pci_device_id
> *pr)
> +{
> + if ((id->vendor == pr->vendor) &&
> + (id->device == pr->device) &&
> + (id->subvendor == pr->subvendor) &&
> + (id->subdevice == pr->subdevice) &&
> + (id->class == pr->class) &&
> + (id->class_mask == pr->class_mask))
> + return id;
> + return NULL;
> +}
> +
> /* PCI slot sysfs helper code */
> #define to_pci_slot(s) container_of(s, struct pci_slot, kobj)
> @@ -638,6 +661,15 @@ void pci_release_bus_of_node(struct pci_bus *bus);
> int devm_of_pci_bridge_init(struct device *dev, struct pci_host_bridge
> *bridge);
> +int of_pci_compat_to_device_id(const char *compat, struct pci_device_id
> *id,
> + __u32 *rev, __u32 *req_pcie);
> +bool of_pci_compat_match_device(const char *compat, const struct pci_dev
> *dev);
> +const struct pci_device_id *
> +of_pci_compat_match_one_id(const char *compat, const struct pci_device_id
> *id);
> +const struct pci_device_id *
> +of_pci_compat_match_id_table(const char *compat, const struct pci_device_id
> *table);
> +const struct pci_device_id *
> +of_pci_compat_match_driver(const char *compat, struct pci_driver *drv);
> #else
> static inline int
> of_pci_parse_bus_range(struct device_node *node, struct resource *res)
> @@ -679,6 +711,30 @@ static inline int devm_of_pci_bridge_init(struct device
> *dev, struct pci_host_br
> return 0;
> }
> +static inline int of_pci_compat_to_device_id(const char *compat, struct
> pci_device_id *id,
> + __u32 *rev, __u32 *req_pcie)
> +{
> + return -EINVAL;
> +}
> +static inline bool of_pci_compat_match_device(const char *compat, const
> struct pci_dev *dev)
> +{
> + return false;
> +}
> +static inline const struct pci_device_id *
> +of_pci_compat_match_one_id(const char *compat, const struct pci_device_id
> *id)
> +{
> + return NULL;
> +}
> +static inline const struct pci_device_id *
> +of_pci_compat_match_id_table(const char *compat, const struct pci_device_id
> *table)
> +{
> + return NULL;
> +}
> +static inline const struct pci_device_id *
> +of_pci_compat_match_driver(const char *compat, struct pci_driver *drv)
> +{
> + return NULL;
> +}
> #endif /* CONFIG_OF */
> #ifdef CONFIG_PCIEAER
> diff --git a/include/linux/of_pci.h b/include/linux/of_pci.h
> index 29658c0ee71f..eef1eaafc03d 100644
> --- a/include/linux/of_pci.h
> +++ b/include/linux/of_pci.h
> @@ -13,6 +13,14 @@ struct device_node *of_pci_find_child_device(struct
> device_node *parent,
> unsigned int devfn);
> int of_pci_get_devfn(struct device_node *np);
> void of_pci_check_probe_only(void);
> +bool of_pci_node_match_device(const struct device_node *node,
> + const struct pci_dev *dev);
> +const struct pci_device_id *
> +of_pci_node_match_id_table(const struct device_node *node,
> + const struct pci_device_id *table);
> +const struct pci_device_id *
> +of_pci_node_match_driver(const struct device_node *node,
> + struct pci_driver *drv);
> #else
> static inline struct device_node *of_pci_find_child_device(struct
> device_node *parent,
> unsigned int devfn)
> @@ -26,6 +34,23 @@ static inline int of_pci_get_devfn(struct device_node
> *np)
> }
> static inline void of_pci_check_probe_only(void) { }
> +static inline bool of_pci_node_match_device(const struct device_node *node,
> + const struct pci_dev *dev)
> +{
> + return false;
> +}
> +static inline const struct pci_device_id *
> +of_pci_node_match_id_table(const struct device_node *node,
> + const struct pci_device_id *table)
> +{
> + return NULL;
> +}
> +static inline const struct pci_device_id *
> +of_pci_node_match_driver(const struct device_node *node,
> + struct pci_driver *drv)
> +{
> + return NULL;
> +}
> #endif
> #if IS_ENABLED(CONFIG_OF_IRQ)
> diff --git a/include/linux/pci.h b/include/linux/pci.h
> index adffd65e84b4..04c908d84b90 100644
> --- a/include/linux/pci.h
> +++ b/include/linux/pci.h
> @@ -1513,6 +1513,12 @@ void pci_unregister_driver(struct pci_driver *dev);
> builtin_driver(__pci_driver, pci_register_driver)
> struct pci_driver *pci_dev_driver(const struct pci_dev *dev);
> +
> +struct pci_dynid {
> + struct list_head node;
> + struct pci_device_id id;
> +};
> +
> int pci_add_dynid(struct pci_driver *drv,
> unsigned int vendor, unsigned int device,
> unsigned int subvendor, unsigned int subdevice,
>
> --
> 2.39.0
>
>