[PATCH] pcmcia: device and driver matching
Dominik Brodowski [Mon, 27 Jun 2005 23:28:06 +0000 (16:28 -0700)]
The actual matching of pcmcia drivers and pcmcia devices.  The original
version of this was written by David Woodhouse.

Signed-off-by: Dominik Brodowski <linux@dominikbrodowski.net>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>

drivers/pcmcia/ds.c
include/linux/mod_devicetable.h
include/pcmcia/device_id.h [new file with mode: 0644]
include/pcmcia/ds.h

index 35d479b..5701b93 100644 (file)
@@ -101,6 +101,9 @@ struct pcmcia_bus_socket {
        u8                      device_count; /* the number of devices, used
                                               * only internally and subject
                                               * to incorrectness and change */
+
+       u8                      device_add_pending;
+       struct work_struct      device_add;
 };
 static spinlock_t pcmcia_dev_list_lock;
 
@@ -512,6 +515,10 @@ static struct pcmcia_device * pcmcia_device_add(struct pcmcia_bus_socket *s, uns
 
        down(&device_add_lock);
 
+       /* max of 2 devices per card */
+       if (s->device_count == 2)
+               goto err_put;
+
        p_dev = kmalloc(sizeof(struct pcmcia_device), GFP_KERNEL);
        if (!p_dev)
                goto err_put;
@@ -537,6 +544,8 @@ static struct pcmcia_device * pcmcia_device_add(struct pcmcia_bus_socket *s, uns
        list_add_tail(&p_dev->socket_device_list, &s->devices_list);
        spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
 
+       pcmcia_device_query(p_dev);
+
        if (device_register(&p_dev->dev)) {
                spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
                list_del(&p_dev->socket_device_list);
@@ -591,14 +600,123 @@ static int pcmcia_card_add(struct pcmcia_socket *s)
 }
 
 
+static void pcmcia_delayed_add_pseudo_device(void *data)
+{
+       struct pcmcia_bus_socket *s = data;
+       pcmcia_device_add(s, 0);
+       s->device_add_pending = 0;
+}
+
+static inline void pcmcia_add_pseudo_device(struct pcmcia_bus_socket *s)
+{
+       if (!s->device_add_pending) {
+               schedule_work(&s->device_add);
+               s->device_add_pending = 1;
+       }
+       return;
+}
+
+
+static inline int pcmcia_devmatch(struct pcmcia_device *dev,
+                                 struct pcmcia_device_id *did)
+{
+       if (did->match_flags & PCMCIA_DEV_ID_MATCH_MANF_ID) {
+               if ((!dev->has_manf_id) || (dev->manf_id != did->manf_id))
+                       return 0;
+       }
+
+       if (did->match_flags & PCMCIA_DEV_ID_MATCH_CARD_ID) {
+               if ((!dev->has_card_id) || (dev->card_id != did->card_id))
+                       return 0;
+       }
+
+       if (did->match_flags & PCMCIA_DEV_ID_MATCH_FUNCTION) {
+               if (dev->func != did->function)
+                       return 0;
+       }
+
+       if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID1) {
+               if (!dev->prod_id[0])
+                       return 0;
+               if (strcmp(did->prod_id[0], dev->prod_id[0]))
+                       return 0;
+       }
+
+       if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID2) {
+               if (!dev->prod_id[1])
+                       return 0;
+               if (strcmp(did->prod_id[1], dev->prod_id[1]))
+                       return 0;
+       }
+
+       if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID3) {
+               if (!dev->prod_id[2])
+                       return 0;
+               if (strcmp(did->prod_id[2], dev->prod_id[2]))
+                       return 0;
+       }
+
+       if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID4) {
+               if (!dev->prod_id[3])
+                       return 0;
+               if (strcmp(did->prod_id[3], dev->prod_id[3]))
+                       return 0;
+       }
+
+       if (did->match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO) {
+               /* handle pseudo multifunction devices:
+                * there are at most two pseudo multifunction devices.
+                * if we're matching against the first, schedule a
+                * call which will then check whether there are two
+                * pseudo devices, and if not, add the second one.
+                */
+               if (dev->device_no == 0)
+                       pcmcia_add_pseudo_device(dev->socket->pcmcia);
+
+               if (dev->device_no != did->device_no)
+                       return 0;
+       }
+
+       if (did->match_flags & PCMCIA_DEV_ID_MATCH_FUNC_ID) {
+               if ((!dev->has_func_id) || (dev->func_id != did->func_id))
+                       return 0;
+
+               /* if this is a pseudo-multi-function device,
+                * we need explicit matches */
+               if (did->match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO)
+                       return 0;
+               if (dev->device_no)
+                       return 0;
+
+               /* also, FUNC_ID matching needs to be activated by userspace
+                * after it has re-checked that there is no possible module
+                * with a prod_id/manf_id/card_id match.
+                */
+               if (!dev->allow_func_id_match)
+                       return 0;
+       }
+
+       dev->dev.driver_data = (void *) did;
+
+       return 1;
+}
+
+
 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;
 
        /* matching by cardmgr */
        if (p_dev->cardmgr == p_drv)
                return 1;
 
+       while (did && did->match_flags) {
+               if (pcmcia_devmatch(p_dev, did))
+                       return 1;
+               did++;
+       }
+
        return 0;
 }
 
@@ -922,7 +1040,9 @@ static int bind_request(struct pcmcia_bus_socket *s, bind_info_t *bind_info)
 rescan:
        p_dev->cardmgr = p_drv;
 
-       pcmcia_device_query(p_dev);
+       /* if a driver is already running, we can abort */
+       if (p_dev->dev.driver)
+               goto err_put_module;
 
        /*
         * Prevent this racing with a card insertion.
@@ -1595,6 +1715,7 @@ static int __devinit pcmcia_bus_add_socket(struct class_device *class_dev)
 
        init_waitqueue_head(&s->queue);
        INIT_LIST_HEAD(&s->devices_list);
+       INIT_WORK(&s->device_add, pcmcia_delayed_add_pseudo_device, s);
 
        /* Set up hotline to Card Services */
        s->callback.owner = THIS_MODULE;
index d6eb7b2..e9651cd 100644 (file)
@@ -175,4 +175,37 @@ struct serio_device_id {
 };
 
 
+/* PCMCIA */
+
+struct pcmcia_device_id {
+       __u16           match_flags;
+
+       __u16           manf_id;
+       __u16           card_id;
+
+       __u8            func_id;
+
+       /* for real multi-function devices */
+       __u8            function;
+
+       /* for pseude multi-function devices */
+       __u8            device_no;
+
+       const char *    prod_id[4];
+       __u32           prod_id_hash[4];
+
+       /* not matched against */
+       kernel_ulong_t  driver_info;
+};
+
+#define PCMCIA_DEV_ID_MATCH_MANF_ID    0x0001
+#define PCMCIA_DEV_ID_MATCH_CARD_ID    0x0002
+#define PCMCIA_DEV_ID_MATCH_FUNC_ID    0x0004
+#define PCMCIA_DEV_ID_MATCH_FUNCTION   0x0008
+#define PCMCIA_DEV_ID_MATCH_PROD_ID1   0x0010
+#define PCMCIA_DEV_ID_MATCH_PROD_ID2   0x0020
+#define PCMCIA_DEV_ID_MATCH_PROD_ID3   0x0040
+#define PCMCIA_DEV_ID_MATCH_PROD_ID4   0x0080
+#define PCMCIA_DEV_ID_MATCH_DEVICE_NO  0x0100
+
 #endif /* LINUX_MOD_DEVICETABLE_H */
diff --git a/include/pcmcia/device_id.h b/include/pcmcia/device_id.h
new file mode 100644 (file)
index 0000000..acf6865
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (2003-2004)       Dominik Brodowski <linux@brodo.de>
+ *                             David Woodhouse
+ *
+ * License: GPL v2
+ */
+
+#define PCMCIA_DEVICE_MANF_CARD(manf, card) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_MANF_ID| \
+                       PCMCIA_DEV_ID_MATCH_CARD_ID, \
+       .manf_id = (manf), \
+       .card_id = (card), }
+
+#define PCMCIA_DEVICE_FUNC_ID(func) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_FUNC_ID, \
+       .func_id = (func), }
+
+#define PCMCIA_DEVICE_PROD_ID1(v1, vh1) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1, \
+       .prod_id = { (v1), NULL, NULL, NULL }, \
+       .prod_id_hash = { (vh1), 0, 0, 0 }, }
+
+#define PCMCIA_DEVICE_PROD_ID2(v2, vh2) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID2, \
+       .prod_id = { NULL, (v2), NULL, NULL },  \
+       .prod_id_hash = { 0, (vh2), 0, 0 }, }
+
+#define PCMCIA_DEVICE_PROD_ID12(v1, v2, vh1, vh2) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID2, \
+       .prod_id = { (v1), (v2), NULL, NULL }, \
+       .prod_id_hash = { (vh1), (vh2), 0, 0 }, }
+
+#define PCMCIA_DEVICE_PROD_ID13(v1, v3, vh1, vh3) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID3, \
+       .prod_id = { (v1), NULL, (v3), NULL }, \
+       .prod_id_hash = { (vh1), 0, (vh3), 0 }, }
+
+#define PCMCIA_DEVICE_PROD_ID14(v1, v4, vh1, vh4) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID4, \
+       .prod_id = { (v1), NULL, NULL, (v4) }, \
+       .prod_id_hash = { (vh1), 0, 0, (vh4) }, }
+
+#define PCMCIA_DEVICE_PROD_ID123(v1, v2, v3, vh1, vh2, vh3) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID2| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID3, \
+       .prod_id = { (v1), (v2), (v3), NULL },\
+       .prod_id_hash = { (vh1), (vh2), (vh3), 0 }, }
+
+#define PCMCIA_DEVICE_PROD_ID124(v1, v2, v4, vh1, vh2, vh4) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID2| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID4, \
+       .prod_id = { (v1), (v2), NULL, (v4) }, \
+       .prod_id_hash = { (vh1), (vh2), 0, (vh4) }, }
+
+#define PCMCIA_DEVICE_PROD_ID134(v1, v3, v4, vh1, vh3, vh4) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID3| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID4, \
+       .prod_id = { (v1), NULL, (v3), (v4) }, \
+       .prod_id_hash = { (vh1), 0, (vh3), (vh4) }, }
+
+#define PCMCIA_DEVICE_PROD_ID1234(v1, v2, v3, v4, vh1, vh2, vh3, vh4) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID2| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID3| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID4, \
+       .prod_id = { (v1), (v2), (v3), (v4) }, \
+       .prod_id_hash = { (vh1), (vh2), (vh3), (vh4) }, }
+
+
+/* multi-function devices */
+
+#define PCMCIA_MFC_DEVICE_MANF_CARD(mfc, manf, card) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_MANF_ID| \
+                       PCMCIA_DEV_ID_MATCH_CARD_ID| \
+                       PCMCIA_DEV_ID_MATCH_FUNCTION, \
+       .manf_id = (manf), \
+       .card_id = (card), \
+       .function = (mfc), }
+
+#define PCMCIA_MFC_DEVICE_PROD_ID1(mfc, v1, vh1) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_FUNCTION, \
+       .prod_id = { (v1), NULL, NULL, NULL }, \
+       .prod_id_hash = { (vh1), 0, 0, 0 }, \
+       .function = (mfc), }
+
+#define PCMCIA_MFC_DEVICE_PROD_ID2(mfc, v2, vh2) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID2| \
+                       PCMCIA_DEV_ID_MATCH_FUNCTION, \
+       .prod_id = { NULL, (v2), NULL, NULL },  \
+       .prod_id_hash = { 0, (vh2), 0, 0 }, \
+       .function = (mfc), }
+
+#define PCMCIA_MFC_DEVICE_PROD_ID12(mfc, v1, v2, vh1, vh2) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID2| \
+                       PCMCIA_DEV_ID_MATCH_FUNCTION, \
+       .prod_id = { (v1), (v2), NULL, NULL }, \
+       .prod_id_hash = { (vh1), (vh2), 0, 0 }, \
+       .function = (mfc), }
+
+#define PCMCIA_MFC_DEVICE_PROD_ID13(mfc, v1, v3, vh1, vh3) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID3| \
+                       PCMCIA_DEV_ID_MATCH_FUNCTION, \
+       .prod_id = { (v1), NULL, (v3), NULL }, \
+       .prod_id_hash = { (vh1), 0, (vh3), 0 }, \
+       .function = (mfc), }
+
+#define PCMCIA_MFC_DEVICE_PROD_ID123(mfc, v1, v2, v3, vh1, vh2, vh3) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID2| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID3| \
+                       PCMCIA_DEV_ID_MATCH_FUNCTION, \
+       .prod_id = { (v1), (v2), (v3), NULL },\
+       .prod_id_hash = { (vh1), (vh2), (vh3), 0 }, \
+       .function = (mfc), }
+
+/* pseudo multi-function devices */
+
+#define PCMCIA_PFC_DEVICE_MANF_CARD(mfc, manf, card) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_MANF_ID| \
+                       PCMCIA_DEV_ID_MATCH_CARD_ID| \
+                       PCMCIA_DEV_ID_MATCH_DEVICE_NO, \
+       .manf_id = (manf), \
+       .card_id = (card), \
+       .device_no = (mfc), }
+
+#define PCMCIA_PFC_DEVICE_PROD_ID1(mfc, v1, vh1) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_DEVICE_NO, \
+       .prod_id = { (v1), NULL, NULL, NULL }, \
+       .prod_id_hash = { (vh1), 0, 0, 0 }, \
+       .device_no = (mfc), }
+
+#define PCMCIA_PFC_DEVICE_PROD_ID2(mfc, v2, vh2) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID2| \
+                       PCMCIA_DEV_ID_MATCH_DEVICE_NO, \
+       .prod_id = { NULL, (v2), NULL, NULL },  \
+       .prod_id_hash = { 0, (vh2), 0, 0 }, \
+       .device_no = (mfc), }
+
+#define PCMCIA_PFC_DEVICE_PROD_ID12(mfc, v1, v2, vh1, vh2) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID2| \
+                       PCMCIA_DEV_ID_MATCH_DEVICE_NO, \
+       .prod_id = { (v1), (v2), NULL, NULL }, \
+       .prod_id_hash = { (vh1), (vh2), 0, 0 }, \
+       .device_no = (mfc), }
+
+#define PCMCIA_PFC_DEVICE_PROD_ID13(mfc, v1, v3, vh1, vh3) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID3| \
+                       PCMCIA_DEV_ID_MATCH_DEVICE_NO, \
+       .prod_id = { (v1), NULL, (v3), NULL }, \
+       .prod_id_hash = { (vh1), 0, (vh3), 0 }, \
+       .device_no = (mfc), }
+
+#define PCMCIA_PFC_DEVICE_PROD_ID123(mfc, v1, v2, v3, vh1, vh2, vh3) { \
+       .match_flags = PCMCIA_DEV_ID_MATCH_PROD_ID1| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID2| \
+                       PCMCIA_DEV_ID_MATCH_PROD_ID3| \
+                       PCMCIA_DEV_ID_MATCH_DEVICE_NO, \
+       .prod_id = { (v1), (v2), (v3), NULL },\
+       .prod_id_hash = { (vh1), (vh2), (vh3), 0 }, \
+       .device_no = (mfc), }
+
+
+#define PCMCIA_DEVICE_NULL { .match_flags = 0, }
index 312fd95..c267edd 100644 (file)
@@ -18,6 +18,8 @@
 
 #include <pcmcia/bulkmem.h>
 #include <pcmcia/cs_types.h>
+#include <pcmcia/device_id.h>
+#include <linux/mod_devicetable.h>
 
 typedef struct tuple_parse_t {
     tuple_t            tuple;
@@ -135,6 +137,7 @@ struct pcmcia_driver {
        dev_link_t              *(*attach)(void);
        void                    (*detach)(dev_link_t *);
        struct module           *owner;
+       struct pcmcia_device_id *id_table;
        struct device_driver    drv;
 };
 
@@ -173,7 +176,9 @@ struct pcmcia_device {
        u8                      has_manf_id:1;
        u8                      has_card_id:1;
        u8                      has_func_id:1;
-       u8                      reserved:5;
+
+       u8                      allow_func_id_match:1;
+       u8                      reserved:4;
 
        u8                      func_id;
        u16                     manf_id;