[PATCH] pcmcia: add some Documentation
[powerpc.git] / drivers / pcmcia / ds.c
index c4ade28..2c3c3da 100644 (file)
@@ -35,6 +35,8 @@
 #include <linux/delay.h>
 #include <linux/kref.h>
 #include <linux/workqueue.h>
+#include <linux/crc32.h>
+#include <linux/firmware.h>
 
 #include <asm/atomic.h>
 
@@ -58,7 +60,7 @@ MODULE_DESCRIPTION("PCMCIA Driver Services");
 MODULE_LICENSE("GPL");
 
 #ifdef DEBUG
-int ds_pc_debug;
+static int ds_pc_debug;
 
 module_param_named(pc_debug, ds_pc_debug, int, 0644);
 
@@ -100,9 +102,14 @@ 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;
 
+static struct bus_type pcmcia_bus_type;
+
 #define DS_SOCKET_PRESENT              0x01
 #define DS_SOCKET_BUSY                 0x02
 #define DS_SOCKET_REMOVAL_PENDING      0x10
@@ -213,7 +220,7 @@ static const lookup_t service_table[] = {
 };
 
 
-int pcmcia_report_error(client_handle_t handle, error_info_t *err)
+static int pcmcia_report_error(client_handle_t handle, error_info_t *err)
 {
        int i;
        char *serv;
@@ -243,7 +250,6 @@ int pcmcia_report_error(client_handle_t handle, error_info_t *err)
 
        return CS_SUCCESS;
 } /* report_error */
-EXPORT_SYMBOL(pcmcia_report_error);
 
 /* end of code which was in cs.c before */
 
@@ -256,6 +262,98 @@ void cs_error(client_handle_t handle, int func, int ret)
 }
 EXPORT_SYMBOL(cs_error);
 
+
+static void pcmcia_check_driver(struct pcmcia_driver *p_drv)
+{
+       struct pcmcia_device_id *did = p_drv->id_table;
+       unsigned int i;
+       u32 hash;
+
+       while (did && did->match_flags) {
+               for (i=0; i<4; i++) {
+                       if (!did->prod_id[i])
+                               continue;
+
+                       hash = crc32(0, did->prod_id[i], strlen(did->prod_id[i]));
+                       if (hash == did->prod_id_hash[i])
+                               continue;
+
+                       printk(KERN_DEBUG "pcmcia: %s: invalid hash for "
+                              "product string \"%s\": is 0x%x, should "
+                              "be 0x%x\n", p_drv->drv.name, did->prod_id[i],
+                              did->prod_id_hash[i], hash);
+                       printk(KERN_DEBUG "pcmcia: see "
+                               "Documentation/pcmcia/devicetable.txt for "
+                               "details\n");
+               }
+               did++;
+       }
+
+       return;
+}
+
+
+#ifdef CONFIG_PCMCIA_LOAD_CIS
+
+/**
+ * pcmcia_load_firmware - load CIS from userspace if device-provided is broken
+ * @dev - the pcmcia device which needs a CIS override
+ * @filename - requested filename in /lib/firmware/cis/
+ *
+ * This uses the in-kernel firmware loading mechanism to use a "fake CIS" if
+ * the one provided by the card is broken. The firmware files reside in
+ * /lib/firmware/cis/ in userspace.
+ */
+static int pcmcia_load_firmware(struct pcmcia_device *dev, char * filename)
+{
+       struct pcmcia_socket *s = dev->socket;
+       const struct firmware *fw;
+       char path[20];
+       int ret=-ENOMEM;
+       cisdump_t *cis;
+
+       if (!filename)
+               return -EINVAL;
+
+       ds_dbg(1, "trying to load firmware %s\n", filename);
+
+       if (strlen(filename) > 14)
+               return -EINVAL;
+
+       snprintf(path, 20, "%s", filename);
+
+       if (request_firmware(&fw, path, &dev->dev) == 0) {
+               if (fw->size >= CISTPL_MAX_CIS_SIZE)
+                       goto release;
+
+               cis = kmalloc(sizeof(cisdump_t), GFP_KERNEL);
+               if (!cis)
+                       goto release;
+
+               memset(cis, 0, sizeof(cisdump_t));
+
+               cis->Length = fw->size + 1;
+               memcpy(cis->Data, fw->data, fw->size);
+
+               if (!pcmcia_replace_cis(s, cis))
+                       ret = 0;
+       }
+ release:
+       release_firmware(fw);
+
+       return (ret);
+}
+
+#else /* !CONFIG_PCMCIA_LOAD_CIS */
+
+static inline int pcmcia_load_firmware(struct pcmcia_device *dev, char * filename)
+{
+       return -ENODEV;
+}
+
+#endif
+
+
 /*======================================================================*/
 
 static struct pcmcia_driver * get_pcmcia_driver (dev_info_t *dev_info);
@@ -292,6 +390,8 @@ int pcmcia_register_driver(struct pcmcia_driver *driver)
        if (!driver)
                return -EINVAL;
 
+       pcmcia_check_driver(driver);
+
        /* initialize common fields */
        driver->drv.bus = &pcmcia_bus_type;
        driver->drv.owner = driver->owner;
@@ -511,6 +611,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;
@@ -536,6 +640,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);
@@ -590,28 +696,257 @@ 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 int pcmcia_requery(struct device *dev, void * _data)
+{
+       struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+       if (!p_dev->dev.driver)
+               pcmcia_device_query(p_dev);
+
+       return 0;
+}
+
+static void pcmcia_bus_rescan(struct pcmcia_socket *skt)
+{
+       int no_devices=0;
+       unsigned long flags;
+
+       /* must be called with skt_sem held */
+       spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
+       if (list_empty(&skt->pcmcia->devices_list))
+               no_devices=1;
+       spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+
+       /* if no devices were added for this socket yet because of
+        * missing resource information or other trouble, we need to
+        * do this now. */
+       if (no_devices) {
+               int ret = pcmcia_card_add(skt);
+               if (ret)
+                       return;
+       }
+
+       /* some device information might have changed because of a CIS
+        * update or because we can finally read it correctly... so
+        * determine it again, overwriting old values if necessary. */
+       bus_for_each_dev(&pcmcia_bus_type, NULL, NULL, pcmcia_requery);
+
+       /* we re-scan all devices, not just the ones connected to this
+        * socket. This does not matter, though. */
+       bus_rescan_devices(&pcmcia_bus_type);
+}
+
+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;
+       }
+
+       if (did->match_flags & PCMCIA_DEV_ID_MATCH_FAKE_CIS) {
+               if (!dev->socket->fake_cis)
+                       pcmcia_load_firmware(dev, did->cisfile);
+
+               if (!dev->socket->fake_cis)
+                       return 0;
+       }
+
+       if (did->match_flags & PCMCIA_DEV_ID_MATCH_ANONYMOUS) {
+               int i;
+               for (i=0; i<4; i++)
+                       if (dev->prod_id[i])
+                               return 0;
+               if (dev->has_manf_id || dev->has_card_id || dev->has_func_id)
+                       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;
 }
 
+#ifdef CONFIG_HOTPLUG
+
+static int pcmcia_bus_hotplug(struct device *dev, char **envp, int num_envp,
+                             char *buffer, int buffer_size)
+{
+       struct pcmcia_device *p_dev;
+       int i, length = 0;
+       u32 hash[4] = { 0, 0, 0, 0};
+
+       if (!dev)
+               return -ENODEV;
+
+       p_dev = to_pcmcia_dev(dev);
+
+       /* calculate hashes */
+       for (i=0; i<4; i++) {
+               if (!p_dev->prod_id[i])
+                       continue;
+               hash[i] = crc32(0, p_dev->prod_id[i], strlen(p_dev->prod_id[i]));
+       }
+
+       i = 0;
+
+       if (add_hotplug_env_var(envp, num_envp, &i,
+                               buffer, buffer_size, &length,
+                               "SOCKET_NO=%u",
+                               p_dev->socket->sock))
+               return -ENOMEM;
+
+       if (add_hotplug_env_var(envp, num_envp, &i,
+                               buffer, buffer_size, &length,
+                               "DEVICE_NO=%02X",
+                               p_dev->device_no))
+               return -ENOMEM;
+
+       if (add_hotplug_env_var(envp, num_envp, &i,
+                               buffer, buffer_size, &length,
+                               "MODALIAS=pcmcia:m%04Xc%04Xf%02Xfn%02Xpfn%02X"
+                               "pa%08Xpb%08Xpc%08Xpd%08X",
+                               p_dev->has_manf_id ? p_dev->manf_id : 0,
+                               p_dev->has_card_id ? p_dev->card_id : 0,
+                               p_dev->has_func_id ? p_dev->func_id : 0,
+                               p_dev->func,
+                               p_dev->device_no,
+                               hash[0],
+                               hash[1],
+                               hash[2],
+                               hash[3]))
+               return -ENOMEM;
+
+       envp[i] = NULL;
+
+       return 0;
+}
+
+#else
+
+static int pcmcia_bus_hotplug(struct device *dev, char **envp, int num_envp,
+                             char *buffer, int buffer_size)
+{
+       return -ENODEV;
+}
+
+#endif
+
 /************************ per-device sysfs output ***************************/
 
 #define pcmcia_device_attr(field, test, format)                                \
-static ssize_t field##_show (struct device *dev, char *buf)            \
+static ssize_t field##_show (struct device *dev, struct device_attribute *attr, char *buf)             \
 {                                                                      \
        struct pcmcia_device *p_dev = to_pcmcia_dev(dev);               \
        return p_dev->test ? sprintf (buf, format, p_dev->field) : -ENODEV; \
 }
 
 #define pcmcia_device_stringattr(name, field)                                  \
-static ssize_t name##_show (struct device *dev, char *buf)             \
+static ssize_t name##_show (struct device *dev, struct device_attribute *attr, char *buf)              \
 {                                                                      \
        struct pcmcia_device *p_dev = to_pcmcia_dev(dev);               \
        return p_dev->field ? sprintf (buf, "%s\n", p_dev->field) : -ENODEV; \
@@ -626,6 +961,23 @@ pcmcia_device_stringattr(prod_id2, prod_id[1]);
 pcmcia_device_stringattr(prod_id3, prod_id[2]);
 pcmcia_device_stringattr(prod_id4, prod_id[3]);
 
+
+static ssize_t pcmcia_store_allow_func_id_match (struct device * dev, struct device_attribute *attr,
+                                                const char * buf, size_t count)
+{
+       struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+        if (!count)
+                return -EINVAL;
+
+       down(&p_dev->socket->skt_sem);
+       p_dev->allow_func_id_match = 1;
+       up(&p_dev->socket->skt_sem);
+
+       bus_rescan_devices(&pcmcia_bus_type);
+
+       return count;
+}
+
 static struct device_attribute pcmcia_dev_attrs[] = {
        __ATTR(function, 0444, func_show, NULL),
        __ATTR_RO(func_id),
@@ -635,6 +987,7 @@ static struct device_attribute pcmcia_dev_attrs[] = {
        __ATTR_RO(prod_id2),
        __ATTR_RO(prod_id3),
        __ATTR_RO(prod_id4),
+       __ATTR(allow_func_id_match, 0200, NULL, pcmcia_store_allow_func_id_match),
        __ATTR_NULL,
 };
 
@@ -856,7 +1209,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.
@@ -1529,11 +1884,12 @@ 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;
        s->callback.event = &ds_event;
-       s->callback.resources_done = &pcmcia_card_add;
+       s->callback.requery = &pcmcia_bus_rescan;
        socket->pcmcia = s;
 
        ret = pccard_register_pcmcia(socket, &s->callback);
@@ -1573,12 +1929,12 @@ static struct class_interface pcmcia_bus_interface = {
 };
 
 
-struct bus_type pcmcia_bus_type = {
+static struct bus_type pcmcia_bus_type = {
        .name = "pcmcia",
+       .hotplug = pcmcia_bus_hotplug,
        .match = pcmcia_bus_match,
        .dev_attrs = pcmcia_dev_attrs,
 };
-EXPORT_SYMBOL(pcmcia_bus_type);
 
 
 static int __init init_pcmcia_bus(void)