1 /*======================================================================
2
3 PCMCIA Bulk Memory Services
4
5 bulkmem.c 1.38 2000/09/25 19:29:51
6
7 The contents of this file are subject to the Mozilla Public
8 License Version 1.1 (the "License"); you may not use this file
9 except in compliance with the License. You may obtain a copy of
10 the License at http://www.mozilla.org/MPL/
11
12 Software distributed under the License is distributed on an "AS
13 IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
14 implied. See the License for the specific language governing
15 rights and limitations under the License.
16
17 The initial developer of the original code is David A. Hinds
18 <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
19 are Copyright (C) 1999 David A. Hinds. All Rights Reserved.
20
21 Alternatively, the contents of this file may be used under the
22 terms of the GNU General Public License version 2 (the "GPL"), in which
23 case the provisions of the GPL are applicable instead of the
24 above. If you wish to allow the use of your version of this file
25 only under the terms of the GPL and not to allow others to use
26 your version of this file under the MPL, indicate your decision
27 by deleting the provisions above and replace them with the notice
28 and other provisions required by the GPL. If you do not delete
29 the provisions above, a recipient may use your version of this
30 file under either the MPL or the GPL.
31
32 ======================================================================*/
33
34 #define __NO_VERSION__
35
36 #include <linux/module.h>
37 #include <linux/kernel.h>
38 #include <linux/string.h>
39 #include <linux/errno.h>
40 #include <linux/slab.h>
41 #include <linux/mm.h>
42 #include <linux/sched.h>
43 #include <linux/timer.h>
44 #include <linux/proc_fs.h>
45
46 #define IN_CARD_SERVICES
47 #include <pcmcia/cs_types.h>
48 #include <pcmcia/ss.h>
49 #include <pcmcia/cs.h>
50 #include <pcmcia/bulkmem.h>
51 #include <pcmcia/cistpl.h>
52 #include "cs_internal.h"
53
54 /*======================================================================
55
56 This function handles submitting an MTD request, and retrying
57 requests when an MTD is busy.
58
59 An MTD request should never block.
60
61 ======================================================================*/
62
do_mtd_request(memory_handle_t handle,mtd_request_t * req,caddr_t buf)63 static int do_mtd_request(memory_handle_t handle, mtd_request_t *req,
64 caddr_t buf)
65 {
66 int ret, tries;
67 client_t *mtd;
68 socket_info_t *s;
69
70 mtd = handle->mtd;
71 if (mtd == NULL)
72 return CS_GENERAL_FAILURE;
73 s = SOCKET(mtd);
74 for (ret = tries = 0; tries < 100; tries++) {
75 mtd->event_callback_args.mtdrequest = req;
76 mtd->event_callback_args.buffer = buf;
77 ret = EVENT(mtd, CS_EVENT_MTD_REQUEST, CS_EVENT_PRI_LOW);
78 if (ret != CS_BUSY)
79 break;
80 switch (req->Status) {
81 case MTD_WAITREQ:
82 /* Not that we should ever need this... */
83 interruptible_sleep_on_timeout(&mtd->mtd_req, HZ);
84 break;
85 case MTD_WAITTIMER:
86 case MTD_WAITRDY:
87 interruptible_sleep_on_timeout(&mtd->mtd_req, req->Timeout*HZ/1000);
88 req->Function |= MTD_REQ_TIMEOUT;
89 break;
90 case MTD_WAITPOWER:
91 interruptible_sleep_on(&mtd->mtd_req);
92 break;
93 }
94 if (signal_pending(current))
95 printk(KERN_NOTICE "cs: do_mtd_request interrupted!\n");
96 }
97 if (tries == 20) {
98 printk(KERN_NOTICE "cs: MTD request timed out!\n");
99 ret = CS_GENERAL_FAILURE;
100 }
101 wake_up_interruptible(&mtd->mtd_req);
102 retry_erase_list(&mtd->erase_busy, 0);
103 return ret;
104 } /* do_mtd_request */
105
106 /*======================================================================
107
108 This stuff is all for handling asynchronous erase requests. It
109 is complicated because all the retry stuff has to be dealt with
110 in timer interrupts or in the card status event handler.
111
112 ======================================================================*/
113
insert_queue(erase_busy_t * head,erase_busy_t * entry)114 static void insert_queue(erase_busy_t *head, erase_busy_t *entry)
115 {
116 DEBUG(2, "cs: adding 0x%p to queue 0x%p\n", entry, head);
117 entry->next = head;
118 entry->prev = head->prev;
119 head->prev->next = entry;
120 head->prev = entry;
121 }
122
remove_queue(erase_busy_t * entry)123 static void remove_queue(erase_busy_t *entry)
124 {
125 DEBUG(2, "cs: unqueueing 0x%p\n", entry);
126 entry->next->prev = entry->prev;
127 entry->prev->next = entry->next;
128 }
129
retry_erase(erase_busy_t * busy,u_int cause)130 static void retry_erase(erase_busy_t *busy, u_int cause)
131 {
132 eraseq_entry_t *erase = busy->erase;
133 mtd_request_t req;
134 client_t *mtd;
135 socket_info_t *s;
136 int ret;
137
138 DEBUG(2, "cs: trying erase request 0x%p...\n", busy);
139 if (busy->next)
140 remove_queue(busy);
141 req.Function = MTD_REQ_ERASE | cause;
142 req.TransferLength = erase->Size;
143 req.DestCardOffset = erase->Offset + erase->Handle->info.CardOffset;
144 req.MediaID = erase->Handle->MediaID;
145 mtd = erase->Handle->mtd;
146 s = SOCKET(mtd);
147 mtd->event_callback_args.mtdrequest = &req;
148 ret = EVENT(mtd, CS_EVENT_MTD_REQUEST, CS_EVENT_PRI_LOW);
149 if (ret == CS_BUSY) {
150 DEBUG(2, " Status = %d, requeueing.\n", req.Status);
151 switch (req.Status) {
152 case MTD_WAITREQ:
153 case MTD_WAITPOWER:
154 insert_queue(&mtd->erase_busy, busy);
155 break;
156 case MTD_WAITTIMER:
157 case MTD_WAITRDY:
158 if (req.Status == MTD_WAITRDY)
159 insert_queue(&s->erase_busy, busy);
160 mod_timer(&busy->timeout, jiffies + req.Timeout*HZ/1000);
161 break;
162 }
163 } else {
164 /* update erase queue status */
165 DEBUG(2, " Ret = %d\n", ret);
166 switch (ret) {
167 case CS_SUCCESS:
168 erase->State = ERASE_PASSED; break;
169 case CS_WRITE_PROTECTED:
170 erase->State = ERASE_MEDIA_WRPROT; break;
171 case CS_BAD_OFFSET:
172 erase->State = ERASE_BAD_OFFSET; break;
173 case CS_BAD_SIZE:
174 erase->State = ERASE_BAD_SIZE; break;
175 case CS_NO_CARD:
176 erase->State = ERASE_BAD_SOCKET; break;
177 default:
178 erase->State = ERASE_FAILED; break;
179 }
180 busy->client->event_callback_args.info = erase;
181 EVENT(busy->client, CS_EVENT_ERASE_COMPLETE, CS_EVENT_PRI_LOW);
182 kfree(busy);
183 /* Resubmit anything waiting for a request to finish */
184 wake_up_interruptible(&mtd->mtd_req);
185 retry_erase_list(&mtd->erase_busy, 0);
186 }
187 } /* retry_erase */
188
retry_erase_list(erase_busy_t * list,u_int cause)189 void retry_erase_list(erase_busy_t *list, u_int cause)
190 {
191 erase_busy_t tmp = *list;
192
193 DEBUG(2, "cs: rescanning erase queue list 0x%p\n", list);
194 if (list->next == list)
195 return;
196 /* First, truncate the original list */
197 list->prev->next = &tmp;
198 list->next->prev = &tmp;
199 list->prev = list->next = list;
200 tmp.prev->next = &tmp;
201 tmp.next->prev = &tmp;
202
203 /* Now, retry each request, in order. */
204 while (tmp.next != &tmp)
205 retry_erase(tmp.next, cause);
206 } /* retry_erase_list */
207
handle_erase_timeout(u_long arg)208 static void handle_erase_timeout(u_long arg)
209 {
210 DEBUG(0, "cs: erase timeout for entry 0x%lx\n", arg);
211 retry_erase((erase_busy_t *)arg, MTD_REQ_TIMEOUT);
212 }
213
setup_erase_request(client_handle_t handle,eraseq_entry_t * erase)214 static void setup_erase_request(client_handle_t handle, eraseq_entry_t *erase)
215 {
216 erase_busy_t *busy;
217 region_info_t *info;
218
219 if (CHECK_REGION(erase->Handle))
220 erase->State = ERASE_BAD_SOCKET;
221 else {
222 info = &erase->Handle->info;
223 if ((erase->Offset >= info->RegionSize) ||
224 (erase->Offset & (info->BlockSize-1)))
225 erase->State = ERASE_BAD_OFFSET;
226 else if ((erase->Offset+erase->Size > info->RegionSize) ||
227 (erase->Size & (info->BlockSize-1)))
228 erase->State = ERASE_BAD_SIZE;
229 else {
230 erase->State = 1;
231 busy = kmalloc(sizeof(erase_busy_t), GFP_KERNEL);
232 if (!busy) {
233 erase->State = ERASE_FAILED;
234 return;
235 }
236 busy->erase = erase;
237 busy->client = handle;
238 init_timer(&busy->timeout);
239 busy->timeout.data = (u_long)busy;
240 busy->timeout.function = &handle_erase_timeout;
241 busy->prev = busy->next = NULL;
242 retry_erase(busy, 0);
243 }
244 }
245 } /* setup_erase_request */
246
247 /*======================================================================
248
249 MTD helper functions
250
251 ======================================================================*/
252
mtd_modify_window(window_handle_t win,mtd_mod_win_t * req)253 static int mtd_modify_window(window_handle_t win, mtd_mod_win_t *req)
254 {
255 if ((win == NULL) || (win->magic != WINDOW_MAGIC))
256 return CS_BAD_HANDLE;
257 win->ctl.flags = MAP_16BIT | MAP_ACTIVE;
258 if (req->Attributes & WIN_USE_WAIT)
259 win->ctl.flags |= MAP_USE_WAIT;
260 if (req->Attributes & WIN_MEMORY_TYPE)
261 win->ctl.flags |= MAP_ATTRIB;
262 win->ctl.speed = req->AccessSpeed;
263 win->ctl.card_start = req->CardOffset;
264 win->sock->ss_entry->set_mem_map(win->sock->sock, &win->ctl);
265 return CS_SUCCESS;
266 }
267
mtd_set_vpp(client_handle_t handle,mtd_vpp_req_t * req)268 static int mtd_set_vpp(client_handle_t handle, mtd_vpp_req_t *req)
269 {
270 socket_info_t *s;
271 if (CHECK_HANDLE(handle))
272 return CS_BAD_HANDLE;
273 if (req->Vpp1 != req->Vpp2)
274 return CS_BAD_VPP;
275 s = SOCKET(handle);
276 s->socket.Vpp = req->Vpp1;
277 if (s->ss_entry->set_socket(s->sock, &s->socket))
278 return CS_BAD_VPP;
279 return CS_SUCCESS;
280 }
281
mtd_rdy_mask(client_handle_t handle,mtd_rdy_req_t * req)282 static int mtd_rdy_mask(client_handle_t handle, mtd_rdy_req_t *req)
283 {
284 socket_info_t *s;
285 if (CHECK_HANDLE(handle))
286 return CS_BAD_HANDLE;
287 s = SOCKET(handle);
288 if (req->Mask & CS_EVENT_READY_CHANGE)
289 s->socket.csc_mask |= SS_READY;
290 else
291 s->socket.csc_mask &= ~SS_READY;
292 if (s->ss_entry->set_socket(s->sock, &s->socket))
293 return CS_GENERAL_FAILURE;
294 return CS_SUCCESS;
295 }
296
MTDHelperEntry(int func,void * a1,void * a2)297 int MTDHelperEntry(int func, void *a1, void *a2)
298 {
299 switch (func) {
300 case MTDRequestWindow:
301 {
302 window_handle_t w;
303 int ret = pcmcia_request_window(a1, a2, &w);
304 a1 = w;
305 return ret;
306 }
307 break;
308 case MTDReleaseWindow:
309 return pcmcia_release_window(a1);
310 case MTDModifyWindow:
311 return mtd_modify_window(a1, a2); break;
312 case MTDSetVpp:
313 return mtd_set_vpp(a1, a2); break;
314 case MTDRDYMask:
315 return mtd_rdy_mask(a1, a2); break;
316 default:
317 return CS_UNSUPPORTED_FUNCTION; break;
318 }
319 } /* MTDHelperEntry */
320
321 /*======================================================================
322
323 This stuff is used by Card Services to initialize the table of
324 region info used for subsequent calls to GetFirstRegion and
325 GetNextRegion.
326
327 ======================================================================*/
328
setup_regions(client_handle_t handle,int attr,memory_handle_t * list)329 static void setup_regions(client_handle_t handle, int attr,
330 memory_handle_t *list)
331 {
332 int i, code, has_jedec, has_geo;
333 u_int offset;
334 cistpl_device_t device;
335 cistpl_jedec_t jedec;
336 cistpl_device_geo_t geo;
337 memory_handle_t r;
338
339 DEBUG(1, "cs: setup_regions(0x%p, %d, 0x%p)\n",
340 handle, attr, list);
341
342 code = (attr) ? CISTPL_DEVICE_A : CISTPL_DEVICE;
343 if (read_tuple(handle, code, &device) != CS_SUCCESS)
344 return;
345 code = (attr) ? CISTPL_JEDEC_A : CISTPL_JEDEC_C;
346 has_jedec = (read_tuple(handle, code, &jedec) == CS_SUCCESS);
347 if (has_jedec && (device.ndev != jedec.nid)) {
348 #ifdef PCMCIA_DEBUG
349 printk(KERN_DEBUG "cs: Device info does not match JEDEC info.\n");
350 #endif
351 has_jedec = 0;
352 }
353 code = (attr) ? CISTPL_DEVICE_GEO_A : CISTPL_DEVICE_GEO;
354 has_geo = (read_tuple(handle, code, &geo) == CS_SUCCESS);
355 if (has_geo && (device.ndev != geo.ngeo)) {
356 #ifdef PCMCIA_DEBUG
357 printk(KERN_DEBUG "cs: Device info does not match geometry tuple.\n");
358 #endif
359 has_geo = 0;
360 }
361
362 offset = 0;
363 for (i = 0; i < device.ndev; i++) {
364 if ((device.dev[i].type != CISTPL_DTYPE_NULL) &&
365 (device.dev[i].size != 0)) {
366 r = kmalloc(sizeof(*r), GFP_KERNEL);
367 if (!r) {
368 printk(KERN_NOTICE "cs: setup_regions: kmalloc failed!\n");
369 return;
370 }
371 r->region_magic = REGION_MAGIC;
372 r->state = 0;
373 r->dev_info[0] = '\0';
374 r->mtd = NULL;
375 r->info.Attributes = (attr) ? REGION_TYPE_AM : 0;
376 r->info.CardOffset = offset;
377 r->info.RegionSize = device.dev[i].size;
378 r->info.AccessSpeed = device.dev[i].speed;
379 if (has_jedec) {
380 r->info.JedecMfr = jedec.id[i].mfr;
381 r->info.JedecInfo = jedec.id[i].info;
382 } else
383 r->info.JedecMfr = r->info.JedecInfo = 0;
384 if (has_geo) {
385 r->info.BlockSize = geo.geo[i].buswidth *
386 geo.geo[i].erase_block * geo.geo[i].interleave;
387 r->info.PartMultiple =
388 r->info.BlockSize * geo.geo[i].partition;
389 } else
390 r->info.BlockSize = r->info.PartMultiple = 1;
391 r->info.next = *list; *list = r;
392 }
393 offset += device.dev[i].size;
394 }
395 } /* setup_regions */
396
397 /*======================================================================
398
399 This is tricky. When get_first_region() is called by Driver
400 Services, we initialize the region info table in the socket
401 structure. When it is called by an MTD, we can just scan the
402 table for matching entries.
403
404 ======================================================================*/
405
match_region(client_handle_t handle,memory_handle_t list,region_info_t * match)406 static int match_region(client_handle_t handle, memory_handle_t list,
407 region_info_t *match)
408 {
409 while (list != NULL) {
410 if (!(handle->Attributes & INFO_MTD_CLIENT) ||
411 (strcmp(handle->dev_info, list->dev_info) == 0)) {
412 *match = list->info;
413 return CS_SUCCESS;
414 }
415 list = list->info.next;
416 }
417 return CS_NO_MORE_ITEMS;
418 } /* match_region */
419
pcmcia_get_first_region(client_handle_t handle,region_info_t * rgn)420 int pcmcia_get_first_region(client_handle_t handle, region_info_t *rgn)
421 {
422 socket_info_t *s = SOCKET(handle);
423 if (CHECK_HANDLE(handle))
424 return CS_BAD_HANDLE;
425
426 if ((handle->Attributes & INFO_MASTER_CLIENT) &&
427 (!(s->state & SOCKET_REGION_INFO))) {
428 setup_regions(handle, 0, &s->c_region);
429 setup_regions(handle, 1, &s->a_region);
430 s->state |= SOCKET_REGION_INFO;
431 }
432
433 if (rgn->Attributes & REGION_TYPE_AM)
434 return match_region(handle, s->a_region, rgn);
435 else
436 return match_region(handle, s->c_region, rgn);
437 } /* get_first_region */
438
pcmcia_get_next_region(client_handle_t handle,region_info_t * rgn)439 int pcmcia_get_next_region(client_handle_t handle, region_info_t *rgn)
440 {
441 if (CHECK_HANDLE(handle))
442 return CS_BAD_HANDLE;
443 return match_region(handle, rgn->next, rgn);
444 } /* get_next_region */
445
446 /*======================================================================
447
448 Connect an MTD with a memory region.
449
450 ======================================================================*/
451
pcmcia_register_mtd(client_handle_t handle,mtd_reg_t * reg)452 int pcmcia_register_mtd(client_handle_t handle, mtd_reg_t *reg)
453 {
454 memory_handle_t list;
455 socket_info_t *s;
456
457 if (CHECK_HANDLE(handle))
458 return CS_BAD_HANDLE;
459 s = SOCKET(handle);
460 if (reg->Attributes & REGION_TYPE_AM)
461 list = s->a_region;
462 else
463 list = s->c_region;
464 DEBUG(1, "cs: register_mtd(0x%p, '%s', 0x%x)\n",
465 handle, handle->dev_info, reg->Offset);
466 while (list) {
467 if (list->info.CardOffset == reg->Offset) break;
468 list = list->info.next;
469 }
470 if (list && (list->mtd == NULL) &&
471 (strcmp(handle->dev_info, list->dev_info) == 0)) {
472 list->info.Attributes = reg->Attributes;
473 list->MediaID = reg->MediaID;
474 list->mtd = handle;
475 handle->mtd_count++;
476 return CS_SUCCESS;
477 } else
478 return CS_BAD_OFFSET;
479 } /* register_mtd */
480
481 /*======================================================================
482
483 Erase queue management functions
484
485 ======================================================================*/
486
pcmcia_register_erase_queue(client_handle_t * handle,eraseq_hdr_t * header,eraseq_handle_t * e)487 int pcmcia_register_erase_queue(client_handle_t *handle, eraseq_hdr_t *header,
488 eraseq_handle_t *e)
489 {
490 eraseq_t *queue;
491
492 if ((handle == NULL) || CHECK_HANDLE(*handle))
493 return CS_BAD_HANDLE;
494 queue = kmalloc(sizeof(*queue), GFP_KERNEL);
495 if (!queue) return CS_OUT_OF_RESOURCE;
496 queue->eraseq_magic = ERASEQ_MAGIC;
497 queue->handle = *handle;
498 queue->count = header->QueueEntryCnt;
499 queue->entry = header->QueueEntryArray;
500 *e = queue;
501 return CS_SUCCESS;
502 } /* register_erase_queue */
503
pcmcia_deregister_erase_queue(eraseq_handle_t eraseq)504 int pcmcia_deregister_erase_queue(eraseq_handle_t eraseq)
505 {
506 int i;
507 if (CHECK_ERASEQ(eraseq))
508 return CS_BAD_HANDLE;
509 for (i = 0; i < eraseq->count; i++)
510 if (ERASE_IN_PROGRESS(eraseq->entry[i].State)) break;
511 if (i < eraseq->count)
512 return CS_BUSY;
513 eraseq->eraseq_magic = 0;
514 kfree(eraseq);
515 return CS_SUCCESS;
516 } /* deregister_erase_queue */
517
pcmcia_check_erase_queue(eraseq_handle_t eraseq)518 int pcmcia_check_erase_queue(eraseq_handle_t eraseq)
519 {
520 int i;
521 if (CHECK_ERASEQ(eraseq))
522 return CS_BAD_HANDLE;
523 for (i = 0; i < eraseq->count; i++)
524 if (eraseq->entry[i].State == ERASE_QUEUED)
525 setup_erase_request(eraseq->handle, &eraseq->entry[i]);
526 return CS_SUCCESS;
527 } /* check_erase_queue */
528
529 /*======================================================================
530
531 Look up the memory region matching the request, and return a
532 memory handle.
533
534 ======================================================================*/
535
pcmcia_open_memory(client_handle_t * handle,open_mem_t * open,memory_handle_t * mh)536 int pcmcia_open_memory(client_handle_t *handle, open_mem_t *open, memory_handle_t *mh)
537 {
538 socket_info_t *s;
539 memory_handle_t region;
540
541 if ((handle == NULL) || CHECK_HANDLE(*handle))
542 return CS_BAD_HANDLE;
543 s = SOCKET(*handle);
544 if (open->Attributes & MEMORY_TYPE_AM)
545 region = s->a_region;
546 else
547 region = s->c_region;
548 while (region) {
549 if (region->info.CardOffset == open->Offset) break;
550 region = region->info.next;
551 }
552 if (region && region->mtd) {
553 *mh = region;
554 DEBUG(1, "cs: open_memory(0x%p, 0x%x) = 0x%p\n",
555 handle, open->Offset, region);
556 return CS_SUCCESS;
557 } else
558 return CS_BAD_OFFSET;
559 } /* open_memory */
560
561 /*======================================================================
562
563 Close a memory handle from an earlier call to OpenMemory.
564
565 For the moment, I don't think this needs to do anything.
566
567 ======================================================================*/
568
pcmcia_close_memory(memory_handle_t handle)569 int pcmcia_close_memory(memory_handle_t handle)
570 {
571 DEBUG(1, "cs: close_memory(0x%p)\n", handle);
572 if (CHECK_REGION(handle))
573 return CS_BAD_HANDLE;
574 return CS_SUCCESS;
575 } /* close_memory */
576
577 /*======================================================================
578
579 Read from a memory device, using a handle previously returned
580 by a call to OpenMemory.
581
582 ======================================================================*/
583
pcmcia_read_memory(memory_handle_t handle,mem_op_t * req,caddr_t buf)584 int pcmcia_read_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf)
585 {
586 mtd_request_t mtd;
587 if (CHECK_REGION(handle))
588 return CS_BAD_HANDLE;
589 if (req->Offset >= handle->info.RegionSize)
590 return CS_BAD_OFFSET;
591 if (req->Offset+req->Count > handle->info.RegionSize)
592 return CS_BAD_SIZE;
593
594 mtd.SrcCardOffset = req->Offset + handle->info.CardOffset;
595 mtd.TransferLength = req->Count;
596 mtd.MediaID = handle->MediaID;
597 mtd.Function = MTD_REQ_READ;
598 if (req->Attributes & MEM_OP_BUFFER_KERNEL)
599 mtd.Function |= MTD_REQ_KERNEL;
600 return do_mtd_request(handle, &mtd, buf);
601 } /* read_memory */
602
603 /*======================================================================
604
605 Write to a memory device, using a handle previously returned by
606 a call to OpenMemory.
607
608 ======================================================================*/
609
pcmcia_write_memory(memory_handle_t handle,mem_op_t * req,caddr_t buf)610 int pcmcia_write_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf)
611 {
612 mtd_request_t mtd;
613 if (CHECK_REGION(handle))
614 return CS_BAD_HANDLE;
615 if (req->Offset >= handle->info.RegionSize)
616 return CS_BAD_OFFSET;
617 if (req->Offset+req->Count > handle->info.RegionSize)
618 return CS_BAD_SIZE;
619
620 mtd.DestCardOffset = req->Offset + handle->info.CardOffset;
621 mtd.TransferLength = req->Count;
622 mtd.MediaID = handle->MediaID;
623 mtd.Function = MTD_REQ_WRITE;
624 if (req->Attributes & MEM_OP_BUFFER_KERNEL)
625 mtd.Function |= MTD_REQ_KERNEL;
626 return do_mtd_request(handle, &mtd, buf);
627 } /* write_memory */
628
629 /*======================================================================
630
631 This isn't needed for anything I could think of.
632
633 ======================================================================*/
634
pcmcia_copy_memory(memory_handle_t handle,copy_op_t * req)635 int pcmcia_copy_memory(memory_handle_t handle, copy_op_t *req)
636 {
637 if (CHECK_REGION(handle))
638 return CS_BAD_HANDLE;
639 return CS_UNSUPPORTED_FUNCTION;
640 }
641
642