source: mainline/uspace/srv/bd/ata_bd/ata_bd.c@ 5f70118

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 5f70118 was 08232ee, checked in by Jiri Svoboda <jiri@…>, 16 years ago

Obtain block device size automatically, if possible. Implement ftell(). Fix seek implementation in VFS if whence == SEEK_END.

  • Property mode set to 100644
File size: 17.5 KB
Line 
1/*
2 * Copyright (c) 2009 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup bd
30 * @{
31 */
32
33/**
34 * @file
35 * @brief ATA disk driver
36 *
37 * This driver supports CHS, 28-bit and 48-bit LBA addressing. It only uses
38 * PIO transfers. There is no support DMA, the PACKET feature set or any other
39 * fancy features such as S.M.A.R.T, removable devices, etc.
40 *
41 * This driver is based on the ATA-1, ATA-2, ATA-3 and ATA/ATAPI-4 through 7
42 * standards, as published by the ANSI, NCITS and INCITS standards bodies,
43 * which are freely available. This driver contains no vendor-specific
44 * code at this moment.
45 *
46 * The driver services a single controller which can have up to two disks
47 * attached.
48 */
49
50#include <stdio.h>
51#include <libarch/ddi.h>
52#include <ddi.h>
53#include <ipc/ipc.h>
54#include <ipc/bd.h>
55#include <async.h>
56#include <as.h>
57#include <fibril_synch.h>
58#include <string.h>
59#include <devmap.h>
60#include <sys/types.h>
61#include <errno.h>
62#include <bool.h>
63#include <task.h>
64#include <macros.h>
65
66#include "ata_bd.h"
67
68#define NAME "ata_bd"
69#define NAMESPACE "bd"
70
71/** Physical block size. Should be always 512. */
72static const size_t block_size = 512;
73
74/** Size of the communication area. */
75static size_t comm_size;
76
77/** I/O base address of the command registers. */
78static uintptr_t cmd_physical = 0x1f0;
79/** I/O base address of the control registers. */
80static uintptr_t ctl_physical = 0x170;
81
82static ata_cmd_t *cmd;
83static ata_ctl_t *ctl;
84
85/** Per-disk state. */
86static disk_t disk[MAX_DISKS];
87
88static int ata_bd_init(void);
89static void ata_bd_connection(ipc_callid_t iid, ipc_call_t *icall);
90static int ata_bd_read_blocks(int disk_id, uint64_t ba, size_t cnt,
91 void *buf);
92static int ata_bd_write_blocks(int disk_id, uint64_t ba, size_t cnt,
93 const void *buf);
94static int ata_bd_read_block(int disk_id, uint64_t ba, size_t cnt,
95 void *buf);
96static int ata_bd_write_block(int disk_id, uint64_t ba, size_t cnt,
97 const void *buf);
98static int disk_init(disk_t *d, int disk_id);
99static int drive_identify(int drive_id, void *buf);
100static void disk_print_summary(disk_t *d);
101static int coord_calc(disk_t *d, uint64_t ba, block_coord_t *bc);
102static void coord_sc_program(const block_coord_t *bc, uint16_t scnt);
103static int wait_status(unsigned set, unsigned n_reset, uint8_t *pstatus,
104 unsigned timeout);
105
106int main(int argc, char **argv)
107{
108 char name[16];
109 int i, rc;
110 int n_disks;
111
112 printf(NAME ": ATA disk driver\n");
113
114 printf("I/O address 0x%p/0x%p\n", ctl_physical, cmd_physical);
115
116 if (ata_bd_init() != EOK)
117 return -1;
118
119 for (i = 0; i < MAX_DISKS; i++) {
120 printf("Identify drive %d... ", i);
121 fflush(stdout);
122
123 rc = disk_init(&disk[i], i);
124
125 if (rc == EOK) {
126 disk_print_summary(&disk[i]);
127 } else {
128 printf("Not found.\n");
129 }
130 }
131
132 n_disks = 0;
133
134 for (i = 0; i < MAX_DISKS; i++) {
135 /* Skip unattached drives. */
136 if (disk[i].present == false)
137 continue;
138
139 snprintf(name, 16, "%s/disk%d", NAMESPACE, i);
140 rc = devmap_device_register(name, &disk[i].dev_handle);
141 if (rc != EOK) {
142 devmap_hangup_phone(DEVMAP_DRIVER);
143 printf(NAME ": Unable to register device %s.\n", name);
144 return rc;
145 }
146 ++n_disks;
147 }
148
149 if (n_disks == 0) {
150 printf("No disks detected.\n");
151 return -1;
152 }
153
154 printf(NAME ": Accepting connections\n");
155 task_retval(0);
156 async_manager();
157
158 /* Not reached */
159 return 0;
160}
161
162/** Print one-line device summary. */
163static void disk_print_summary(disk_t *d)
164{
165 uint64_t mbytes;
166
167 printf("%s: ", d->model);
168
169 switch (d->amode) {
170 case am_chs:
171 printf("CHS %u cylinders, %u heads, %u sectors",
172 disk->geom.cylinders, disk->geom.heads, disk->geom.sectors);
173 break;
174 case am_lba28:
175 printf("LBA-28");
176 break;
177 case am_lba48:
178 printf("LBA-48");
179 break;
180 }
181
182 printf(" %llu blocks", d->blocks, d->blocks / (2 * 1024));
183
184 mbytes = d->blocks / (2 * 1024);
185 if (mbytes > 0)
186 printf(" %llu MB.", mbytes);
187
188 printf("\n");
189}
190
191/** Register driver and enable device I/O. */
192static int ata_bd_init(void)
193{
194 void *vaddr;
195 int rc;
196
197 rc = devmap_driver_register(NAME, ata_bd_connection);
198 if (rc < 0) {
199 printf(NAME ": Unable to register driver.\n");
200 return rc;
201 }
202
203 rc = pio_enable((void *) cmd_physical, sizeof(ata_cmd_t), &vaddr);
204 if (rc != EOK) {
205 printf(NAME ": Could not initialize device I/O space.\n");
206 return rc;
207 }
208
209 cmd = vaddr;
210
211 rc = pio_enable((void *) ctl_physical, sizeof(ata_ctl_t), &vaddr);
212 if (rc != EOK) {
213 printf(NAME ": Could not initialize device I/O space.\n");
214 return rc;
215 }
216
217 ctl = vaddr;
218
219
220 return EOK;
221}
222
223/** Block device connection handler */
224static void ata_bd_connection(ipc_callid_t iid, ipc_call_t *icall)
225{
226 void *fs_va = NULL;
227 ipc_callid_t callid;
228 ipc_call_t call;
229 ipcarg_t method;
230 dev_handle_t dh;
231 int flags;
232 int retval;
233 uint64_t ba;
234 size_t cnt;
235 int disk_id, i;
236
237 /* Get the device handle. */
238 dh = IPC_GET_ARG1(*icall);
239
240 /* Determine which disk device is the client connecting to. */
241 disk_id = -1;
242 for (i = 0; i < MAX_DISKS; i++)
243 if (disk[i].dev_handle == dh)
244 disk_id = i;
245
246 if (disk_id < 0 || disk[disk_id].present == false) {
247 ipc_answer_0(iid, EINVAL);
248 return;
249 }
250
251 /* Answer the IPC_M_CONNECT_ME_TO call. */
252 ipc_answer_0(iid, EOK);
253
254 if (!async_share_out_receive(&callid, &comm_size, &flags)) {
255 ipc_answer_0(callid, EHANGUP);
256 return;
257 }
258
259 fs_va = as_get_mappable_page(comm_size);
260 if (fs_va == NULL) {
261 ipc_answer_0(callid, EHANGUP);
262 return;
263 }
264
265 (void) async_share_out_finalize(callid, fs_va);
266
267 while (1) {
268 callid = async_get_call(&call);
269 method = IPC_GET_METHOD(call);
270 switch (method) {
271 case IPC_M_PHONE_HUNGUP:
272 /* The other side has hung up. */
273 ipc_answer_0(callid, EOK);
274 return;
275 case BD_READ_BLOCKS:
276 ba = MERGE_LOUP32(IPC_GET_ARG1(call),
277 IPC_GET_ARG2(call));
278 cnt = IPC_GET_ARG3(call);
279 if (cnt * block_size > comm_size) {
280 retval = ELIMIT;
281 break;
282 }
283 retval = ata_bd_read_blocks(disk_id, ba, cnt, fs_va);
284 break;
285 case BD_WRITE_BLOCKS:
286 ba = MERGE_LOUP32(IPC_GET_ARG1(call),
287 IPC_GET_ARG2(call));
288 cnt = IPC_GET_ARG3(call);
289 if (cnt * block_size > comm_size) {
290 retval = ELIMIT;
291 break;
292 }
293 retval = ata_bd_write_blocks(disk_id, ba, cnt, fs_va);
294 break;
295 case BD_GET_BLOCK_SIZE:
296 ipc_answer_1(callid, EOK, block_size);
297 continue;
298 case BD_GET_NUM_BLOCKS:
299 ipc_answer_2(callid, EOK, LOWER32(disk[disk_id].blocks),
300 UPPER32(disk[disk_id].blocks));
301 continue;
302 default:
303 retval = EINVAL;
304 break;
305 }
306 ipc_answer_0(callid, retval);
307 }
308}
309
310/** Initialize a disk.
311 *
312 * Probes for a disk, determines its parameters and initializes
313 * the disk structure.
314 */
315static int disk_init(disk_t *d, int disk_id)
316{
317 identify_data_t idata;
318 uint8_t model[40];
319 uint16_t w;
320 uint8_t c;
321 size_t pos, len;
322 int rc;
323 unsigned i;
324
325 rc = drive_identify(disk_id, &idata);
326 if (rc != EOK) {
327 d->present = false;
328 return rc;
329 }
330
331 if ((idata.caps & cap_lba) == 0) {
332 /* Device only supports CHS addressing. */
333 d->amode = am_chs;
334
335 d->geom.cylinders = idata.cylinders;
336 d->geom.heads = idata.heads;
337 d->geom.sectors = idata.sectors;
338
339 d->blocks = d->geom.cylinders * d->geom.heads * d->geom.sectors;
340 } else if ((idata.cmd_set1 & cs1_addr48) == 0) {
341 /* Device only supports LBA-28 addressing. */
342 d->amode = am_lba28;
343
344 d->geom.cylinders = 0;
345 d->geom.heads = 0;
346 d->geom.sectors = 0;
347
348 d->blocks =
349 (uint32_t) idata.total_lba28_0 |
350 ((uint32_t) idata.total_lba28_1 << 16);
351 } else {
352 /* Device supports LBA-48 addressing. */
353 d->amode = am_lba48;
354
355 d->geom.cylinders = 0;
356 d->geom.heads = 0;
357 d->geom.sectors = 0;
358
359 d->blocks =
360 (uint64_t) idata.total_lba48_0 |
361 ((uint64_t) idata.total_lba48_1 << 16) |
362 ((uint64_t) idata.total_lba48_2 << 32) |
363 ((uint64_t) idata.total_lba48_3 << 48);
364 }
365
366 /*
367 * Convert model name to string representation.
368 */
369 for (i = 0; i < 20; i++) {
370 w = idata.model_name[i];
371 model[2 * i] = w >> 8;
372 model[2 * i + 1] = w & 0x00ff;
373 }
374
375 len = 40;
376 while (len > 0 && model[len - 1] == 0x20)
377 --len;
378
379 pos = 0;
380 for (i = 0; i < len; ++i) {
381 c = model[i];
382 if (c >= 0x80) c = '?';
383
384 chr_encode(c, d->model, &pos, 40);
385 }
386 d->model[pos] = '\0';
387
388 d->present = true;
389 fibril_mutex_initialize(&d->lock);
390
391 return EOK;
392}
393
394/** Read multiple blocks from the device. */
395static int ata_bd_read_blocks(int disk_id, uint64_t ba, size_t cnt,
396 void *buf) {
397
398 int rc;
399
400 while (cnt > 0) {
401 rc = ata_bd_read_block(disk_id, ba, 1, buf);
402 if (rc != EOK)
403 return rc;
404
405 ++ba;
406 --cnt;
407 buf += block_size;
408 }
409
410 return EOK;
411}
412
413/** Write multiple blocks to the device. */
414static int ata_bd_write_blocks(int disk_id, uint64_t ba, size_t cnt,
415 const void *buf) {
416
417 int rc;
418
419 while (cnt > 0) {
420 rc = ata_bd_write_block(disk_id, ba, 1, buf);
421 if (rc != EOK)
422 return rc;
423
424 ++ba;
425 --cnt;
426 buf += block_size;
427 }
428
429 return EOK;
430}
431
432/** Issue IDENTIFY command.
433 *
434 * Reads @c identify data into the provided buffer. This is used to detect
435 * whether an ATA device is present and if so, to determine its parameters.
436 *
437 * @param disk_id Device ID, 0 or 1.
438 * @param buf Pointer to a 512-byte buffer.
439 */
440static int drive_identify(int disk_id, void *buf)
441{
442 uint16_t data;
443 uint8_t status;
444 uint8_t drv_head;
445 size_t i;
446
447 drv_head = ((disk_id != 0) ? DHR_DRV : 0);
448
449 if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
450 return EIO;
451
452 pio_write_8(&cmd->drive_head, drv_head);
453
454 /*
455 * This is where we would most likely expect a non-existing device to
456 * show up by not setting SR_DRDY.
457 */
458 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
459 return EIO;
460
461 pio_write_8(&cmd->command, CMD_IDENTIFY_DRIVE);
462
463 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK)
464 return EIO;
465
466 /* Read data from the disk buffer. */
467
468 if ((status & SR_DRQ) != 0) {
469 for (i = 0; i < block_size / 2; i++) {
470 data = pio_read_16(&cmd->data_port);
471 ((uint16_t *) buf)[i] = data;
472 }
473 }
474
475 if ((status & SR_ERR) != 0)
476 return EIO;
477
478 return EOK;
479}
480
481/** Read a physical from the device.
482 *
483 * @param disk_id Device index (0 or 1)
484 * @param ba Address the first block.
485 * @param cnt Number of blocks to transfer.
486 * @param buf Buffer for holding the data.
487 *
488 * @return EOK on success, EIO on error.
489 */
490static int ata_bd_read_block(int disk_id, uint64_t ba, size_t blk_cnt,
491 void *buf)
492{
493 size_t i;
494 uint16_t data;
495 uint8_t status;
496 uint8_t drv_head;
497 disk_t *d;
498 block_coord_t bc;
499
500 d = &disk[disk_id];
501 bc.h = 0; /* Silence warning. */
502
503 /* Compute block coordinates. */
504 if (coord_calc(d, ba, &bc) != EOK)
505 return EINVAL;
506
507 /* New value for Drive/Head register */
508 drv_head =
509 ((disk_id != 0) ? DHR_DRV : 0) |
510 ((d->amode != am_chs) ? DHR_LBA : 0) |
511 (bc.h & 0x0f);
512
513 fibril_mutex_lock(&d->lock);
514
515 /* Program a Read Sectors operation. */
516
517 if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
518 fibril_mutex_unlock(&d->lock);
519 return EIO;
520 }
521
522 pio_write_8(&cmd->drive_head, drv_head);
523
524 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
525 fibril_mutex_unlock(&d->lock);
526 return EIO;
527 }
528
529 /* Program block coordinates into the device. */
530 coord_sc_program(&bc, 1);
531
532 pio_write_8(&cmd->command, d->amode == am_lba48 ?
533 CMD_READ_SECTORS_EXT : CMD_READ_SECTORS);
534
535 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
536 fibril_mutex_unlock(&d->lock);
537 return EIO;
538 }
539
540 if ((status & SR_DRQ) != 0) {
541 /* Read data from the device buffer. */
542
543 for (i = 0; i < block_size / 2; i++) {
544 data = pio_read_16(&cmd->data_port);
545 ((uint16_t *) buf)[i] = data;
546 }
547 }
548
549 if ((status & SR_ERR) != 0)
550 return EIO;
551
552 fibril_mutex_unlock(&d->lock);
553 return EOK;
554}
555
556/** Write a physical block to the device.
557 *
558 * @param disk_id Device index (0 or 1)
559 * @param ba Address of the first block.
560 * @param cnt Number of blocks to transfer.
561 * @param buf Buffer holding the data to write.
562 *
563 * @return EOK on success, EIO on error.
564 */
565static int ata_bd_write_block(int disk_id, uint64_t ba, size_t cnt,
566 const void *buf)
567{
568 size_t i;
569 uint8_t status;
570 uint8_t drv_head;
571 disk_t *d;
572 block_coord_t bc;
573
574 d = &disk[disk_id];
575 bc.h = 0; /* Silence warning. */
576
577 /* Compute block coordinates. */
578 if (coord_calc(d, ba, &bc) != EOK)
579 return EINVAL;
580
581 /* New value for Drive/Head register */
582 drv_head =
583 ((disk_id != 0) ? DHR_DRV : 0) |
584 ((d->amode != am_chs) ? DHR_LBA : 0) |
585 (bc.h & 0x0f);
586
587 fibril_mutex_lock(&d->lock);
588
589 /* Program a Write Sectors operation. */
590
591 if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
592 fibril_mutex_unlock(&d->lock);
593 return EIO;
594 }
595
596 pio_write_8(&cmd->drive_head, drv_head);
597
598 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
599 fibril_mutex_unlock(&d->lock);
600 return EIO;
601 }
602
603 /* Program block coordinates into the device. */
604 coord_sc_program(&bc, 1);
605
606 pio_write_8(&cmd->command, d->amode == am_lba48 ?
607 CMD_WRITE_SECTORS_EXT : CMD_WRITE_SECTORS);
608
609 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
610 fibril_mutex_unlock(&d->lock);
611 return EIO;
612 }
613
614 if ((status & SR_DRQ) != 0) {
615 /* Write data to the device buffer. */
616
617 for (i = 0; i < block_size / 2; i++) {
618 pio_write_16(&cmd->data_port, ((uint16_t *) buf)[i]);
619 }
620 }
621
622 fibril_mutex_unlock(&d->lock);
623
624 if (status & SR_ERR)
625 return EIO;
626
627 return EOK;
628}
629
630/** Calculate block coordinates.
631 *
632 * Calculates block coordinates in the best coordinate system supported
633 * by the device. These can be later programmed into the device using
634 * @c coord_sc_program().
635 *
636 * @return EOK on success or EINVAL if block index is past end of device.
637 */
638static int coord_calc(disk_t *d, uint64_t ba, block_coord_t *bc)
639{
640 uint64_t c;
641 uint64_t idx;
642
643 /* Check device bounds. */
644 if (ba >= d->blocks)
645 return EINVAL;
646
647 bc->amode = d->amode;
648
649 switch (d->amode) {
650 case am_chs:
651 /* Compute CHS coordinates. */
652 c = ba / (d->geom.heads * d->geom.sectors);
653 idx = ba % (d->geom.heads * d->geom.sectors);
654
655 bc->cyl_lo = c & 0xff;
656 bc->cyl_hi = (c >> 8) & 0xff;
657 bc->h = (idx / d->geom.sectors) & 0x0f;
658 bc->sector = (1 + (idx % d->geom.sectors)) & 0xff;
659 break;
660
661 case am_lba28:
662 /* Compute LBA-28 coordinates. */
663 bc->c0 = ba & 0xff; /* bits 0-7 */
664 bc->c1 = (ba >> 8) & 0xff; /* bits 8-15 */
665 bc->c2 = (ba >> 16) & 0xff; /* bits 16-23 */
666 bc->h = (ba >> 24) & 0x0f; /* bits 24-27 */
667 break;
668
669 case am_lba48:
670 /* Compute LBA-48 coordinates. */
671 bc->c0 = ba & 0xff; /* bits 0-7 */
672 bc->c1 = (ba >> 8) & 0xff; /* bits 8-15 */
673 bc->c2 = (ba >> 16) & 0xff; /* bits 16-23 */
674 bc->c3 = (ba >> 24) & 0xff; /* bits 24-31 */
675 bc->c4 = (ba >> 32) & 0xff; /* bits 32-39 */
676 bc->c5 = (ba >> 40) & 0xff; /* bits 40-47 */
677 bc->h = 0;
678 break;
679 }
680
681 return EOK;
682}
683
684/** Program block coordinates and sector count into ATA registers.
685 *
686 * Note that bc->h must be programmed separately into the device/head register.
687 */
688static void coord_sc_program(const block_coord_t *bc, uint16_t scnt)
689{
690 if (bc->amode == am_lba48) {
691 /* Write high-order bits. */
692 pio_write_8(&cmd->sector_count, scnt >> 8);
693 pio_write_8(&cmd->sector_number, bc->c3);
694 pio_write_8(&cmd->cylinder_low, bc->c4);
695 pio_write_8(&cmd->cylinder_high, bc->c5);
696 }
697
698 /* Write low-order bits. */
699 pio_write_8(&cmd->sector_count, scnt & 0x00ff);
700 pio_write_8(&cmd->sector_number, bc->c0);
701 pio_write_8(&cmd->cylinder_low, bc->c1);
702 pio_write_8(&cmd->cylinder_high, bc->c2);
703}
704
705/** Wait until some status bits are set and some are reset.
706 *
707 * Example: wait_status(SR_DRDY, ~SR_BSY) waits for SR_DRDY to become
708 * set and SR_BSY to become reset.
709 *
710 * @param set Combination if bits which must be all set.
711 * @param n_reset Negated combination of bits which must be all reset.
712 * @param pstatus Pointer where to store last read status or NULL.
713 * @param timeout Timeout in 10ms units.
714 *
715 * @return EOK on success, EIO on timeout.
716 */
717static int wait_status(unsigned set, unsigned n_reset, uint8_t *pstatus,
718 unsigned timeout)
719{
720 uint8_t status;
721 int cnt;
722
723 status = pio_read_8(&cmd->status);
724
725 /*
726 * This is crude, yet simple. First try with 1us delays
727 * (most likely the device will respond very fast). If not,
728 * start trying every 10 ms.
729 */
730
731 cnt = 100;
732 while ((status & ~n_reset) != 0 || (status & set) != set) {
733 async_usleep(1);
734 --cnt;
735 if (cnt <= 0) break;
736
737 status = pio_read_8(&cmd->status);
738 }
739
740 cnt = timeout;
741 while ((status & ~n_reset) != 0 || (status & set) != set) {
742 async_usleep(10000);
743 --cnt;
744 if (cnt <= 0) break;
745
746 status = pio_read_8(&cmd->status);
747 }
748
749 if (pstatus)
750 *pstatus = status;
751
752 if (cnt == 0)
753 return EIO;
754
755 return EOK;
756}
757
758/**
759 * @}
760 */
Note: See TracBrowser for help on using the repository browser.