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
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>
[f8ef660]52#include <devmap.h>
53#include <sys/types.h>
54#include <errno.h>
[1806e5d]55#include <bool.h>
[95bc57c]56#include <task.h>
[f8ef660]57
[4f5caea]58#include "ata_bd.h"
[f8ef660]59
[4f5caea]60#define NAME "ata_bd"
[1806e5d]61
[54d0ddc]62/** Physical block size. Should be always 512. */
[f8ef660]63static const size_t block_size = 512;
[54d0ddc]64
65/** Size of the communication area. */
[f8ef660]66static size_t comm_size;
67
[54d0ddc]68/** I/O base address of the command registers. */
[f8ef660]69static uintptr_t cmd_physical = 0x1f0;
[54d0ddc]70/** I/O base address of the control registers. */
[f8ef660]71static uintptr_t ctl_physical = 0x170;
[54d0ddc]72
[f8ef660]73static ata_cmd_t *cmd;
74static ata_ctl_t *ctl;
75
[12956e57]76/** Per-disk state. */
[5481d1bb]77static disk_t disk[MAX_DISKS];
[f8ef660]78
79static int ata_bd_init(void);
80static void ata_bd_connection(ipc_callid_t iid, ipc_call_t *icall);
[fbbbb8a]81static int ata_bd_rdwr(int disk_id, ipcarg_t method, off_t offset, size_t size,
[f8ef660]82 void *buf);
83static int ata_bd_read_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
84 void *buf);
[d9f4c76]85static int ata_bd_write_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
86 const void *buf);
[1806e5d]87static int drive_identify(int drive_id, disk_t *d);
[31de325]88static int wait_status(unsigned set, unsigned n_reset, uint8_t *pstatus,
89 unsigned timeout);
[f8ef660]90
91int main(int argc, char **argv)
92{
[1806e5d]93 char name[16];
94 int i, rc;
95 int n_disks;
[f8ef660]96
97 printf(NAME ": ATA disk driver\n");
98
[12956e57]99 printf("I/O address 0x%x\n", cmd_physical);
[f8ef660]100
101 if (ata_bd_init() != EOK)
102 return -1;
103
[31de325]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 }
[1806e5d]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);
[12956e57]126 rc = devmap_device_register(name, &disk[i].dev_handle);
[1806e5d]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");
[95bc57c]142 task_retval(0);
[1806e5d]143 async_manager();
144
145 /* Not reached */
146 return 0;
147}
148
[f8ef660]149
[54d0ddc]150/** Register driver and enable device I/O. */
[f8ef660]151static int ata_bd_init(void)
152{
153 void *vaddr;
[1806e5d]154 int rc;
[f8ef660]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
[54d0ddc]182/** Block device connection handler */
[f8ef660]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;
[fbbbb8a]193 size_t size;
[f8ef660]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++)
[12956e57]202 if (disk[i].dev_handle == dh)
[f8ef660]203 disk_id = i;
204
[1806e5d]205 if (disk_id < 0 || disk[disk_id].present == false) {
[f8ef660]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
[54d0ddc]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 */
[fbbbb8a]263static int ata_bd_rdwr(int disk_id, ipcarg_t method, off_t blk_idx, size_t size,
[f8ef660]264 void *buf)
265{
266 int rc;
[fbbbb8a]267 size_t now;
[f8ef660]268
269 while (size > 0) {
[fbbbb8a]270 now = size < block_size ? size : block_size;
[f8ef660]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
[d9f4c76]277 rc = ata_bd_write_block(disk_id, blk_idx, 1, buf);
[f8ef660]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
[54d0ddc]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;
[f8ef660]308
[54d0ddc]309 drv_head = ((disk_id != 0) ? DHR_DRV : 0);
310 d->present = false;
311
[31de325]312 if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
313 return EIO;
314
[54d0ddc]315 pio_write_8(&cmd->drive_head, drv_head);
316
317 /*
[31de325]318 * This is where we would most likely expect a non-existing device to
319 * show up by not setting SR_DRDY.
[54d0ddc]320 */
[31de325]321 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
322 return EIO;
[54d0ddc]323
324 pio_write_8(&cmd->command, CMD_IDENTIFY_DRIVE);
325
[31de325]326 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_PROBE) != EOK)
327 return EIO;
[54d0ddc]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 */
[f8ef660]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;
[1806e5d]377 disk_t *d;
378
379 d = &disk[disk_id];
[f8ef660]380
381 /* Check device bounds. */
[1806e5d]382 if (blk_idx >= d->blocks)
[f8ef660]383 return EINVAL;
384
385 /* Compute CHS. */
[1806e5d]386 c = blk_idx / (d->heads * d->sectors);
387 idx = blk_idx % (d->heads * d->sectors);
[f8ef660]388
[1806e5d]389 h = idx / d->sectors;
390 s = 1 + (idx % d->sectors);
[f8ef660]391
392 /* New value for Drive/Head register */
393 drv_head =
394 ((disk_id != 0) ? DHR_DRV : 0) |
395 (h & 0x0f);
396
[12956e57]397 fibril_mutex_lock(&d->lock);
[f8ef660]398
399 /* Program a Read Sectors operation. */
400
[31de325]401 if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
402 fibril_mutex_unlock(&d->lock);
403 return EIO;
404 }
405
[f8ef660]406 pio_write_8(&cmd->drive_head, drv_head);
[a7de7907]407
[31de325]408 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
409 fibril_mutex_unlock(&d->lock);
410 return EIO;
411 }
412
[f8ef660]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);
[a7de7907]417
[5481d1bb]418 pio_write_8(&cmd->command, CMD_READ_SECTORS);
[f8ef660]419
[31de325]420 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
421 fibril_mutex_unlock(&d->lock);
422 return EIO;
423 }
[f8ef660]424
[a7de7907]425 if ((status & SR_DRQ) != 0) {
[31de325]426 /* Read data from the device buffer. */
427
[a7de7907]428 for (i = 0; i < block_size / 2; i++) {
429 data = pio_read_16(&cmd->data_port);
430 ((uint16_t *) buf)[i] = data;
431 }
[f8ef660]432 }
433
[a7de7907]434 if ((status & SR_ERR) != 0)
435 return EIO;
436
[12956e57]437 fibril_mutex_unlock(&d->lock);
[f8ef660]438 return EOK;
439}
440
[54d0ddc]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 */
[d9f4c76]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
[12956e57]478 fibril_mutex_lock(&d->lock);
[d9f4c76]479
[31de325]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 }
[d9f4c76]486
487 pio_write_8(&cmd->drive_head, drv_head);
[a7de7907]488
[31de325]489 if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
490 fibril_mutex_unlock(&d->lock);
491 return EIO;
492 }
493
[d9f4c76]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);
[a7de7907]498
[d9f4c76]499 pio_write_8(&cmd->command, CMD_WRITE_SECTORS);
500
[31de325]501 if (wait_status(0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
502 fibril_mutex_unlock(&d->lock);
503 return EIO;
504 }
[d9f4c76]505
[a7de7907]506 if ((status & SR_DRQ) != 0) {
[31de325]507 /* Write data to the device buffer. */
508
[a7de7907]509 for (i = 0; i < block_size / 2; i++) {
510 pio_write_16(&cmd->data_port, ((uint16_t *) buf)[i]);
511 }
[d9f4c76]512 }
513
[12956e57]514 fibril_mutex_unlock(&d->lock);
[a7de7907]515
516 if (status & SR_ERR)
517 return EIO;
518
[d9f4c76]519 return EOK;
520}
521
[31de325]522/** Wait until some status bits are set and some are reset.
[54d0ddc]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.
[31de325]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.
[54d0ddc]533 */
[31de325]534static int wait_status(unsigned set, unsigned n_reset, uint8_t *pstatus,
535 unsigned timeout)
[54d0ddc]536{
537 uint8_t status;
[31de325]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;
[54d0ddc]562
563 status = pio_read_8(&cmd->status);
[31de325]564 }
565
566 if (pstatus)
567 *pstatus = status;
[54d0ddc]568
[31de325]569 if (cnt == 0)
570 return EIO;
571
572 return EOK;
[54d0ddc]573}
574
[f8ef660]575/**
576 * @}
577 */
Note: See TracBrowser for help on using the repository browser.