Here's my latest patch against 2.5.67 which introduces a proper state machine into the PCMCIA layer for handling the sockets. Unfortunately, I fear that this isn't the answer for the following reasons: * We create our own workqueue (which spawns N threads, one thread per CPU.) We need to use a separate thread from the keventd since we call PCI probe and remove methods from this thread, which are free to use flush_scheduled_work() - which would be another deadlock waiting to happen. I think we need to go to a per-socket thread instead. * The state machine isn't as readable as it should be. To be quite frank, I think it was a mistake to code it as a state machine - IMO its completely unreadable. * We allow cardbus cards to be suspended and reset as though they are normal PCMCIA cards. Unfortunately, PCI drivers have no knowledge that these operations occur. This also applies to older kernels, so this isn't really a problem that's created by this patch. This is even more true now that we have the capability to plug in a complete (possibly complex) PCI bus structure. * There seems to be a whole bunch of setup stuff going on in pcmcia_register_client(). This is run each time a card device driver is inserted by cardmgr. Although this has buggy for the case where all drivers are built in, this patch makes it more buggy; if a card is inserted at the time ds.ko is loaded, we kick off the asynchronous state machine to process the card and carry on regardless. However, we can not wait here - if we do wait for the state machine to complete, we will hit the same deadlock in the device model which we're hitting today. It appears that it would mainly affect multi-function PCMCIA cards. Unfortunately, I don't have any to test. That said, it seems to work for me. The patch can be found at http://patches.arm.linux.org.uk/pcmcia/pcmcia-1.diff Now, thing is, I can't test this patch on its own; I can test it on ARM boxen with yenta cardbus bridges, or statically mapped PCMCIA-only sockets, but the former requires several other patches to the PCMCIA resource subsystem to be functional. Hence I need other peoples feedback on this patch before I push it Linus-wards. 25-akpm/drivers/pcmcia/cs.c | 827 ++++++++++++++++++++++++----------- 25-akpm/drivers/pcmcia/cs_internal.h | 18 25-akpm/drivers/pcmcia/rsrc_mgr.c | 14 3 files changed, 612 insertions(+), 247 deletions(-) diff -puN drivers/pcmcia/cs.c~pcmcia-deadlock-fix drivers/pcmcia/cs.c --- 25/drivers/pcmcia/cs.c~pcmcia-deadlock-fix Mon Apr 14 16:05:02 2003 +++ 25-akpm/drivers/pcmcia/cs.c Mon Apr 14 16:05:02 2003 @@ -82,6 +82,8 @@ #define OPTIONS PCI_OPT CB_OPT PM_OPT #endif +#define EVENT_DEBUG + static const char *release = "Linux Kernel Card Services " CS_RELEASE; static const char *options = "options: " OPTIONS; @@ -127,6 +129,19 @@ socket_state_t dead_socket = { socket_t sockets = 0; socket_info_t *socket_table[MAX_SOCK]; +/* + * Our private work queue. We need this because we will be running + * driver probe and remove functions from our workqueue. If we use + * keventd, and their remove function does a flush_scheduled_work(), + * we will deadlock. + * + * The unfortunate side effect of this is that we will have one + * pcmcia thread per CPU; unfortunately there doesn't seem to be a + * way of creating just one thread with the current workqueue + * implementation. + */ +static struct workqueue_struct *pcmcia_wq; + #ifdef CONFIG_PROC_FS struct proc_dir_entry *proc_pccard = NULL; #endif @@ -302,11 +317,10 @@ static int proc_read_clients(char *buf, ======================================================================*/ -static int setup_socket(socket_info_t *); static void shutdown_socket(socket_info_t *); -static void reset_socket(socket_info_t *); -static void unreset_socket(socket_info_t *); static void parse_events(void *info, u_int events); +static void sm_init(socket_info_t *s); +static void sm_exit(socket_info_t *s); #define to_class_data(dev) dev->class_data @@ -343,7 +357,8 @@ int pcmcia_register_socket(struct device s->cis_mem.speed = cis_speed; s->erase_busy.next = s->erase_busy.prev = &s->erase_busy; spin_lock_init(&s->lock); - + sm_init(s); + /* TBD: remove usage of socket_table, use class_for_each_dev instead */ for (j = 0; j < sockets; j++) if (socket_table[j] == NULL) break; @@ -406,8 +421,8 @@ void pcmcia_unregister_socket(struct dev remove_proc_entry(name, proc_pccard); } #endif - - shutdown_socket(s); + + sm_exit(s); release_cis_mem(s); while (s->clients) { client = s->clients; @@ -449,15 +464,6 @@ static void free_regions(memory_handle_t static int send_event(socket_info_t *s, event_t event, int priority); -/* - * Sleep for n_cs centiseconds (1 cs = 1/100th of a second) - */ -static void cs_sleep(unsigned int n_cs) -{ - current->state = TASK_INTERRUPTIBLE; - schedule_timeout( (n_cs * HZ + 99) / 100); -} - static void shutdown_socket(socket_info_t *s) { client_t **c; @@ -505,132 +511,9 @@ static void shutdown_socket(socket_info_ free_regions(&s->c_region); } /* shutdown_socket */ -/* - * Return zero if we think the card isn't actually present - */ -static int setup_socket(socket_info_t *s) -{ - int val, ret; - int setup_timeout = 100; - - /* Wait for "not pending" */ - for (;;) { - get_socket_status(s, &val); - if (!(val & SS_PENDING)) - break; - if (--setup_timeout) { - cs_sleep(10); - continue; - } - printk(KERN_NOTICE "cs: socket %p voltage interrogation" - " timed out\n", s); - ret = 0; - goto out; - } - - if (val & SS_DETECT) { - DEBUG(1, "cs: setup_socket(%p): applying power\n", s); - s->state |= SOCKET_PRESENT; - s->socket.flags &= SS_DEBOUNCED; - if (val & SS_3VCARD) - s->socket.Vcc = s->socket.Vpp = 33; - else if (!(val & SS_XVCARD)) - s->socket.Vcc = s->socket.Vpp = 50; - else { - printk(KERN_NOTICE "cs: socket %p: unsupported " - "voltage key\n", s); - s->socket.Vcc = 0; - } - if (val & SS_CARDBUS) { - s->state |= SOCKET_CARDBUS; -#ifndef CONFIG_CARDBUS - printk(KERN_NOTICE "cs: unsupported card type detected!\n"); -#endif - } - set_socket(s, &s->socket); - cs_sleep(vcc_settle); - reset_socket(s); - ret = 1; - } else { - DEBUG(0, "cs: setup_socket(%p): no card!\n", s); - ret = 0; - } -out: - return ret; -} /* setup_socket */ - -/*====================================================================== - - Reset_socket() and unreset_socket() handle hard resets. Resets - have several causes: card insertion, a call to reset_socket, or - recovery from a suspend/resume cycle. Unreset_socket() sends - a CS event that matches the cause of the reset. - -======================================================================*/ - -static void reset_socket(socket_info_t *s) -{ - DEBUG(1, "cs: resetting socket %p\n", s); - s->socket.flags |= SS_OUTPUT_ENA | SS_RESET; - set_socket(s, &s->socket); - udelay((long)reset_time); - s->socket.flags &= ~SS_RESET; - set_socket(s, &s->socket); - cs_sleep(unreset_delay); - unreset_socket(s); -} /* reset_socket */ - #define EVENT_MASK \ (SOCKET_SETUP_PENDING|SOCKET_SUSPEND|SOCKET_RESET_PENDING) -static void unreset_socket(socket_info_t *s) -{ - int setup_timeout = unreset_limit; - int val; - - /* Wait for "ready" */ - for (;;) { - get_socket_status(s, &val); - if (val & SS_READY) - break; - DEBUG(2, "cs: socket %d not ready yet\n", s->sock); - if (--setup_timeout) { - cs_sleep(unreset_check); - continue; - } - printk(KERN_NOTICE "cs: socket %p timed out during" - " reset. Try increasing setup_delay.\n", s); - s->state &= ~EVENT_MASK; - return; - } - - DEBUG(1, "cs: reset done on socket %p\n", s); - if (s->state & SOCKET_SUSPEND) { - s->state &= ~EVENT_MASK; - if (verify_cis_cache(s) != 0) - parse_events(s, SS_DETECT); - else - send_event(s, CS_EVENT_PM_RESUME, CS_EVENT_PRI_LOW); - } else if (s->state & SOCKET_SETUP_PENDING) { -#ifdef CONFIG_CARDBUS - if (s->state & SOCKET_CARDBUS) { - cb_alloc(s); - s->state |= SOCKET_CARDBUS_CONFIG; - } -#endif - send_event(s, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW); - s->state &= ~SOCKET_SETUP_PENDING; - } else { - send_event(s, CS_EVENT_CARD_RESET, CS_EVENT_PRI_LOW); - if (s->reset_handle) { - s->reset_handle->event_callback_args.info = NULL; - EVENT(s->reset_handle, CS_EVENT_RESET_COMPLETE, - CS_EVENT_PRI_LOW); - } - s->state &= ~EVENT_MASK; - } -} /* unreset_socket */ - /*====================================================================== The central event handler. Send_event() sends an event to all @@ -661,62 +544,556 @@ static int send_event(socket_info_t *s, return ret; } /* send_event */ -static void do_shutdown(socket_info_t *s) +/* + * Event mask. + */ +#define EV_TIMER (1<<0) +#define EV_DETECT (1<<1) +#define EV_READY (1<<2) +#define EV_SUSPEND (1<<3) +#define EV_RESUME (1<<4) +#define EV_RESOURCES (1<<5) +#define EV_RESET (1<<6) +#define EV_EJECT (1<<7) +#define EV_INSERT (1<<8) +#define EV_FORCE (1<<31) + +static void state_error(socket_info_t *s, unsigned int status, unsigned int events); +static void state_empty(socket_info_t *s, unsigned int status, unsigned int events); +static void state_setup(socket_info_t *s, unsigned int status, unsigned int events); +static void state_reset(socket_info_t *s, unsigned int status, unsigned int events); +static void state_wait_ready(socket_info_t *s, unsigned int status, unsigned int events); +static void state_cardpresent(socket_info_t *s, unsigned int status, unsigned int events); +static void state_suspend(socket_info_t *s, unsigned int status, unsigned int events); +static void state_removed(socket_info_t *s, unsigned int status, unsigned int events); +static void state_shutdown(socket_info_t *s, unsigned int status, unsigned int events); + +static inline void __sm_event_queue(socket_info_t *s, unsigned int event) { - client_t *client; - if (s->state & SOCKET_SHUTDOWN_PENDING) - return; - s->state |= SOCKET_SHUTDOWN_PENDING; - send_event(s, CS_EVENT_CARD_REMOVAL, CS_EVENT_PRI_HIGH); - for (client = s->clients; client; client = client->next) - if (!(client->Attributes & INFO_MASTER_CLIENT)) - client->state |= CLIENT_STALE; - if (s->state & (SOCKET_SETUP_PENDING|SOCKET_RESET_PENDING)) { - DEBUG(0, "cs: flushing pending setup\n"); - s->state &= ~EVENT_MASK; - } - cs_sleep(shutdown_delay); - s->state &= ~SOCKET_PRESENT; - shutdown_socket(s); + unsigned long flags; + + spin_lock_irqsave(&s->sm.lock, flags); + s->sm.event_pending |= event; + spin_unlock_irqrestore(&s->sm.lock, flags); } -static void parse_events(void *info, u_int events) +static inline void sm_event_clear(socket_info_t *s, unsigned int event) +{ + unsigned long flags; + + spin_lock_irqsave(&s->sm.lock, flags); + s->sm.event_pending &= ~event; + spin_unlock_irqrestore(&s->sm.lock, flags); +} + +static void sm_event_ack(socket_info_t *s, unsigned int events) +{ + unsigned long flags; + + if (events & EV_RESET) + s->state &= ~SOCKET_RESET_PENDING; + if (events & EV_DETECT) + s->state &= ~(SOCKET_SETUP_PENDING|SOCKET_RESET_PENDING); + + spin_lock_irqsave(&s->sm.lock, flags); + s->sm.event_ack |= events; + s->sm.event_pending &= ~events; + spin_unlock_irqrestore(&s->sm.lock, flags); + + if (events) + wake_up(&s->sm.wait_ack); +} + +static inline void sm_set_timer(socket_info_t *s, unsigned int cs) { - socket_info_t *s = info; - if (events & SS_DETECT) { - int status; - - get_socket_status(s, &status); - if ((s->state & SOCKET_PRESENT) && - (!(s->state & SOCKET_SUSPEND) || - !(status & SS_DETECT))) - do_shutdown(s); - if (status & SS_DETECT) { - if (s->state & SOCKET_SETUP_PENDING) { - DEBUG(1, "cs: delaying pending setup\n"); + sm_event_clear(s, EV_TIMER); + s->sm.event_mask |= EV_TIMER; + mod_timer(&s->sm.timer, jiffies + ((cs * HZ + 99) / 100)); +} + +/* + * Move to the next state. + */ +static void +sm_next(socket_info_t *s, + void (*fn)(socket_info_t *,unsigned int,unsigned int), + unsigned int mask) +{ + s->sm.event_mask = mask | EV_FORCE; + s->sm.func = fn; + __sm_event_queue(s, EV_FORCE); +} + + +static void sm_error(socket_info_t *s, const char *fmt, ...) +{ + static char buf[128]; + va_list ap; + int len; + + va_start(ap, fmt); + len = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + buf[len] = '\0'; + + printk(KERN_ERR "PCMCIA: socket %p: %s", s, buf); + + sm_event_clear(s, EV_DETECT|EV_READY|EV_RESET); + sm_next(s, state_error, 0); +} + +/* + * We hit an error. Stop the state machine, but respond to + * suspend, resume, reset and resources events. + */ +static void state_error(socket_info_t *s, unsigned int status, unsigned int events) +{ + /* + * We hit an error. Ensure that the socket is off. + */ + s->socket.flags &= ~(SS_DEBOUNCED|SS_OUTPUT_ENA|SS_RESET|SS_IOCARD); + s->socket.Vcc = 0; + s->socket.Vpp = 0; + set_socket(s, &s->socket); + s->state &= ~SOCKET_SUSPEND; + + s->sm.event_mask = EV_RESET|EV_SUSPEND|EV_RESUME|EV_INSERT|EV_EJECT|EV_DETECT|EV_RESOURCES; + + /* + * If we received a reset event from the user, re-detect + * the card. This will automatically reset the card. + */ + if (events & (EV_RESET|EV_RESOURCES|EV_EJECT)) { + s->sm.event_mask = EV_DETECT|EV_INSERT|EV_SUSPEND; + s->sm.func = state_empty; + __sm_event_queue(s, EV_DETECT); + } + + sm_event_ack(s, events & (EV_SUSPEND|EV_RESUME|EV_INSERT|EV_DETECT)); +} + +/* + * The socket is empty. If a card is inserted (or we receive an + * insert request,) setup and reset the socket. If we receive a + * suspend, go to the suspend state and acknowledge the suspend. + */ +static void +state_empty(socket_info_t *s, unsigned int status, unsigned int events) +{ + if (events & (EV_DETECT|EV_INSERT) && status & SS_DETECT) { + s->socket.flags = SS_DEBOUNCED; + s->sm.tries = 100; + s->sm.func = state_setup; + s->sm.event_mask = 0; + + s->state |= SOCKET_SETUP_PENDING; + + sm_set_timer(s, setup_delay); return; - } - s->state |= SOCKET_SETUP_PENDING; - if (s->state & SOCKET_SUSPEND) - cs_sleep(resume_delay); - else - cs_sleep(setup_delay); - s->socket.flags |= SS_DEBOUNCED; - if (setup_socket(s) == 0) - s->state &= ~SOCKET_SETUP_PENDING; - s->socket.flags &= ~SS_DEBOUNCED; - } - } - if (events & SS_BATDEAD) - send_event(s, CS_EVENT_BATTERY_DEAD, CS_EVENT_PRI_LOW); - if (events & SS_BATWARN) - send_event(s, CS_EVENT_BATTERY_LOW, CS_EVENT_PRI_LOW); - if (events & SS_READY) { - if (!(s->state & SOCKET_RESET_PENDING)) - send_event(s, CS_EVENT_READY_CHANGE, CS_EVENT_PRI_LOW); - else DEBUG(1, "cs: ready change during reset\n"); - } -} /* parse_events */ + } + + if (events & EV_SUSPEND) { + s->sm.func = state_suspend; + s->sm.event_mask = EV_RESUME; + } + + /* Acknowledge all other events. */ + sm_event_ack(s, events); +} + +/* + * The setup/resume delay has expired. Wait for the socket hardware to + * debounce the voltage sense/card type sense, claim a resource for the + * CIS, and apply power to the card. Generally, we receive timer events, + * unless we are unable to grab resources for CIS. + */ +static void +state_setup(socket_info_t *s, unsigned int status, unsigned int events) +{ + int ret; + + if (events & EV_RESET) + sm_event_ack(s, EV_RESET); + + if (!(status & SS_DETECT) || events & EV_EJECT) { + sm_next(s, state_removed, EV_DETECT|EV_INSERT); + return; + } + + if (events & EV_SUSPEND) { + sm_next(s, state_suspend, EV_SUSPEND); + return; + } + + /* + * Some sockets debounce the voltage sense themselves. Wait for + * the debounce period to end, indicated by SS_PENDING clear. + */ + if (status & SS_PENDING) { + if (events & EV_TIMER) { + if (--s->sm.tries) { + sm_set_timer(s, 10); + } else { + sm_error(s, "voltage interrogation timed out\n"); + } + } + return; + } + + if (status & SS_CARDBUS) { + s->state |= SOCKET_CARDBUS; +#ifndef CONFIG_CARDBUS + sm_error(s, "cardbus cards are not supported\n"); + return; +#endif + } + + s->state |= SOCKET_PRESENT; + + /* + * Decode the card voltage requirements, and apply power to the card. + */ + if (status & SS_3VCARD) + s->socket.Vcc = s->socket.Vpp = 33; + else if (!(status & SS_XVCARD)) + s->socket.Vcc = s->socket.Vpp = 50; + else { + sm_error(s, "unsupported voltage key\n"); + return; + } + + set_socket(s, &s->socket); + + /* + * Wait "vcc_settle" for the supply to stabilise, and reset + * the card. Note that we only want to see timer events. + */ + s->sm.event_mask = 0; + s->sm.func = state_reset; + sm_set_timer(s, vcc_settle); +} + +/* + * Enable the socket tri-state drivers, and reset the card. + * Note that we also clear any pending reset events. + */ +static void state_reset(socket_info_t *s, unsigned int status, unsigned int events) +{ + if (!(status & SS_DETECT)) { + sm_next(s, state_removed, 0); + return; + } + + sm_event_clear(s, EV_READY); + + s->socket.flags |= SS_OUTPUT_ENA | SS_RESET; + set_socket(s, &s->socket); + udelay((long)reset_time); + + s->socket.flags &= ~SS_RESET; + set_socket(s, &s->socket); + + s->sm.tries = unreset_limit; + s->sm.func = state_wait_ready; + sm_set_timer(s, unreset_delay); +} + +/* + * Wait for the card to signal that it is ready for us to read things + * like the CIS. We also allow the user to request the card to be + * reset. We only deal with eject, reset and ready events here. + */ +static void state_wait_ready(socket_info_t *s, unsigned int status, unsigned int events) +{ + if (!(status & SS_DETECT) || events & EV_EJECT) { + sm_next(s, state_removed, 0); + return; + } + + if (!(status & SS_READY) && events & EV_TIMER) { + if (--s->sm.tries) { + s->sm.event_mask = EV_EJECT|EV_READY; + sm_set_timer(s, unreset_check); + } else { + s->state &= ~EVENT_MASK; + sm_error(s, "timed out during reset. Try increasing setup_delay.\n"); + } + return; + } + + s->sm.event_mask = EV_EJECT|EV_SUSPEND|EV_RESET|EV_READY|EV_DETECT; + s->sm.func = state_cardpresent; + + if (s->state & SOCKET_SUSPEND) { + s->state &= ~(SOCKET_SUSPEND|SOCKET_SETUP_PENDING); + if (verify_cis_cache(s) != 0) { + __sm_event_queue(s, EV_DETECT); + sm_next(s, state_removed, 0); + } else + send_event(s, CS_EVENT_PM_RESUME, CS_EVENT_PRI_LOW); + events = EV_RESUME|EV_INSERT; + } else if (s->state & SOCKET_SETUP_PENDING) { +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) { + cb_alloc(s); + s->state |= SOCKET_CARDBUS_CONFIG; + } +#endif + send_event(s, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW); + events = EV_DETECT|EV_INSERT; + } else if (s->state & SOCKET_RESET_PENDING) { + send_event(s, CS_EVENT_CARD_RESET, CS_EVENT_PRI_LOW); + if (s->reset_handle) { + s->reset_handle->event_callback_args.info = NULL; + EVENT(s->reset_handle, CS_EVENT_RESET_COMPLETE, + CS_EVENT_PRI_LOW); + } + events = EV_RESET; + } + + sm_event_ack(s, events|EV_READY); +} + +/* + * The card is present, and has indicated that it is ready. We're + * ready to announce the device to the world. + */ +static void state_cardpresent(socket_info_t *s, unsigned int status, unsigned int events) +{ + if (!(status & SS_DETECT) || events & EV_EJECT) { + sm_next(s, state_removed, 0); + return; + } + + if (events & EV_SUSPEND) { + sm_next(s, state_suspend, EV_SUSPEND); + return; + } + + if (events & EV_RESET) { + sm_next(s, state_reset, EV_RESET); + return; + } + + if (events & EV_READY) + send_event(s, CS_EVENT_READY_CHANGE, CS_EVENT_PRI_LOW); + + sm_event_ack(s, events); +} + +/* + * The socket was suspended. Handle resume and eject events. + * Clear any pending detect events on resume. + * + * Handles: EV_SUSPEND|EV_EJECT|EV_RESUME + */ +static void state_suspend(socket_info_t *s, unsigned int status, unsigned int events) +{ + if (events & EV_SUSPEND) { + send_event(s, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW); + suspend_socket(s); + s->state |= SOCKET_SUSPEND; + s->sm.event_mask = EV_RESUME|EV_EJECT; + sm_event_ack(s, EV_DETECT|EV_SUSPEND); + } + + if (events & EV_EJECT) { + sm_next(s, state_removed, 0); + } else if (events & EV_RESUME) { + /* Do this just to reinitialize the socket */ + init_socket(s); + if (status & SS_DETECT) { + s->socket.flags = SS_DEBOUNCED; + s->sm.tries = 100; + s->sm.func = state_setup; + s->sm.event_mask = 0; + sm_set_timer(s, resume_delay); + sm_event_ack(s, EV_DETECT); + } else if (s->state & SOCKET_PRESENT) { + s->sm.func = state_removed; + } else { + s->sm.func = state_empty; + s->sm.event_mask = EV_DETECT|EV_RESET|EV_SUSPEND; + sm_event_ack(s, EV_RESUME|EV_DETECT); + } + } +} + +/* + * We come here when the socket becomes empty. + * Which events are acknowledged depends on the event mask. + */ +static void state_removed(socket_info_t *s, unsigned int status, unsigned int events) +{ + client_t *client; + + sm_event_ack(s, events); + + send_event(s, CS_EVENT_CARD_REMOVAL, CS_EVENT_PRI_HIGH); + for (client = s->clients; client; client = client->next) + if (!(client->Attributes & INFO_MASTER_CLIENT)) + client->state |= CLIENT_STALE; + + if (s->state & (SOCKET_SETUP_PENDING|SOCKET_RESET_PENDING)) { + DEBUG(0, "cs: flushing pending setup\n"); + s->state &= ~EVENT_MASK; + } + + s->sm.func = state_shutdown; + s->sm.event_mask = 0; + sm_set_timer(s, shutdown_delay); +} + +/* + * Complete socket shutdown. + * + * Handles: EV_TIMER + */ +static void state_shutdown(socket_info_t *s, unsigned int status, unsigned int events) +{ + s->state &= ~SOCKET_PRESENT; + shutdown_socket(s); + + s->sm.func = state_empty; + s->sm.event_mask = EV_DETECT|EV_RESET|EV_SUSPEND|EV_INSERT; + + sm_event_ack(s, EV_EJECT); +} + +static void sm_do_work(void *data) +{ + socket_info_t *s = data; + unsigned int events; + + do { + int status; + + /* + * Get pending events, and then read the status. + * Always clear the timer. + */ + spin_lock_irq(&s->sm.lock); + events = s->sm.event_pending & s->sm.event_mask; + spin_unlock_irq(&s->sm.lock); + + if (events == 0) + break; + + get_socket_status(s, &status); + +#ifdef EVENT_DEBUG + printk("cs: sock %p old smfunc %p mask 0x%08x pending 0x%08x " + "sockstate 0x%08x status 0x%08x delivering 0x%08x\n", + s, s->sm.func, s->sm.event_mask, s->sm.event_pending, + s->state, status, events); +#endif + + if (events) + s->sm.func(s, status, events); + +#ifdef EVENT_DEBUG + printk("cs: sock %p new smfunc %p mask 0x%08x pending 0x%08x " + "sockstate 0x%08x\n", + s, s->sm.func, s->sm.event_mask, s->sm.event_pending, + s->state); +#endif + } while (events); +} + +static void sm_event_queue(socket_info_t *s, unsigned int event) +{ + __sm_event_queue(s, event); + if (event & s->sm.event_mask) + queue_work(pcmcia_wq, &s->sm.work); +} + +static void sm_timer_expire(unsigned long data) +{ + socket_info_t *s = (socket_info_t *)data; + sm_event_queue(s, EV_TIMER); +} + +static void sm_init(socket_info_t *s) +{ + s->sm.event_mask = EV_DETECT|EV_RESET|EV_SUSPEND; + s->sm.func = state_empty; + + spin_lock_init(&s->sm.lock); + + init_timer(&s->sm.timer); + s->sm.timer.data = (unsigned long)s; + s->sm.timer.function = sm_timer_expire; + + init_waitqueue_head(&s->sm.wait_ack); + + INIT_WORK(&s->sm.work, sm_do_work, s); +} + +static void sm_exit(socket_info_t *s) +{ + del_timer_sync(&s->sm.timer); + flush_scheduled_work(); + shutdown_socket(s); +} + +/** + * sm_event_wait - queue an event and wait for acknowledgement + * @s: socket info structure describing socket + * @event: event to queue. + * + * Queue an event for the CS state machine to process, and wait + * for the state machine to acknowledge. + * + * Restrictions: If this function is called from the kernel + * event thread, we will deadlock. + */ +static void sm_event_wait(socket_info_t *s, unsigned int event) +{ +#if 0 + int is_keventd = current_is_keventd(); + WARN_ON(is_keventd); + if (is_keventd) + return; +#endif + + s->sm.event_ack &= ~event; + sm_event_queue(s, event); + wait_event(s->sm.wait_ack, s->sm.event_ack & event); +} + +void pcmcia_resources_changed(void) +{ + int i; + + for (i = 0; i < sockets; i++) { + socket_info_t *s = socket_table[i]; + if (!s) + continue; + sm_event_queue(s, EV_RESOURCES); +#ifdef CONFIG_CARDBUS + cb_release_cis_mem(s); +#endif + } +} + +static void parse_events(void *info, u_int events) +{ + socket_info_t *s = info; + unsigned int mask = 0; + + if (events & SS_DETECT) + mask |= EV_DETECT; + if (events & SS_READY) + mask |= EV_READY; + + if (mask) + sm_event_queue(s, mask); + + if (events & SS_BATDEAD) + send_event(s, CS_EVENT_BATTERY_DEAD, CS_EVENT_PRI_LOW); + if (events & SS_BATWARN) + send_event(s, CS_EVENT_BATTERY_LOW, CS_EVENT_PRI_LOW); +} /*====================================================================== @@ -729,25 +1106,12 @@ static void parse_events(void *info, u_i void pcmcia_suspend_socket (socket_info_t *s) { - if ((s->state & SOCKET_PRESENT) && !(s->state & SOCKET_SUSPEND)) { - send_event(s, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW); - suspend_socket(s); - s->state |= SOCKET_SUSPEND; - } + sm_event_wait(s, EV_SUSPEND); } void pcmcia_resume_socket (socket_info_t *s) { - int stat; - - /* Do this just to reinitialize the socket */ - init_socket(s); - get_socket_status(s, &stat); - - /* If there was or is a card here, we need to do something - about it... but parse_events will sort it all out. */ - if ((s->state & SOCKET_PRESENT) || (stat & SS_DETECT)) - parse_events(s, SS_DETECT); + sm_event_wait(s, EV_RESUME); } @@ -1446,6 +1810,7 @@ int pcmcia_register_client(client_handle client_t *client; socket_info_t *s; socket_t ns; + int nr_clients; /* Look for unbound client with matching dev_info */ client = NULL; @@ -1462,17 +1827,7 @@ int pcmcia_register_client(client_handle return CS_OUT_OF_RESOURCE; s = socket_table[ns]; - if (++s->real_clients == 1) { - int status; - register_callback(s, &parse_events, s); - get_socket_status(s, &status); - if ((status & SS_DETECT) && - !(s->state & SOCKET_SETUP_PENDING)) { - s->state |= SOCKET_SETUP_PENDING; - if (setup_socket(s) == 0) - s->state &= ~SOCKET_SETUP_PENDING; - } - } + nr_clients = ++s->real_clients; *handle = client; client->state &= ~CLIENT_UNBOUND; @@ -1512,6 +1867,10 @@ int pcmcia_register_client(client_handle else client->PendingEvents |= CS_EVENT_CARD_INSERTION; } + if (nr_clients == 1) { + register_callback(s, &parse_events, s); + sm_event_queue(s, EV_DETECT); + } return CS_SUCCESS; } /* register_client */ @@ -2044,8 +2403,10 @@ int pcmcia_reset_card(client_handle_t ha } else { DEBUG(1, "cs: resetting socket %d\n", i); send_event(s, CS_EVENT_RESET_PHYSICAL, CS_EVENT_PRI_LOW); + s->reset_handle = handle; - reset_socket(s); + + sm_event_wait(s, EV_RESET); } return CS_SUCCESS; } /* reset_card */ @@ -2071,9 +2432,7 @@ int pcmcia_suspend_card(client_handle_t return CS_IN_USE; DEBUG(1, "cs: suspending socket %d\n", i); - send_event(s, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW); - suspend_socket(s); - s->state |= SOCKET_SUSPEND; + sm_event_wait(s, EV_SUSPEND); return CS_SUCCESS; } /* suspend_card */ @@ -2092,7 +2451,7 @@ int pcmcia_resume_card(client_handle_t h return CS_IN_USE; DEBUG(1, "cs: waking up socket %d\n", i); - setup_socket(s); + sm_event_wait(s, EV_RESUME); return CS_SUCCESS; } /* resume_card */ @@ -2107,7 +2466,6 @@ int pcmcia_eject_card(client_handle_t ha { int i, ret; socket_info_t *s; - u_long flags; if (CHECK_HANDLE(handle)) return CS_BAD_HANDLE; @@ -2121,9 +2479,7 @@ int pcmcia_eject_card(client_handle_t ha if (ret != 0) return ret; - spin_lock_irqsave(&s->lock, flags); - do_shutdown(s); - spin_unlock_irqrestore(&s->lock, flags); + sm_event_wait(s, EV_EJECT); return CS_SUCCESS; @@ -2131,9 +2487,8 @@ int pcmcia_eject_card(client_handle_t ha int pcmcia_insert_card(client_handle_t handle, client_req_t *req) { - int i, status; + int i; socket_info_t *s; - u_long flags; if (CHECK_HANDLE(handle)) return CS_BAD_HANDLE; @@ -2143,17 +2498,9 @@ int pcmcia_insert_card(client_handle_t h DEBUG(1, "cs: user insert request on socket %d\n", i); - spin_lock_irqsave(&s->lock, flags); - if (!(s->state & SOCKET_SETUP_PENDING)) { - s->state |= SOCKET_SETUP_PENDING; - spin_unlock_irqrestore(&s->lock, flags); - get_socket_status(s, &status); - if ((status & SS_DETECT) == 0 || (setup_socket(s) == 0)) { - s->state &= ~SOCKET_SETUP_PENDING; - return CS_NO_CARD; - } - } else - spin_unlock_irqrestore(&s->lock, flags); + sm_event_wait(s, EV_INSERT); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; return CS_SUCCESS; } /* insert_card */ @@ -2433,6 +2780,10 @@ EXPORT_SYMBOL(pcmcia_socket_class); static int __init init_pcmcia_cs(void) { + pcmcia_wq = create_workqueue("pcmcia"); + if (!pcmcia_wq) + return -ENOMEM; + printk(KERN_INFO "%s\n", release); printk(KERN_INFO " %s\n", options); DEBUG(0, "%s\n", version); @@ -2454,6 +2805,8 @@ static void __exit exit_pcmcia_cs(void) #endif release_resource_db(); devclass_unregister(&pcmcia_socket_class); + + destroy_workqueue(pcmcia_wq); } subsys_initcall(init_pcmcia_cs); diff -puN drivers/pcmcia/cs_internal.h~pcmcia-deadlock-fix drivers/pcmcia/cs_internal.h --- 25/drivers/pcmcia/cs_internal.h~pcmcia-deadlock-fix Mon Apr 14 16:05:02 2003 +++ 25-akpm/drivers/pcmcia/cs_internal.h Mon Apr 14 16:05:02 2003 @@ -118,9 +118,24 @@ typedef struct config_t { #define MAX_CIS_TABLE 64 #define MAX_CIS_DATA 512 +struct socket_info_t; + +struct sm_state { + void (*func)(struct socket_info_t *, unsigned int, unsigned int); + spinlock_t lock; + unsigned int event_pending; + unsigned int event_mask; + unsigned int event_ack; + unsigned int tries; + wait_queue_head_t wait_ack; + struct timer_list timer; + struct work_struct work; +}; + typedef struct socket_info_t { spinlock_t lock; struct pccard_operations * ss_entry; + struct sm_state sm; u_int sock; socket_state_t socket; socket_cap_t cap; @@ -216,6 +231,9 @@ int validate_cis(client_handle_t handle, int replace_cis(client_handle_t handle, cisdump_t *cis); int read_tuple(client_handle_t handle, cisdata_t code, void *parse); +/* In cs.c */ +void pcmcia_resources_changed(void); + /* In bulkmem.c */ void retry_erase_list(struct erase_busy_t *list, u_int cause); int get_first_region(client_handle_t handle, region_info_t *rgn); diff -puN drivers/pcmcia/rsrc_mgr.c~pcmcia-deadlock-fix drivers/pcmcia/rsrc_mgr.c --- 25/drivers/pcmcia/rsrc_mgr.c~pcmcia-deadlock-fix Mon Apr 14 16:05:02 2003 +++ 25-akpm/drivers/pcmcia/rsrc_mgr.c Mon Apr 14 16:05:02 2003 @@ -724,7 +724,7 @@ void undo_irq(u_int Attributes, int irq) static int adjust_memory(adjust_t *adj) { u_long base, num; - int i, ret; + int ret; base = adj->resource.memory.Base; num = adj->resource.memory.Size; @@ -740,20 +740,14 @@ static int adjust_memory(adjust_t *adj) break; case REMOVE_MANAGED_RESOURCE: ret = sub_interval(&mem_db, base, num); - if (ret == CS_SUCCESS) { - for (i = 0; i < sockets; i++) { - release_cis_mem(socket_table[i]); -#ifdef CONFIG_CARDBUS - cb_release_cis_mem(socket_table[i]); -#endif - } - } break; default: ret = CS_UNSUPPORTED_FUNCTION; } up(&rsrc_sem); - + if (ret == CS_SUCCESS) + pcmcia_resources_changed(); + return ret; } _