diff options
-rw-r--r-- | drivers/pcmcia/Kconfig | 14 | ||||
-rw-r--r-- | drivers/pcmcia/ds.c | 71 |
2 files changed, 81 insertions, 4 deletions
diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig index 14e4124e152..22f7e8ca658 100644 --- a/drivers/pcmcia/Kconfig +++ b/drivers/pcmcia/Kconfig @@ -58,6 +58,20 @@ config PCMCIA If unsure, say Y. +config PCMCIA_LOAD_CIS + bool "Load CIS updates from userspace (EXPERIMENTAL)" + depends on PCMCIA && EXPERIMENTAL + select FW_LOADER + default y + help + Some PCMCIA cards require an updated Card Information Structure (CIS) + to be loaded from userspace to work correctly. If you say Y here, + and your userspace is arranged correctly, this will be loaded + automatically using the in-kernel firmware loader and the hotplug + subsystem, instead of relying on cardmgr from pcmcia-cs to do so. + + If unsure, say Y. + config CARDBUS bool "32-bit CardBus support" depends on PCI diff --git a/drivers/pcmcia/ds.c b/drivers/pcmcia/ds.c index 19b7dacdbe7..5c88ff8e7d7 100644 --- a/drivers/pcmcia/ds.c +++ b/drivers/pcmcia/ds.c @@ -36,6 +36,7 @@ #include <linux/kref.h> #include <linux/workqueue.h> #include <linux/crc32.h> +#include <linux/firmware.h> #include <asm/atomic.h> @@ -295,6 +296,68 @@ static inline void pcmcia_check_driver(struct pcmcia_driver *p_drv) { } #endif + +#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); @@ -739,11 +802,11 @@ static inline int pcmcia_devmatch(struct pcmcia_device *dev, } if (did->match_flags & PCMCIA_DEV_ID_MATCH_FAKE_CIS) { - if (!dev->socket->fake_cis) { - /* FIXME: evaluate using firmware helpers to - * automagically load it from userspace */ + 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) { |