struct pcmcia_callback callback;
int state;
user_info_t *user;
- int req_pending, req_result;
- wait_queue_head_t queue, request;
+ wait_queue_head_t queue;
struct pcmcia_socket *parent;
/* the PCMCIA devices connected to this socket (normally one, more
*
* Registers a PCMCIA driver with the PCMCIA bus core.
*/
+static int pcmcia_device_probe(struct device *dev);
+static int pcmcia_device_remove(struct device * dev);
+
int pcmcia_register_driver(struct pcmcia_driver *driver)
{
if (!driver)
return -EINVAL;
+ /* initialize common fields */
driver->drv.bus = &pcmcia_bus_type;
driver->drv.owner = driver->owner;
+ driver->drv.probe = pcmcia_device_probe;
+ driver->drv.remove = pcmcia_device_remove;
return driver_register(&driver->drv);
}
static void pcmcia_put_dev(struct pcmcia_device *p_dev)
{
- put_device(&p_dev->dev);
+ if (p_dev)
+ put_device(&p_dev->dev);
}
static void pcmcia_release_dev(struct device *dev)
}
+static int pcmcia_device_probe(struct device * dev)
+{
+ struct pcmcia_device *p_dev;
+ struct pcmcia_driver *p_drv;
+ int ret = 0;
+
+ dev = get_device(dev);
+ if (!dev)
+ return -ENODEV;
+
+ p_dev = to_pcmcia_dev(dev);
+ p_drv = to_pcmcia_drv(dev->driver);
+
+ if (!try_module_get(p_drv->owner)) {
+ ret = -EINVAL;
+ goto put_dev;
+ }
+
+ if (p_drv->attach) {
+ p_dev->instance = p_drv->attach();
+ if ((!p_dev->instance) || (p_dev->client.state & CLIENT_UNBOUND)) {
+ printk(KERN_NOTICE "ds: unable to create instance "
+ "of '%s'!\n", p_drv->drv.name);
+ ret = -EINVAL;
+ }
+ }
+
+ if (ret)
+ module_put(p_drv->owner);
+ put_dev:
+ if ((ret) || !(p_drv->attach))
+ put_device(dev);
+ return (ret);
+}
+
+
+static int pcmcia_device_remove(struct device * dev)
+{
+ struct pcmcia_device *p_dev;
+ struct pcmcia_driver *p_drv;
+
+ /* detach the "instance" */
+ p_dev = to_pcmcia_dev(dev);
+ p_drv = to_pcmcia_drv(dev->driver);
+
+ if (p_drv) {
+ if ((p_drv->detach) && (p_dev->instance)) {
+ p_drv->detach(p_dev->instance);
+ /* from pcmcia_probe_device */
+ put_device(&p_dev->dev);
+ }
+ module_put(p_drv->owner);
+ }
+
+ return 0;
+}
+
+
+
+/*
+ * pcmcia_device_query -- determine information about a pcmcia device
+ */
+static int pcmcia_device_query(struct pcmcia_device *p_dev)
+{
+ cistpl_manfid_t manf_id;
+ cistpl_funcid_t func_id;
+ cistpl_vers_1_t vers1;
+ unsigned int i;
+
+ 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;
+ 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;
+ }
+ }
+
+ 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 < 3) || (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);
+ }
+ }
+
+ return 0;
+}
+
+
+/* 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 DECLARE_MUTEX(device_add_lock);
+
+static struct pcmcia_device * pcmcia_device_add(struct pcmcia_bus_socket *s, unsigned int function)
+{
+ struct pcmcia_device *p_dev;
+ unsigned long flags;
+
+ s = pcmcia_get_bus_socket(s);
+ if (!s)
+ return NULL;
+
+ down(&device_add_lock);
+
+ p_dev = kmalloc(sizeof(struct pcmcia_device), GFP_KERNEL);
+ if (!p_dev)
+ goto err_put;
+ memset(p_dev, 0, sizeof(struct pcmcia_device));
+
+ p_dev->socket = s->parent;
+ p_dev->device_no = (s->device_count++);
+ p_dev->func = function;
+
+ p_dev->dev.bus = &pcmcia_bus_type;
+ p_dev->dev.parent = s->parent->dev.dev;
+ p_dev->dev.release = pcmcia_release_dev;
+ sprintf (p_dev->dev.bus_id, "%d.%d", p_dev->socket->sock, p_dev->device_no);
+
+ /* compat */
+ p_dev->client.client_magic = CLIENT_MAGIC;
+ p_dev->client.Socket = s->parent;
+ p_dev->client.Function = function;
+ p_dev->client.state = CLIENT_UNBOUND;
+
+ /* Add to the list in pcmcia_bus_socket */
+ spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
+ list_add_tail(&p_dev->socket_device_list, &s->devices_list);
+ spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+
+ if (device_register(&p_dev->dev)) {
+ spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
+ list_del(&p_dev->socket_device_list);
+ spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+
+ goto err_free;
+ }
+
+ up(&device_add_lock);
+
+ return p_dev;
+
+ err_free:
+ kfree(p_dev);
+ s->device_count--;
+ err_put:
+ up(&device_add_lock);
+ pcmcia_put_bus_socket(s);
+
+ return NULL;
+}
+
+
+static int pcmcia_card_add(struct pcmcia_socket *s)
+{
+ cisinfo_t cisinfo;
+ cistpl_longlink_mfc_t mfc;
+ unsigned int no_funcs, i;
+ int ret = 0;
+
+ if (!(s->resource_setup_done))
+ return -EAGAIN; /* try again, but later... */
+
+ pcmcia_validate_mem(s);
+ 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;
+
+ /* this doesn't handle multifunction devices on one pcmcia function
+ * yet. */
+ for (i=0; i < no_funcs; i++)
+ pcmcia_device_add(s->pcmcia, i);
+
+ return (ret);
+}
+
+
+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);
+
+ /* matching by cardmgr */
+ if (p_dev->cardmgr == p_drv)
+ return 1;
+
+ return 0;
+}
+
+/************************ per-device sysfs output ***************************/
+
+#define pcmcia_device_attr(field, test, format) \
+static ssize_t field##_show (struct device *dev, 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) \
+{ \
+ 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]);
+
+static struct device_attribute pcmcia_dev_attrs[] = {
+ __ATTR(function, 0444, func_show, NULL),
+ __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_NULL,
+};
+
+
/*======================================================================
These manage a ring buffer of events pending for one user process
wake_up_interruptible(&s->queue);
}
-static int handle_request(struct pcmcia_bus_socket *s, event_t event)
-{
- 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;
-}
/*======================================================================
case CS_EVENT_CARD_INSERTION:
s->state |= DS_SOCKET_PRESENT;
+ pcmcia_card_add(skt);
handle_event(s, event);
- send_event(skt, event, priority);
break;
case CS_EVENT_EJECTION_REQUEST:
- ret = handle_request(s, event);
- if (ret)
- break;
ret = send_event(skt, event, priority);
break;
static int bind_request(struct pcmcia_bus_socket *s, bind_info_t *bind_info)
{
struct pcmcia_driver *p_drv;
- struct pcmcia_device *p_dev, *tmp_dev;
- unsigned long flags;
+ struct pcmcia_device *p_dev;
int ret = 0;
+ unsigned long flags;
s = pcmcia_get_bus_socket(s);
if (!s)
goto err_put_driver;
}
- /* Currently, the userspace pcmcia cardmgr detects pcmcia devices.
- * Here this information is translated into a kernel
- * struct pcmcia_device.
- */
-
- p_dev = kmalloc(sizeof(struct pcmcia_device), GFP_KERNEL);
- if (!p_dev) {
- ret = -ENOMEM;
- goto err_put_module;
- }
- memset(p_dev, 0, sizeof(struct pcmcia_device));
-
- p_dev->socket = s->parent;
- p_dev->device_no = (s->device_count++);
- p_dev->func = bind_info->function;
-
- p_dev->dev.bus = &pcmcia_bus_type;
- p_dev->dev.parent = s->parent->dev.dev;
- p_dev->dev.release = pcmcia_release_dev;
- sprintf (p_dev->dev.bus_id, "%d.%d", p_dev->socket->sock, p_dev->device_no);
- p_dev->dev.driver = &p_drv->drv;
-
- /* compat */
- p_dev->client.client_magic = CLIENT_MAGIC;
- p_dev->client.Socket = s->parent;
- p_dev->client.Function = bind_info->function;
- p_dev->client.state = CLIENT_UNBOUND;
-
- ret = device_register(&p_dev->dev);
- if (ret) {
- kfree(p_dev);
- goto err_put_module;
- }
-
- /* Add to the list in pcmcia_bus_socket, but only if no device
- * with the same func _and_ driver exists */
spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
- list_for_each_entry(tmp_dev, &s->devices_list, socket_device_list) {
- if ((tmp_dev->func == bind_info->function) &&
- (tmp_dev->dev.driver == p_dev->dev.driver)){
- spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
- bind_info->instance = tmp_dev->instance;
- ret = -EBUSY;
- goto err_unregister;
+ list_for_each_entry(p_dev, &s->devices_list, socket_device_list) {
+ if (p_dev->func == bind_info->function) {
+ if ((p_dev->dev.driver == &p_drv->drv)) {
+ if (p_dev->cardmgr) {
+ /* if there's already a device
+ * registered, and it was registered
+ * by userspace before, we need to
+ * return the "instance". */
+ spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+ bind_info->instance = p_dev->instance;
+ ret = -EBUSY;
+ goto err_put_module;
+ } else {
+ /* the correct driver managed to bind
+ * itself magically to the correct
+ * device. */
+ spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+ p_dev->cardmgr = p_drv;
+ ret = 0;
+ goto err_put_module;
+ }
+ } else if (!p_dev->dev.driver) {
+ /* there's already a device available where
+ * no device has been bound to yet. So we don't
+ * need to register a device! */
+ spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+ goto rescan;
+ }
}
}
- list_add_tail(&p_dev->socket_device_list, &s->devices_list);
spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
- if (p_drv->attach) {
- p_dev->instance = p_drv->attach();
- if ((!p_dev->instance) || (p_dev->client.state & CLIENT_UNBOUND)) {
- printk(KERN_NOTICE "ds: unable to create instance "
- "of '%s'!\n", (char *)bind_info->dev_info);
- ret = -ENODEV;
- goto err_unregister;
- }
+ p_dev = pcmcia_device_add(s, bind_info->function);
+ if (!p_dev) {
+ ret = -EIO;
+ goto err_put_module;
}
- put_driver(&p_drv->drv);
+rescan:
+ p_dev->cardmgr = p_drv;
- return 0;
+ pcmcia_device_query(p_dev);
- err_unregister:
- device_unregister(&p_dev->dev);
- module_put(p_drv->owner);
- put_driver(&p_drv->drv);
- return (ret);
+ /*
+ * Prevent this racing with a card insertion.
+ */
+ down(&s->parent->skt_sem);
+ bus_rescan_devices(&pcmcia_bus_type);
+ up(&s->parent->skt_sem);
+
+ /* check whether the driver indeed matched. I don't care if this
+ * is racy or not, because it can only happen on cardmgr access
+ * paths...
+ */
+ if (!(p_dev->dev.driver == &p_drv->drv))
+ p_dev->cardmgr = NULL;
err_put_module:
module_put(p_drv->owner);
put_driver(&p_drv->drv);
err_put:
pcmcia_put_bus_socket(s);
+
return (ret);
} /* bind_request */
+
int pcmcia_register_client(client_handle_t *handle, client_reg_t *req)
{
client_t *client = NULL;
pcmcia_put_bus_socket(skt); /* safe, as we already hold a reference from bind_device */
- /*
- * Prevent this racing with a card insertion.
- */
- down(&s->skt_sem);
*handle = client;
client->state &= ~CLIENT_UNBOUND;
client->Socket = s;
EVENT(client, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW);
}
- up(&s->skt_sem);
return CS_SUCCESS;
out_no_resource:
- up(&s->skt_sem);
pcmcia_put_dev(p_dev);
return CS_OUT_OF_RESOURCE;
} /* register_client */
static int unbind_request(struct pcmcia_bus_socket *s)
{
struct pcmcia_device *p_dev;
- struct pcmcia_driver *p_drv;
unsigned long flags;
ds_dbg(2, "unbind_request(%d)\n", s->parent->sock);
p_dev->client.state |= CLIENT_STALE;
spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
- /* detach the "instance" */
- p_drv = to_pcmcia_drv(p_dev->dev.driver);
- if (p_drv) {
- if ((p_drv->detach) && (p_dev->instance))
- p_drv->detach(p_dev->instance);
- module_put(p_drv->owner);
- }
-
device_unregister(&p_dev->dev);
}
/* 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)
static ssize_t ds_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
- struct pcmcia_bus_socket *s;
- user_info_t *user;
-
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 __user *)buf);
- if ((s->req_result != 0) || (s->req_pending == 0))
- wake_up_interruptible(&s->request);
- } else
- return -EIO;
-
- return 4;
+ return -EIO;
} /* ds_write */
/*====================================================================*/
return -EPERM;
if (cmd & IOC_IN) {
- err = verify_area(VERIFY_READ, uarg, size);
- if (err) {
- ds_dbg(3, "ds_ioctl(): verify_read = %d\n", err);
- return err;
+ if (!access_ok(VERIFY_READ, uarg, size)) {
+ ds_dbg(3, "ds_ioctl(): verify_read = %d\n", -EFAULT);
+ return -EFAULT;
}
}
if (cmd & IOC_OUT) {
- err = verify_area(VERIFY_WRITE, uarg, size);
- if (err) {
- ds_dbg(3, "ds_ioctl(): verify_write = %d\n", err);
- return err;
+ if (!access_ok(VERIFY_WRITE, uarg, size)) {
+ ds_dbg(3, "ds_ioctl(): verify_write = %d\n", -EFAULT);
+ return -EFAULT;
}
}
buf = kmalloc(sizeof(ds_ioctl_arg_t), GFP_KERNEL);
buf->config.Function, &buf->config);
break;
case DS_GET_FIRST_TUPLE:
+ down(&s->parent->skt_sem);
pcmcia_validate_mem(s->parent);
+ up(&s->parent->skt_sem);
ret = pccard_get_first_tuple(s->parent, BIND_FN_ALL, &buf->tuple);
break;
case DS_GET_NEXT_TUPLE:
ret = pccard_get_status(s->parent, buf->status.Function, &buf->status);
break;
case DS_VALIDATE_CIS:
+ down(&s->parent->skt_sem);
pcmcia_validate_mem(s->parent);
+ up(&s->parent->skt_sem);
ret = pccard_validate_cis(s->parent, BIND_FN_ALL, &buf->cisinfo);
break;
case DS_SUSPEND_CARD:
printed++;
}
}
- ret = -EINVAL;
+ err = -EINVAL;
goto free_out;
break;
case DS_GET_FIRST_WINDOW:
msleep(250);
init_waitqueue_head(&s->queue);
- init_waitqueue_head(&s->request);
INIT_LIST_HEAD(&s->devices_list);
/* Set up hotline to Card Services */
s->callback.owner = THIS_MODULE;
s->callback.event = &ds_event;
+ s->callback.resources_done = &pcmcia_card_add;
socket->pcmcia = s;
ret = pccard_register_pcmcia(socket, &s->callback);
struct bus_type pcmcia_bus_type = {
.name = "pcmcia",
+ .match = pcmcia_bus_match,
+ .dev_attrs = pcmcia_dev_attrs,
};
EXPORT_SYMBOL(pcmcia_bus_type);
/* Set up character device for user mode clients */
i = register_chrdev(0, "pcmcia", &ds_fops);
- if (i == -EBUSY)
+ if (i < 0)
printk(KERN_NOTICE "unable to find a free device # for "
- "Driver Services\n");
+ "Driver Services (error=%d)\n", i);
else
major_dev = i;