patch-2.3.17 linux/drivers/pcmcia/cs.c
Next file: linux/drivers/pcmcia/cs_internal.h
Previous file: linux/drivers/pcmcia/cistpl.c
Back to the patch index
Back to the overall index
- Lines: 2257
- Date:
Tue Sep 7 11:03:10 1999
- Orig file:
v2.3.16/linux/drivers/pcmcia/cs.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.3.16/linux/drivers/pcmcia/cs.c linux/drivers/pcmcia/cs.c
@@ -0,0 +1,2256 @@
+/*======================================================================
+
+ PCMCIA Card Services -- core services
+
+ cs.c 1.225 1999/09/07 15:19:32
+
+ 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
+ <dhinds@hyper.stanford.edu>. 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 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.
+
+======================================================================*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/major.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <asm/system.h>
+#include <asm/irq.h>
+
+#define IN_CARD_SERVICES
+#include <pcmcia/version.h>
+#include <pcmcia/cs_types.h>
+#include <pcmcia/ss.h>
+#include <pcmcia/cs.h>
+#include <pcmcia/bulkmem.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/bus_ops.h>
+#include "cs_internal.h"
+#include "rsrc_mgr.h"
+
+#ifdef CONFIG_APM
+#include <linux/apm_bios.h>
+static int handle_apm_event(apm_event_t event);
+#endif
+
+#ifdef PCMCIA_DEBUG
+int pc_debug = PCMCIA_DEBUG;
+MODULE_PARM(pc_debug, "i");
+static const char *version =
+"cs.c 1.225 1999/09/07 15:19:32 (David Hinds)";
+#endif
+
+static const char *release = "Linux PCMCIA Card Services " CS_RELEASE;
+
+static const char *options = "options: "
+#ifdef CONFIG_PCI
+" [pci]"
+#endif
+#ifdef CONFIG_CARDBUS
+" [cardbus]"
+#endif
+#ifdef CONFIG_APM
+" [apm]"
+#endif
+#if !defined(CONFIG_CARDBUS) && !defined(CONFIG_PCI) && !defined(CONFIG_APM)
+" none"
+#endif
+;
+
+/*====================================================================*/
+
+/* Parameters that can be set with 'insmod' */
+
+static int setup_delay = HZ/20; /* ticks */
+static int resume_delay = HZ/5; /* ticks */
+static int shutdown_delay = HZ/40; /* ticks */
+static int vcc_settle = HZ*3/10; /* ticks */
+static int reset_time = 10; /* usecs */
+static int unreset_delay = HZ/10; /* ticks */
+static int unreset_check = HZ/10; /* ticks */
+static int unreset_limit = 30; /* unreset_check's */
+
+/* Access speed for attribute memory windows */
+static int cis_speed = 300; /* ns */
+
+/* Access speed for IO windows */
+static int io_speed = 0; /* ns */
+
+/* Optional features */
+#ifdef CONFIG_APM
+static int do_apm = 1;
+MODULE_PARM(do_apm, "i");
+#endif
+
+MODULE_PARM(setup_delay, "i");
+MODULE_PARM(resume_delay, "i");
+MODULE_PARM(shutdown_delay, "i");
+MODULE_PARM(vcc_settle, "i");
+MODULE_PARM(reset_time, "i");
+MODULE_PARM(unreset_delay, "i");
+MODULE_PARM(unreset_check, "i");
+MODULE_PARM(unreset_limit, "i");
+MODULE_PARM(cis_speed, "i");
+MODULE_PARM(io_speed, "i");
+
+/*====================================================================*/
+
+static socket_state_t dead_socket = {
+ 0, SS_DETECT, 0, 0, 0
+};
+
+/* Table of sockets */
+socket_t sockets = 0;
+socket_info_t *socket_table[MAX_SOCK];
+
+#ifdef CONFIG_PROC_FS
+struct proc_dir_entry *proc_pccard = NULL;
+#endif
+
+/*====================================================================*/
+
+/* String tables for error messages */
+
+typedef struct lookup_t {
+ int key;
+ char *msg;
+} lookup_t;
+
+static const lookup_t error_table[] = {
+ { CS_SUCCESS, "Operation succeeded" },
+ { CS_BAD_ADAPTER, "Bad adapter" },
+ { CS_BAD_ATTRIBUTE, "Bad attribute", },
+ { CS_BAD_BASE, "Bad base address" },
+ { CS_BAD_EDC, "Bad EDC" },
+ { CS_BAD_IRQ, "Bad IRQ" },
+ { CS_BAD_OFFSET, "Bad offset" },
+ { CS_BAD_PAGE, "Bad page number" },
+ { CS_READ_FAILURE, "Read failure" },
+ { CS_BAD_SIZE, "Bad size" },
+ { CS_BAD_SOCKET, "Bad socket" },
+ { CS_BAD_TYPE, "Bad type" },
+ { CS_BAD_VCC, "Bad Vcc" },
+ { CS_BAD_VPP, "Bad Vpp" },
+ { CS_BAD_WINDOW, "Bad window" },
+ { CS_WRITE_FAILURE, "Write failure" },
+ { CS_NO_CARD, "No card present" },
+ { CS_UNSUPPORTED_FUNCTION, "Usupported function" },
+ { CS_UNSUPPORTED_MODE, "Unsupported mode" },
+ { CS_BAD_SPEED, "Bad speed" },
+ { CS_BUSY, "Resource busy" },
+ { CS_GENERAL_FAILURE, "General failure" },
+ { CS_WRITE_PROTECTED, "Write protected" },
+ { CS_BAD_ARG_LENGTH, "Bad argument length" },
+ { CS_BAD_ARGS, "Bad arguments" },
+ { CS_CONFIGURATION_LOCKED, "Configuration locked" },
+ { CS_IN_USE, "Resource in use" },
+ { CS_NO_MORE_ITEMS, "No more items" },
+ { CS_OUT_OF_RESOURCE, "Out of resource" },
+ { CS_BAD_HANDLE, "Bad handle" },
+ { CS_BAD_TUPLE, "Bad CIS tuple" }
+};
+#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t))
+
+static const lookup_t service_table[] = {
+ { AccessConfigurationRegister, "AccessConfigurationRegister" },
+ { AddSocketServices, "AddSocketServices" },
+ { AdjustResourceInfo, "AdjustResourceInfo" },
+ { CheckEraseQueue, "CheckEraseQueue" },
+ { CloseMemory, "CloseMemory" },
+ { DeregisterClient, "DeregisterClient" },
+ { DeregisterEraseQueue, "DeregisterEraseQueue" },
+ { GetCardServicesInfo, "GetCardServicesInfo" },
+ { GetClientInfo, "GetClientInfo" },
+ { GetConfigurationInfo, "GetConfigurationInfo" },
+ { GetEventMask, "GetEventMask" },
+ { GetFirstClient, "GetFirstClient" },
+ { GetFirstRegion, "GetFirstRegion" },
+ { GetFirstTuple, "GetFirstTuple" },
+ { GetNextClient, "GetNextClient" },
+ { GetNextRegion, "GetNextRegion" },
+ { GetNextTuple, "GetNextTuple" },
+ { GetStatus, "GetStatus" },
+ { GetTupleData, "GetTupleData" },
+ { MapMemPage, "MapMemPage" },
+ { ModifyConfiguration, "ModifyConfiguration" },
+ { ModifyWindow, "ModifyWindow" },
+ { OpenMemory, "OpenMemory" },
+ { ParseTuple, "ParseTuple" },
+ { ReadMemory, "ReadMemory" },
+ { RegisterClient, "RegisterClient" },
+ { RegisterEraseQueue, "RegisterEraseQueue" },
+ { RegisterMTD, "RegisterMTD" },
+ { ReleaseConfiguration, "ReleaseConfiguration" },
+ { ReleaseIO, "ReleaseIO" },
+ { ReleaseIRQ, "ReleaseIRQ" },
+ { ReleaseWindow, "ReleaseWindow" },
+ { RequestConfiguration, "RequestConfiguration" },
+ { RequestIO, "RequestIO" },
+ { RequestIRQ, "RequestIRQ" },
+ { RequestSocketMask, "RequestSocketMask" },
+ { RequestWindow, "RequestWindow" },
+ { ResetCard, "ResetCard" },
+ { SetEventMask, "SetEventMask" },
+ { ValidateCIS, "ValidateCIS" },
+ { WriteMemory, "WriteMemory" },
+ { BindDevice, "BindDevice" },
+ { BindMTD, "BindMTD" },
+ { ReportError, "ReportError" },
+ { SuspendCard, "SuspendCard" },
+ { ResumeCard, "ResumeCard" },
+ { EjectCard, "EjectCard" },
+ { InsertCard, "InsertCard" },
+ { ReplaceCIS, "ReplaceCIS" }
+};
+#define SERVICE_COUNT (sizeof(service_table)/sizeof(lookup_t))
+
+/*======================================================================
+
+ Reset a socket to the default state
+
+======================================================================*/
+
+static void init_socket(socket_info_t *s)
+{
+ int i;
+ pccard_io_map io = { 0, 0, 0, 0, 1 };
+ pccard_mem_map mem = { 0, 0, 0, 0, 0, 0 };
+
+ mem.sys_stop = s->cap.map_size;
+ s->socket = dead_socket;
+ s->ss_entry(s->sock, SS_SetSocket, &s->socket);
+ for (i = 0; i < 2; i++) {
+ io.map = i;
+ s->ss_entry(s->sock, SS_SetIOMap, &io);
+ }
+ for (i = 0; i < 5; i++) {
+ mem.map = i;
+ s->ss_entry(s->sock, SS_SetMemMap, &mem);
+ }
+}
+
+/*====================================================================*/
+
+#if defined(CONFIG_PROC_FS) && defined(PCMCIA_DEBUG)
+int proc_read_clients(char *buf, char **start, off_t pos,
+ int count, int *eof, void *data)
+{
+ socket_info_t *s = data;
+ client_handle_t c;
+ char *p = buf;
+
+ for (c = s->clients; c; c = c->next)
+ p += sprintf(p, "fn %x: '%s' [attr 0x%04x] [state 0x%04x]\n",
+ c->Function, c->dev_info, c->Attributes, c->state);
+ return (p - buf);
+}
+#endif
+
+/*======================================================================
+
+ Low-level PC Card interface drivers need to register with Card
+ Services using these calls.
+
+======================================================================*/
+
+static void setup_socket(u_long i);
+static void shutdown_socket(u_long i);
+static void reset_socket(u_long i);
+static void unreset_socket(u_long i);
+static void parse_events(void *info, u_int events);
+
+int register_ss_entry(int nsock, ss_entry_t ss_entry)
+{
+ int i, ns;
+ socket_info_t *s;
+
+ DEBUG(0, "cs: register_ss_entry(%d, 0x%p)\n", nsock, ss_entry);
+
+ for (ns = 0; ns < nsock; ns++) {
+ s = kmalloc(sizeof(struct socket_info_t), GFP_KERNEL);
+ memset(s, 0, sizeof(socket_info_t));
+
+ s->ss_entry = ss_entry;
+ s->sock = ns;
+ s->setup.data = sockets;
+ s->setup.function = &setup_socket;
+ s->shutdown.data = sockets;
+ s->shutdown.function = &shutdown_socket;
+ /* base address = 0, map = 0 */
+ s->cis_mem.flags = 0;
+ s->cis_mem.speed = cis_speed;
+ s->erase_busy.next = s->erase_busy.prev = &s->erase_busy;
+
+ for (i = 0; i < sockets; i++)
+ if (socket_table[i] == NULL) break;
+ socket_table[i] = s;
+ if (i == sockets) sockets++;
+
+ init_socket(s);
+ ss_entry(ns, SS_InquireSocket, &s->cap);
+#ifdef CONFIG_PROC_FS
+ if (proc_pccard) {
+ char name[3];
+#ifdef PCMCIA_DEBUG
+ struct proc_dir_entry *ent;
+#endif
+ sprintf(name, "%02d", i);
+ s->proc = create_proc_entry(name, S_IFDIR, proc_pccard);
+#ifdef PCMCIA_DEBUG
+ ent = create_proc_entry("clients", 0, s->proc);
+ ent->read_proc = proc_read_clients;
+ ent->data = s;
+#endif
+ ss_entry(ns, SS_ProcSetup, s->proc);
+ }
+#endif
+ }
+
+ return 0;
+} /* register_ss_entry */
+
+/*====================================================================*/
+
+void unregister_ss_entry(ss_entry_t ss_entry)
+{
+ int i, j;
+ socket_info_t *s = NULL;
+ client_t *client;
+
+ for (;;) {
+ for (i = 0; i < sockets; i++) {
+ s = socket_table[i];
+ if (s->ss_entry == ss_entry) break;
+ }
+ if (i == sockets) {
+ break;
+ } else {
+#ifdef CONFIG_PROC_FS
+ if (proc_pccard) {
+ char name[3];
+ sprintf(name, "%02d", i);
+#ifdef PCMCIA_DEBUG
+ remove_proc_entry("clients", s->proc);
+#endif
+ remove_proc_entry(name, proc_pccard);
+ }
+#endif
+ while (s->clients) {
+ client = s->clients;
+ s->clients = s->clients->next;
+ kfree(client);
+ }
+ init_socket(s);
+ release_cis_mem(s);
+#ifdef CONFIG_CARDBUS
+ cb_release_cis_mem(s);
+#endif
+ s->ss_entry = NULL;
+ kfree(s);
+ socket_table[i] = NULL;
+ for (j = i; j < sockets-1; j++)
+ socket_table[j] = socket_table[j+1];
+ sockets--;
+ }
+ }
+
+} /* unregister_ss_entry */
+
+/*======================================================================
+
+ Shutdown_Socket() and setup_socket() are scheduled using add_timer
+ calls by the main event handler when card insertion and removal
+ events are received. Shutdown_Socket() unconfigures a socket and
+ turns off socket power. Setup_socket() turns on socket power
+ and resets the socket, in two stages.
+
+======================================================================*/
+
+static void free_regions(memory_handle_t *list)
+{
+ memory_handle_t tmp;
+ while (*list != NULL) {
+ tmp = *list;
+ *list = tmp->info.next;
+ tmp->region_magic = 0;
+ kfree(tmp);
+ }
+}
+
+static int send_event(socket_info_t *s, event_t event, int priority);
+
+static void shutdown_socket(u_long i)
+{
+ socket_info_t *s = socket_table[i];
+ client_t **c;
+
+ DEBUG(1, "cs: shutdown_socket(%ld)\n", i);
+
+ /* Blank out the socket state */
+ s->state &= SOCKET_PRESENT|SOCKET_SETUP_PENDING;
+ init_socket(s);
+ s->irq.AssignedIRQ = s->irq.Config = 0;
+ s->functions = 0;
+ s->lock_count = 0;
+ s->cis_used = 0;
+ if (s->fake_cis) {
+ kfree(s->fake_cis);
+ s->fake_cis = NULL;
+ }
+#ifdef CONFIG_CARDBUS
+ cb_release_cis_mem(s);
+ cb_free(s);
+#endif
+ if (s->config) {
+ kfree(s->config);
+ s->config = NULL;
+ }
+ for (c = &s->clients; *c; ) {
+ if ((*c)->state & CLIENT_UNBOUND) {
+ client_t *d = *c;
+ *c = (*c)->next;
+ kfree(d);
+ } else {
+ c = &((*c)->next);
+ }
+ }
+ free_regions(&s->a_region);
+ free_regions(&s->c_region);
+} /* shutdown_socket */
+
+static void setup_socket(u_long i)
+{
+ int val;
+ socket_info_t *s = socket_table[i];
+
+ s->ss_entry(s->sock, SS_GetStatus, &val);
+ if (val & SS_DETECT) {
+ DEBUG(1, "cs: setup_socket(%ld): applying power\n", i);
+ s->state |= SOCKET_PRESENT;
+ s->socket.flags = 0;
+ 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 %ld: unsupported "
+ "voltage key\n", i);
+ 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
+ }
+ s->ss_entry(s->sock, SS_SetSocket, &s->socket);
+ s->setup.function = &reset_socket;
+ s->setup.expires = jiffies + vcc_settle;
+ add_timer(&s->setup);
+ } else
+ DEBUG(0, "cs: setup_socket(%ld): no card!\n", i);
+} /* 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(u_long i)
+{
+ socket_info_t *s = socket_table[i];
+
+ DEBUG(1, "cs: resetting socket %ld\n", i);
+ s->socket.flags |= SS_OUTPUT_ENA | SS_RESET;
+ s->ss_entry(s->sock, SS_SetSocket, &s->socket);
+ udelay((long)reset_time);
+ s->socket.flags &= ~SS_RESET;
+ s->ss_entry(s->sock, SS_SetSocket, &s->socket);
+ s->unreset_timeout = 0;
+ s->setup.expires = jiffies + unreset_delay;
+ s->setup.function = &unreset_socket;
+ add_timer(&s->setup);
+} /* reset_socket */
+
+#define EVENT_MASK \
+(SOCKET_SETUP_PENDING|SOCKET_SUSPEND|SOCKET_RESET_PENDING)
+
+static void unreset_socket(u_long i)
+{
+ socket_info_t *s = socket_table[i];
+ int val;
+
+ s->ss_entry(s->sock, SS_GetStatus, &val);
+ if (val & SS_READY) {
+ DEBUG(1, "cs: reset done on socket %ld\n", i);
+ 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);
+#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);
+ s->reset_handle->event_callback_args.info = NULL;
+ EVENT(s->reset_handle, CS_EVENT_RESET_COMPLETE,
+ CS_EVENT_PRI_LOW);
+ s->state &= ~EVENT_MASK;
+ }
+ } else {
+ DEBUG(2, "cs: socket %ld not ready yet\n", i);
+ if (s->unreset_timeout > unreset_limit) {
+ printk(KERN_NOTICE "cs: socket %ld timed out during"
+ " reset\n", i);
+ s->state &= ~EVENT_MASK;
+ } else {
+ s->unreset_timeout++;
+ s->setup.expires = jiffies + unreset_check;
+ add_timer(&s->setup);
+ }
+ }
+} /* unreset_socket */
+
+/*======================================================================
+
+ The central event handler. Send_event() sends an event to all
+ valid clients. Parse_events() interprets the event bits from
+ a card status change report. Do_shotdown() handles the high
+ priority stuff associated with a card removal.
+
+======================================================================*/
+
+static int send_event(socket_info_t *s, event_t event, int priority)
+{
+ client_t *client = s->clients;
+ int ret;
+ DEBUG(1, "cs: send_event(sock %d, event %d, pri %d)\n",
+ s->sock, event, priority);
+ ret = 0;
+ for (; client; client = client->next) {
+ if (client->state & (CLIENT_UNBOUND|CLIENT_STALE))
+ continue;
+ if (client->EventMask & event) {
+ ret = EVENT(client, event, priority);
+ if (ret != 0)
+ return ret;
+ }
+ }
+ return ret;
+} /* send_event */
+
+static void do_shutdown(socket_info_t *s)
+{
+ 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");
+ del_timer(&s->setup);
+ s->state &= ~EVENT_MASK;
+ }
+ s->shutdown.expires = jiffies + shutdown_delay;
+ add_timer(&s->shutdown);
+ s->state &= ~SOCKET_PRESENT;
+}
+
+static void parse_events(void *info, u_int events)
+{
+ socket_info_t *s = info;
+ if (events & SS_DETECT) {
+ int status;
+ u_long flags;
+ spin_lock_irqsave(&s->lock, flags);
+ s->ss_entry(s->sock, SS_GetStatus, &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) {
+ del_timer(&s->setup);
+ DEBUG(1, "cs: delaying pending setup\n");
+ }
+ s->state |= SOCKET_SETUP_PENDING;
+ s->setup.function = &setup_socket;
+ if (s->state & SOCKET_SUSPEND)
+ s->setup.expires = jiffies + resume_delay;
+ else
+ s->setup.expires = jiffies + setup_delay;
+ add_timer(&s->setup);
+ }
+ spin_unlock_irqrestore(&s->lock, flags);
+ }
+ 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 */
+
+/*======================================================================
+
+ Another event handler, for power management events.
+
+ This does not comply with the latest PC Card spec for handling
+ power management events.
+
+======================================================================*/
+
+#ifdef CONFIG_APM
+static int handle_apm_event(apm_event_t event)
+{
+ int i, stat;
+ socket_info_t *s;
+ static int down = 0;
+
+ switch (event) {
+ case APM_SYS_SUSPEND:
+ case APM_USER_SUSPEND:
+ DEBUG(1, "cs: received suspend notification\n");
+ if (down) {
+ printk(KERN_DEBUG "cs: received extra suspend event\n");
+ break;
+ }
+ down = 1;
+ for (i = 0; i < sockets; i++) {
+ s = socket_table[i];
+ if ((s->state & SOCKET_PRESENT) &&
+ !(s->state & SOCKET_SUSPEND)){
+ send_event(s, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW);
+ s->ss_entry(s->sock, SS_SetSocket, &dead_socket);
+ s->state |= SOCKET_SUSPEND;
+ }
+ }
+ break;
+ case APM_NORMAL_RESUME:
+ case APM_CRITICAL_RESUME:
+ DEBUG(1, "cs: received resume notification\n");
+ if (!down) {
+ printk(KERN_DEBUG "cs: received bogus resume event\n");
+ break;
+ }
+ down = 0;
+ for (i = 0; i < sockets; i++) {
+ s = socket_table[i];
+ /* Do this just to reinitialize the socket */
+ init_socket(s);
+ s->ss_entry(s->sock, SS_GetStatus, &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);
+ }
+ break;
+ }
+ return 0;
+} /* handle_apm_event */
+#endif
+
+/*======================================================================
+
+ Special stuff for managing IO windows, because they are scarce.
+
+======================================================================*/
+
+static int alloc_io_space(socket_info_t *s, u_int attr, ioaddr_t *base,
+ ioaddr_t num, char *name)
+{
+ int i;
+ ioaddr_t try;
+
+ for (i = 0; i < MAX_IO_WIN; i++) {
+ if (s->io[i].NumPorts == 0) {
+ if (find_io_region(base, num, name) == 0) {
+ s->io[i].Attributes = attr;
+ s->io[i].BasePort = *base;
+ s->io[i].NumPorts = s->io[i].InUse = num;
+ break;
+ } else
+ return 1;
+ } else if (s->io[i].Attributes != attr)
+ continue;
+ /* Try to extend top of window */
+ try = s->io[i].BasePort + s->io[i].NumPorts;
+ if ((*base == 0) || (*base == try))
+ if (find_io_region(&try, num, name) == 0) {
+ *base = try;
+ s->io[i].NumPorts += num;
+ s->io[i].InUse += num;
+ break;
+ }
+ /* Try to extend bottom of window */
+ try = s->io[i].BasePort - num;
+ if ((*base == 0) || (*base == try))
+ if (find_io_region(&try, num, name) == 0) {
+ s->io[i].BasePort = *base = try;
+ s->io[i].NumPorts += num;
+ s->io[i].InUse += num;
+ break;
+ }
+ }
+ return (i == MAX_IO_WIN);
+} /* alloc_io_space */
+
+static void release_io_space(socket_info_t *s, ioaddr_t base,
+ ioaddr_t num)
+{
+ int i;
+ release_region(base, num);
+ for (i = 0; i < MAX_IO_WIN; i++) {
+ if ((s->io[i].BasePort <= base) &&
+ (s->io[i].BasePort+s->io[i].NumPorts >= base+num)) {
+ s->io[i].InUse -= num;
+ /* Free the window if no one else is using it */
+ if (s->io[i].InUse == 0)
+ s->io[i].NumPorts = 0;
+ }
+ }
+}
+
+/*======================================================================
+
+ Access_configuration_register() reads and writes configuration
+ registers in attribute memory. Memory window 0 is reserved for
+ this and the tuple reading services.
+
+======================================================================*/
+
+static int access_configuration_register(client_handle_t handle,
+ conf_reg_t *reg)
+{
+ socket_info_t *s;
+ config_t *c;
+ int addr;
+ u_char val;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ s = SOCKET(handle);
+ if (handle->Function == BIND_FN_ALL) {
+ if (reg->Function >= s->functions)
+ return CS_BAD_ARGS;
+ c = &s->config[reg->Function];
+ } else
+ c = CONFIG(handle);
+ if (!(c->state & CONFIG_LOCKED))
+ return CS_CONFIGURATION_LOCKED;
+
+ addr = (c->ConfigBase + reg->Offset) >> 1;
+
+ switch (reg->Action) {
+ case CS_READ:
+ read_cis_mem(s, 1, addr, 1, &val);
+ reg->Value = val;
+ break;
+ case CS_WRITE:
+ val = reg->Value;
+ write_cis_mem(s, 1, addr, 1, &val);
+ break;
+ default:
+ return CS_BAD_ARGS;
+ break;
+ }
+ return CS_SUCCESS;
+} /* access_configuration_register */
+
+/*======================================================================
+
+ 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 bind_device(bind_req_t *req)
+{
+ client_t *client;
+ socket_info_t *s;
+
+ if (CHECK_SOCKET(req->Socket))
+ return CS_BAD_SOCKET;
+ s = SOCKET(req);
+
+ client = (client_t *)kmalloc(sizeof(client_t), GFP_KERNEL);
+ memset(client, '\0', sizeof(client_t));
+ client->client_magic = CLIENT_MAGIC;
+ strncpy(client->dev_info, (char *)req->dev_info, DEV_NAME_LEN);
+ client->Socket = req->Socket;
+ 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;
+ DEBUG(1, "cs: bind_device(): client 0x%p, sock %d, dev %s\n",
+ client, client->Socket, 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 bind_mtd(mtd_bind_t *req)
+{
+ socket_info_t *s;
+ memory_handle_t region;
+
+ if (CHECK_SOCKET(req->Socket))
+ return CS_BAD_SOCKET;
+ s = SOCKET(req);
+
+ 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;
+ strncpy(region->dev_info, (char *)req->dev_info, DEV_NAME_LEN);
+
+ DEBUG(1, "cs: bind_mtd(): attr 0x%x, offset 0x%x, dev %s\n",
+ req->Attributes, req->CardOffset, (char *)req->dev_info);
+ return CS_SUCCESS;
+} /* bind_mtd */
+
+/*====================================================================*/
+
+static int deregister_client(client_handle_t handle)
+{
+ client_t **client;
+ socket_info_t *s;
+ memory_handle_t region;
+ u_long flags;
+ int i, sn;
+
+ DEBUG(1, "cs: deregister_client(%p)\n", handle);
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ if (handle->state &
+ (CLIENT_IRQ_REQ|CLIENT_IO_REQ|CLIENT_CONFIG_LOCKED))
+ return CS_IN_USE;
+ for (i = 0; i < MAX_WIN; i++)
+ if (handle->state & CLIENT_WIN_REQ(i))
+ return CS_IN_USE;
+
+ /* Disconnect all MTD links */
+ s = SOCKET(handle);
+ if (handle->mtd_count) {
+ for (region = s->a_region; region; region = region->info.next)
+ if (region->mtd == handle) region->mtd = NULL;
+ for (region = s->c_region; region; region = region->info.next)
+ if (region->mtd == handle) region->mtd = NULL;
+ }
+
+ sn = handle->Socket; s = socket_table[sn];
+
+ if ((handle->state & CLIENT_STALE) ||
+ (handle->Attributes & INFO_MASTER_CLIENT)) {
+ spin_lock_irqsave(&s->lock, flags);
+ client = &s->clients;
+ while ((*client) && ((*client) != handle))
+ client = &(*client)->next;
+ if (*client == NULL)
+ return CS_BAD_HANDLE;
+ *client = handle->next;
+ handle->client_magic = 0;
+ kfree(handle);
+ spin_unlock_irqrestore(&s->lock, flags);
+ } else {
+ handle->state = CLIENT_UNBOUND;
+ handle->mtd_count = 0;
+ handle->event_handler = NULL;
+ }
+
+ if (--s->real_clients == 0)
+ s->ss_entry(sn, SS_RegisterCallback, NULL);
+
+ return CS_SUCCESS;
+} /* deregister_client */
+
+/*====================================================================*/
+
+static int get_configuration_info(client_handle_t handle,
+ config_info_t *config)
+{
+ socket_info_t *s;
+ config_t *c;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ s = SOCKET(handle);
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+
+ if (handle->Function == BIND_FN_ALL) {
+ if (config->Function && (config->Function >= s->functions))
+ return CS_BAD_ARGS;
+ } else
+ config->Function = handle->Function;
+
+#ifdef CONFIG_CARDBUS
+ if (s->state & SOCKET_CARDBUS) {
+ u_char fn = config->Function;
+ memset(config, 0, sizeof(config_info_t));
+ config->Function = fn;
+ config->Vcc = s->socket.Vcc;
+ config->Vpp1 = config->Vpp2 = s->socket.Vpp;
+ config->Option = s->cap.cardbus;
+ if (s->cb_config) {
+ config->Attributes = CONF_VALID_CLIENT;
+ config->IntType = INT_CARDBUS;
+ config->AssignedIRQ = s->irq.AssignedIRQ;
+ if (config->AssignedIRQ)
+ config->Attributes |= CONF_ENABLE_IRQ;
+ config->BasePort1 = s->io[0].BasePort;
+ config->NumPorts1 = s->io[0].NumPorts;
+ }
+ return CS_SUCCESS;
+ }
+#endif
+
+ c = (s->config != NULL) ? &s->config[config->Function] : NULL;
+
+ if ((c == NULL) || !(c->state & CONFIG_LOCKED)) {
+ config->Attributes = 0;
+ config->Vcc = s->socket.Vcc;
+ config->Vpp1 = config->Vpp2 = s->socket.Vpp;
+ return CS_SUCCESS;
+ }
+
+ /* !!! This is a hack !!! */
+ memcpy(&config->Attributes, &c->Attributes, sizeof(config_t));
+ config->Attributes |= CONF_VALID_CLIENT;
+ config->CardValues = c->CardValues;
+ config->IRQAttributes = c->irq.Attributes;
+ config->AssignedIRQ = s->irq.AssignedIRQ;
+ config->BasePort1 = c->io.BasePort1;
+ config->NumPorts1 = c->io.NumPorts1;
+ config->Attributes1 = c->io.Attributes1;
+ config->BasePort2 = c->io.BasePort2;
+ config->NumPorts2 = c->io.NumPorts2;
+ config->Attributes2 = c->io.Attributes2;
+ config->IOAddrLines = c->io.IOAddrLines;
+
+ return CS_SUCCESS;
+} /* get_configuration_info */
+
+/*======================================================================
+
+ Return information about this version of Card Services.
+
+======================================================================*/
+
+static int get_card_services_info(servinfo_t *info)
+{
+ info->Signature[0] = 'C';
+ info->Signature[1] = 'S';
+ info->Count = sockets;
+ info->Revision = CS_RELEASE_CODE;
+ info->CSLevel = 0x0210;
+ info->VendorString = (char *)release;
+ return CS_SUCCESS;
+} /* get_card_services_info */
+
+/*======================================================================
+
+ Note that get_first_client() *does* recognize the Socket field
+ in the request structure.
+
+======================================================================*/
+
+static int get_first_client(client_handle_t *handle, client_req_t *req)
+{
+ socket_t s;
+ if (req->Attributes & CLIENT_THIS_SOCKET)
+ s = req->Socket;
+ else
+ s = 0;
+ if (CHECK_SOCKET(req->Socket))
+ return CS_BAD_SOCKET;
+ if (socket_table[s]->clients == NULL)
+ return CS_NO_MORE_ITEMS;
+ *handle = socket_table[s]->clients;
+ return CS_SUCCESS;
+} /* get_first_client */
+
+/*====================================================================*/
+
+static int get_next_client(client_handle_t *handle, client_req_t *req)
+{
+ socket_info_t *s;
+ if ((handle == NULL) || CHECK_HANDLE(*handle))
+ return CS_BAD_HANDLE;
+ if ((*handle)->next == NULL) {
+ if (req->Attributes & CLIENT_THIS_SOCKET)
+ return CS_NO_MORE_ITEMS;
+ s = SOCKET(*handle);
+ if (s->clients == NULL)
+ return CS_NO_MORE_ITEMS;
+ *handle = s->clients;
+ } else
+ *handle = (*handle)->next;
+ return CS_SUCCESS;
+} /* get_next_client */
+
+/*====================================================================*/
+
+static int get_window(window_handle_t *handle, int idx, win_req_t *req)
+{
+ socket_info_t *s;
+ window_t *win;
+ int w;
+
+ if (idx == 0)
+ s = SOCKET((client_handle_t)*handle);
+ else
+ s = (*handle)->sock;
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+ for (w = idx; w < MAX_WIN; w++)
+ if (s->state & SOCKET_WIN_REQ(w)) break;
+ if (w == MAX_WIN)
+ return CS_NO_MORE_ITEMS;
+ win = &s->win[w];
+ req->Base = win->ctl.sys_start;
+ req->Size = win->ctl.sys_stop - win->ctl.sys_start + 1;
+ req->AccessSpeed = win->ctl.speed;
+ req->Attributes = 0;
+ if (win->ctl.flags & MAP_ATTRIB)
+ req->Attributes |= WIN_MEMORY_TYPE_AM;
+ if (win->ctl.flags & MAP_ACTIVE)
+ req->Attributes |= WIN_ENABLE;
+ if (win->ctl.flags & MAP_16BIT)
+ req->Attributes |= WIN_DATA_WIDTH;
+ if (win->ctl.flags & MAP_USE_WAIT)
+ req->Attributes |= WIN_USE_WAIT;
+ *handle = win;
+ return CS_SUCCESS;
+} /* get_window */
+
+static int get_first_window(client_handle_t *handle, win_req_t *req)
+{
+ if ((handle == NULL) || CHECK_HANDLE(*handle))
+ return CS_BAD_HANDLE;
+ return get_window((window_handle_t *)handle, 0, req);
+}
+
+static int get_next_window(window_handle_t *win, win_req_t *req)
+{
+ if ((win == NULL) || ((*win)->magic != WINDOW_MAGIC))
+ return CS_BAD_HANDLE;
+ return get_window(win, (*win)->index+1, req);
+}
+
+/*======================================================================
+
+ Get the current socket state bits. We don't support the latched
+ SocketState yet: I haven't seen any point for it.
+
+======================================================================*/
+
+static int get_status(client_handle_t handle, cs_status_t *status)
+{
+ socket_info_t *s;
+ config_t *c;
+ int val;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ s = SOCKET(handle);
+ s->ss_entry(s->sock, SS_GetStatus, &val);
+ status->CardState = status->SocketState = 0;
+ status->CardState |= (val & SS_DETECT) ? CS_EVENT_CARD_DETECT : 0;
+ status->CardState |= (val & SS_CARDBUS) ? CS_EVENT_CB_DETECT : 0;
+ status->CardState |= (val & SS_3VCARD) ? CS_EVENT_3VCARD : 0;
+ status->CardState |= (val & SS_XVCARD) ? CS_EVENT_XVCARD : 0;
+ if (s->state & SOCKET_SUSPEND)
+ status->CardState |= CS_EVENT_PM_SUSPEND;
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+ if (s->state & SOCKET_SETUP_PENDING)
+ status->CardState |= CS_EVENT_CARD_INSERTION;
+
+ /* Get info from the PRR, if necessary */
+ if (handle->Function == BIND_FN_ALL) {
+ if (status->Function && (status->Function >= s->functions))
+ return CS_BAD_ARGS;
+ c = (s->config != NULL) ? &s->config[status->Function] : NULL;
+ } else
+ c = CONFIG(handle);
+ if ((c != NULL) && (c->state & CONFIG_LOCKED) &&
+ (c->IntType & INT_MEMORY_AND_IO)) {
+ u_char reg;
+ if (c->Present & PRESENT_PIN_REPLACE) {
+ read_cis_mem(s, 1, (c->ConfigBase+CISREG_PRR)>>1, 1, ®);
+ status->CardState |=
+ (reg & PRR_WP_STATUS) ? CS_EVENT_WRITE_PROTECT : 0;
+ status->CardState |=
+ (reg & PRR_READY_STATUS) ? CS_EVENT_READY_CHANGE : 0;
+ status->CardState |=
+ (reg & PRR_BVD2_STATUS) ? CS_EVENT_BATTERY_LOW : 0;
+ status->CardState |=
+ (reg & PRR_BVD1_STATUS) ? CS_EVENT_BATTERY_DEAD : 0;
+ } else {
+ /* No PRR? Then assume we're always ready */
+ status->CardState |= CS_EVENT_READY_CHANGE;
+ }
+ if (c->Present & PRESENT_EXT_STATUS) {
+ read_cis_mem(s, 1, (c->ConfigBase+CISREG_ESR)>>1, 1, ®);
+ status->CardState |=
+ (reg & ESR_REQ_ATTN) ? CS_EVENT_REQUEST_ATTENTION : 0;
+ }
+ return CS_SUCCESS;
+ }
+ status->CardState |=
+ (val & SS_WRPROT) ? CS_EVENT_WRITE_PROTECT : 0;
+ status->CardState |=
+ (val & SS_BATDEAD) ? CS_EVENT_BATTERY_DEAD : 0;
+ status->CardState |=
+ (val & SS_BATWARN) ? CS_EVENT_BATTERY_LOW : 0;
+ status->CardState |=
+ (val & SS_READY) ? CS_EVENT_READY_CHANGE : 0;
+ return CS_SUCCESS;
+} /* get_status */
+
+/*======================================================================
+
+ Change the card address of an already open memory window.
+
+======================================================================*/
+
+static int get_mem_page(window_handle_t win, memreq_t *req)
+{
+ if ((win == NULL) || (win->magic != WINDOW_MAGIC))
+ return CS_BAD_HANDLE;
+ req->Page = 0;
+ req->CardOffset = win->ctl.card_start;
+ return CS_SUCCESS;
+} /* get_mem_page */
+
+static int map_mem_page(window_handle_t win, memreq_t *req)
+{
+ socket_info_t *s;
+ if ((win == NULL) || (win->magic != WINDOW_MAGIC))
+ return CS_BAD_HANDLE;
+ if (req->Page != 0)
+ return CS_BAD_PAGE;
+ s = win->sock;
+ win->ctl.card_start = req->CardOffset;
+ if (s->ss_entry(s->sock, SS_SetMemMap, &win->ctl) != 0)
+ return CS_BAD_OFFSET;
+ return CS_SUCCESS;
+} /* map_mem_page */
+
+/*======================================================================
+
+ Modify a locked socket configuration
+
+======================================================================*/
+
+static int modify_configuration(client_handle_t handle,
+ modconf_t *mod)
+{
+ socket_info_t *s;
+ config_t *c;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ s = SOCKET(handle); c = CONFIG(handle);
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+ if (!(c->state & CONFIG_LOCKED))
+ return CS_CONFIGURATION_LOCKED;
+
+ if (mod->Attributes & CONF_IRQ_CHANGE_VALID) {
+ if (mod->Attributes & CONF_ENABLE_IRQ) {
+ c->Attributes |= CONF_ENABLE_IRQ;
+ s->socket.io_irq = s->irq.AssignedIRQ;
+ } else {
+ c->Attributes &= ~CONF_ENABLE_IRQ;
+ s->socket.io_irq = 0;
+ }
+ s->ss_entry(s->sock, SS_SetSocket, &s->socket);
+ }
+
+ if (mod->Attributes & CONF_VCC_CHANGE_VALID)
+ return CS_BAD_VCC;
+
+ /* We only allow changing Vpp1 and Vpp2 to the same value */
+ if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) &&
+ (mod->Attributes & CONF_VPP2_CHANGE_VALID)) {
+ if (mod->Vpp1 != mod->Vpp2)
+ return CS_BAD_VPP;
+ c->Vpp1 = c->Vpp2 = s->socket.Vpp = mod->Vpp1;
+ if (s->ss_entry(s->sock, SS_SetSocket, &s->socket))
+ return CS_BAD_VPP;
+ } else if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) ||
+ (mod->Attributes & CONF_VPP2_CHANGE_VALID))
+ return CS_BAD_VPP;
+
+ return CS_SUCCESS;
+} /* modify_configuration */
+
+/*======================================================================
+
+ Modify the attributes of a window returned by RequestWindow.
+
+======================================================================*/
+
+static int modify_window(window_handle_t win, modwin_t *req)
+{
+ if ((win == NULL) || (win->magic != WINDOW_MAGIC))
+ return CS_BAD_HANDLE;
+
+ win->ctl.flags &= ~(MAP_ATTRIB|MAP_ACTIVE);
+ if (req->Attributes & WIN_MEMORY_TYPE)
+ win->ctl.flags |= MAP_ATTRIB;
+ if (req->Attributes & WIN_ENABLE)
+ win->ctl.flags |= MAP_ACTIVE;
+ if (req->Attributes & WIN_DATA_WIDTH)
+ win->ctl.flags |= MAP_16BIT;
+ if (req->Attributes & WIN_USE_WAIT)
+ win->ctl.flags |= MAP_USE_WAIT;
+ win->ctl.speed = req->AccessSpeed;
+ win->sock->ss_entry(win->sock->sock, SS_SetMemMap, &win->ctl);
+
+ return CS_SUCCESS;
+} /* modify_window */
+
+/*======================================================================
+
+ Register_client() uses the dev_info_t handle to match the
+ caller with a socket. The driver must have already been bound
+ to a socket with bind_device() -- in fact, bind_device()
+ allocates the client structure that will be used.
+
+======================================================================*/
+
+static int register_client(client_handle_t *handle, client_reg_t *req)
+{
+ client_t *client;
+ socket_info_t *s;
+ socket_t ns;
+
+ /* Look for unbound client with matching dev_info */
+ client = NULL;
+ for (ns = 0; ns < sockets; ns++) {
+ client = socket_table[ns]->clients;
+ while (client != NULL) {
+ if ((strcmp(client->dev_info, (char *)req->dev_info) == 0)
+ && (client->state & CLIENT_UNBOUND)) break;
+ client = client->next;
+ }
+ if (client != NULL) break;
+ }
+ if (client == NULL)
+ return CS_OUT_OF_RESOURCE;
+
+ s = socket_table[ns];
+ if (++s->real_clients == 1) {
+ ss_callback_t call;
+ int status;
+ call.handler = &parse_events;
+ call.info = s;
+ s->ss_entry(ns, SS_RegisterCallback, &call);
+ s->ss_entry(ns, SS_GetStatus, &status);
+ if ((status & SS_DETECT) &&
+ !(s->state & SOCKET_SETUP_PENDING)) {
+ s->state |= SOCKET_SETUP_PENDING;
+ setup_socket(ns);
+ }
+ }
+
+ *handle = client;
+ client->state &= ~CLIENT_UNBOUND;
+ client->Socket = ns;
+ client->Attributes = req->Attributes;
+ client->EventMask = req->EventMask;
+ client->event_handler = req->event_handler;
+ client->event_callback_args = req->event_callback_args;
+ client->event_callback_args.client_handle = client;
+ client->event_callback_args.bus = s->cap.bus;
+
+ if (s->state & SOCKET_CARDBUS)
+ client->state |= CLIENT_CARDBUS;
+
+ if ((!(s->state & SOCKET_CARDBUS)) && (s->functions == 0) &&
+ (client->Function != BIND_FN_ALL)) {
+ cistpl_longlink_mfc_t mfc;
+ if (read_tuple(client, CISTPL_LONGLINK_MFC, &mfc)
+ == CS_SUCCESS)
+ s->functions = mfc.nfn;
+ else
+ s->functions = 1;
+ s->config = kmalloc(sizeof(config_t) * s->functions,
+ GFP_KERNEL);
+ memset(s->config, 0, sizeof(config_t) * s->functions);
+ }
+
+ DEBUG(1, "cs: register_client(): client 0x%p, sock %d, dev %s\n",
+ client, client->Socket, client->dev_info);
+ if (client->EventMask & CS_EVENT_REGISTRATION_COMPLETE)
+ EVENT(client, CS_EVENT_REGISTRATION_COMPLETE, CS_EVENT_PRI_LOW);
+ if ((socket_table[ns]->state & SOCKET_PRESENT) &&
+ !(socket_table[ns]->state & SOCKET_SETUP_PENDING)) {
+ if (client->EventMask & CS_EVENT_CARD_INSERTION)
+ EVENT(client, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW);
+ else
+ client->PendingEvents |= CS_EVENT_CARD_INSERTION;
+ }
+ return CS_SUCCESS;
+} /* register_client */
+
+/*====================================================================*/
+
+static int release_configuration(client_handle_t handle,
+ socket_t *Socket)
+{
+ pccard_io_map io;
+ socket_info_t *s;
+ int i;
+
+ if (CHECK_HANDLE(handle) ||
+ !(handle->state & CLIENT_CONFIG_LOCKED))
+ return CS_BAD_HANDLE;
+ handle->state &= ~CLIENT_CONFIG_LOCKED;
+ s = SOCKET(handle);
+
+#ifdef CONFIG_CARDBUS
+ if (handle->state & CLIENT_CARDBUS) {
+ cb_disable(s);
+ s->lock_count = 0;
+ return CS_SUCCESS;
+ }
+#endif
+
+ if (!(handle->state & CLIENT_STALE)) {
+ config_t *c = CONFIG(handle);
+ if (--(s->lock_count) == 0) {
+ s->socket.flags = SS_OUTPUT_ENA;
+ s->socket.Vpp = 0;
+ s->socket.io_irq = 0;
+ s->ss_entry(s->sock, SS_SetSocket, &s->socket);
+ }
+ if (c->state & CONFIG_IO_REQ)
+ for (i = 0; i < MAX_IO_WIN; i++) {
+ if (s->io[i].NumPorts == 0)
+ continue;
+ s->io[i].Config--;
+ if (s->io[i].Config != 0)
+ continue;
+ io.map = i;
+ s->ss_entry(s->sock, SS_GetIOMap, &io);
+ io.flags &= ~MAP_ACTIVE;
+ s->ss_entry(s->sock, SS_SetIOMap, &io);
+ }
+ c->state &= ~CONFIG_LOCKED;
+ }
+
+ return CS_SUCCESS;
+} /* release_configuration */
+
+/*======================================================================
+
+ Release_io() releases the I/O ranges allocated by a client. This
+ may be invoked some time after a card ejection has already dumped
+ the actual socket configuration, so if the client is "stale", we
+ don't bother checking the port ranges against the current socket
+ values.
+
+======================================================================*/
+
+static int release_io(client_handle_t handle, io_req_t *req)
+{
+ socket_info_t *s;
+
+ if (CHECK_HANDLE(handle) || !(handle->state & CLIENT_IO_REQ))
+ return CS_BAD_HANDLE;
+ handle->state &= ~CLIENT_IO_REQ;
+ s = SOCKET(handle);
+
+#ifdef CONFIG_CARDBUS
+ if (handle->state & CLIENT_CARDBUS) {
+ cb_release(s);
+ return CS_SUCCESS;
+ }
+#endif
+
+ if (!(handle->state & CLIENT_STALE)) {
+ config_t *c = CONFIG(handle);
+ if (c->state & CONFIG_LOCKED)
+ return CS_CONFIGURATION_LOCKED;
+ if ((c->io.BasePort1 != req->BasePort1) ||
+ (c->io.NumPorts1 != req->NumPorts1) ||
+ (c->io.BasePort2 != req->BasePort2) ||
+ (c->io.NumPorts2 != req->NumPorts2))
+ return CS_BAD_ARGS;
+ c->state &= ~CONFIG_IO_REQ;
+ }
+
+ release_io_space(s, req->BasePort1, req->NumPorts1);
+ if (req->NumPorts2)
+ release_io_space(s, req->BasePort2, req->NumPorts2);
+
+ return CS_SUCCESS;
+} /* release_io */
+
+/*====================================================================*/
+
+static int cs_release_irq(client_handle_t handle, irq_req_t *req)
+{
+ socket_info_t *s;
+ if (CHECK_HANDLE(handle) || !(handle->state & CLIENT_IRQ_REQ))
+ return CS_BAD_HANDLE;
+ handle->state &= ~CLIENT_IRQ_REQ;
+ s = SOCKET(handle);
+
+ if (!(handle->state & CLIENT_STALE)) {
+ config_t *c = CONFIG(handle);
+ if (c->state & CONFIG_LOCKED)
+ return CS_CONFIGURATION_LOCKED;
+ if (c->irq.Attributes != req->Attributes)
+ return CS_BAD_ATTRIBUTE;
+ if (s->irq.AssignedIRQ != req->AssignedIRQ)
+ return CS_BAD_IRQ;
+ if (--s->irq.Config == 0) {
+ c->state &= ~CONFIG_IRQ_REQ;
+ s->irq.AssignedIRQ = 0;
+ }
+ }
+
+ if (req->Attributes & IRQ_HANDLE_PRESENT) {
+ bus_free_irq(s->cap.bus, req->AssignedIRQ, req->Instance);
+ }
+
+#ifdef CONFIG_ISA
+ if (req->AssignedIRQ != s->cap.pci_irq)
+ undo_irq(req->Attributes, req->AssignedIRQ);
+#endif
+
+ return CS_SUCCESS;
+} /* cs_release_irq */
+
+/*====================================================================*/
+
+static int release_window(window_handle_t win)
+{
+ socket_info_t *s;
+
+ if ((win == NULL) || (win->magic != WINDOW_MAGIC))
+ return CS_BAD_HANDLE;
+ s = win->sock;
+ if (!(win->handle->state & CLIENT_WIN_REQ(win->index)))
+ return CS_BAD_HANDLE;
+
+ /* Shut down memory window */
+ win->ctl.flags &= ~MAP_ACTIVE;
+ s->ss_entry(s->sock, SS_SetMemMap, &win->ctl);
+ s->state &= ~SOCKET_WIN_REQ(win->index);
+
+ /* Release system memory */
+ release_mem_region(win->base, win->size);
+ win->handle->state &= ~CLIENT_WIN_REQ(win->index);
+
+ win->magic = 0;
+
+ return CS_SUCCESS;
+} /* release_window */
+
+/*====================================================================*/
+
+static int request_configuration(client_handle_t handle,
+ config_req_t *req)
+{
+ int i;
+ u_int base;
+ socket_info_t *s;
+ config_t *c;
+ pccard_io_map iomap;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ i = handle->Socket; s = socket_table[i];
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+
+#ifdef CONFIG_CARDBUS
+ if (handle->state & CLIENT_CARDBUS) {
+ if (!(req->IntType & INT_CARDBUS))
+ return CS_UNSUPPORTED_MODE;
+ if (s->lock_count != 0)
+ return CS_CONFIGURATION_LOCKED;
+ cb_enable(s);
+ handle->state |= CLIENT_CONFIG_LOCKED;
+ s->lock_count++;
+ return CS_SUCCESS;
+ }
+#endif
+
+ if (req->IntType & INT_CARDBUS)
+ return CS_UNSUPPORTED_MODE;
+ c = CONFIG(handle);
+ if (c->state & CONFIG_LOCKED)
+ return CS_CONFIGURATION_LOCKED;
+
+ /* Do power control. We don't allow changes in Vcc. */
+ if (s->socket.Vcc != req->Vcc)
+ return CS_BAD_VCC;
+ if (req->Vpp1 != req->Vpp2)
+ return CS_BAD_VPP;
+ s->socket.Vpp = req->Vpp1;
+ if (s->ss_entry(s->sock, SS_SetSocket, &s->socket))
+ return CS_BAD_VPP;
+
+ c->Vcc = req->Vcc; c->Vpp1 = c->Vpp2 = req->Vpp1;
+
+ /* Pick memory or I/O card, DMA mode, interrupt */
+ c->IntType = req->IntType;
+ c->Attributes = req->Attributes;
+ if (req->IntType & INT_MEMORY_AND_IO)
+ s->socket.flags |= SS_IOCARD;
+ if (req->Attributes & CONF_ENABLE_DMA)
+ s->socket.flags |= SS_DMA_MODE;
+ if (req->Attributes & CONF_ENABLE_SPKR)
+ s->socket.flags |= SS_SPKR_ENA;
+ if (req->Attributes & CONF_ENABLE_IRQ)
+ s->socket.io_irq = s->irq.AssignedIRQ;
+ else
+ s->socket.io_irq = 0;
+ s->ss_entry(s->sock, SS_SetSocket, &s->socket);
+ s->lock_count++;
+
+ /* Set up CIS configuration registers */
+ base = c->ConfigBase = req->ConfigBase;
+ c->Present = c->CardValues = req->Present;
+ if (req->Present & PRESENT_COPY) {
+ c->Copy = req->Copy;
+ write_cis_mem(s, 1, (base + CISREG_SCR)>>1, 1, &c->Copy);
+ }
+ if (req->Present & PRESENT_OPTION) {
+ if (s->functions == 1)
+ c->Option = req->ConfigIndex & COR_CONFIG_MASK;
+ else {
+ c->Option = req->ConfigIndex & COR_MFC_CONFIG_MASK;
+ c->Option |= COR_FUNC_ENA|COR_ADDR_DECODE|COR_IREQ_ENA;
+ }
+ if (c->state & CONFIG_IRQ_REQ)
+ if (!(c->irq.Attributes & IRQ_FORCED_PULSE))
+ c->Option |= COR_LEVEL_REQ;
+ write_cis_mem(s, 1, (base + CISREG_COR)>>1, 1, &c->Option);
+ udelay(40*1000);
+ }
+ if (req->Present & PRESENT_STATUS) {
+ c->Status = req->Status;
+ write_cis_mem(s, 1, (base + CISREG_CCSR)>>1, 1, &c->Status);
+ }
+ if (req->Present & PRESENT_PIN_REPLACE) {
+ c->Pin = req->Pin;
+ write_cis_mem(s, 1, (base + CISREG_PRR)>>1, 1, &c->Pin);
+ }
+ if (req->Present & PRESENT_EXT_STATUS) {
+ c->ExtStatus = req->ExtStatus;
+ write_cis_mem(s, 1, (base + CISREG_ESR)>>1, 1, &c->ExtStatus);
+ }
+ if (req->Present & PRESENT_IOBASE_0) {
+ i = c->io.BasePort1 & 0xff;
+ write_cis_mem(s, 1, (base + CISREG_IOBASE_0)>>1, 1, &i);
+ i = (c->io.BasePort1 >> 8) & 0xff;
+ write_cis_mem(s, 1, (base + CISREG_IOBASE_1)>>1, 1, &i);
+ }
+ if (req->Present & PRESENT_IOSIZE) {
+ i = c->io.NumPorts1 + c->io.NumPorts2 - 1;
+ write_cis_mem(s, 1, (base + CISREG_IOSIZE)>>1, 1, &i);
+ }
+
+ /* Configure I/O windows */
+ if (c->state & CONFIG_IO_REQ) {
+ iomap.speed = io_speed;
+ for (i = 0; i < MAX_IO_WIN; i++)
+ if (s->io[i].NumPorts != 0) {
+ iomap.map = i;
+ iomap.flags = MAP_ACTIVE;
+ switch (s->io[i].Attributes & IO_DATA_PATH_WIDTH) {
+ case IO_DATA_PATH_WIDTH_16:
+ iomap.flags |= MAP_16BIT; break;
+ case IO_DATA_PATH_WIDTH_AUTO:
+ iomap.flags |= MAP_AUTOSZ; break;
+ default:
+ break;
+ }
+ iomap.start = s->io[i].BasePort;
+ iomap.stop = iomap.start + s->io[i].NumPorts - 1;
+ s->ss_entry(s->sock, SS_SetIOMap, &iomap);
+ s->io[i].Config++;
+ }
+ }
+
+ c->state |= CONFIG_LOCKED;
+ handle->state |= CLIENT_CONFIG_LOCKED;
+ return CS_SUCCESS;
+} /* request_configuration */
+
+/*======================================================================
+
+ Request_io() reserves ranges of port addresses for a socket.
+ I have not implemented range sharing or alias addressing.
+
+======================================================================*/
+
+static int request_io(client_handle_t handle, io_req_t *req)
+{
+ socket_info_t *s;
+ config_t *c;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ s = SOCKET(handle);
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+
+ if (handle->state & CLIENT_CARDBUS) {
+#ifdef CONFIG_CARDBUS
+ int ret = cb_config(s);
+ if (ret == CS_SUCCESS)
+ handle->state |= CLIENT_IO_REQ;
+ return ret;
+#else
+ return CS_UNSUPPORTED_FUNCTION;
+#endif
+ }
+
+ if (!req)
+ return CS_UNSUPPORTED_MODE;
+ c = CONFIG(handle);
+ if (c->state & CONFIG_LOCKED)
+ return CS_CONFIGURATION_LOCKED;
+ if (c->state & CONFIG_IO_REQ)
+ return CS_IN_USE;
+ if (req->Attributes1 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS))
+ return CS_BAD_ATTRIBUTE;
+ if ((req->NumPorts2 > 0) &&
+ (req->Attributes2 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS)))
+ return CS_BAD_ATTRIBUTE;
+
+ if (alloc_io_space(s, req->Attributes1, &req->BasePort1,
+ req->NumPorts1, handle->dev_info))
+ return CS_IN_USE;
+
+ if (req->NumPorts2) {
+ if (alloc_io_space(s, req->Attributes2, &req->BasePort2,
+ req->NumPorts2, handle->dev_info)) {
+ release_io_space(s, req->BasePort1, req->NumPorts1);
+ return CS_IN_USE;
+ }
+ }
+
+ c->io = *req;
+ c->state |= CONFIG_IO_REQ;
+ handle->state |= CLIENT_IO_REQ;
+ return CS_SUCCESS;
+} /* request_io */
+
+/*======================================================================
+
+ Request_irq() reserves an irq for this client.
+
+ Also, since Linux only reserves irq's when they are actually
+ hooked, we don't guarantee that an irq will still be available
+ when the configuration is locked. Now that I think about it,
+ there might be a way to fix this using a dummy handler.
+
+======================================================================*/
+
+static int cs_request_irq(client_handle_t handle, irq_req_t *req)
+{
+ socket_info_t *s;
+ config_t *c;
+ int try, ret = 0, irq = 0;
+ u_int mask;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ s = SOCKET(handle);
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+ c = CONFIG(handle);
+ if (c->state & CONFIG_LOCKED)
+ return CS_CONFIGURATION_LOCKED;
+ if (c->state & CONFIG_IRQ_REQ)
+ return CS_IN_USE;
+
+ /* Short cut: if the interrupt is PCI, there are no options */
+ if (s->cap.irq_mask == (1 << s->cap.pci_irq))
+ irq = s->cap.pci_irq;
+#ifdef CONFIG_ISA
+ else if (s->irq.AssignedIRQ != 0) {
+ /* If the interrupt is already assigned, it must match */
+ irq = s->irq.AssignedIRQ;
+ if (req->IRQInfo1 & IRQ_INFO2_VALID) {
+ mask = req->IRQInfo2 & s->cap.irq_mask;
+ ret = ((mask >> irq) & 1) ? 0 : CS_BAD_ARGS;
+ } else
+ ret = ((req->IRQInfo1&IRQ_MASK) == irq) ? 0 : CS_BAD_ARGS;
+ } else {
+ ret = CS_IN_USE;
+ if (req->IRQInfo1 & IRQ_INFO2_VALID) {
+ mask = req->IRQInfo2 & s->cap.irq_mask;
+ mask &= ~(1 << s->cap.pci_irq);
+ for (try = 0; try < 2; try++) {
+ for (irq = 0; irq < 32; irq++)
+ if ((mask >> irq) & 1) {
+ ret = try_irq(req->Attributes, irq, try);
+ if (ret == 0) break;
+ }
+ if (ret == 0) break;
+ }
+ } else {
+ irq = req->IRQInfo1 & IRQ_MASK;
+ ret = try_irq(req->Attributes, irq, 1);
+ }
+ }
+#endif
+ if (ret != 0) return ret;
+
+ if (req->Attributes & IRQ_HANDLE_PRESENT) {
+ if (bus_request_irq(s->cap.bus, irq, req->Handler,
+ ((req->Attributes & IRQ_TYPE_DYNAMIC_SHARING) ||
+ (s->functions > 1) ||
+ (irq == s->cap.pci_irq)) ? SA_SHIRQ : 0,
+ handle->dev_info, req->Instance))
+ return CS_IN_USE;
+ }
+
+ c->irq.Attributes = req->Attributes;
+ s->irq.AssignedIRQ = req->AssignedIRQ = irq;
+ s->irq.Config++;
+
+ c->state |= CONFIG_IRQ_REQ;
+ handle->state |= CLIENT_IRQ_REQ;
+ return CS_SUCCESS;
+} /* cs_request_irq */
+
+/*======================================================================
+
+ Request_window() establishes a mapping between card memory space
+ and system memory space.
+
+======================================================================*/
+
+static int request_window(client_handle_t *handle, win_req_t *req)
+{
+ socket_info_t *s;
+ window_t *win;
+ int w;
+
+ if (CHECK_HANDLE(*handle))
+ return CS_BAD_HANDLE;
+ s = SOCKET(*handle);
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+ if (req->Attributes & (WIN_PAGED | WIN_SHARED))
+ return CS_BAD_ATTRIBUTE;
+
+ for (w = 0; w < MAX_WIN; w++)
+ if (!(s->state & SOCKET_WIN_REQ(w))) break;
+ if (w == MAX_WIN)
+ return CS_OUT_OF_RESOURCE;
+
+ /* Window size defaults to smallest available */
+ if (req->Size == 0)
+ req->Size = s->cap.map_size;
+
+ /* Allocate system memory window */
+ win = &s->win[w];
+ win->magic = WINDOW_MAGIC;
+ win->index = w;
+ win->handle = *handle;
+ win->sock = s;
+ win->base = req->Base;
+ win->size = req->Size;
+ if (find_mem_region(&win->base, win->size, (*handle)->dev_info,
+ ((s->cap.features & SS_CAP_MEM_ALIGN) ?
+ req->Size : s->cap.map_size),
+ (req->Attributes & WIN_MAP_BELOW_1MB) ||
+ !(s->cap.features & SS_CAP_PAGE_REGS)))
+ return CS_IN_USE;
+ req->Base = win->base;
+ (*handle)->state |= CLIENT_WIN_REQ(w);
+
+ /* Configure the socket controller */
+ win->ctl.map = w+1;
+ win->ctl.flags = 0;
+ win->ctl.speed = req->AccessSpeed;
+ if (req->Attributes & WIN_MEMORY_TYPE)
+ win->ctl.flags |= MAP_ATTRIB;
+ if (req->Attributes & WIN_ENABLE)
+ win->ctl.flags |= MAP_ACTIVE;
+ if (req->Attributes & WIN_DATA_WIDTH)
+ win->ctl.flags |= MAP_16BIT;
+ if (req->Attributes & WIN_USE_WAIT)
+ win->ctl.flags |= MAP_USE_WAIT;
+ win->ctl.sys_start = req->Base;
+ win->ctl.sys_stop = req->Base + req->Size-1;
+ win->ctl.card_start = 0;
+ if (s->ss_entry(s->sock, SS_SetMemMap, &win->ctl) != 0)
+ return CS_BAD_ARGS;
+ s->state |= SOCKET_WIN_REQ(w);
+
+ /* Return window handle */
+ *handle = (client_handle_t)win;
+
+ return CS_SUCCESS;
+} /* request_window */
+
+/*======================================================================
+
+ I'm not sure which "reset" function this is supposed to use,
+ but for now, it uses the low-level interface's reset, not the
+ CIS register.
+
+======================================================================*/
+
+static int reset_card(client_handle_t handle, client_req_t *req)
+{
+ int i, ret;
+ socket_info_t *s;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ i = handle->Socket; s = socket_table[i];
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+ if (s->state & SOCKET_RESET_PENDING)
+ return CS_IN_USE;
+ s->state |= SOCKET_RESET_PENDING;
+
+ ret = send_event(s, CS_EVENT_RESET_REQUEST, CS_EVENT_PRI_LOW);
+ if (ret != 0) {
+ s->state &= ~SOCKET_RESET_PENDING;
+ handle->event_callback_args.info = (void *)(u_long)ret;
+ EVENT(handle, CS_EVENT_RESET_COMPLETE, CS_EVENT_PRI_LOW);
+ } 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(i);
+ }
+ return CS_SUCCESS;
+} /* reset_card */
+
+/*======================================================================
+
+ These shut down or wake up a socket. They are sort of user
+ initiated versions of the APM suspend and resume actions.
+
+======================================================================*/
+
+static int suspend_card(client_handle_t handle, client_req_t *req)
+{
+ int i;
+ socket_info_t *s;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ i = handle->Socket; s = socket_table[i];
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+ if (s->state & SOCKET_SUSPEND)
+ return CS_IN_USE;
+
+ DEBUG(1, "cs: suspending socket %d\n", i);
+ send_event(s, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW);
+ s->ss_entry(s->sock, SS_SetSocket, &dead_socket);
+ s->state |= SOCKET_SUSPEND;
+
+ return CS_SUCCESS;
+} /* suspend_card */
+
+static int resume_card(client_handle_t handle, client_req_t *req)
+{
+ int i;
+ socket_info_t *s;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ i = handle->Socket; s = socket_table[i];
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+ if (!(s->state & SOCKET_SUSPEND))
+ return CS_IN_USE;
+
+ DEBUG(1, "cs: waking up socket %d\n", i);
+ setup_socket(i);
+
+ return CS_SUCCESS;
+} /* resume_card */
+
+/*======================================================================
+
+ These handle user requests to eject or insert a card.
+
+======================================================================*/
+
+static int eject_card(client_handle_t handle, client_req_t *req)
+{
+ int i, ret;
+ socket_info_t *s;
+ u_long flags;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ i = handle->Socket; s = socket_table[i];
+ if (!(s->state & SOCKET_PRESENT))
+ return CS_NO_CARD;
+
+ DEBUG(1, "cs: user eject request on socket %d\n", i);
+
+ ret = send_event(s, CS_EVENT_EJECTION_REQUEST, CS_EVENT_PRI_LOW);
+ if (ret != 0)
+ return ret;
+
+ spin_lock_irqsave(&s->lock, flags);
+ do_shutdown(s);
+ spin_unlock_irqrestore(&s->lock, flags);
+
+ return CS_SUCCESS;
+
+} /* eject_card */
+
+static int insert_card(client_handle_t handle, client_req_t *req)
+{
+ int i, status;
+ socket_info_t *s;
+ u_long flags;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ i = handle->Socket; s = socket_table[i];
+ if (s->state & SOCKET_PRESENT)
+ return CS_IN_USE;
+
+ 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);
+ s->ss_entry(i, SS_GetStatus, &status);
+ if (status & SS_DETECT)
+ setup_socket(i);
+ else {
+ s->state &= ~SOCKET_SETUP_PENDING;
+ return CS_NO_CARD;
+ }
+ } else
+ spin_unlock_irqrestore(&s->lock, flags);
+
+ return CS_SUCCESS;
+} /* insert_card */
+
+/*======================================================================
+
+ Maybe this should send a CS_EVENT_CARD_INSERTION event if we
+ haven't sent one to this client yet?
+
+======================================================================*/
+
+static int set_event_mask(client_handle_t handle, eventmask_t *mask)
+{
+ u_int events, bit;
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ if (handle->Attributes & CONF_EVENT_MASK_VALID)
+ return CS_BAD_SOCKET;
+ handle->EventMask = mask->EventMask;
+ events = handle->PendingEvents & handle->EventMask;
+ handle->PendingEvents -= events;
+ while (events != 0) {
+ bit = ((events ^ (events-1)) + 1) >> 1;
+ EVENT(handle, bit, CS_EVENT_PRI_LOW);
+ events -= bit;
+ }
+ return CS_SUCCESS;
+} /* set_event_mask */
+
+/*====================================================================*/
+
+static int report_error(client_handle_t handle, error_info_t *err)
+{
+ int i;
+ char *serv;
+
+ if (CHECK_HANDLE(handle))
+ printk(KERN_NOTICE);
+ else
+ printk(KERN_NOTICE "%s: ", handle->dev_info);
+
+ for (i = 0; i < SERVICE_COUNT; i++)
+ if (service_table[i].key == err->func) break;
+ if (i < SERVICE_COUNT)
+ serv = service_table[i].msg;
+ else
+ serv = "Unknown service number";
+
+ for (i = 0; i < ERROR_COUNT; i++)
+ if (error_table[i].key == err->retcode) break;
+ if (i < ERROR_COUNT)
+ printk("%s: %s\n", serv, error_table[i].msg);
+ else
+ printk("%s: Unknown error code %#x\n", serv, err->retcode);
+
+ return CS_SUCCESS;
+} /* report_error */
+
+/*====================================================================*/
+
+int CardServices(int func, void *a1, void *a2, void *a3)
+{
+
+#ifdef PCMCIA_DEBUG
+ if (pc_debug > 1) {
+ int i;
+ for (i = 0; i < SERVICE_COUNT; i++)
+ if (service_table[i].key == func) break;
+ if (i < SERVICE_COUNT)
+ printk(KERN_DEBUG "cs: CardServices(%s, 0x%p, 0x%p)\n",
+ service_table[i].msg, a1, a2);
+ else
+ printk(KERN_DEBUG "cs: CardServices(Unknown func %d, "
+ "0x%p, 0x%p)\n", func, a1, a2);
+ }
+#endif
+ switch (func) {
+ case AccessConfigurationRegister:
+ return access_configuration_register(a1, a2); break;
+ case AdjustResourceInfo:
+ return adjust_resource_info(a1, a2); break;
+ case CheckEraseQueue:
+ return check_erase_queue(a1); break;
+ case CloseMemory:
+ return close_memory(a1); break;
+ case CopyMemory:
+ return copy_memory(a1, a2); break;
+ case DeregisterClient:
+ return deregister_client(a1); break;
+ case DeregisterEraseQueue:
+ return deregister_erase_queue(a1); break;
+ case GetFirstClient:
+ return get_first_client(a1, a2); break;
+ case GetCardServicesInfo:
+ return get_card_services_info(a1); break;
+ case GetConfigurationInfo:
+ return get_configuration_info(a1, a2); break;
+ case GetNextClient:
+ return get_next_client(a1, a2); break;
+ case GetFirstRegion:
+ return get_first_region(a1, a2); break;
+ case GetFirstTuple:
+ return get_first_tuple(a1, a2); break;
+ case GetNextRegion:
+ return get_next_region(a1, a2); break;
+ case GetNextTuple:
+ return get_next_tuple(a1, a2); break;
+ case GetStatus:
+ return get_status(a1, a2); break;
+ case GetTupleData:
+ return get_tuple_data(a1, a2); break;
+ case MapMemPage:
+ return map_mem_page(a1, a2); break;
+ case ModifyConfiguration:
+ return modify_configuration(a1, a2); break;
+ case ModifyWindow:
+ return modify_window(a1, a2); break;
+ case OpenMemory:
+ return open_memory(a1, a2);
+ case ParseTuple:
+ return parse_tuple(a1, a2, a3); break;
+ case ReadMemory:
+ return read_memory(a1, a2, a3); break;
+ case RegisterClient:
+ return register_client(a1, a2); break;
+ case RegisterEraseQueue:
+ return register_erase_queue(a1, a2); break;
+ case RegisterMTD:
+ return register_mtd(a1, a2); break;
+ case ReleaseConfiguration:
+ return release_configuration(a1, a2); break;
+ case ReleaseIO:
+ return release_io(a1, a2); break;
+ case ReleaseIRQ:
+ return cs_release_irq(a1, a2); break;
+ case ReleaseWindow:
+ return release_window(a1); break;
+ case RequestConfiguration:
+ return request_configuration(a1, a2); break;
+ case RequestIO:
+ return request_io(a1, a2); break;
+ case RequestIRQ:
+ return cs_request_irq(a1, a2); break;
+ case RequestWindow:
+ return request_window(a1, a2); break;
+ case ResetCard:
+ return reset_card(a1, a2); break;
+ case SetEventMask:
+ return set_event_mask(a1, a2); break;
+ case ValidateCIS:
+ return validate_cis(a1, a2); break;
+ case WriteMemory:
+ return write_memory(a1, a2, a3); break;
+ case BindDevice:
+ return bind_device(a1); break;
+ case BindMTD:
+ return bind_mtd(a1); break;
+ case ReportError:
+ return report_error(a1, a2); break;
+ case SuspendCard:
+ return suspend_card(a1, a2); break;
+ case ResumeCard:
+ return resume_card(a1, a2); break;
+ case EjectCard:
+ return eject_card(a1, a2); break;
+ case InsertCard:
+ return insert_card(a1, a2); break;
+ case ReplaceCIS:
+ return replace_cis(a1, a2); break;
+ case GetFirstWindow:
+ return get_first_window(a1, a2); break;
+ case GetNextWindow:
+ return get_next_window(a1, a2); break;
+ case GetMemPage:
+ return get_mem_page(a1, a2); break;
+ default:
+ return CS_UNSUPPORTED_FUNCTION; break;
+ }
+
+} /* CardServices */
+
+/*======================================================================
+
+ OS-specific module glue goes here
+
+======================================================================*/
+
+EXPORT_SYMBOL(register_ss_entry);
+EXPORT_SYMBOL(unregister_ss_entry);
+EXPORT_SYMBOL(CardServices);
+EXPORT_SYMBOL(MTDHelperEntry);
+
+static int pcmcia_cs_init(void)
+{
+ printk(KERN_INFO "%s\n", release);
+ printk(KERN_INFO " %s\n", options);
+ DEBUG(0, "%s\n", version);
+#ifdef CONFIG_APM
+ if (do_apm)
+ apm_register_callback(&handle_apm_event);
+#endif
+#ifdef CONFIG_PROC_FS
+ proc_pccard = create_proc_entry("pccard", S_IFDIR, proc_bus);
+#endif
+ return 0;
+}
+
+#ifdef MODULE
+
+int init_module(void)
+{
+ return pcmcia_cs_init();
+}
+
+void cleanup_module(void)
+{
+ printk(KERN_INFO "unloading PCMCIA Card Services\n");
+#ifdef CONFIG_PROC_FS
+ if (proc_pccard) {
+ remove_proc_entry("pccard", proc_bus);
+ }
+#endif
+#ifdef CONFIG_APM
+ if (do_apm)
+ apm_unregister_callback(&handle_apm_event);
+#endif
+ release_resource_db();
+}
+
+#else
+
+extern int pcmcia_ds_init(void);
+extern int pcmcia_i82365_init(void);
+
+int pcmcia_init(void)
+{
+ /* Start core services */
+ pcmcia_cs_init();
+
+ /* Load the socket drivers */
+ pcmcia_i82365_init();
+
+ /* Get the ball rolling.. */
+ return pcmcia_ds_init();
+}
+
+#endif
+
+/*====================================================================*/
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)