X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fpcmcia%2Fds.c;h=7355eb455a881324f68a34dfd4cbe9f91acf619d;hb=97bf2856c6014879bd04983a3e9dfcdac1e7fe85;hp=7e93bfccbbc4f2285a164cdfdd46e3b840eb076c;hpb=5273a3df6485dc2ad6aa7ddd441b9a21970f003b;p=linux-2.6.git diff --git a/drivers/pcmcia/ds.c b/drivers/pcmcia/ds.c index 7e93bfccb..7355eb455 100644 --- a/drivers/pcmcia/ds.c +++ b/drivers/pcmcia/ds.c @@ -1,69 +1,38 @@ -/*====================================================================== - - PC Card Driver Services - - ds.c 1.112 2001/10/13 00:08:28 - - The contents of this file are subject to the Mozilla Public - License Version 1.1 (the "License"); you may not use this file - except in compliance with the License. You may obtain a copy of - the License at http://www.mozilla.org/MPL/ - - Software distributed under the License is distributed on an "AS - IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - implied. See the License for the specific language governing - rights and limitations under the License. - - The initial developer of the original code is David A. Hinds - . Portions created by David A. Hinds - are Copyright (C) 1999 David A. Hinds. All Rights Reserved. - - Alternatively, the contents of this file may be used under the - terms of the GNU General Public License version 2 (the "GPL"), in - which case the provisions of the GPL are applicable instead of the - above. If you wish to allow the use of your version of this file - only under the terms of the GPL and not to allow others to use - your version of this file under the MPL, indicate your decision - by deleting the provisions above and replace them with the notice - and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this - file under either the MPL or the GPL. - -======================================================================*/ +/* + * ds.c -- 16-bit PCMCIA core support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * . Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + * (C) 2003 - 2006 Dominik Brodowski + */ -#include +#include #include -#include #include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include #include - -#include +#include +#include +#include #define IN_CARD_SERVICES -#include #include #include -#include #include #include #include #include "cs_internal.h" +#include "ds_internal.h" /*====================================================================*/ @@ -71,151 +40,27 @@ MODULE_AUTHOR("David Hinds "); MODULE_DESCRIPTION("PCMCIA Driver Services"); -MODULE_LICENSE("Dual MPL/GPL"); +MODULE_LICENSE("GPL"); #ifdef DEBUG -static int pc_debug; +int ds_pc_debug; -module_param(pc_debug, int, 0644); +module_param_named(pc_debug, ds_pc_debug, int, 0644); #define ds_dbg(lvl, fmt, arg...) do { \ - if (pc_debug > (lvl)) \ + if (ds_pc_debug > (lvl)) \ printk(KERN_DEBUG "ds: " fmt , ## arg); \ } while (0) #else #define ds_dbg(lvl, fmt, arg...) do { } while (0) #endif -/*====================================================================*/ - -typedef struct socket_bind_t { - struct pcmcia_driver *driver; - u_char function; - dev_link_t *instance; - struct socket_bind_t *next; -} socket_bind_t; - -/* Device user information */ -#define MAX_EVENTS 32 -#define USER_MAGIC 0x7ea4 -#define CHECK_USER(u) \ - (((u) == NULL) || ((u)->user_magic != USER_MAGIC)) -typedef struct user_info_t { - u_int user_magic; - int event_head, event_tail; - event_t event[MAX_EVENTS]; - struct user_info_t *next; - struct pcmcia_bus_socket *socket; -} user_info_t; - -/* Socket state information */ -struct pcmcia_bus_socket { - atomic_t refcount; - client_handle_t handle; - int state; - user_info_t *user; - int req_pending, req_result; - wait_queue_head_t queue, request; - struct work_struct removal; - socket_bind_t *bind; - struct pcmcia_socket *parent; -}; - -#define DS_SOCKET_PRESENT 0x01 -#define DS_SOCKET_BUSY 0x02 -#define DS_SOCKET_REMOVAL_PENDING 0x10 -#define DS_SOCKET_DEAD 0x80 - -/*====================================================================*/ - -/* Device driver ID passed to Card Services */ -static dev_info_t dev_info = "Driver Services"; - -static int major_dev = -1; - -extern struct proc_dir_entry *proc_pccard; +spinlock_t pcmcia_dev_list_lock; /*====================================================================*/ /* code which was in cs.c before */ -/*====================================================================== - - Bind_device() associates a device driver with a particular socket. - It is normally called by Driver Services after it has identified - a newly inserted card. An instance of that driver will then be - eligible to register as a client of this socket. - -======================================================================*/ - -static int pcmcia_bind_device(bind_req_t *req) -{ - client_t *client; - struct pcmcia_socket *s; - - s = req->Socket; - if (!s) - return CS_BAD_SOCKET; - - client = (client_t *) kmalloc(sizeof(client_t), GFP_KERNEL); - if (!client) - return CS_OUT_OF_RESOURCE; - memset(client, '\0', sizeof(client_t)); - client->client_magic = CLIENT_MAGIC; - strlcpy(client->dev_info, (char *)req->dev_info, DEV_NAME_LEN); - client->Socket = s; - client->Function = req->Function; - client->state = CLIENT_UNBOUND; - client->erase_busy.next = &client->erase_busy; - client->erase_busy.prev = &client->erase_busy; - init_waitqueue_head(&client->mtd_req); - client->next = s->clients; - s->clients = client; - ds_dbg(1, "%s: bind_device(): client 0x%p, dev %s\n", - cs_socket_name(client->Socket), client, client->dev_info); - return CS_SUCCESS; -} /* bind_device */ - - -/*====================================================================== - - Bind_mtd() associates a device driver with a particular memory - region. It is normally called by Driver Services after it has - identified a memory device type. An instance of the corresponding - driver will then be able to register to control this region. - -======================================================================*/ - -static int pcmcia_bind_mtd(mtd_bind_t *req) -{ - struct pcmcia_socket *s; - memory_handle_t region; - - s = req->Socket; - if (!s) - return CS_BAD_SOCKET; - - if (req->Attributes & REGION_TYPE_AM) - region = s->a_region; - else - region = s->c_region; - - while (region) { - if (region->info.CardOffset == req->CardOffset) - break; - region = region->info.next; - } - if (!region || (region->mtd != NULL)) - return CS_BAD_OFFSET; - strlcpy(region->dev_info, (char *)req->dev_info, DEV_NAME_LEN); - - ds_dbg(1, "%s: bind_mtd: attr 0x%x, offset 0x%x, dev %s\n", - cs_socket_name(s), req->Attributes, req->CardOffset, - (char *)req->dev_info); - return CS_SUCCESS; -} /* bind_mtd */ - - /* String tables for error messages */ typedef struct lookup_t { @@ -311,15 +156,15 @@ static const lookup_t service_table[] = { }; -int pcmcia_report_error(client_handle_t handle, error_info_t *err) +static int pcmcia_report_error(struct pcmcia_device *p_dev, error_info_t *err) { int i; char *serv; - if (CHECK_HANDLE(handle)) + if (!p_dev) printk(KERN_NOTICE); else - printk(KERN_NOTICE "%s: ", handle->dev_info); + printk(KERN_NOTICE "%s: ", p_dev->dev.bus_id); for (i = 0; i < ARRAY_SIZE(service_table); i++) if (service_table[i].key == err->func) @@ -339,41 +184,55 @@ 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 */ /*======================================================================*/ -void cs_error(client_handle_t handle, int func, int ret) +void cs_error(struct pcmcia_device *p_dev, int func, int ret) { error_info_t err = { func, ret }; - pcmcia_report_error(handle, &err); + pcmcia_report_error(p_dev, &err); } EXPORT_SYMBOL(cs_error); -/*======================================================================*/ - -static struct pcmcia_driver * get_pcmcia_driver (dev_info_t *dev_info); -static struct pcmcia_bus_socket * get_socket_info_by_nr(unsigned int nr); -static void pcmcia_put_bus_socket(struct pcmcia_bus_socket *s) +static void pcmcia_check_driver(struct pcmcia_driver *p_drv) { - if (atomic_dec_and_test(&s->refcount)) - kfree(s); + struct pcmcia_device_id *did = p_drv->id_table; + unsigned int i; + u32 hash; + + if (!p_drv->probe || !p_drv->remove) + printk(KERN_DEBUG "pcmcia: %s lacks a requisite callback " + "function\n", p_drv->drv.name); + + 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; } -static struct pcmcia_bus_socket *pcmcia_get_bus_socket(int nr) -{ - struct pcmcia_bus_socket *s; - s = get_socket_info_by_nr(nr); - if (s) { - WARN_ON(atomic_read(&s->refcount) == 0); - atomic_inc(&s->refcount); - } - return s; -} +/*======================================================================*/ + /** * pcmcia_register_driver - register a PCMCIA driver with the bus core @@ -385,8 +244,13 @@ int pcmcia_register_driver(struct pcmcia_driver *driver) if (!driver) return -EINVAL; - driver->use_count = 0; + pcmcia_check_driver(driver); + + /* initialize common fields */ driver->drv.bus = &pcmcia_bus_type; + driver->drv.owner = driver->owner; + + ds_dbg(3, "registering driver %s\n", driver->drv.name); return driver_register(&driver->drv); } @@ -397,744 +261,1126 @@ EXPORT_SYMBOL(pcmcia_register_driver); */ void pcmcia_unregister_driver(struct pcmcia_driver *driver) { + ds_dbg(3, "unregistering driver %s\n", driver->drv.name); driver_unregister(&driver->drv); } EXPORT_SYMBOL(pcmcia_unregister_driver); -#ifdef CONFIG_PROC_FS -static struct proc_dir_entry *proc_pccard = NULL; -static int proc_read_drivers_callback(struct device_driver *driver, void *d) +/* pcmcia_device handling */ + +struct pcmcia_device * pcmcia_get_dev(struct pcmcia_device *p_dev) { - char **p = d; - struct pcmcia_driver *p_dev = container_of(driver, - struct pcmcia_driver, drv); + struct device *tmp_dev; + tmp_dev = get_device(&p_dev->dev); + if (!tmp_dev) + return NULL; + return to_pcmcia_dev(tmp_dev); +} - *p += sprintf(*p, "%-24.24s 1 %d\n", driver->name, p_dev->use_count); - d = (void *) p; +void pcmcia_put_dev(struct pcmcia_device *p_dev) +{ + if (p_dev) + put_device(&p_dev->dev); +} - return 0; +static void pcmcia_release_function(struct kref *ref) +{ + struct config_t *c = container_of(ref, struct config_t, ref); + ds_dbg(1, "releasing config_t\n"); + kfree(c); } -static int proc_read_drivers(char *buf, char **start, off_t pos, - int count, int *eof, void *data) +static void pcmcia_release_dev(struct device *dev) { - char *p = buf; + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); + ds_dbg(1, "releasing device %s\n", p_dev->dev.bus_id); + pcmcia_put_socket(p_dev->socket); + kfree(p_dev->devname); + kref_put(&p_dev->function_config->ref, pcmcia_release_function); + kfree(p_dev); +} + +static void pcmcia_add_device_later(struct pcmcia_socket *s, int mfc) +{ + if (!s->pcmcia_state.device_add_pending) { + ds_dbg(1, "scheduling to add %s secondary" + " device to %d\n", mfc ? "mfc" : "pfc", s->sock); + s->pcmcia_state.device_add_pending = 1; + s->pcmcia_state.mfc_pfc = mfc; + schedule_work(&s->device_add); + } + return; +} + +static int pcmcia_device_probe(struct device * dev) +{ + struct pcmcia_device *p_dev; + struct pcmcia_driver *p_drv; + struct pcmcia_device_id *did; + struct pcmcia_socket *s; + cistpl_config_t cis_config; + int ret = 0; + + dev = get_device(dev); + if (!dev) + return -ENODEV; + + p_dev = to_pcmcia_dev(dev); + p_drv = to_pcmcia_drv(dev->driver); + s = p_dev->socket; + + ds_dbg(1, "trying to bind %s to %s\n", p_dev->dev.bus_id, + p_drv->drv.name); + + if ((!p_drv->probe) || (!p_dev->function_config) || + (!try_module_get(p_drv->owner))) { + ret = -EINVAL; + goto put_dev; + } + + /* set up some more device information */ + ret = pccard_read_tuple(p_dev->socket, p_dev->func, CISTPL_CONFIG, + &cis_config); + if (!ret) { + p_dev->conf.ConfigBase = cis_config.base; + p_dev->conf.Present = cis_config.rmask[0]; + } else { + printk(KERN_INFO "pcmcia: could not parse base and rmask0 of CIS\n"); + p_dev->conf.ConfigBase = 0; + p_dev->conf.Present = 0; + } - bus_for_each_drv(&pcmcia_bus_type, NULL, - (void *) &p, proc_read_drivers_callback); + ret = p_drv->probe(p_dev); + if (ret) { + ds_dbg(1, "binding %s to %s failed with %d\n", + p_dev->dev.bus_id, p_drv->drv.name, ret); + goto put_module; + } + + /* 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. + */ + did = p_dev->dev.driver_data; + if (did && (did->match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO) && + (p_dev->socket->device_count == 1) && (p_dev->device_no == 0)) + pcmcia_add_device_later(p_dev->socket, 0); - return (p - buf); + put_module: + if (ret) + module_put(p_drv->owner); + put_dev: + if (ret) + put_device(dev); + return (ret); } -#endif -/*====================================================================== - These manage a ring buffer of events pending for one user process - -======================================================================*/ +/* + * Removes a PCMCIA card from the device tree and socket list. + */ +static void pcmcia_card_remove(struct pcmcia_socket *s, struct pcmcia_device *leftover) +{ + struct pcmcia_device *p_dev; + struct pcmcia_device *tmp; + unsigned long flags; + + ds_dbg(2, "pcmcia_card_remove(%d) %s\n", s->sock, + leftover ? leftover->devname : ""); + + if (!leftover) + s->device_count = 0; + else + s->device_count = 1; + + /* unregister all pcmcia_devices registered with this socket, except leftover */ + list_for_each_entry_safe(p_dev, tmp, &s->devices_list, socket_device_list) { + if (p_dev == leftover) + continue; -static int queue_empty(user_info_t *user) + spin_lock_irqsave(&pcmcia_dev_list_lock, flags); + list_del(&p_dev->socket_device_list); + p_dev->_removed=1; + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + + ds_dbg(2, "unregistering device %s\n", p_dev->dev.bus_id); + device_unregister(&p_dev->dev); + } + + return; +} + +static int pcmcia_device_remove(struct device * dev) { - return (user->event_head == user->event_tail); + struct pcmcia_device *p_dev; + struct pcmcia_driver *p_drv; + struct pcmcia_device_id *did; + int i; + + p_dev = to_pcmcia_dev(dev); + p_drv = to_pcmcia_drv(dev->driver); + + ds_dbg(1, "removing device %s\n", p_dev->dev.bus_id); + + /* If we're removing the primary module driving a + * pseudo multi-function card, we need to unbind + * all devices + */ + did = p_dev->dev.driver_data; + if (did && (did->match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO) && + (p_dev->socket->device_count != 0) && + (p_dev->device_no == 0)) + pcmcia_card_remove(p_dev->socket, p_dev); + + /* detach the "instance" */ + if (!p_drv) + return 0; + + if (p_drv->remove) + p_drv->remove(p_dev); + + p_dev->dev_node = NULL; + + /* check for proper unloading */ + if (p_dev->_irq || p_dev->_io || p_dev->_locked) + printk(KERN_INFO "pcmcia: driver %s did not release config properly\n", + p_drv->drv.name); + + for (i = 0; i < MAX_WIN; i++) + if (p_dev->_win & CLIENT_WIN_REQ(i)) + printk(KERN_INFO "pcmcia: driver %s did not release windows properly\n", + p_drv->drv.name); + + /* references from pcmcia_probe_device */ + pcmcia_put_dev(p_dev); + module_put(p_drv->owner); + + return 0; } -static event_t get_queued_event(user_info_t *user) + +/* + * pcmcia_device_query -- determine information about a pcmcia device + */ +static int pcmcia_device_query(struct pcmcia_device *p_dev) { - user->event_tail = (user->event_tail+1) % MAX_EVENTS; - return user->event[user->event_tail]; + cistpl_manfid_t manf_id; + cistpl_funcid_t func_id; + cistpl_vers_1_t *vers1; + unsigned int i; + + vers1 = kmalloc(sizeof(*vers1), GFP_KERNEL); + if (!vers1) + return -ENOMEM; + + if (!pccard_read_tuple(p_dev->socket, p_dev->func, + CISTPL_MANFID, &manf_id)) { + p_dev->manf_id = manf_id.manf; + p_dev->card_id = manf_id.card; + p_dev->has_manf_id = 1; + p_dev->has_card_id = 1; + } + + if (!pccard_read_tuple(p_dev->socket, p_dev->func, + CISTPL_FUNCID, &func_id)) { + p_dev->func_id = func_id.func; + p_dev->has_func_id = 1; + } else { + /* rule of thumb: cards with no FUNCID, but with + * common memory device geometry information, are + * probably memory cards (from pcmcia-cs) */ + cistpl_device_geo_t *devgeo; + + devgeo = kmalloc(sizeof(*devgeo), GFP_KERNEL); + if (!devgeo) { + kfree(vers1); + return -ENOMEM; + } + if (!pccard_read_tuple(p_dev->socket, p_dev->func, + CISTPL_DEVICE_GEO, devgeo)) { + ds_dbg(0, "mem device geometry probably means " + "FUNCID_MEMORY\n"); + p_dev->func_id = CISTPL_FUNCID_MEMORY; + p_dev->has_func_id = 1; + } + kfree(devgeo); + } + + if (!pccard_read_tuple(p_dev->socket, p_dev->func, CISTPL_VERS_1, + vers1)) { + for (i=0; i < vers1->ns; i++) { + char *tmp; + unsigned int length; + + tmp = vers1->str + vers1->ofs[i]; + + length = strlen(tmp) + 1; + if ((length < 2) || (length > 255)) + continue; + + p_dev->prod_id[i] = kmalloc(sizeof(char) * length, + GFP_KERNEL); + if (!p_dev->prod_id[i]) + continue; + + p_dev->prod_id[i] = strncpy(p_dev->prod_id[i], + tmp, length); + } + } + + kfree(vers1); + return 0; } -static void queue_event(user_info_t *user, event_t event) + +/* device_add_lock is needed to avoid double registration by cardmgr and kernel. + * Serializes pcmcia_device_add; will most likely be removed in future. + * + * While it has the caveat that adding new PCMCIA devices inside(!) device_register() + * won't work, this doesn't matter much at the moment: the driver core doesn't + * support it either. + */ +static DEFINE_MUTEX(device_add_lock); + +struct pcmcia_device * pcmcia_device_add(struct pcmcia_socket *s, unsigned int function) { - user->event_head = (user->event_head+1) % MAX_EVENTS; - if (user->event_head == user->event_tail) - user->event_tail = (user->event_tail+1) % MAX_EVENTS; - user->event[user->event_head] = event; + struct pcmcia_device *p_dev, *tmp_dev; + unsigned long flags; + int bus_id_len; + + s = pcmcia_get_socket(s); + if (!s) + return NULL; + + mutex_lock(&device_add_lock); + + ds_dbg(3, "adding device to %d, function %d\n", s->sock, function); + + /* max of 4 devices per card */ + if (s->device_count == 4) + goto err_put; + + p_dev = kzalloc(sizeof(struct pcmcia_device), GFP_KERNEL); + if (!p_dev) + goto err_put; + + p_dev->socket = s; + p_dev->device_no = (s->device_count++); + p_dev->func = function; + + p_dev->dev.bus = &pcmcia_bus_type; + p_dev->dev.parent = s->dev.dev; + p_dev->dev.release = pcmcia_release_dev; + bus_id_len = sprintf (p_dev->dev.bus_id, "%d.%d", p_dev->socket->sock, p_dev->device_no); + + p_dev->devname = kmalloc(6 + bus_id_len + 1, GFP_KERNEL); + if (!p_dev->devname) + goto err_free; + sprintf (p_dev->devname, "pcmcia%s", p_dev->dev.bus_id); + ds_dbg(3, "devname is %s\n", p_dev->devname); + + spin_lock_irqsave(&pcmcia_dev_list_lock, flags); + + /* + * p_dev->function_config must be the same for all card functions. + * Note that this is serialized by the device_add_lock, so that + * only one such struct will be created. + */ + list_for_each_entry(tmp_dev, &s->devices_list, socket_device_list) + if (p_dev->func == tmp_dev->func) { + p_dev->function_config = tmp_dev->function_config; + kref_get(&p_dev->function_config->ref); + } + + /* Add to the list in pcmcia_bus_socket */ + list_add(&p_dev->socket_device_list, &s->devices_list); + + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + + if (!p_dev->function_config) { + ds_dbg(3, "creating config_t for %s\n", p_dev->dev.bus_id); + p_dev->function_config = kzalloc(sizeof(struct config_t), + GFP_KERNEL); + if (!p_dev->function_config) + goto err_unreg; + kref_init(&p_dev->function_config->ref); + } + + printk(KERN_NOTICE "pcmcia: registering new device %s\n", + p_dev->devname); + + pcmcia_device_query(p_dev); + + if (device_register(&p_dev->dev)) + goto err_unreg; + + mutex_unlock(&device_add_lock); + + return p_dev; + + err_unreg: + spin_lock_irqsave(&pcmcia_dev_list_lock, flags); + list_del(&p_dev->socket_device_list); + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + + err_free: + kfree(p_dev->devname); + kfree(p_dev); + s->device_count--; + err_put: + mutex_unlock(&device_add_lock); + pcmcia_put_socket(s); + + return NULL; } -static void handle_event(struct pcmcia_bus_socket *s, event_t event) + +static int pcmcia_card_add(struct pcmcia_socket *s) { - user_info_t *user; - for (user = s->user; user; user = user->next) - queue_event(user, event); - wake_up_interruptible(&s->queue); + cisinfo_t cisinfo; + cistpl_longlink_mfc_t mfc; + unsigned int no_funcs, i; + int ret = 0; + + if (!(s->resource_setup_done)) { + ds_dbg(3, "no resources available, delaying card_add\n"); + return -EAGAIN; /* try again, but later... */ + } + + if (pcmcia_validate_mem(s)) { + ds_dbg(3, "validating mem resources failed, " + "delaying card_add\n"); + return -EAGAIN; /* try again, but later... */ + } + + ret = pccard_validate_cis(s, BIND_FN_ALL, &cisinfo); + if (ret || !cisinfo.Chains) { + ds_dbg(0, "invalid CIS or invalid resources\n"); + return -ENODEV; + } + + if (!pccard_read_tuple(s, BIND_FN_ALL, CISTPL_LONGLINK_MFC, &mfc)) + no_funcs = mfc.nfn; + else + no_funcs = 1; + s->functions = no_funcs; + + for (i=0; i < no_funcs; i++) + pcmcia_device_add(s, i); + + return (ret); } -static int handle_request(struct pcmcia_bus_socket *s, event_t event) + +static void pcmcia_delayed_add_device(struct work_struct *work) { - if (s->req_pending != 0) - return CS_IN_USE; - if (s->state & DS_SOCKET_BUSY) - s->req_pending = 1; - handle_event(s, event); - if (wait_event_interruptible(s->request, s->req_pending <= 0)) - return CS_IN_USE; - if (s->state & DS_SOCKET_BUSY) - return s->req_result; - return CS_SUCCESS; + struct pcmcia_socket *s = + container_of(work, struct pcmcia_socket, device_add); + ds_dbg(1, "adding additional device to %d\n", s->sock); + pcmcia_device_add(s, s->pcmcia_state.mfc_pfc); + s->pcmcia_state.device_add_pending = 0; + s->pcmcia_state.mfc_pfc = 0; } -static void handle_removal(void *data) +static int pcmcia_requery(struct device *dev, void * _data) { - struct pcmcia_bus_socket *s = data; - handle_event(s, CS_EVENT_CARD_REMOVAL); - s->state &= ~DS_SOCKET_REMOVAL_PENDING; + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); + if (!p_dev->dev.driver) { + ds_dbg(1, "update device information for %s\n", + p_dev->dev.bus_id); + pcmcia_device_query(p_dev); + } + + return 0; } -/*====================================================================== +static void pcmcia_bus_rescan(struct pcmcia_socket *skt, int new_cis) +{ + int no_devices = 0; + int ret = 0; + unsigned long flags; + + /* must be called with skt_mutex held */ + ds_dbg(0, "re-scanning socket %d\n", skt->sock); + + spin_lock_irqsave(&pcmcia_dev_list_lock, flags); + if (list_empty(&skt->devices_list)) + no_devices = 1; + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + + /* If this is because of a CIS override, start over */ + if (new_cis && !no_devices) + pcmcia_card_remove(skt, NULL); + + /* 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 || new_cis) { + ret = pcmcia_card_add(skt); + if (ret) + return; + } - The card status event handler. - -======================================================================*/ + /* 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. */ + ret = bus_rescan_devices(&pcmcia_bus_type); + if (ret) + printk(KERN_INFO "pcmcia: bus_rescan_devices failed\n"); +} + +#ifdef CONFIG_PCMCIA_LOAD_CIS -static int ds_event(event_t event, int priority, - event_callback_args_t *args) +/** + * 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/ + * + * 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/ in userspace. + */ +static int pcmcia_load_firmware(struct pcmcia_device *dev, char * filename) { - struct pcmcia_bus_socket *s; + struct pcmcia_socket *s = dev->socket; + const struct firmware *fw; + char path[20]; + int ret = -ENOMEM; + int no_funcs; + int old_funcs; + cisdump_t *cis; + cistpl_longlink_mfc_t mfc; + + if (!filename) + return -EINVAL; - ds_dbg(1, "ds_event(0x%06x, %d, 0x%p)\n", - event, priority, args->client_handle); - s = args->client_data; - - switch (event) { - - case CS_EVENT_CARD_REMOVAL: - s->state &= ~DS_SOCKET_PRESENT; - if (!(s->state & DS_SOCKET_REMOVAL_PENDING)) { - s->state |= DS_SOCKET_REMOVAL_PENDING; - schedule_delayed_work(&s->removal, HZ/10); + ds_dbg(1, "trying to load CIS file %s\n", filename); + + if (strlen(filename) > 14) { + printk(KERN_WARNING "pcmcia: CIS filename is too long\n"); + return -EINVAL; } - break; - - case CS_EVENT_CARD_INSERTION: - s->state |= DS_SOCKET_PRESENT; - handle_event(s, event); - break; - - case CS_EVENT_EJECTION_REQUEST: - return handle_request(s, event); - break; - - default: - handle_event(s, event); - break; - } - return 0; -} /* ds_event */ + snprintf(path, 20, "%s", filename); -/*====================================================================== + if (request_firmware(&fw, path, &dev->dev) == 0) { + if (fw->size >= CISTPL_MAX_CIS_SIZE) { + ret = -EINVAL; + printk(KERN_ERR "pcmcia: CIS override is too big\n"); + goto release; + } - bind_mtd() connects a memory region with an MTD client. - -======================================================================*/ + cis = kzalloc(sizeof(cisdump_t), GFP_KERNEL); + if (!cis) { + ret = -ENOMEM; + goto release; + } + + cis->Length = fw->size + 1; + memcpy(cis->Data, fw->data, fw->size); + + if (!pcmcia_replace_cis(s, cis)) + ret = 0; + else { + printk(KERN_ERR "pcmcia: CIS override failed\n"); + goto release; + } + + + /* update information */ + pcmcia_device_query(dev); + + /* does this cis override add or remove functions? */ + old_funcs = s->functions; + + if (!pccard_read_tuple(s, BIND_FN_ALL, CISTPL_LONGLINK_MFC, &mfc)) + no_funcs = mfc.nfn; + else + no_funcs = 1; + s->functions = no_funcs; -static int bind_mtd(struct pcmcia_bus_socket *bus_sock, mtd_info_t *mtd_info) + if (old_funcs > no_funcs) + pcmcia_card_remove(s, dev); + else if (no_funcs > old_funcs) + pcmcia_add_device_later(s, 1); + } + release: + release_firmware(fw); + + return (ret); +} + +#else /* !CONFIG_PCMCIA_LOAD_CIS */ + +static inline int pcmcia_load_firmware(struct pcmcia_device *dev, char * filename) { - mtd_bind_t bind_req; - int ret; - - bind_req.dev_info = &mtd_info->dev_info; - bind_req.Attributes = mtd_info->Attributes; - bind_req.Socket = bus_sock->parent; - bind_req.CardOffset = mtd_info->CardOffset; - ret = pcmcia_bind_mtd(&bind_req); - if (ret != CS_SUCCESS) { - cs_error(NULL, BindMTD, ret); - printk(KERN_NOTICE "ds: unable to bind MTD '%s' to socket %d" - " offset 0x%x\n", - (char *)bind_req.dev_info, bus_sock->parent->sock, bind_req.CardOffset); return -ENODEV; - } - return 0; -} /* bind_mtd */ +} -/*====================================================================== +#endif - bind_request() connects a socket to a particular client driver. - It looks up the specified device ID in the list of registered - drivers, binds it to the socket, and tries to create an instance - of the device. unbind_request() deletes a driver instance. - -======================================================================*/ -static int bind_request(struct pcmcia_bus_socket *s, bind_info_t *bind_info) +static inline int pcmcia_devmatch(struct pcmcia_device *dev, + struct pcmcia_device_id *did) { - struct pcmcia_driver *driver; - socket_bind_t *b; - bind_req_t bind_req; - int ret; - - if (!s) - return -EINVAL; - - ds_dbg(2, "bind_request(%d, '%s')\n", s->parent->sock, - (char *)bind_info->dev_info); - driver = get_pcmcia_driver(&bind_info->dev_info); - if (!driver) - return -EINVAL; - - for (b = s->bind; b; b = b->next) - if ((driver == b->driver) && - (bind_info->function == b->function)) - break; - if (b != NULL) { - bind_info->instance = b->instance; - return -EBUSY; - } + if (did->match_flags & PCMCIA_DEV_ID_MATCH_MANF_ID) { + if ((!dev->has_manf_id) || (dev->manf_id != did->manf_id)) + return 0; + } - if (!try_module_get(driver->owner)) - return -EINVAL; - - bind_req.Socket = s->parent; - bind_req.Function = bind_info->function; - bind_req.dev_info = (dev_info_t *) driver->drv.name; - ret = pcmcia_bind_device(&bind_req); - if (ret != CS_SUCCESS) { - cs_error(NULL, BindDevice, ret); - printk(KERN_NOTICE "ds: unable to bind '%s' to socket %d\n", - (char *)dev_info, s->parent->sock); - module_put(driver->owner); - return -ENODEV; - } + if (did->match_flags & PCMCIA_DEV_ID_MATCH_CARD_ID) { + if ((!dev->has_card_id) || (dev->card_id != did->card_id)) + return 0; + } - /* Add binding to list for this socket */ - driver->use_count++; - b = kmalloc(sizeof(socket_bind_t), GFP_KERNEL); - if (!b) - { - driver->use_count--; - module_put(driver->owner); - return -ENOMEM; - } - b->driver = driver; - b->function = bind_info->function; - b->instance = NULL; - b->next = s->bind; - s->bind = b; - - if (driver->attach) { - b->instance = driver->attach(); - if (b->instance == NULL) { - printk(KERN_NOTICE "ds: unable to create instance " - "of '%s'!\n", (char *)bind_info->dev_info); - module_put(driver->owner); - return -ENODEV; + if (did->match_flags & PCMCIA_DEV_ID_MATCH_FUNCTION) { + if (dev->func != did->function) + return 0; } - } - - return 0; -} /* bind_request */ -/*====================================================================*/ + 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; + } -static int get_device_info(struct pcmcia_bus_socket *s, bind_info_t *bind_info, int first) -{ - socket_bind_t *b; - dev_node_t *node; - -#ifdef CONFIG_CARDBUS - /* - * Some unbelievably ugly code to associate the PCI cardbus - * device and its driver with the PCMCIA "bind" information. - */ - { - struct pci_bus *bus; - - bus = pcmcia_lookup_bus(s->handle); - if (bus) { - struct list_head *list; - struct pci_dev *dev = NULL; - - list = bus->devices.next; - while (list != &bus->devices) { - struct pci_dev *pdev = pci_dev_b(list); - list = list->next; - - if (first) { - dev = pdev; - break; - } - - /* Try to handle "next" here some way? */ - } - if (dev && dev->driver) { - strlcpy(bind_info->name, dev->driver->name, DEV_NAME_LEN); - bind_info->major = 0; - bind_info->minor = 0; - bind_info->next = NULL; + 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) { + 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. + */ + ds_dbg(0, "skipping FUNC_ID match for %s until userspace " + "interaction\n", dev->dev.bus_id); + if (!dev->allow_func_id_match) + return 0; + } + + if (did->match_flags & PCMCIA_DEV_ID_MATCH_FAKE_CIS) { + ds_dbg(0, "device %s needs a fake CIS\n", dev->dev.bus_id); + 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; + +#ifdef CONFIG_PCMCIA_IOCTL + /* matching by cardmgr */ + if (p_dev->cardmgr == p_drv) { + ds_dbg(0, "cardmgr matched %s to %s\n", dev->bus_id, + drv->name); + return 1; + } #endif - for (b = s->bind; b; b = b->next) - if ((strcmp((char *)b->driver->drv.name, - (char *)bind_info->dev_info) == 0) && - (b->function == bind_info->function)) - break; - if (b == NULL) return -ENODEV; - if ((b->instance == NULL) || - (b->instance->state & DEV_CONFIG_PENDING)) - return -EAGAIN; - if (first) - node = b->instance->dev; - else - for (node = b->instance->dev; node; node = node->next) - if (node == bind_info->next) break; - if (node == NULL) return -ENODEV; - - strlcpy(bind_info->name, node->dev_name, DEV_NAME_LEN); - bind_info->major = node->major; - bind_info->minor = node->minor; - bind_info->next = node->next; - - return 0; -} /* get_device_info */ + while (did && did->match_flags) { + ds_dbg(3, "trying to match %s to %s\n", dev->bus_id, + drv->name); + if (pcmcia_devmatch(p_dev, did)) { + ds_dbg(0, "matched %s to %s\n", dev->bus_id, + drv->name); + return 1; + } + did++; + } -/*====================================================================*/ + return 0; +} + +#ifdef CONFIG_HOTPLUG -static int unbind_request(struct pcmcia_bus_socket *s, bind_info_t *bind_info) +static int pcmcia_bus_uevent(struct device *dev, char **envp, int num_envp, + char *buffer, int buffer_size) { - socket_bind_t **b, *c; - - ds_dbg(2, "unbind_request(%d, '%s')\n", s->parent->sock, - (char *)bind_info->dev_info); - for (b = &s->bind; *b; b = &(*b)->next) - if ((strcmp((char *)(*b)->driver->drv.name, - (char *)bind_info->dev_info) == 0) && - ((*b)->function == bind_info->function)) - break; - if (*b == NULL) - return -ENODEV; - - c = *b; - c->driver->use_count--; - if (c->driver->detach) { - if (c->instance) - c->driver->detach(c->instance); - } - module_put(c->driver->owner); - *b = c->next; - kfree(c); - return 0; -} /* unbind_request */ + struct pcmcia_device *p_dev; + int i, length = 0; + u32 hash[4] = { 0, 0, 0, 0}; -/*====================================================================== + if (!dev) + return -ENODEV; - The user-mode PC Card device interface + 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_uevent_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "SOCKET_NO=%u", + p_dev->socket->sock)) + return -ENOMEM; -static int ds_open(struct inode *inode, struct file *file) + if (add_uevent_var(envp, num_envp, &i, + buffer, buffer_size, &length, + "DEVICE_NO=%02X", + p_dev->device_no)) + return -ENOMEM; + + if (add_uevent_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_uevent(struct device *dev, char **envp, int num_envp, + char *buffer, int buffer_size) { - socket_t i = iminor(inode); - struct pcmcia_bus_socket *s; - user_info_t *user; + return -ENODEV; +} + +#endif + +/************************ per-device sysfs output ***************************/ + +#define pcmcia_device_attr(field, test, format) \ +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; \ +} - ds_dbg(0, "ds_open(socket %d)\n", i); +#define pcmcia_device_stringattr(name, field) \ +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; \ +} + +pcmcia_device_attr(func, socket, "0x%02x\n"); +pcmcia_device_attr(func_id, has_func_id, "0x%02x\n"); +pcmcia_device_attr(manf_id, has_manf_id, "0x%04x\n"); +pcmcia_device_attr(card_id, has_card_id, "0x%04x\n"); +pcmcia_device_stringattr(prod_id1, prod_id[0]); +pcmcia_device_stringattr(prod_id2, prod_id[1]); +pcmcia_device_stringattr(prod_id3, prod_id[2]); +pcmcia_device_stringattr(prod_id4, prod_id[3]); - s = pcmcia_get_bus_socket(i); - if (!s) - return -ENODEV; - if ((file->f_flags & O_ACCMODE) != O_RDONLY) { - if (s->state & DS_SOCKET_BUSY) - return -EBUSY; +static ssize_t pcmcia_show_pm_state(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); + + if (p_dev->suspended) + return sprintf(buf, "off\n"); else - s->state |= DS_SOCKET_BUSY; - } - - user = kmalloc(sizeof(user_info_t), GFP_KERNEL); - if (!user) return -ENOMEM; - user->event_tail = user->event_head = 0; - user->next = s->user; - user->user_magic = USER_MAGIC; - user->socket = s; - s->user = user; - file->private_data = user; - - if (s->state & DS_SOCKET_PRESENT) - queue_event(user, CS_EVENT_CARD_INSERTION); - return 0; -} /* ds_open */ + return sprintf(buf, "on\n"); +} -/*====================================================================*/ +static ssize_t pcmcia_store_pm_state(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); + int ret = 0; + + if (!count) + return -EINVAL; + + if ((!p_dev->suspended) && !strncmp(buf, "off", 3)) + ret = dpm_runtime_suspend(dev, PMSG_SUSPEND); + else if (p_dev->suspended && !strncmp(buf, "on", 2)) + dpm_runtime_resume(dev); + + return ret ? ret : count; +} -static int ds_release(struct inode *inode, struct file *file) + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct pcmcia_bus_socket *s; - user_info_t *user, **link; + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); + int i; + u32 hash[4] = { 0, 0, 0, 0}; - ds_dbg(0, "ds_release(socket %d)\n", iminor(inode)); + /* 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])); + } + return sprintf(buf, "pcmcia:m%04Xc%04Xf%02Xfn%02Xpfn%02X" + "pa%08Xpb%08Xpc%08Xpd%08X\n", + 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]); +} + +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); + int ret; - user = file->private_data; - if (CHECK_USER(user)) - goto out; + if (!count) + return -EINVAL; - s = user->socket; + mutex_lock(&p_dev->socket->skt_mutex); + p_dev->allow_func_id_match = 1; + mutex_unlock(&p_dev->socket->skt_mutex); - /* Unlink user data structure */ - if ((file->f_flags & O_ACCMODE) != O_RDONLY) { - s->state &= ~DS_SOCKET_BUSY; - s->req_pending = 0; - wake_up_interruptible(&s->request); - } - file->private_data = NULL; - for (link = &s->user; *link; link = &(*link)->next) - if (*link == user) break; - if (link == NULL) - goto out; - *link = user->next; - user->user_magic = 0; - kfree(user); - pcmcia_put_bus_socket(s); -out: - return 0; -} /* ds_release */ + ret = bus_rescan_devices(&pcmcia_bus_type); + if (ret) + printk(KERN_INFO "pcmcia: bus_rescan_devices failed after " + "allowing func_id matches\n"); -/*====================================================================*/ + return count; +} -static ssize_t ds_read(struct file *file, char *buf, - size_t count, loff_t *ppos) +static struct device_attribute pcmcia_dev_attrs[] = { + __ATTR(function, 0444, func_show, NULL), + __ATTR(pm_state, 0644, pcmcia_show_pm_state, pcmcia_store_pm_state), + __ATTR_RO(func_id), + __ATTR_RO(manf_id), + __ATTR_RO(card_id), + __ATTR_RO(prod_id1), + __ATTR_RO(prod_id2), + __ATTR_RO(prod_id3), + __ATTR_RO(prod_id4), + __ATTR_RO(modalias), + __ATTR(allow_func_id_match, 0200, NULL, pcmcia_store_allow_func_id_match), + __ATTR_NULL, +}; + +/* PM support, also needed for reset */ + +static int pcmcia_dev_suspend(struct device * dev, pm_message_t state) { - struct pcmcia_bus_socket *s; - user_info_t *user; - int ret; + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); + struct pcmcia_driver *p_drv = NULL; + int ret = 0; - ds_dbg(2, "ds_read(socket %d)\n", iminor(file->f_dentry->d_inode)); - - if (count < 4) - return -EINVAL; + ds_dbg(2, "suspending %s\n", dev->bus_id); - user = file->private_data; - if (CHECK_USER(user)) - return -EIO; - - s = user->socket; - if (s->state & DS_SOCKET_DEAD) - return -EIO; + if (dev->driver) + p_drv = to_pcmcia_drv(dev->driver); - ret = wait_event_interruptible(s->queue, !queue_empty(user)); - if (ret == 0) - ret = put_user(get_queued_event(user), (int *)buf) ? -EFAULT : 4; + if (!p_drv) + goto out; - return ret; -} /* ds_read */ + if (p_drv->suspend) { + ret = p_drv->suspend(p_dev); + if (ret) { + printk(KERN_ERR "pcmcia: device %s (driver %s) did " + "not want to go to sleep (%d)\n", + p_dev->devname, p_drv->drv.name, ret); + goto out; + } + } -/*====================================================================*/ + if (p_dev->device_no == p_dev->func) { + ds_dbg(2, "releasing configuration for %s\n", dev->bus_id); + pcmcia_release_configuration(p_dev); + } -static ssize_t ds_write(struct file *file, const char *buf, - size_t count, loff_t *ppos) + out: + if (!ret) + p_dev->suspended = 1; + return ret; +} + + +static int pcmcia_dev_resume(struct device * dev) { - struct pcmcia_bus_socket *s; - user_info_t *user; + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); + struct pcmcia_driver *p_drv = NULL; + int ret = 0; - ds_dbg(2, "ds_write(socket %d)\n", iminor(file->f_dentry->d_inode)); - - if (count != 4) - return -EINVAL; - if ((file->f_flags & O_ACCMODE) == O_RDONLY) - return -EBADF; - - user = file->private_data; - if (CHECK_USER(user)) - return -EIO; - - s = user->socket; - if (s->state & DS_SOCKET_DEAD) - return -EIO; - - if (s->req_pending) { - s->req_pending--; - get_user(s->req_result, (int *)buf); - if ((s->req_result != 0) || (s->req_pending == 0)) - wake_up_interruptible(&s->request); - } else - return -EIO; - - return 4; -} /* ds_write */ + ds_dbg(2, "resuming %s\n", dev->bus_id); -/*====================================================================*/ + if (dev->driver) + p_drv = to_pcmcia_drv(dev->driver); + + if (!p_drv) + goto out; -/* No kernel lock - fine */ -static u_int ds_poll(struct file *file, poll_table *wait) + if (p_dev->device_no == p_dev->func) { + ds_dbg(2, "requesting configuration for %s\n", dev->bus_id); + ret = pcmcia_request_configuration(p_dev, &p_dev->conf); + if (ret) + goto out; + } + + if (p_drv->resume) + ret = p_drv->resume(p_dev); + + out: + if (!ret) + p_dev->suspended = 0; + return ret; +} + + +static int pcmcia_bus_suspend_callback(struct device *dev, void * _data) { - struct pcmcia_bus_socket *s; - user_info_t *user; + struct pcmcia_socket *skt = _data; + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); - ds_dbg(2, "ds_poll(socket %d)\n", iminor(file->f_dentry->d_inode)); - - user = file->private_data; - if (CHECK_USER(user)) - return POLLERR; - s = user->socket; - /* - * We don't check for a dead socket here since that - * will send cardmgr into an endless spin. - */ - poll_wait(file, &s->queue, wait); - if (!queue_empty(user)) - return POLLIN | POLLRDNORM; - return 0; -} /* ds_poll */ + if (p_dev->socket != skt) + return 0; -/*====================================================================*/ + return dpm_runtime_suspend(dev, PMSG_SUSPEND); +} -static int ds_ioctl(struct inode * inode, struct file * file, - u_int cmd, u_long arg) +static int pcmcia_bus_resume_callback(struct device *dev, void * _data) { - struct pcmcia_bus_socket *s; - u_int size; - int ret, err; - ds_ioctl_arg_t buf; - user_info_t *user; + struct pcmcia_socket *skt = _data; + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); - ds_dbg(2, "ds_ioctl(socket %d, %#x, %#lx)\n", iminor(inode), cmd, arg); - - user = file->private_data; - if (CHECK_USER(user)) - return -EIO; + if (p_dev->socket != skt) + return 0; - s = user->socket; - if (s->state & DS_SOCKET_DEAD) - return -EIO; - - size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; - if (size > sizeof(ds_ioctl_arg_t)) return -EINVAL; - - /* Permission check */ - if (!(cmd & IOC_OUT) && !capable(CAP_SYS_ADMIN)) - return -EPERM; - - if (cmd & IOC_IN) { - err = verify_area(VERIFY_READ, (char *)arg, size); - if (err) { - ds_dbg(3, "ds_ioctl(): verify_read = %d\n", err); - return err; - } - } - if (cmd & IOC_OUT) { - err = verify_area(VERIFY_WRITE, (char *)arg, size); - if (err) { - ds_dbg(3, "ds_ioctl(): verify_write = %d\n", err); - return err; + dpm_runtime_resume(dev); + + return 0; +} + +static int pcmcia_bus_resume(struct pcmcia_socket *skt) +{ + ds_dbg(2, "resuming socket %d\n", skt->sock); + bus_for_each_dev(&pcmcia_bus_type, NULL, skt, pcmcia_bus_resume_callback); + return 0; +} + +static int pcmcia_bus_suspend(struct pcmcia_socket *skt) +{ + ds_dbg(2, "suspending socket %d\n", skt->sock); + if (bus_for_each_dev(&pcmcia_bus_type, NULL, skt, + pcmcia_bus_suspend_callback)) { + pcmcia_bus_resume(skt); + return -EIO; } - } - - err = ret = 0; - - if (cmd & IOC_IN) __copy_from_user((char *)&buf, (char *)arg, size); - - switch (cmd) { - case DS_ADJUST_RESOURCE_INFO: - ret = pcmcia_adjust_resource_info(s->handle, &buf.adjust); - break; - case DS_GET_CARD_SERVICES_INFO: - ret = pcmcia_get_card_services_info(&buf.servinfo); - break; - case DS_GET_CONFIGURATION_INFO: - ret = pcmcia_get_configuration_info(s->handle, &buf.config); - break; - case DS_GET_FIRST_TUPLE: - ret = pcmcia_get_first_tuple(s->handle, &buf.tuple); - break; - case DS_GET_NEXT_TUPLE: - ret = pcmcia_get_next_tuple(s->handle, &buf.tuple); - break; - case DS_GET_TUPLE_DATA: - buf.tuple.TupleData = buf.tuple_parse.data; - buf.tuple.TupleDataMax = sizeof(buf.tuple_parse.data); - ret = pcmcia_get_tuple_data(s->handle, &buf.tuple); - break; - case DS_PARSE_TUPLE: - buf.tuple.TupleData = buf.tuple_parse.data; - ret = pcmcia_parse_tuple(s->handle, &buf.tuple, &buf.tuple_parse.parse); - break; - case DS_RESET_CARD: - ret = pcmcia_reset_card(s->handle, NULL); - break; - case DS_GET_STATUS: - ret = pcmcia_get_status(s->handle, &buf.status); - break; - case DS_VALIDATE_CIS: - ret = pcmcia_validate_cis(s->handle, &buf.cisinfo); - break; - case DS_SUSPEND_CARD: - ret = pcmcia_suspend_card(s->parent); - break; - case DS_RESUME_CARD: - ret = pcmcia_resume_card(s->parent); - break; - case DS_EJECT_CARD: - ret = pcmcia_eject_card(s->parent); - break; - case DS_INSERT_CARD: - ret = pcmcia_insert_card(s->parent); - break; - case DS_ACCESS_CONFIGURATION_REGISTER: - if ((buf.conf_reg.Action == CS_WRITE) && !capable(CAP_SYS_ADMIN)) - return -EPERM; - ret = pcmcia_access_configuration_register(s->handle, &buf.conf_reg); - break; - case DS_GET_FIRST_REGION: - ret = pcmcia_get_first_region(s->handle, &buf.region); - break; - case DS_GET_NEXT_REGION: - ret = pcmcia_get_next_region(s->handle, &buf.region); - break; - case DS_GET_FIRST_WINDOW: - buf.win_info.handle = (window_handle_t)s->handle; - ret = pcmcia_get_first_window(&buf.win_info.handle, &buf.win_info.window); - break; - case DS_GET_NEXT_WINDOW: - ret = pcmcia_get_next_window(&buf.win_info.handle, &buf.win_info.window); - break; - case DS_GET_MEM_PAGE: - ret = pcmcia_get_mem_page(buf.win_info.handle, - &buf.win_info.map); - break; - case DS_REPLACE_CIS: - ret = pcmcia_replace_cis(s->handle, &buf.cisdump); - break; - case DS_BIND_REQUEST: - if (!capable(CAP_SYS_ADMIN)) return -EPERM; - err = bind_request(s, &buf.bind_info); - break; - case DS_GET_DEVICE_INFO: - err = get_device_info(s, &buf.bind_info, 1); - break; - case DS_GET_NEXT_DEVICE: - err = get_device_info(s, &buf.bind_info, 0); - break; - case DS_UNBIND_REQUEST: - err = unbind_request(s, &buf.bind_info); - break; - case DS_BIND_MTD: - if (!capable(CAP_SYS_ADMIN)) return -EPERM; - err = bind_mtd(s, &buf.mtd_info); - break; - default: - err = -EINVAL; - } + return 0; +} + + +/*====================================================================== + + The card status event handler. - if ((err == 0) && (ret != CS_SUCCESS)) { - ds_dbg(2, "ds_ioctl: ret = %d\n", ret); - switch (ret) { - case CS_BAD_SOCKET: case CS_NO_CARD: - err = -ENODEV; break; - case CS_BAD_ARGS: case CS_BAD_ATTRIBUTE: case CS_BAD_IRQ: - case CS_BAD_TUPLE: - err = -EINVAL; break; - case CS_IN_USE: - err = -EBUSY; break; - case CS_OUT_OF_RESOURCE: - err = -ENOSPC; break; - case CS_NO_MORE_ITEMS: - err = -ENODATA; break; - case CS_UNSUPPORTED_FUNCTION: - err = -ENOSYS; break; - default: - err = -EIO; break; +======================================================================*/ + +/* Normally, the event is passed to individual drivers after + * informing userspace. Only for CS_EVENT_CARD_REMOVAL this + * is inversed to maintain historic compatibility. + */ + +static int ds_event(struct pcmcia_socket *skt, event_t event, int priority) +{ + struct pcmcia_socket *s = pcmcia_get_socket(skt); + + if (!s) { + printk(KERN_ERR "PCMCIA obtaining reference to socket %p " \ + "failed, event 0x%x lost!\n", skt, event); + return -ENODEV; } + + ds_dbg(1, "ds_event(0x%06x, %d, 0x%p)\n", + event, priority, skt); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + s->pcmcia_state.present = 0; + pcmcia_card_remove(skt, NULL); + handle_event(skt, event); + break; + + case CS_EVENT_CARD_INSERTION: + s->pcmcia_state.present = 1; + pcmcia_card_add(skt); + handle_event(skt, event); + break; + + case CS_EVENT_EJECTION_REQUEST: + break; + + case CS_EVENT_PM_SUSPEND: + case CS_EVENT_PM_RESUME: + case CS_EVENT_RESET_PHYSICAL: + case CS_EVENT_CARD_RESET: + default: + handle_event(skt, event); + break; } - if (cmd & IOC_OUT) __copy_to_user((char *)arg, (char *)&buf, size); + pcmcia_put_socket(s); - return err; -} /* ds_ioctl */ + return 0; +} /* ds_event */ -/*====================================================================*/ -static struct file_operations ds_fops = { - .owner = THIS_MODULE, - .open = ds_open, - .release = ds_release, - .ioctl = ds_ioctl, - .read = ds_read, - .write = ds_write, - .poll = ds_poll, +struct pcmcia_device * pcmcia_dev_present(struct pcmcia_device *_p_dev) +{ + struct pcmcia_device *p_dev; + struct pcmcia_device *ret = NULL; + + p_dev = pcmcia_get_dev(_p_dev); + if (!p_dev) + return NULL; + + if (!p_dev->socket->pcmcia_state.present) + goto out; + + if (p_dev->_removed) + goto out; + + if (p_dev->suspended) + goto out; + + ret = p_dev; + out: + pcmcia_put_dev(p_dev); + return ret; +} +EXPORT_SYMBOL(pcmcia_dev_present); + + +static struct pcmcia_callback pcmcia_bus_callback = { + .owner = THIS_MODULE, + .event = ds_event, + .requery = pcmcia_bus_rescan, + .suspend = pcmcia_bus_suspend, + .resume = pcmcia_bus_resume, }; -static int __devinit pcmcia_bus_add_socket(struct class_device *class_dev) +static int __devinit pcmcia_bus_add_socket(struct class_device *class_dev, + struct class_interface *class_intf) { - struct pcmcia_socket *socket = class_dev->class_data; - client_reg_t client_reg; - bind_req_t bind; - struct pcmcia_bus_socket *s; + struct pcmcia_socket *socket = class_get_devdata(class_dev); int ret; - s = kmalloc(sizeof(struct pcmcia_bus_socket), GFP_KERNEL); - if(!s) - return -ENOMEM; - memset(s, 0, sizeof(struct pcmcia_bus_socket)); - atomic_set(&s->refcount, 1); - + socket = pcmcia_get_socket(socket); + if (!socket) { + printk(KERN_ERR "PCMCIA obtaining reference to socket %p failed\n", socket); + return -ENODEV; + } + /* * Ugly. But we want to wait for the socket threads to have started up. * We really should let the drivers themselves drive some of this.. */ - current->state = TASK_INTERRUPTIBLE; - schedule_timeout(HZ/4); - - init_waitqueue_head(&s->queue); - init_waitqueue_head(&s->request); - - /* initialize data */ - INIT_WORK(&s->removal, handle_removal, s); - s->parent = socket; - - /* Set up hotline to Card Services */ - client_reg.dev_info = bind.dev_info = &dev_info; - - bind.Socket = socket; - bind.Function = BIND_FN_ALL; - ret = pcmcia_bind_device(&bind); - if (ret != CS_SUCCESS) { - cs_error(NULL, BindDevice, ret); - kfree(s); - return -EINVAL; - } + msleep(250); - client_reg.Attributes = INFO_MASTER_CLIENT; - client_reg.EventMask = - CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | - CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | - CS_EVENT_EJECTION_REQUEST | CS_EVENT_INSERTION_REQUEST | - CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; - client_reg.event_handler = &ds_event; - client_reg.Version = 0x0210; - client_reg.event_callback_args.client_data = s; - ret = pcmcia_register_client(&s->handle, &client_reg); - if (ret != CS_SUCCESS) { - cs_error(NULL, RegisterClient, ret); - kfree(s); - return -EINVAL; +#ifdef CONFIG_PCMCIA_IOCTL + init_waitqueue_head(&socket->queue); +#endif + INIT_LIST_HEAD(&socket->devices_list); + INIT_WORK(&socket->device_add, pcmcia_delayed_add_device); + memset(&socket->pcmcia_state, 0, sizeof(u8)); + socket->device_count = 0; + + ret = pccard_register_pcmcia(socket, &pcmcia_bus_callback); + if (ret) { + printk(KERN_ERR "PCMCIA registration PCCard core failed for socket %p\n", socket); + pcmcia_put_socket(socket); + return (ret); } - socket->pcmcia = s; - return 0; } - -static void pcmcia_bus_remove_socket(struct class_device *class_dev) +static void pcmcia_bus_remove_socket(struct class_device *class_dev, + struct class_interface *class_intf) { - struct pcmcia_socket *socket = class_dev->class_data; + struct pcmcia_socket *socket = class_get_devdata(class_dev); - if (!socket || !socket->pcmcia) + if (!socket) return; - flush_scheduled_work(); + socket->pcmcia_state.dead = 1; + pccard_register_pcmcia(socket, NULL); - pcmcia_deregister_client(socket->pcmcia->handle); + /* unregister any unbound devices */ + mutex_lock(&socket->skt_mutex); + pcmcia_card_remove(socket, NULL); + mutex_unlock(&socket->skt_mutex); - socket->pcmcia->state |= DS_SOCKET_DEAD; - pcmcia_put_bus_socket(socket->pcmcia); - socket->pcmcia = NULL; + pcmcia_put_socket(socket); return; } @@ -1150,30 +1396,36 @@ static struct class_interface pcmcia_bus_interface = { struct bus_type pcmcia_bus_type = { .name = "pcmcia", + .uevent = pcmcia_bus_uevent, + .match = pcmcia_bus_match, + .dev_attrs = pcmcia_dev_attrs, + .probe = pcmcia_device_probe, + .remove = pcmcia_device_remove, + .suspend = pcmcia_dev_suspend, + .resume = pcmcia_dev_resume, }; -EXPORT_SYMBOL(pcmcia_bus_type); static int __init init_pcmcia_bus(void) { - int i; + int ret; - bus_register(&pcmcia_bus_type); - class_interface_register(&pcmcia_bus_interface); + spin_lock_init(&pcmcia_dev_list_lock); - /* Set up character device for user mode clients */ - i = register_chrdev(0, "pcmcia", &ds_fops); - if (i == -EBUSY) - printk(KERN_NOTICE "unable to find a free device # for " - "Driver Services\n"); - else - major_dev = i; + ret = bus_register(&pcmcia_bus_type); + if (ret < 0) { + printk(KERN_WARNING "pcmcia: bus_register error: %d\n", ret); + return ret; + } + ret = class_interface_register(&pcmcia_bus_interface); + if (ret < 0) { + printk(KERN_WARNING + "pcmcia: class_interface_register error: %d\n", ret); + bus_unregister(&pcmcia_bus_type); + return ret; + } -#ifdef CONFIG_PROC_FS - proc_pccard = proc_mkdir("pccard", proc_bus); - if (proc_pccard) - create_proc_read_entry("drivers",0,proc_pccard,proc_read_drivers,NULL); -#endif + pcmcia_setup_ioctl(); return 0; } @@ -1183,61 +1435,13 @@ fs_initcall(init_pcmcia_bus); /* one level after subsys_initcall so that static void __exit exit_pcmcia_bus(void) { - class_interface_unregister(&pcmcia_bus_interface); + pcmcia_cleanup_ioctl(); -#ifdef CONFIG_PROC_FS - if (proc_pccard) { - remove_proc_entry("drivers", proc_pccard); - remove_proc_entry("pccard", proc_bus); - } -#endif - if (major_dev != -1) - unregister_chrdev(major_dev, "pcmcia"); + class_interface_unregister(&pcmcia_bus_interface); bus_unregister(&pcmcia_bus_type); } module_exit(exit_pcmcia_bus); - -/* helpers for backwards-compatible functions */ - -static struct pcmcia_bus_socket * get_socket_info_by_nr(unsigned int nr) -{ - struct pcmcia_socket * s = pcmcia_get_socket_by_nr(nr); - if (s && s->pcmcia) - return s->pcmcia; - else - return NULL; -} - -/* backwards-compatible accessing of driver --- by name! */ - -struct cmp_data { - void *dev_info; - struct pcmcia_driver *drv; -}; - -static int cmp_drv_callback(struct device_driver *drv, void *data) -{ - struct cmp_data *cmp = data; - if (strncmp((char *)cmp->dev_info, (char *)drv->name, - DEV_NAME_LEN) == 0) { - cmp->drv = container_of(drv, struct pcmcia_driver, drv); - return -EINVAL; - } - return 0; -} - -static struct pcmcia_driver * get_pcmcia_driver (dev_info_t *dev_info) -{ - int ret; - struct cmp_data cmp = { - .dev_info = dev_info, - }; - - ret = bus_for_each_drv(&pcmcia_bus_type, NULL, &cmp, cmp_drv_callback); - if (ret) - return cmp.drv; - return NULL; -} +MODULE_ALIAS("ds");