source: mainline/uspace/srv/bd/ata_bd/ata_bd.c@ a71c158

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

Implement simple timeout when waiting for device.

  • Property mode set to 100644
File size: 12.9 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 currently works only with CHS addressing and uses PIO.
38 * Currently based on the (now obsolete) ATA-1, ATA-2 standards.
39 *
40 * The driver services a single controller which can have up to two disks
41 * attached.
42 */
43
44#include <stdio.h>
45#include <libarch/ddi.h>
46#include <ddi.h>
47#include <ipc/ipc.h>
48#include <ipc/bd.h>
49#include <async.h>
50#include <as.h>
51#include <fibril_sync.h>
52#include <devmap.h>
53#include <sys/types.h>
54#include <errno.h>
55#include <bool.h>
56#include <task.h>
57
58#include "ata_bd.h"
59
60#define NAME "ata_bd"
61
62/** Physical block size. Should be always 512. */
63static const size_t block_size = 512;
64
65/** Size of the communication area. */
66static size_t comm_size;
67
68/** I/O base address of the command registers. */
69static uintptr_t cmd_physical = 0x1f0;
70/** I/O base address of the control registers. */
71static uintptr_t ctl_physical = 0x170;
72
73static ata_cmd_t *cmd;
74static ata_ctl_t *ctl;
75
76/** Per-disk state. */
77static disk_t disk[MAX_DISKS];
78
79static int ata_bd_init(void);
80static void ata_bd_connection(ipc_callid_t iid, ipc_call_t *icall);
81static int ata_bd_rdwr(int disk_id, ipcarg_t method, off_t offset, size_t size,
82 void *buf);
83static int ata_bd_read_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
84 void *buf);
85static int ata_bd_write_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
86 const void *buf);
87static int drive_identify(int drive_id, disk_t *d);
88static int wait_status(unsigned set, unsigned n_reset, uint8_t *pstatus,
89 unsigned timeout);
90
91int main(int argc, char **argv)
92{
93 char name[16];
94 int i, rc;
95 int n_disks;
96
97 printf(NAME ": ATA disk driver\n");
98
99 printf("I/O address 0x%x\n", cmd_physical);
100
101 if (ata_bd_init() != EOK)
102 return -1;
103
104 for (i = 0; i < MAX_DISKS; i++) {
105 printf("Identify drive %d... ", i);
106 fflush(stdout);
107
108 rc = drive_identify(i, &disk[i]);
109
110 if (rc == EOK) {
111 printf("%u cylinders, %u heads, %u sectors\n",
112 disk[i].cylinders, disk[i].heads, disk[i].sectors);
113 } else {
114 printf("Not found.\n");
115 }
116 }
117
118 n_disks = 0;
119
120 for (i = 0; i < MAX_DISKS; i++) {
121 /* Skip unattached drives. */
122 if (disk[i].present == false)
123 continue;
124
125 snprintf(name, 16, "disk%d", i);
126 rc = devmap_device_register(name, &disk[i].dev_handle);
127 if (rc != EOK) {
128 devmap_hangup_phone(DEVMAP_DRIVER);
129 printf(NAME ": Unable to register device %s.\n",
130 name);
131 return rc;
132 }
133 ++n_disks;
134 }
135
136 if (n_disks == 0) {
137 printf("No disks detected.\n");
138 return -1;
139 }
140
141 printf(NAME ": Accepting connections\n");
142 task_retval(0);
143 async_manager();
144
145 /* Not reached */
146 return 0;
147}
148
149
150/** Register driver and enable device I/O. */
151static int ata_bd_init(void)
152{
153 void *vaddr;
154 int rc;
155
156 rc = devmap_driver_register(NAME, ata_bd_connection);
157 if (rc < 0) {
158 printf(NAME ": Unable to register driver.\n");
159 return rc;
160 }
161
162 rc = pio_enable((void *) cmd_physical, sizeof(ata_cmd_t), &vaddr);
163 if (rc != EOK) {
164 printf(NAME ": Could not initialize device I/O space.\n");
165 return rc;
166 }
167
168 cmd = vaddr;
169
170 rc = pio_enable((void *) ctl_physical, sizeof(ata_ctl_t), &vaddr);
171 if (rc != EOK) {
172 printf(NAME ": Could not initialize device I/O space.\n");
173 return rc;
174 }
175
176 ctl = vaddr;
177
178
179 return EOK;
180}
181
182/** Block device connection handler */
183static void ata_bd_connection(ipc_callid_t iid, ipc_call_t *icall)
184{
185 void *fs_va = NULL;
186 ipc_callid_t callid;
187 ipc_call_t call;
188 ipcarg_t method;
189 dev_handle_t dh;
190 int flags;
191 int retval;
192 off_t idx;
193 size_t size;
194 int disk_id, i;
195
196 /* Get the device handle. */
197 dh = IPC_GET_ARG1(*icall);
198
199 /* Determine which disk device is the client connecting to. */
200 disk_id = -1;
201 for (i = 0; i < MAX_DISKS; i++)
202 if (disk[i].dev_handle == dh)
203 disk_id = i;
204
205 if (disk_id < 0 || disk[disk_id].present == false) {
206 ipc_answer_0(iid, EINVAL);
207 return;
208 }
209
210 /* Answer the IPC_M_CONNECT_ME_TO call. */
211 ipc_answer_0(iid, EOK);
212
213 if (!ipc_share_out_receive(&callid, &comm_size, &flags)) {
214 ipc_answer_0(callid, EHANGUP);
215 return;
216 }
217
218 fs_va = as_get_mappable_page(comm_size);
219 if (fs_va == NULL) {
220 ipc_answer_0(callid, EHANGUP);
221 return;
222 }
223
224 (void) ipc_share_out_finalize(callid, fs_va);
225
226 while (1) {
227 callid = async_get_call(&call);
228 method = IPC_GET_METHOD(call);
229 switch (method) {
230 case IPC_M_PHONE_HUNGUP:
231 /* The other side has hung up. */
232 ipc_answer_0(callid, EOK);
233 return;
234 case BD_READ_BLOCK:
235 case BD_WRITE_BLOCK:
236 idx = IPC_GET_ARG1(call);
237 size = IPC_GET_ARG2(call);
238 if (size > comm_size) {
239 retval = EINVAL;
240 break;
241 }
242 retval = ata_bd_rdwr(disk_id, method, idx,
243 size, fs_va);
244 break;
245 default:
246 retval = EINVAL;
247 break;
248 }
249 ipc_answer_0(callid, retval);
250 }
251}
252
253/** Transfer a logical block from/to the device.
254 *
255 * @param disk_id Device index (0 or 1)
256 * @param method @c BD_READ_BLOCK or @c BD_WRITE_BLOCK
257 * @param blk_idx Index of the first block.
258 * @param size Size of the logical block.
259 * @param buf Data buffer.
260 *
261 * @return EOK on success, EIO on error.
262 */
263static int ata_bd_rdwr(int disk_id, ipcarg_t method, off_t blk_idx, size_t size,
264 void *buf)
265{
266 int rc;
267 size_t now;
268
269 while (size > 0) {
270 now = size < block_size ? size : block_size;
271 if (now != block_size)
272 return EINVAL;
273
274 if (method == BD_READ_BLOCK)
275 rc = ata_bd_read_block(disk_id, blk_idx, 1, buf);
276 else
277 rc = ata_bd_write_block(disk_id, blk_idx, 1, buf);
278
279 if (rc != EOK)
280 return rc;
281
282 buf += block_size;
283 blk_idx++;
284
285 if (size > block_size)
286 size -= block_size;
287 else
288 size = 0;
289 }
290
291 return EOK;
292}
293
294/** Issue IDENTIFY command.
295 *
296 * This is used to detect whether an ATA device is present and if so,
297 * to determine its parameters. The parameters are written to @a d.
298 *
299 * @param disk_id Device ID, 0 or 1.
300 * @param d Device structure to store parameters in.
301 */
302static int drive_identify(int disk_id, disk_t *d)
303{
304 uint16_t data;
305 uint8_t status;
306 uint8_t drv_head;
307 size_t i;
308
309 drv_head = ((disk_id != 0) ? DHR_DRV : 0);
310 d->present = false;
311
312 if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
313 return EIO;
314
315 pio_write_8(&cmd->drive_head, drv_head);
316
317 /*
318 * This is where we would most likely expect a non-existing device to
319 * show up by not setting SR_DRDY.
320 */
321 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
322 return EIO;
323
324 pio_write_8(&cmd->command, CMD_IDENTIFY_DRIVE);
325
326 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK)
327 return EIO;
328
329 /* Read data from the disk buffer. */
330
331 if ((status & SR_DRQ) != 0) {
332// for (i = 0; i < block_size / 2; i++) {
333// data = pio_read_16(&cmd->data_port);
334// ((uint16_t *) buf)[i] = data;
335// }
336
337 for (i = 0; i < block_size / 2; i++) {
338 data = pio_read_16(&cmd->data_port);
339
340 switch (i) {
341 case 1: d->cylinders = data; break;
342 case 3: d->heads = data; break;
343 case 6: d->sectors = data; break;
344 }
345 }
346 }
347
348 if ((status & SR_ERR) != 0)
349 return EIO;
350
351 d->blocks = d->cylinders * d->heads * d->sectors;
352
353 d->present = true;
354 fibril_mutex_initialize(&d->lock);
355
356 return EOK;
357}
358
359/** Read a physical from the device.
360 *
361 * @param disk_id Device index (0 or 1)
362 * @param blk_idx Index of the first block.
363 * @param blk_cnt Number of blocks to transfer.
364 * @param buf Buffer for holding the data.
365 *
366 * @return EOK on success, EIO on error.
367 */
368static int ata_bd_read_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
369 void *buf)
370{
371 size_t i;
372 uint16_t data;
373 uint8_t status;
374 uint64_t c, h, s;
375 uint64_t idx;
376 uint8_t drv_head;
377 disk_t *d;
378
379 d = &disk[disk_id];
380
381 /* Check device bounds. */
382 if (blk_idx >= d->blocks)
383 return EINVAL;
384
385 /* Compute CHS. */
386 c = blk_idx / (d->heads * d->sectors);
387 idx = blk_idx % (d->heads * d->sectors);
388
389 h = idx / d->sectors;
390 s = 1 + (idx % d->sectors);
391
392 /* New value for Drive/Head register */
393 drv_head =
394 ((disk_id != 0) ? DHR_DRV : 0) |
395 (h & 0x0f);
396
397 fibril_mutex_lock(&d->lock);
398
399 /* Program a Read Sectors operation. */
400
401 if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
402 fibril_mutex_unlock(&d->lock);
403 return EIO;
404 }
405
406 pio_write_8(&cmd->drive_head, drv_head);
407
408 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
409 fibril_mutex_unlock(&d->lock);
410 return EIO;
411 }
412
413 pio_write_8(&cmd->sector_count, 1);
414 pio_write_8(&cmd->sector_number, s);
415 pio_write_8(&cmd->cylinder_low, c & 0xff);
416 pio_write_8(&cmd->cylinder_high, c >> 16);
417
418 pio_write_8(&cmd->command, CMD_READ_SECTORS);
419
420 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
421 fibril_mutex_unlock(&d->lock);
422 return EIO;
423 }
424
425 if ((status & SR_DRQ) != 0) {
426 /* Read data from the device buffer. */
427
428 for (i = 0; i < block_size / 2; i++) {
429 data = pio_read_16(&cmd->data_port);
430 ((uint16_t *) buf)[i] = data;
431 }
432 }
433
434 if ((status & SR_ERR) != 0)
435 return EIO;
436
437 fibril_mutex_unlock(&d->lock);
438 return EOK;
439}
440
441/** Write a physical block to the device.
442 *
443 * @param disk_id Device index (0 or 1)
444 * @param blk_idx Index of the first block.
445 * @param blk_cnt Number of blocks to transfer.
446 * @param buf Buffer holding the data to write.
447 *
448 * @return EOK on success, EIO on error.
449 */
450static int ata_bd_write_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
451 const void *buf)
452{
453 size_t i;
454 uint8_t status;
455 uint64_t c, h, s;
456 uint64_t idx;
457 uint8_t drv_head;
458 disk_t *d;
459
460 d = &disk[disk_id];
461
462 /* Check device bounds. */
463 if (blk_idx >= d->blocks)
464 return EINVAL;
465
466 /* Compute CHS. */
467 c = blk_idx / (d->heads * d->sectors);
468 idx = blk_idx % (d->heads * d->sectors);
469
470 h = idx / d->sectors;
471 s = 1 + (idx % d->sectors);
472
473 /* New value for Drive/Head register */
474 drv_head =
475 ((disk_id != 0) ? DHR_DRV : 0) |
476 (h & 0x0f);
477
478 fibril_mutex_lock(&d->lock);
479
480 /* Program a Write Sectors operation. */
481
482 if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
483 fibril_mutex_unlock(&d->lock);
484 return EIO;
485 }
486
487 pio_write_8(&cmd->drive_head, drv_head);
488
489 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
490 fibril_mutex_unlock(&d->lock);
491 return EIO;
492 }
493
494 pio_write_8(&cmd->sector_count, 1);
495 pio_write_8(&cmd->sector_number, s);
496 pio_write_8(&cmd->cylinder_low, c & 0xff);
497 pio_write_8(&cmd->cylinder_high, c >> 16);
498
499 pio_write_8(&cmd->command, CMD_WRITE_SECTORS);
500
501 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
502 fibril_mutex_unlock(&d->lock);
503 return EIO;
504 }
505
506 if ((status & SR_DRQ) != 0) {
507 /* Write data to the device buffer. */
508
509 for (i = 0; i < block_size / 2; i++) {
510 pio_write_16(&cmd->data_port, ((uint16_t *) buf)[i]);
511 }
512 }
513
514 fibril_mutex_unlock(&d->lock);
515
516 if (status & SR_ERR)
517 return EIO;
518
519 return EOK;
520}
521
522/** Wait until some status bits are set and some are reset.
523 *
524 * Example: wait_status(SR_DRDY, ~SR_BSY) waits for SR_DRDY to become
525 * set and SR_BSY to become reset.
526 *
527 * @param set Combination if bits which must be all set.
528 * @param n_reset Negated combination of bits which must be all reset.
529 * @param pstatus Pointer where to store last read status or NULL.
530 * @param timeout Timeout in 10ms units.
531 *
532 * @return EOK on success, EIO on timeout.
533 */
534static int wait_status(unsigned set, unsigned n_reset, uint8_t *pstatus,
535 unsigned timeout)
536{
537 uint8_t status;
538 int cnt;
539
540 status = pio_read_8(&cmd->status);
541
542 /*
543 * This is crude, yet simple. First try with 1us delays
544 * (most likely the device will respond very fast). If not,
545 * start trying every 10 ms.
546 */
547
548 cnt = 100;
549 while ((status & ~n_reset) != 0 || (status & set) != set) {
550 async_usleep(1);
551 --cnt;
552 if (cnt <= 0) break;
553
554 status = pio_read_8(&cmd->status);
555 }
556
557 cnt = timeout;
558 while ((status & ~n_reset) != 0 || (status & set) != set) {
559 async_usleep(10000);
560 --cnt;
561 if (cnt <= 0) break;
562
563 status = pio_read_8(&cmd->status);
564 }
565
566 if (pstatus)
567 *pstatus = status;
568
569 if (cnt == 0)
570 return EIO;
571
572 return EOK;
573}
574
575/**
576 * @}
577 */
Note: See TracBrowser for help on using the repository browser.