diff mbox series

[v3,1/3] PCI: of: Match pci devices or drivers against OF DT nodes

Message ID 20230202075524.2911058-2-equu@openmail.cc (mailing list archive)
State Superseded
Delegated to: Johannes Berg
Headers show
Series [v3,1/3] PCI: of: Match pci devices or drivers against OF DT nodes | expand

Commit Message

Mad Horse Feb. 2, 2023, 7:55 a.m. UTC
From: Edward Chow <equu@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 <equu@openmail.cc>
Cc: Bjorn Helgaas <helgaas@kernel.org>
---
 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(-)

Comments

kernel test robot Feb. 3, 2023, 8:23 a.m. UTC | #1
Hi,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on helgaas-pci/next]
[also build test ERROR on helgaas-pci/for-linus wireless-next/main wireless/main linus/master v6.2-rc6 next-20230203]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/equu-openmail-cc/wifi-ath9k-stop-loading-incompatible-DT-cal-data/20230202-165536
base:   https://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci.git next
patch link:    https://lore.kernel.org/r/20230202075524.2911058-2-equu%40openmail.cc
patch subject: [PATCH v3 1/3] PCI: of: Match pci devices or drivers against OF DT nodes
config: i386-randconfig-a005 (https://download.01.org/0day-ci/archive/20230203/202302031611.JepssFdR-lkp@intel.com/config)
compiler: gcc-11 (Debian 11.3.0-8) 11.3.0
reproduce (this is a W=1 build):
        # https://github.com/intel-lab-lkp/linux/commit/1c1bc10b9cc6f7a71ba1fb7bdc505195a2c3e759
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review equu-openmail-cc/wifi-ath9k-stop-loading-incompatible-DT-cal-data/20230202-165536
        git checkout 1c1bc10b9cc6f7a71ba1fb7bdc505195a2c3e759
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        make W=1 O=build_dir ARCH=i386 olddefconfig
        make W=1 O=build_dir ARCH=i386 SHELL=/bin/bash drivers/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   drivers/pci/of.c: In function 'of_pci_compat_to_device_id':
>> drivers/pci/of.c:296:25: error: expected ';' before '}' token
     296 |                 return 0
         |                         ^
         |                         ;
     297 |         }
         |         ~                
   drivers/pci/of.c:319:25: error: expected ';' before '}' token
     319 |                 return 0
         |                         ^
         |                         ;
     320 |         }
         |         ~                
   drivers/pci/of.c:330:25: error: expected ';' before '}' token
     330 |                 return 0
         |                         ^
         |                         ;
     331 |         }
         |         ~                
>> drivers/pci/of.c:346:25: error: expected ';' before 'compat'
     346 |                 return 0
         |                         ^
         |                         ;
     347 | 
     348 |         compat++;
         |         ~~~~~~           


vim +296 drivers/pci/of.c

   255	
   256	/**
   257	 * of_pci_compat_to_device_id() - Decode an OF compatibility string into a
   258	 * pci_device_id structure.
   259	 * @compat: the compatibility string to decode, could be NULL
   260	 * @id: pointer to a struct pci_device_id, to store the result
   261	 * @rev: pointer to output revision info, PCI_ANY_ID if no revision in @compat
   262	 * @req_pcie: pointer to output whether @compat mandates PCIe compatibility
   263	 *
   264	 * returns 0 when success, -EINVAL when failed.
   265	 */
   266	int of_pci_compat_to_device_id(const char *compat, struct pci_device_id *id,
   267				       u32 *rev, u32 *req_pcie)
   268	{
   269		union {
   270			u8 u8;
   271			u16 u16;
   272			u32 u32;
   273		} res = {0};
   274		*req_pcie = 0;
   275		*rev = PCI_ANY_ID;
   276		if (!compat || strncasecmp(compat, "pci", 3) != 0)
   277			return -EINVAL;
   278		compat += 3;
   279	
   280		if (strncasecmp(compat, "class,", 6) == 0) {
   281			/* pciclass,CCSSPP */
   282			compat += 6;
   283			if ((strlen(compat) < 4)
   284			   || kstrtouint(compat, 16, &id->class))
   285				return -EINVAL;
   286			if (id->class < 0x10000) {
   287				id->class <<= 8;
   288				id->class_mask = 0xFFFF00;
   289			} else {
   290				id->class_mask = PCI_ANY_ID;
   291			}
   292			id->vendor = PCI_ANY_ID;
   293			id->device = PCI_ANY_ID;
   294			id->subvendor = PCI_ANY_ID;
   295			id->subdevice = PCI_ANY_ID;
 > 296			return 0
   297		}
   298	
   299		if (strncasecmp(compat, "ex", 2) == 0) {
   300			/* pciex...  */
   301			*req_pcie = 1;
   302			compat += 2;
   303		}
   304		if (kstrtou16(compat, 16, &res.u16))
   305			return -EINVAL;
   306		id->vendor = res.u16;
   307		compat = strchr(compat, ',');
   308		if (!compat)
   309			return -EINVAL;
   310		compat++;
   311		if (kstrtou16(compat, 16, &res.u16))
   312			return -EINVAL;
   313		id->device = res.u16;
   314		compat = strchr(compat, '.');
   315		if (compat == NULL) {
   316			/* pciVVVV,DDDD */
   317			id->subvendor = PCI_ANY_ID;
   318			id->subdevice = PCI_ANY_ID;
   319			return 0
   320		}
   321	
   322		compat++;
   323		if (strlen(compat) == 2) {
   324			/* pciVVVV,DDDD.RR */
   325			if (kstrtou8(compat, 16, &res.u8))
   326				return -EINVAL;
   327			*rev = res.u8;
   328			id->subvendor = PCI_ANY_ID;
   329			id->subdevice = PCI_ANY_ID;
   330			return 0
   331		}
   332	
   333		if (kstrtou16(compat, 16, &res.u16))
   334			return -EINVAL;
   335		id->subvendor = res.u16;
   336		compat = strchr(compat, '.');
   337		if (!compat)
   338			return -EINVAL;
   339		compat++;
   340		if (kstrtou16(compat, 16, &res.u16))
   341			return -EINVAL;
   342		id->subdevice = res.u16;
   343		compat = strchr(compat, '.');
   344		if (compat == NULL)
   345			/* pciVVVV,DDDD.SSSS.ssss */
 > 346			return 0
   347	
   348		compat++;
   349		if (strlen(compat) == 2) {
   350			/* pciVVVV,DDDD.SSSS.ssss.RR */
   351			if (kstrtou8(compat, 16, &res.u8))
   352				return -EINVAL;
   353			*rev = res.u8;
   354		}
   355		return 0;
   356	}
   357
diff mbox series

Patch

diff --git a/drivers/pci/of.c b/drivers/pci/of.c
index 196834ed44fe..edb61195ea53 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,