From: Edward Chow <e...@openmail.cc>

Currently, 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 is not supported.

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.

Signed-off-by: Edward Chow <e...@openmail.cc>
Cc: Bjorn Helgaas <helg...@kernel.org>
Reported-by: kernel test robot <l...@intel.com>
---
 drivers/pci/of.c         | 299 +++++++++++++++++++++++++++++++++++++++
 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, 386 insertions(+), 5 deletions(-)

diff --git a/drivers/pci/of.c b/drivers/pci/of.c
index 196834ed44fe..a9c3648cd9e9 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,303 @@ 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 PCIe compatibility
+ *
+ * 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;
+       } res = {0};
+       *req_pcie = 0;
+       *rev = PCI_ANY_ID;
+       if (!compat || strncasecmp(compat, "pci", 3) != 0)
+               return -EINVAL;
+       compat += 3;
+
+       if (strncasecmp(compat, "class,", 6) == 0) {
+               /* pciclass,CCSSPP */
+               compat += 6;
+               if ((strlen(compat) < 4)
+                  || kstrtouint(compat, 16, &id->class))
+                       return -EINVAL;
+               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;
+               return 0;
+       }
+
+       if (strncasecmp(compat, "ex", 2) == 0) {
+               /* pciex...  */
+               *req_pcie = 1;
+               compat += 2;
+       }
+       if (kstrtou16(compat, 16, &res.u16))
+               return -EINVAL;
+       id->vendor = res.u16;
+       compat = strchr(compat, ',');
+       if (!compat)
+               return -EINVAL;
+       compat++;
+       if (kstrtou16(compat, 16, &res.u16))
+               return -EINVAL;
+       id->device = res.u16;
+       compat = strchr(compat, '.');
+       if (compat == NULL) {
+               /* pciVVVV,DDDD */
+               id->subvendor = PCI_ANY_ID;
+               id->subdevice = PCI_ANY_ID;
+               return 0;
+       }
+
+       compat++;
+       if (strlen(compat) == 2) {
+               /* pciVVVV,DDDD.RR */
+               if (kstrtou8(compat, 16, &res.u8))
+                       return -EINVAL;
+               *rev = res.u8;
+               id->subvendor = PCI_ANY_ID;
+               id->subdevice = PCI_ANY_ID;
+               return 0;
+       }
+
+       if (kstrtou16(compat, 16, &res.u16))
+               return -EINVAL;
+       id->subvendor = res.u16;
+       compat = strchr(compat, '.');
+       if (!compat)
+               return -EINVAL;
+       compat++;
+       if (kstrtou16(compat, 16, &res.u16))
+               return -EINVAL;
+       id->subdevice = res.u16;
+       compat = strchr(compat, '.');
+       if (compat == NULL)
+               /* pciVVVV,DDDD.SSSS.ssss */
+               return 0;
+
+       compat++;
+       if (strlen(compat) == 2) {
+               /* pciVVVV,DDDD.SSSS.ssss.RR */
+               if (kstrtou8(compat, 16, &res.u8))
+                       return -EINVAL;
+               *rev = res.u8;
+       }
+       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 
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.1


_______________________________________________
ath10k mailing list
ath10k@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/ath10k

Reply via email to