source: mainline/uspace/srv/bd/ata_bd/ata_bd.c@ 0e6dce8

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

Display device model name upon initialization.

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