Commit 1118d921 authored by Dominik Brodowski's avatar Dominik Brodowski Committed by Russell King

[PCMCIA] "driver services" socket add/remove abstraction

Previously, "Driver Services" could only be called when the socket
drivers were initialized earlier. This caused an awful lot of
problems, especially when modprobe tried to load ds.ko and a pcmcia
card driver at once.

As all socket devices are registered with the driver model core as
being of "class_type pcmcia_socket_class", we can take use of that and
register them with "Driver Services" upon detection or upon
module loading of ds.c.

Also, the "I-need-two-initcalls-in-a-module"-tweak can go away.

Unfortunately, this patch reportedly breaks some RedHat pcmcia init
scritps - they relied on the failed loading of ds.c to detect that no
socket driver was loaded previously. To properly detect this, you
should take a look at the /sys/class/pcmcia_socket/devices directory.
parent 811bbb96
......@@ -48,6 +48,7 @@
#include <linux/proc_fs.h>
#include <linux/poll.h>
#include <linux/pci.h>
#include <linux/list.h>
#include <pcmcia/version.h>
#include <pcmcia/cs_types.h>
......@@ -55,6 +56,7 @@
#include <pcmcia/bulkmem.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/ds.h>
#include <pcmcia/ss.h>
/*====================================================================*/
......@@ -105,6 +107,9 @@ typedef struct socket_info_t {
wait_queue_head_t queue, request;
struct timer_list removal;
socket_bind_t *bind;
struct device *socket_dev;
struct list_head socket_list;
unsigned int socket_no; /* deprecated */
} socket_info_t;
#define SOCKET_PRESENT 0x01
......@@ -116,8 +121,11 @@ typedef struct socket_info_t {
/* Device driver ID passed to Card Services */
static dev_info_t dev_info = "Driver Services";
static int sockets = 0, major_dev = -1;
static socket_info_t *socket_table = NULL;
static int major_dev = -1;
/* list of all sockets registered with the pcmcia bus driver */
static DECLARE_RWSEM(bus_socket_list_rwsem);
static LIST_HEAD(bus_socket_list);
extern struct proc_dir_entry *proc_pccard;
......@@ -135,6 +143,7 @@ static void cs_error(client_handle_t handle, int func, int ret)
/*======================================================================*/
static struct pcmcia_driver * get_pcmcia_driver (dev_info_t *dev_info);
static socket_info_t * get_socket_info_by_nr(unsigned int nr);
/**
* pcmcia_register_driver - register a PCMCIA driver with the bus core
......@@ -160,17 +169,19 @@ EXPORT_SYMBOL(pcmcia_register_driver);
void pcmcia_unregister_driver(struct pcmcia_driver *driver)
{
socket_bind_t *b;
int i;
socket_info_t *bus_sock;
if (driver->use_count > 0) {
/* Blank out any left-over device instances */
driver->attach = NULL; driver->detach = NULL;
for (i = 0; i < sockets; i++)
for (b = socket_table[i].bind; b; b = b->next)
down_read(&bus_socket_list_rwsem);
list_for_each_entry(bus_sock, &bus_socket_list, socket_list) {
for (b = bus_sock->bind; b; b = b->next)
if (b->driver == driver)
b->instance = NULL;
}
up_read(&bus_socket_list_rwsem);
}
driver_unregister(&driver->drv);
}
EXPORT_SYMBOL(pcmcia_unregister_driver);
......@@ -182,7 +193,7 @@ int register_pccard_driver(dev_info_t *dev_info,
{
struct pcmcia_driver *driver;
socket_bind_t *b;
int i;
socket_info_t *bus_sock;
DEBUG(0, "ds: register_pccard_driver('%s')\n", (char *)dev_info);
driver = get_pcmcia_driver(dev_info);
......@@ -199,15 +210,17 @@ int register_pccard_driver(dev_info_t *dev_info,
if (driver->use_count == 0) return 0;
/* Instantiate any already-bound devices */
for (i = 0; i < sockets; i++)
for (b = socket_table[i].bind; b; b = b->next) {
down_read(&bus_socket_list_rwsem);
list_for_each_entry(bus_sock, &bus_socket_list, socket_list) {
for (b = bus_sock->bind; b; b = b->next) {
if (b->driver != driver) continue;
b->instance = driver->attach();
if (b->instance == NULL)
printk(KERN_NOTICE "ds: unable to create instance "
"of '%s'!\n", driver->drv.name);
}
}
up_read(&bus_socket_list_rwsem);
return 0;
} /* register_pccard_driver */
......@@ -309,7 +322,7 @@ static int handle_request(socket_info_t *s, event_t event)
static void handle_removal(u_long sn)
{
socket_info_t *s = &socket_table[sn];
socket_info_t *s = get_socket_info_by_nr(sn);
handle_event(s, CS_EVENT_CARD_REMOVAL);
s->state &= ~SOCKET_REMOVAL_PENDING;
}
......@@ -329,7 +342,7 @@ static int ds_event(event_t event, int priority,
DEBUG(1, "ds: ds_event(0x%06x, %d, 0x%p)\n",
event, priority, args->client_handle);
s = args->client_data;
i = s - socket_table;
i = s->socket_no;
switch (event) {
......@@ -400,9 +413,12 @@ static int bind_request(int i, bind_info_t *bind_info)
struct pcmcia_driver *driver;
socket_bind_t *b;
bind_req_t bind_req;
socket_info_t *s = &socket_table[i];
socket_info_t *s = get_socket_info_by_nr(i);
int ret;
if (!s)
return -EINVAL;
DEBUG(2, "bind_request(%d, '%s')\n", i,
(char *)bind_info->dev_info);
driver = get_pcmcia_driver(&bind_info->dev_info);
......@@ -464,7 +480,7 @@ static int bind_request(int i, bind_info_t *bind_info)
static int get_device_info(int i, bind_info_t *bind_info, int first)
{
socket_info_t *s = &socket_table[i];
socket_info_t *s = get_socket_info_by_nr(i);
socket_bind_t *b;
dev_node_t *node;
......@@ -534,7 +550,7 @@ static int get_device_info(int i, bind_info_t *bind_info, int first)
static int unbind_request(int i, bind_info_t *bind_info)
{
socket_info_t *s = &socket_table[i];
socket_info_t *s = get_socket_info_by_nr(i);
socket_bind_t **b, *c;
DEBUG(2, "unbind_request(%d, '%s')\n", i,
......@@ -572,9 +588,11 @@ static int ds_open(struct inode *inode, struct file *file)
user_info_t *user;
DEBUG(0, "ds_open(socket %d)\n", i);
if ((i >= sockets) || (sockets == 0))
s = get_socket_info_by_nr(i);
if (!s)
return -ENODEV;
s = &socket_table[i];
if ((file->f_flags & O_ACCMODE) != O_RDONLY) {
if (s->state & SOCKET_BUSY)
return -EBUSY;
......@@ -604,9 +622,11 @@ static int ds_release(struct inode *inode, struct file *file)
user_info_t *user, **link;
DEBUG(0, "ds_release(socket %d)\n", i);
if ((i >= sockets) || (sockets == 0))
s = get_socket_info_by_nr(i);
if (!s)
return 0;
s = &socket_table[i];
user = file->private_data;
if (CHECK_USER(user))
goto out;
......@@ -637,11 +657,13 @@ static ssize_t ds_read(struct file *file, char *buf,
DEBUG(2, "ds_read(socket %d)\n", i);
if ((i >= sockets) || (sockets == 0))
return -ENODEV;
if (count < 4)
return -EINVAL;
s = &socket_table[i];
s = get_socket_info_by_nr(i);
if (!s)
return -ENODEV;
user = file->private_data;
if (CHECK_USER(user))
return -EIO;
......@@ -666,13 +688,15 @@ static ssize_t ds_write(struct file *file, const char *buf,
DEBUG(2, "ds_write(socket %d)\n", i);
if ((i >= sockets) || (sockets == 0))
return -ENODEV;
if (count != 4)
return -EINVAL;
if ((file->f_flags & O_ACCMODE) == O_RDONLY)
return -EBADF;
s = &socket_table[i];
s = get_socket_info_by_nr(i);
if (!s)
return -ENODEV;
user = file->private_data;
if (CHECK_USER(user))
return -EIO;
......@@ -699,9 +723,10 @@ static u_int ds_poll(struct file *file, poll_table *wait)
DEBUG(2, "ds_poll(socket %d)\n", i);
if ((i >= sockets) || (sockets == 0))
s = get_socket_info_by_nr(i);
if (!s)
return POLLERR;
s = &socket_table[i];
user = file->private_data;
if (CHECK_USER(user))
return POLLERR;
......@@ -724,9 +749,9 @@ static int ds_ioctl(struct inode * inode, struct file * file,
DEBUG(2, "ds_ioctl(socket %d, %#x, %#lx)\n", i, cmd, arg);
if ((i >= sockets) || (sockets == 0))
s = get_socket_info_by_nr(i);
if (!s)
return -ENODEV;
s = &socket_table[i];
size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
if (size > sizeof(ds_ioctl_arg_t)) return -EINVAL;
......@@ -889,26 +914,18 @@ EXPORT_SYMBOL(unregister_pccard_driver);
/*====================================================================*/
struct bus_type pcmcia_bus_type = {
.name = "pcmcia",
};
EXPORT_SYMBOL(pcmcia_bus_type);
static int __init init_pcmcia_bus(void)
{
bus_register(&pcmcia_bus_type);
return 0;
}
static int __init init_pcmcia_ds(void)
static int __devinit pcmcia_bus_add_socket(struct device *dev, unsigned int socket_nr)
{
client_reg_t client_reg;
servinfo_t serv;
bind_req_t bind;
socket_info_t *s;
int i, ret;
socket_info_t *s, *tmp_s;
int ret;
int i;
DEBUG(0, "%s\n", version);
s = kmalloc(sizeof(socket_info_t), GFP_KERNEL);
if(!s)
return -ENOMEM;
memset(s, 0, sizeof(socket_info_t));
/*
* Ugly. But we want to wait for the socket threads to have started up.
......@@ -917,34 +934,41 @@ static int __init init_pcmcia_ds(void)
current->state = TASK_INTERRUPTIBLE;
schedule_timeout(HZ/4);
pcmcia_get_card_services_info(&serv);
if (serv.Revision != CS_RELEASE_CODE) {
printk(KERN_NOTICE "ds: Card Services release does not match!\n");
return -1;
init_waitqueue_head(&s->queue);
init_waitqueue_head(&s->request);
/* find the lowest, unused socket no. Please note that this is a
* temporary workaround until "struct pcmcia_socket" is introduced
* into cs.c which will include this number, and which will be
* accessible to ds.c directly */
i = 0;
next_try:
list_for_each_entry(tmp_s, &bus_socket_list, socket_list) {
if (tmp_s->socket_no == i) {
i++;
goto next_try;
}
if (serv.Count == 0) {
printk(KERN_NOTICE "ds: no socket drivers loaded!\n");
return -1;
}
s->socket_no = i;
sockets = serv.Count;
socket_table = kmalloc(sockets*sizeof(socket_info_t), GFP_KERNEL);
if (!socket_table) return -1;
for (i = 0, s = socket_table; i < sockets; i++, s++) {
s->state = 0;
s->user = NULL;
s->req_pending = 0;
init_waitqueue_head(&s->queue);
init_waitqueue_head(&s->request);
s->handle = NULL;
init_timer(&s->removal);
s->removal.data = i;
/* initialize data */
s->socket_dev = dev;
s->removal.data = s->socket_no;
s->removal.function = &handle_removal;
s->bind = NULL;
}
init_timer(&s->removal);
/* Set up hotline to Card Services */
client_reg.dev_info = bind.dev_info = &dev_info;
bind.Socket = s->socket_no;
bind.Function = BIND_FN_ALL;
ret = pcmcia_bind_device(&bind);
if (ret != CS_SUCCESS) {
cs_error(NULL, BindDevice, ret);
kfree(s);
return -EINVAL;
}
client_reg.Attributes = INFO_MASTER_CLIENT;
client_reg.EventMask =
CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL |
......@@ -953,22 +977,83 @@ static int __init init_pcmcia_ds(void)
CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME;
client_reg.event_handler = &ds_event;
client_reg.Version = 0x0210;
for (i = 0; i < sockets; i++) {
bind.Socket = i;
bind.Function = BIND_FN_ALL;
ret = pcmcia_bind_device(&bind);
if (ret != CS_SUCCESS) {
cs_error(NULL, BindDevice, ret);
break;
}
client_reg.event_callback_args.client_data = &socket_table[i];
ret = pcmcia_register_client(&socket_table[i].handle,
&client_reg);
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);
break;
kfree(s);
return -EINVAL;
}
list_add(&s->socket_list, &bus_socket_list);
return 0;
}
static int __devinit pcmcia_bus_add_socket_dev(struct device *dev)
{
struct pcmcia_socket_class_data *cls_d = dev->class_data;
unsigned int i;
unsigned int ret = 0;
if (!cls_d)
return -ENODEV;
down_write(&bus_socket_list_rwsem);
for (i = 0; i < cls_d->nsock; i++)
ret += pcmcia_bus_add_socket(dev, i);
up_write(&bus_socket_list_rwsem);
return ret;
}
static int __devexit pcmcia_bus_remove_socket_dev(struct device *dev)
{
struct pcmcia_socket_class_data *cls_d = dev->class_data;
struct list_head *list_loop;
struct list_head *tmp_storage;
if (!cls_d)
return -ENODEV;
down_write(&bus_socket_list_rwsem);
list_for_each_safe(list_loop, tmp_storage, &bus_socket_list) {
socket_info_t *bus_sock = container_of(list_loop, socket_info_t, socket_list);
if (bus_sock->socket_dev == dev) {
pcmcia_deregister_client(bus_sock->handle);
list_del(&bus_sock->socket_list);
kfree(bus_sock);
}
}
up_write(&bus_socket_list_rwsem);
return 0;
}
/* the pcmcia_bus_interface is used to handle pcmcia socket devices */
static struct device_interface pcmcia_bus_interface = {
.name = "pcmcia-bus",
.devclass = &pcmcia_socket_class,
.add_device = &pcmcia_bus_add_socket_dev,
.remove_device = __devexit_p(&pcmcia_bus_remove_socket_dev),
.kset = { .subsys = &pcmcia_socket_class.subsys, },
.devnum = 0,
};
struct bus_type pcmcia_bus_type = {
.name = "pcmcia",
};
EXPORT_SYMBOL(pcmcia_bus_type);
static int __init init_pcmcia_bus(void)
{
int i;
bus_register(&pcmcia_bus_type);
interface_register(&pcmcia_bus_interface);
/* Set up character device for user mode clients */
i = register_chrdev(0, "pcmcia", &ds_fops);
......@@ -981,48 +1066,46 @@ static int __init init_pcmcia_ds(void)
#ifdef CONFIG_PROC_FS
if (proc_pccard)
create_proc_read_entry("drivers",0,proc_pccard,proc_read_drivers,NULL);
init_status = 0;
#endif
return 0;
}
fs_initcall(init_pcmcia_bus); /* one level after subsys_initcall so that
* pcmcia_socket_class is already registered */
static void __exit exit_pcmcia_ds(void)
static void __exit exit_pcmcia_bus(void)
{
int i;
interface_unregister(&pcmcia_bus_interface);
#ifdef CONFIG_PROC_FS
if (proc_pccard)
remove_proc_entry("drivers", proc_pccard);
#endif
if (major_dev != -1)
unregister_chrdev(major_dev, "pcmcia");
for (i = 0; i < sockets; i++)
pcmcia_deregister_client(socket_table[i].handle);
sockets = 0;
kfree(socket_table);
bus_unregister(&pcmcia_bus_type);
}
module_exit(exit_pcmcia_bus);
#ifdef MODULE
/* init_pcmcia_bus must be done early, init_pcmcia_ds late. If we load this
* as a module, we can only specify one initcall, though...
*/
static int __init init_pcmcia_module(void) {
init_pcmcia_bus();
return init_pcmcia_ds();
}
module_init(init_pcmcia_module);
#else /* !MODULE */
subsys_initcall(init_pcmcia_bus);
late_initcall(init_pcmcia_ds);
#endif
module_exit(exit_pcmcia_ds);
/* helpers for backwards-compatible functions */
/* helpers for backwards-compatible functions */
static socket_info_t * get_socket_info_by_nr(unsigned int nr)
{
socket_info_t * s;
down_read(&bus_socket_list_rwsem);
list_for_each_entry(s, &bus_socket_list, socket_list)
if (s->socket_no == nr) {
up_read(&bus_socket_list_rwsem);
return s;
}
up_read(&bus_socket_list_rwsem);
return NULL;
}
/* backwards-compatible accessing of driver --- by name! */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment