summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/pcmcia/driver.txt30
-rw-r--r--drivers/pcmcia/ds.c113
-rw-r--r--include/pcmcia/ds.h6
3 files changed, 148 insertions, 1 deletions
diff --git a/Documentation/pcmcia/driver.txt b/Documentation/pcmcia/driver.txt
new file mode 100644
index 000000000000..0ac167920778
--- /dev/null
+++ b/Documentation/pcmcia/driver.txt
@@ -0,0 +1,30 @@
+PCMCIA Driver
+-------------
+
+
+sysfs
+-----
+
+New PCMCIA IDs may be added to a device driver pcmcia_device_id table at
+runtime as shown below:
+
+echo "match_flags manf_id card_id func_id function device_no \
+prod_id_hash[0] prod_id_hash[1] prod_id_hash[2] prod_id_hash[3]" > \
+/sys/bus/pcmcia/drivers/{driver}/new_id
+
+All fields are passed in as hexadecimal values (no leading 0x).
+The meaning is described in the PCMCIA specification, the match_flags is
+a bitwise or-ed combination from PCMCIA_DEV_ID_MATCH_* constants
+defined in include/linux/mod_devicetable.h.
+
+Once added, the driver probe routine will be invoked for any unclaimed
+PCMCIA device listed in its (newly updated) pcmcia_device_id list.
+
+A common use-case is to add a new device according to the manufacturer ID
+and the card ID (form the manf_id and card_id file in the device tree).
+For this, just use:
+
+echo "0x3 manf_id card_id 0 0 0 0 0 0 0" > \
+ /sys/bus/pcmcia/drivers/{driver}/new_id
+
+after loading the driver.
diff --git a/drivers/pcmcia/ds.c b/drivers/pcmcia/ds.c
index 18e111e12339..143c6efc478a 100644
--- a/drivers/pcmcia/ds.c
+++ b/drivers/pcmcia/ds.c
@@ -234,6 +234,89 @@ static void pcmcia_check_driver(struct pcmcia_driver *p_drv)
/*======================================================================*/
+struct pcmcia_dynid {
+ struct list_head node;
+ struct pcmcia_device_id id;
+};
+
+/**
+ * pcmcia_store_new_id - add a new PCMCIA device ID to this driver and re-probe devices
+ * @driver: target device driver
+ * @buf: buffer for scanning device ID data
+ * @count: input size
+ *
+ * Adds a new dynamic PCMCIA device ID to this driver,
+ * and causes the driver to probe for all devices again.
+ */
+static ssize_t
+pcmcia_store_new_id(struct device_driver *driver, const char *buf, size_t count)
+{
+ struct pcmcia_dynid *dynid;
+ struct pcmcia_driver *pdrv = to_pcmcia_drv(driver);
+ __u16 match_flags, manf_id, card_id;
+ __u8 func_id, function, device_no;
+ __u32 prod_id_hash[4] = {0, 0, 0, 0};
+ int fields=0;
+ int retval = 0;
+
+ fields = sscanf(buf, "%hx %hx %hx %hhx %hhx %hhx %x %x %x %x",
+ &match_flags, &manf_id, &card_id, &func_id, &function, &device_no,
+ &prod_id_hash[0], &prod_id_hash[1], &prod_id_hash[2], &prod_id_hash[3]);
+ if (fields < 6)
+ return -EINVAL;
+
+ dynid = kzalloc(sizeof(struct pcmcia_dynid), GFP_KERNEL);
+ if (!dynid)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&dynid->node);
+ dynid->id.match_flags = match_flags;
+ dynid->id.manf_id = manf_id;
+ dynid->id.card_id = card_id;
+ dynid->id.func_id = func_id;
+ dynid->id.function = function;
+ dynid->id.device_no = device_no;
+ memcpy(dynid->id.prod_id_hash, prod_id_hash, sizeof(__u32) * 4);
+
+ spin_lock(&pdrv->dynids.lock);
+ list_add_tail(&pdrv->dynids.list, &dynid->node);
+ spin_unlock(&pdrv->dynids.lock);
+
+ if (get_driver(&pdrv->drv)) {
+ retval = driver_attach(&pdrv->drv);
+ put_driver(&pdrv->drv);
+ }
+
+ if (retval)
+ return retval;
+ return count;
+}
+static DRIVER_ATTR(new_id, S_IWUSR, NULL, pcmcia_store_new_id);
+
+static void
+pcmcia_free_dynids(struct pcmcia_driver *drv)
+{
+ struct pcmcia_dynid *dynid, *n;
+
+ spin_lock(&drv->dynids.lock);
+ list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) {
+ list_del(&dynid->node);
+ kfree(dynid);
+ }
+ spin_unlock(&drv->dynids.lock);
+}
+
+static int
+pcmcia_create_newid_file(struct pcmcia_driver *drv)
+{
+ int error = 0;
+ if (drv->probe != NULL)
+ error = sysfs_create_file(&drv->drv.kobj,
+ &driver_attr_new_id.attr);
+ return error;
+}
+
+
/**
* pcmcia_register_driver - register a PCMCIA driver with the bus core
*
@@ -241,6 +324,8 @@ static void pcmcia_check_driver(struct pcmcia_driver *p_drv)
*/
int pcmcia_register_driver(struct pcmcia_driver *driver)
{
+ int error;
+
if (!driver)
return -EINVAL;
@@ -249,10 +334,20 @@ int pcmcia_register_driver(struct pcmcia_driver *driver)
/* initialize common fields */
driver->drv.bus = &pcmcia_bus_type;
driver->drv.owner = driver->owner;
+ spin_lock_init(&driver->dynids.lock);
+ INIT_LIST_HEAD(&driver->dynids.list);
ds_dbg(3, "registering driver %s\n", driver->drv.name);
- return driver_register(&driver->drv);
+ error = driver_register(&driver->drv);
+ if (error < 0)
+ return error;
+
+ error = pcmcia_create_newid_file(driver);
+ if (error)
+ driver_unregister(&driver->drv);
+
+ return error;
}
EXPORT_SYMBOL(pcmcia_register_driver);
@@ -263,6 +358,7 @@ void pcmcia_unregister_driver(struct pcmcia_driver *driver)
{
ds_dbg(3, "unregistering driver %s\n", driver->drv.name);
driver_unregister(&driver->drv);
+ pcmcia_free_dynids(driver);
}
EXPORT_SYMBOL(pcmcia_unregister_driver);
@@ -927,6 +1023,21 @@ static int pcmcia_bus_match(struct device * dev, struct device_driver * drv) {
struct pcmcia_device * p_dev = to_pcmcia_dev(dev);
struct pcmcia_driver * p_drv = to_pcmcia_drv(drv);
struct pcmcia_device_id *did = p_drv->id_table;
+ struct pcmcia_dynid *dynid;
+
+ /* match dynamic devices first */
+ spin_lock(&p_drv->dynids.lock);
+ list_for_each_entry(dynid, &p_drv->dynids.list, node) {
+ ds_dbg(3, "trying to match %s to %s\n", dev->bus_id,
+ drv->name);
+ if (pcmcia_devmatch(p_dev, &dynid->id)) {
+ ds_dbg(0, "matched %s to %s\n", dev->bus_id,
+ drv->name);
+ spin_unlock(&p_drv->dynids.lock);
+ return 1;
+ }
+ }
+ spin_unlock(&p_drv->dynids.lock);
#ifdef CONFIG_PCMCIA_IOCTL
/* matching by cardmgr */
diff --git a/include/pcmcia/ds.h b/include/pcmcia/ds.h
index 8c339f5678cf..90ef552c42dd 100644
--- a/include/pcmcia/ds.h
+++ b/include/pcmcia/ds.h
@@ -108,6 +108,11 @@ typedef struct dev_node_t {
struct pcmcia_socket;
struct config_t;
+struct pcmcia_dynids {
+ spinlock_t lock;
+ struct list_head list;
+};
+
struct pcmcia_driver {
int (*probe) (struct pcmcia_device *dev);
void (*remove) (struct pcmcia_device *dev);
@@ -118,6 +123,7 @@ struct pcmcia_driver {
struct module *owner;
struct pcmcia_device_id *id_table;
struct device_driver drv;
+ struct pcmcia_dynids dynids;
};
/* driver registration */