#include <pcmcia/cistpl.h>
#include "cs_internal.h"
-#ifdef DEBUG
-extern int ds_pc_debug;
-#define cs_socket_name(skt) ((skt)->dev.class_id)
-
-#define ds_dbg(skt, lvl, fmt, arg...) do { \
- if (ds_pc_debug >= lvl) \
- printk(KERN_DEBUG "ds: %s: " fmt, \
- cs_socket_name(skt) , ## arg); \
-} while (0)
-#else
-#define ds_dbg(lvl, fmt, arg...) do { } while (0)
-#endif
+static void retry_erase_list(erase_busy_t *list, u_int cause);
+
+/*======================================================================
+
+ This function handles submitting an MTD request, and retrying
+ requests when an MTD is busy.
+
+ An MTD request should never block.
+
+======================================================================*/
+
+static int do_mtd_request(memory_handle_t handle, mtd_request_t *req,
+ caddr_t buf)
+{
+ int ret, tries;
+ client_t *mtd;
+ struct pcmcia_socket *s;
+
+ mtd = handle->mtd;
+ if (mtd == NULL)
+ return CS_GENERAL_FAILURE;
+ s = SOCKET(mtd);
+ for (ret = tries = 0; tries < 100; tries++) {
+ mtd->event_callback_args.mtdrequest = req;
+ mtd->event_callback_args.buffer = buf;
+ ret = EVENT(mtd, CS_EVENT_MTD_REQUEST, CS_EVENT_PRI_LOW);
+ if (ret != CS_BUSY)
+ break;
+ switch (req->Status) {
+ case MTD_WAITREQ:
+ /* Not that we should ever need this... */
+ interruptible_sleep_on_timeout(&mtd->mtd_req, HZ);
+ break;
+ case MTD_WAITTIMER:
+ case MTD_WAITRDY:
+ interruptible_sleep_on_timeout(&mtd->mtd_req, req->Timeout*HZ/1000);
+ req->Function |= MTD_REQ_TIMEOUT;
+ break;
+ case MTD_WAITPOWER:
+ interruptible_sleep_on(&mtd->mtd_req);
+ break;
+ }
+ if (signal_pending(current))
+ printk(KERN_NOTICE "cs: do_mtd_request interrupted!\n");
+ }
+ if (tries == 20) {
+ printk(KERN_NOTICE "cs: MTD request timed out!\n");
+ ret = CS_GENERAL_FAILURE;
+ }
+ wake_up_interruptible(&mtd->mtd_req);
+ retry_erase_list(&mtd->erase_busy, 0);
+ return ret;
+} /* do_mtd_request */
+
+/*======================================================================
+
+ This stuff is all for handling asynchronous erase requests. It
+ is complicated because all the retry stuff has to be dealt with
+ in timer interrupts or in the card status event handler.
+
+======================================================================*/
+
+static void insert_queue(struct pcmcia_socket *s, erase_busy_t *head, erase_busy_t *entry)
+{
+ cs_dbg(s, 2, "adding 0x%p to queue 0x%p\n", entry, head);
+ entry->next = head;
+ entry->prev = head->prev;
+ head->prev->next = entry;
+ head->prev = entry;
+}
+
+static void remove_queue(struct pcmcia_socket *s, erase_busy_t *entry)
+{
+ cs_dbg(s, 2, "unqueueing 0x%p\n", entry);
+ entry->next->prev = entry->prev;
+ entry->prev->next = entry->next;
+}
+
+static void retry_erase(erase_busy_t *busy, u_int cause)
+{
+ eraseq_entry_t *erase = busy->erase;
+ mtd_request_t req;
+ client_t *mtd;
+ struct pcmcia_socket *s;
+ int ret;
+
+ mtd = erase->Handle->mtd;
+ s = SOCKET(mtd);
+
+ cs_dbg(s, 2, "trying erase request 0x%p...\n", busy);
+ if (busy->next)
+ remove_queue(s, busy);
+ req.Function = MTD_REQ_ERASE | cause;
+ req.TransferLength = erase->Size;
+ req.DestCardOffset = erase->Offset + erase->Handle->info.CardOffset;
+ req.MediaID = erase->Handle->MediaID;
+ mtd->event_callback_args.mtdrequest = &req;
+ ret = EVENT(mtd, CS_EVENT_MTD_REQUEST, CS_EVENT_PRI_LOW);
+ if (ret == CS_BUSY) {
+ cs_dbg(s, 2, " Status = %d, requeueing.\n", req.Status);
+ switch (req.Status) {
+ case MTD_WAITREQ:
+ case MTD_WAITPOWER:
+ insert_queue(s, &mtd->erase_busy, busy);
+ break;
+ case MTD_WAITTIMER:
+ case MTD_WAITRDY:
+ if (req.Status == MTD_WAITRDY)
+ insert_queue(s, &s->erase_busy, busy);
+ mod_timer(&busy->timeout, jiffies + req.Timeout*HZ/1000);
+ break;
+ }
+ } else {
+ /* update erase queue status */
+ cs_dbg(s, 2, " Ret = %d\n", ret);
+ switch (ret) {
+ case CS_SUCCESS:
+ erase->State = ERASE_PASSED; break;
+ case CS_WRITE_PROTECTED:
+ erase->State = ERASE_MEDIA_WRPROT; break;
+ case CS_BAD_OFFSET:
+ erase->State = ERASE_BAD_OFFSET; break;
+ case CS_BAD_SIZE:
+ erase->State = ERASE_BAD_SIZE; break;
+ case CS_NO_CARD:
+ erase->State = ERASE_BAD_SOCKET; break;
+ default:
+ erase->State = ERASE_FAILED; break;
+ }
+ busy->client->event_callback_args.info = erase;
+ EVENT(busy->client, CS_EVENT_ERASE_COMPLETE, CS_EVENT_PRI_LOW);
+ kfree(busy);
+ /* Resubmit anything waiting for a request to finish */
+ wake_up_interruptible(&mtd->mtd_req);
+ retry_erase_list(&mtd->erase_busy, 0);
+ }
+} /* retry_erase */
+
+static void retry_erase_list(erase_busy_t *list, u_int cause)
+{
+ erase_busy_t tmp = *list;
+
+ cs_dbg(SOCKET(list->client), 2, "rescanning erase queue list 0x%p\n", list);
+ if (list->next == list)
+ return;
+ /* First, truncate the original list */
+ list->prev->next = &tmp;
+ list->next->prev = &tmp;
+ list->prev = list->next = list;
+ tmp.prev->next = &tmp;
+ tmp.next->prev = &tmp;
+
+ /* Now, retry each request, in order. */
+ while (tmp.next != &tmp)
+ retry_erase(tmp.next, cause);
+} /* retry_erase_list */
+
+static void handle_erase_timeout(u_long arg)
+{
+ erase_busy_t *busy = (erase_busy_t *)arg;
+ cs_dbg(SOCKET(busy->client), 0, "erase timeout for entry 0x%lx\n", arg);
+ retry_erase(busy, MTD_REQ_TIMEOUT);
+}
+
+static void setup_erase_request(client_handle_t handle, eraseq_entry_t *erase)
+{
+ erase_busy_t *busy;
+ region_info_t *info;
+
+ if (CHECK_REGION(erase->Handle))
+ erase->State = ERASE_BAD_SOCKET;
+ else {
+ info = &erase->Handle->info;
+ if ((erase->Offset >= info->RegionSize) ||
+ (erase->Offset & (info->BlockSize-1)))
+ erase->State = ERASE_BAD_OFFSET;
+ else if ((erase->Offset+erase->Size > info->RegionSize) ||
+ (erase->Size & (info->BlockSize-1)))
+ erase->State = ERASE_BAD_SIZE;
+ else {
+ erase->State = 1;
+ busy = kmalloc(sizeof(erase_busy_t), GFP_KERNEL);
+ if (!busy) {
+ erase->State = ERASE_FAILED;
+ return;
+ }
+ busy->erase = erase;
+ busy->client = handle;
+ init_timer(&busy->timeout);
+ busy->timeout.data = (u_long)busy;
+ busy->timeout.function = &handle_erase_timeout;
+ busy->prev = busy->next = NULL;
+ retry_erase(busy, 0);
+ }
+ }
+} /* setup_erase_request */
+
+/*======================================================================
+
+ MTD helper functions
+
+======================================================================*/
+
+static int mtd_modify_window(window_handle_t win, mtd_mod_win_t *req)
+{
+ if ((win == NULL) || (win->magic != WINDOW_MAGIC))
+ return CS_BAD_HANDLE;
+ win->ctl.flags = MAP_16BIT | MAP_ACTIVE;
+ if (req->Attributes & WIN_USE_WAIT)
+ win->ctl.flags |= MAP_USE_WAIT;
+ if (req->Attributes & WIN_MEMORY_TYPE)
+ win->ctl.flags |= MAP_ATTRIB;
+ win->ctl.speed = req->AccessSpeed;
+ win->ctl.card_start = req->CardOffset;
+ win->sock->ops->set_mem_map(win->sock, &win->ctl);
+ return CS_SUCCESS;
+}
+
+static int mtd_set_vpp(client_handle_t handle, mtd_vpp_req_t *req)
+{
+ struct pcmcia_socket *s;
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ if (req->Vpp1 != req->Vpp2)
+ return CS_BAD_VPP;
+ s = SOCKET(handle);
+ s->socket.Vpp = req->Vpp1;
+ if (s->ops->set_socket(s, &s->socket))
+ return CS_BAD_VPP;
+ return CS_SUCCESS;
+}
+
+static int mtd_rdy_mask(client_handle_t handle, mtd_rdy_req_t *req)
+{
+ struct pcmcia_socket *s;
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ s = SOCKET(handle);
+ if (req->Mask & CS_EVENT_READY_CHANGE)
+ s->socket.csc_mask |= SS_READY;
+ else
+ s->socket.csc_mask &= ~SS_READY;
+ if (s->ops->set_socket(s, &s->socket))
+ return CS_GENERAL_FAILURE;
+ return CS_SUCCESS;
+}
+
+int MTDHelperEntry(int func, void *a1, void *a2)
+{
+ switch (func) {
+ case MTDRequestWindow:
+ {
+ window_handle_t w;
+ int ret = pcmcia_request_window(a1, a2, &w);
+ a1 = w;
+ return ret;
+ }
+ break;
+ case MTDReleaseWindow:
+ return pcmcia_release_window(a1);
+ case MTDModifyWindow:
+ return mtd_modify_window(a1, a2); break;
+ case MTDSetVpp:
+ return mtd_set_vpp(a1, a2); break;
+ case MTDRDYMask:
+ return mtd_rdy_mask(a1, a2); break;
+ default:
+ return CS_UNSUPPORTED_FUNCTION; break;
+ }
+} /* MTDHelperEntry */
/*======================================================================
======================================================================*/
-static void setup_regions(struct pcmcia_socket *s, unsigned int function,
- int attr, memory_handle_t *list)
+static void setup_regions(client_handle_t handle, int attr,
+ memory_handle_t *list)
{
int i, code, has_jedec, has_geo;
u_int offset;
cistpl_device_geo_t geo;
memory_handle_t r;
- ds_dbg(s, 1, "setup_regions(0x%d, %d, 0x%p)\n",
- function, attr, list);
+ cs_dbg(SOCKET(handle), 1, "setup_regions(0x%p, %d, 0x%p)\n",
+ handle, attr, list);
code = (attr) ? CISTPL_DEVICE_A : CISTPL_DEVICE;
- if (pccard_read_tuple(s, function, code, &device) != CS_SUCCESS)
+ if (read_tuple(handle, code, &device) != CS_SUCCESS)
return;
code = (attr) ? CISTPL_JEDEC_A : CISTPL_JEDEC_C;
- has_jedec = (pccard_read_tuple(s, function, code, &jedec) == CS_SUCCESS);
+ has_jedec = (read_tuple(handle, code, &jedec) == CS_SUCCESS);
if (has_jedec && (device.ndev != jedec.nid)) {
- ds_dbg(s, 0, "Device info does not match JEDEC info.\n");
+ cs_dbg(SOCKET(handle), 0, "Device info does not match JEDEC info.\n");
has_jedec = 0;
}
code = (attr) ? CISTPL_DEVICE_GEO_A : CISTPL_DEVICE_GEO;
- has_geo = (pccard_read_tuple(s, function, code, &geo) == CS_SUCCESS);
+ has_geo = (read_tuple(handle, code, &geo) == CS_SUCCESS);
if (has_geo && (device.ndev != geo.ngeo)) {
- ds_dbg(s, 0, "Device info does not match geometry tuple.\n");
+ cs_dbg(SOCKET(handle), 0, "Device info does not match geometry tuple.\n");
has_geo = 0;
}
======================================================================*/
-static int pccard_match_region(memory_handle_t list, region_info_t *match)
-{
- if (list) {
- *match = list->info;
- return CS_SUCCESS;
- }
- return CS_NO_MORE_ITEMS;
-} /* match_region */
-
-int pccard_get_first_region(struct pcmcia_socket *s, region_info_t *rgn)
-{
- if (!(s->state & SOCKET_REGION_INFO)) {
- setup_regions(s, BIND_FN_ALL, 0, &s->c_region);
- setup_regions(s, BIND_FN_ALL, 1, &s->a_region);
- s->state |= SOCKET_REGION_INFO;
- }
-
- if (rgn->Attributes & REGION_TYPE_AM)
- return pccard_match_region(s->a_region, rgn);
- else
- return pccard_match_region(s->c_region, rgn);
-} /* get_first_region */
-
-int pccard_get_next_region(struct pcmcia_socket *s, region_info_t *rgn)
-{
- return pccard_match_region(rgn->next, rgn);
-} /* get_next_region */
-
-
-#ifdef CONFIG_PCMCIA_OBSOLETE
-
static int match_region(client_handle_t handle, memory_handle_t list,
region_info_t *match)
{
if ((handle->Attributes & INFO_MASTER_CLIENT) &&
(!(s->state & SOCKET_REGION_INFO))) {
- setup_regions(s, handle->Function, 0, &s->c_region);
- setup_regions(s, handle->Function, 1, &s->a_region);
+ setup_regions(handle, 0, &s->c_region);
+ setup_regions(handle, 1, &s->a_region);
s->state |= SOCKET_REGION_INFO;
}
else
return match_region(handle, s->c_region, rgn);
} /* get_first_region */
-EXPORT_SYMBOL(pcmcia_get_first_region);
int pcmcia_get_next_region(client_handle_t handle, region_info_t *rgn)
{
return CS_BAD_HANDLE;
return match_region(handle, rgn->next, rgn);
} /* get_next_region */
-EXPORT_SYMBOL(pcmcia_get_next_region);
-#endif
+/*======================================================================
+
+ Connect an MTD with a memory region.
+
+======================================================================*/
+
+int pcmcia_register_mtd(client_handle_t handle, mtd_reg_t *reg)
+{
+ memory_handle_t list;
+ struct pcmcia_socket *s;
+
+ if (CHECK_HANDLE(handle))
+ return CS_BAD_HANDLE;
+ s = SOCKET(handle);
+ if (reg->Attributes & REGION_TYPE_AM)
+ list = s->a_region;
+ else
+ list = s->c_region;
+ cs_dbg(s, 1, "register_mtd(0x%p, '%s', 0x%x)\n",
+ handle, handle->dev_info, reg->Offset);
+ while (list) {
+ if (list->info.CardOffset == reg->Offset) break;
+ list = list->info.next;
+ }
+ if (list && (list->mtd == NULL) &&
+ (strcmp(handle->dev_info, list->dev_info) == 0)) {
+ list->info.Attributes = reg->Attributes;
+ list->MediaID = reg->MediaID;
+ list->mtd = handle;
+ handle->mtd_count++;
+ return CS_SUCCESS;
+ } else
+ return CS_BAD_OFFSET;
+} /* register_mtd */
+
+/*======================================================================
+
+ Erase queue management functions
+
+======================================================================*/
+
+int pcmcia_register_erase_queue(client_handle_t *handle, eraseq_hdr_t *header,
+ eraseq_handle_t *e)
+{
+ eraseq_t *queue;
+
+ if ((handle == NULL) || CHECK_HANDLE(*handle))
+ return CS_BAD_HANDLE;
+ queue = kmalloc(sizeof(*queue), GFP_KERNEL);
+ if (!queue) return CS_OUT_OF_RESOURCE;
+ queue->eraseq_magic = ERASEQ_MAGIC;
+ queue->handle = *handle;
+ queue->count = header->QueueEntryCnt;
+ queue->entry = header->QueueEntryArray;
+ *e = queue;
+ return CS_SUCCESS;
+} /* register_erase_queue */
+
+int pcmcia_deregister_erase_queue(eraseq_handle_t eraseq)
+{
+ int i;
+ if (CHECK_ERASEQ(eraseq))
+ return CS_BAD_HANDLE;
+ for (i = 0; i < eraseq->count; i++)
+ if (ERASE_IN_PROGRESS(eraseq->entry[i].State)) break;
+ if (i < eraseq->count)
+ return CS_BUSY;
+ eraseq->eraseq_magic = 0;
+ kfree(eraseq);
+ return CS_SUCCESS;
+} /* deregister_erase_queue */
+
+int pcmcia_check_erase_queue(eraseq_handle_t eraseq)
+{
+ int i;
+ if (CHECK_ERASEQ(eraseq))
+ return CS_BAD_HANDLE;
+ for (i = 0; i < eraseq->count; i++)
+ if (eraseq->entry[i].State == ERASE_QUEUED)
+ setup_erase_request(eraseq->handle, &eraseq->entry[i]);
+ return CS_SUCCESS;
+} /* check_erase_queue */
+
+/*======================================================================
+
+ Look up the memory region matching the request, and return a
+ memory handle.
+
+======================================================================*/
+
+int pcmcia_open_memory(client_handle_t *handle, open_mem_t *open, memory_handle_t *mh)
+{
+ struct pcmcia_socket *s;
+ memory_handle_t region;
+
+ if ((handle == NULL) || CHECK_HANDLE(*handle))
+ return CS_BAD_HANDLE;
+ s = (*handle)->Socket;
+ if (open->Attributes & MEMORY_TYPE_AM)
+ region = s->a_region;
+ else
+ region = s->c_region;
+ while (region) {
+ if (region->info.CardOffset == open->Offset) break;
+ region = region->info.next;
+ }
+ if (region && region->mtd) {
+ *mh = region;
+ cs_dbg(s, 1, "open_memory(0x%p, 0x%x) = 0x%p\n",
+ handle, open->Offset, region);
+ return CS_SUCCESS;
+ } else
+ return CS_BAD_OFFSET;
+} /* open_memory */
+
+/*======================================================================
+
+ Close a memory handle from an earlier call to OpenMemory.
+
+ For the moment, I don't think this needs to do anything.
+
+======================================================================*/
+
+int pcmcia_close_memory(memory_handle_t handle)
+{
+ cs_dbg(SOCKET(handle->mtd), 1, "cs: close_memory(0x%p)\n", handle);
+ if (CHECK_REGION(handle))
+ return CS_BAD_HANDLE;
+ return CS_SUCCESS;
+} /* close_memory */
+
+/*======================================================================
+
+ Read from a memory device, using a handle previously returned
+ by a call to OpenMemory.
+
+======================================================================*/
+
+int pcmcia_read_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf)
+{
+ mtd_request_t mtd;
+ if (CHECK_REGION(handle))
+ return CS_BAD_HANDLE;
+ if (req->Offset >= handle->info.RegionSize)
+ return CS_BAD_OFFSET;
+ if (req->Offset+req->Count > handle->info.RegionSize)
+ return CS_BAD_SIZE;
+
+ mtd.SrcCardOffset = req->Offset + handle->info.CardOffset;
+ mtd.TransferLength = req->Count;
+ mtd.MediaID = handle->MediaID;
+ mtd.Function = MTD_REQ_READ;
+ if (req->Attributes & MEM_OP_BUFFER_KERNEL)
+ mtd.Function |= MTD_REQ_KERNEL;
+ return do_mtd_request(handle, &mtd, buf);
+} /* read_memory */
+
+/*======================================================================
+
+ Write to a memory device, using a handle previously returned by
+ a call to OpenMemory.
+
+======================================================================*/
+
+int pcmcia_write_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf)
+{
+ mtd_request_t mtd;
+ if (CHECK_REGION(handle))
+ return CS_BAD_HANDLE;
+ if (req->Offset >= handle->info.RegionSize)
+ return CS_BAD_OFFSET;
+ if (req->Offset+req->Count > handle->info.RegionSize)
+ return CS_BAD_SIZE;
+
+ mtd.DestCardOffset = req->Offset + handle->info.CardOffset;
+ mtd.TransferLength = req->Count;
+ mtd.MediaID = handle->MediaID;
+ mtd.Function = MTD_REQ_WRITE;
+ if (req->Attributes & MEM_OP_BUFFER_KERNEL)
+ mtd.Function |= MTD_REQ_KERNEL;
+ return do_mtd_request(handle, &mtd, buf);
+} /* write_memory */
+
+/*======================================================================
+
+ This isn't needed for anything I could think of.
+
+======================================================================*/
+
+int pcmcia_copy_memory(memory_handle_t handle, copy_op_t *req)
+{
+ if (CHECK_REGION(handle))
+ return CS_BAD_HANDLE;
+ return CS_UNSUPPORTED_FUNCTION;
+}
+