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
RevLine 
[f8ef660]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.
[a7de7907]38 * Currently based on the (now obsolete) ATA-1, ATA-2 standards.
[12956e57]39 *
40 * The driver services a single controller which can have up to two disks
41 * attached.
[f8ef660]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>
[12956e57]51#include <fibril_sync.h>
[0e6dce8]52#include <string.h>
[f8ef660]53#include <devmap.h>
54#include <sys/types.h>
55#include <errno.h>
[1806e5d]56#include <bool.h>
[95bc57c]57#include <task.h>
[f8ef660]58
[4f5caea]59#include "ata_bd.h"
[f8ef660]60
[4f5caea]61#define NAME "ata_bd"
[1806e5d]62
[54d0ddc]63/** Physical block size. Should be always 512. */
[f8ef660]64static const size_t block_size = 512;
[54d0ddc]65
66/** Size of the communication area. */
[f8ef660]67static size_t comm_size;
68
[54d0ddc]69/** I/O base address of the command registers. */
[f8ef660]70static uintptr_t cmd_physical = 0x1f0;
[54d0ddc]71/** I/O base address of the control registers. */
[f8ef660]72static uintptr_t ctl_physical = 0x170;
[54d0ddc]73
[f8ef660]74static ata_cmd_t *cmd;
75static ata_ctl_t *ctl;
76
[12956e57]77/** Per-disk state. */
[5481d1bb]78static disk_t disk[MAX_DISKS];
[f8ef660]79
80static int ata_bd_init(void);
81static void ata_bd_connection(ipc_callid_t iid, ipc_call_t *icall);
[fbbbb8a]82static int ata_bd_rdwr(int disk_id, ipcarg_t method, off_t offset, size_t size,
[f8ef660]83 void *buf);
84static int ata_bd_read_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
85 void *buf);
[d9f4c76]86static int ata_bd_write_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
87 const void *buf);
[0e6dce8]88static int disk_init(disk_t *d, int disk_id);
89static int drive_identify(int drive_id, void *buf);
[31de325]90static int wait_status(unsigned set, unsigned n_reset, uint8_t *pstatus,
91 unsigned timeout);
[f8ef660]92
93int main(int argc, char **argv)
94{
[1806e5d]95 char name[16];
96 int i, rc;
97 int n_disks;
[f8ef660]98
99 printf(NAME ": ATA disk driver\n");
100
[12956e57]101 printf("I/O address 0x%x\n", cmd_physical);
[f8ef660]102
103 if (ata_bd_init() != EOK)
104 return -1;
105
[31de325]106 for (i = 0; i < MAX_DISKS; i++) {
107 printf("Identify drive %d... ", i);
108 fflush(stdout);
109
[0e6dce8]110 rc = disk_init(&disk[i], i);
[31de325]111
112 if (rc == EOK) {
[0e6dce8]113 printf("%s: %u cylinders, %u heads, %u sectors.\n",
114 disk[i].model, disk[i].cylinders, disk[i].heads,
115 disk[i].sectors);
[31de325]116 } else {
117 printf("Not found.\n");
118 }
119 }
[1806e5d]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);
[12956e57]129 rc = devmap_device_register(name, &disk[i].dev_handle);
[1806e5d]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");
[95bc57c]145 task_retval(0);
[1806e5d]146 async_manager();
147
148 /* Not reached */
149 return 0;
150}
151
[f8ef660]152
[54d0ddc]153/** Register driver and enable device I/O. */
[f8ef660]154static int ata_bd_init(void)
155{
156 void *vaddr;
[1806e5d]157 int rc;
[f8ef660]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
[54d0ddc]185/** Block device connection handler */
[f8ef660]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;
[fbbbb8a]196 size_t size;
[f8ef660]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++)
[12956e57]205 if (disk[i].dev_handle == dh)
[f8ef660]206 disk_id = i;
207
[1806e5d]208 if (disk_id < 0 || disk[disk_id].present == false) {
[f8ef660]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
[0e6dce8]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
[54d0ddc]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 */
[fbbbb8a]321static int ata_bd_rdwr(int disk_id, ipcarg_t method, off_t blk_idx, size_t size,
[f8ef660]322 void *buf)
323{
324 int rc;
[fbbbb8a]325 size_t now;
[f8ef660]326
327 while (size > 0) {
[fbbbb8a]328 now = size < block_size ? size : block_size;
[f8ef660]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
[d9f4c76]335 rc = ata_bd_write_block(disk_id, blk_idx, 1, buf);
[f8ef660]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
[54d0ddc]352/** Issue IDENTIFY command.
353 *
[0e6dce8]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.
[54d0ddc]356 *
357 * @param disk_id Device ID, 0 or 1.
[0e6dce8]358 * @param buf Pointer to a 512-byte buffer.
[54d0ddc]359 */
[0e6dce8]360static int drive_identify(int disk_id, void *buf)
[54d0ddc]361{
362 uint16_t data;
363 uint8_t status;
364 uint8_t drv_head;
365 size_t i;
[f8ef660]366
[54d0ddc]367 drv_head = ((disk_id != 0) ? DHR_DRV : 0);
368
[31de325]369 if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
370 return EIO;
371
[54d0ddc]372 pio_write_8(&cmd->drive_head, drv_head);
373
374 /*
[31de325]375 * This is where we would most likely expect a non-existing device to
376 * show up by not setting SR_DRDY.
[54d0ddc]377 */
[31de325]378 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
379 return EIO;
[54d0ddc]380
381 pio_write_8(&cmd->command, CMD_IDENTIFY_DRIVE);
382
[31de325]383 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK)
384 return EIO;
[54d0ddc]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);
[0e6dce8]391 ((uint16_t *) buf)[i] = data;
[54d0ddc]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 */
[f8ef660]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;
[1806e5d]419 disk_t *d;
420
421 d = &disk[disk_id];
[f8ef660]422
423 /* Check device bounds. */
[1806e5d]424 if (blk_idx >= d->blocks)
[f8ef660]425 return EINVAL;
426
427 /* Compute CHS. */
[1806e5d]428 c = blk_idx / (d->heads * d->sectors);
429 idx = blk_idx % (d->heads * d->sectors);
[f8ef660]430
[1806e5d]431 h = idx / d->sectors;
432 s = 1 + (idx % d->sectors);
[f8ef660]433
434 /* New value for Drive/Head register */
435 drv_head =
436 ((disk_id != 0) ? DHR_DRV : 0) |
437 (h & 0x0f);
438
[12956e57]439 fibril_mutex_lock(&d->lock);
[f8ef660]440
441 /* Program a Read Sectors operation. */
442
[31de325]443 if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
444 fibril_mutex_unlock(&d->lock);
445 return EIO;
446 }
447
[f8ef660]448 pio_write_8(&cmd->drive_head, drv_head);
[a7de7907]449
[31de325]450 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
451 fibril_mutex_unlock(&d->lock);
452 return EIO;
453 }
454
[f8ef660]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);
[a7de7907]459
[5481d1bb]460 pio_write_8(&cmd->command, CMD_READ_SECTORS);
[f8ef660]461
[31de325]462 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
463 fibril_mutex_unlock(&d->lock);
464 return EIO;
465 }
[f8ef660]466
[a7de7907]467 if ((status & SR_DRQ) != 0) {
[31de325]468 /* Read data from the device buffer. */
469
[a7de7907]470 for (i = 0; i < block_size / 2; i++) {
471 data = pio_read_16(&cmd->data_port);
472 ((uint16_t *) buf)[i] = data;
473 }
[f8ef660]474 }
475
[a7de7907]476 if ((status & SR_ERR) != 0)
477 return EIO;
478
[12956e57]479 fibril_mutex_unlock(&d->lock);
[f8ef660]480 return EOK;
481}
482
[54d0ddc]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 */
[d9f4c76]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
[12956e57]520 fibril_mutex_lock(&d->lock);
[d9f4c76]521
[31de325]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 }
[d9f4c76]528
529 pio_write_8(&cmd->drive_head, drv_head);
[a7de7907]530
[31de325]531 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
532 fibril_mutex_unlock(&d->lock);
533 return EIO;
534 }
535
[d9f4c76]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);
[a7de7907]540
[d9f4c76]541 pio_write_8(&cmd->command, CMD_WRITE_SECTORS);
542
[31de325]543 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
544 fibril_mutex_unlock(&d->lock);
545 return EIO;
546 }
[d9f4c76]547
[a7de7907]548 if ((status & SR_DRQ) != 0) {
[31de325]549 /* Write data to the device buffer. */
550
[a7de7907]551 for (i = 0; i < block_size / 2; i++) {
552 pio_write_16(&cmd->data_port, ((uint16_t *) buf)[i]);
553 }
[d9f4c76]554 }
555
[12956e57]556 fibril_mutex_unlock(&d->lock);
[a7de7907]557
558 if (status & SR_ERR)
559 return EIO;
560
[d9f4c76]561 return EOK;
562}
563
[31de325]564/** Wait until some status bits are set and some are reset.
[54d0ddc]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.
[31de325]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.
[54d0ddc]575 */
[31de325]576static int wait_status(unsigned set, unsigned n_reset, uint8_t *pstatus,
577 unsigned timeout)
[54d0ddc]578{
579 uint8_t status;
[31de325]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;
[54d0ddc]604
605 status = pio_read_8(&cmd->status);
[31de325]606 }
607
608 if (pstatus)
609 *pstatus = status;
[54d0ddc]610
[31de325]611 if (cnt == 0)
612 return EIO;
613
614 return EOK;
[54d0ddc]615}
616
[f8ef660]617/**
618 * @}
619 */
Note: See TracBrowser for help on using the repository browser.