source: mainline/uspace/srv/bd/ata_bd/ata_bd.c@ 1cab2f41

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 1cab2f41 was 12956e57, checked in by Jiri Svoboda <jirik.svoboda@…>, 16 years ago

Use fibril synchronization in bd drivers. Use per-disk locks.

  • Property mode set to 100644
File size: 9.7 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) ANSI X3.221-1994 (ATA-1) standard.
39 * At this point only reading is possible, not writing.
40 *
41 * The driver services a single controller which can have up to two disks
42 * attached.
43 */
44
45#include <stdio.h>
46#include <libarch/ddi.h>
47#include <ddi.h>
48#include <ipc/ipc.h>
49#include <ipc/bd.h>
50#include <async.h>
51#include <as.h>
52#include <fibril_sync.h>
53#include <devmap.h>
54#include <sys/types.h>
55#include <errno.h>
56#include <bool.h>
57
58#include "ata_bd.h"
59
60#define NAME "ata_bd"
61
62static const size_t block_size = 512;
63static size_t comm_size;
64
65static uintptr_t cmd_physical = 0x1f0;
66static uintptr_t ctl_physical = 0x170;
67static ata_cmd_t *cmd;
68static ata_ctl_t *ctl;
69
70/** Per-disk state. */
71static disk_t disk[MAX_DISKS];
72
73static int ata_bd_init(void);
74static void ata_bd_connection(ipc_callid_t iid, ipc_call_t *icall);
75static int ata_bd_rdwr(int disk_id, ipcarg_t method, off_t offset, size_t size,
76 void *buf);
77static int ata_bd_read_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
78 void *buf);
79static int ata_bd_write_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
80 const void *buf);
81static int drive_identify(int drive_id, disk_t *d);
82
83int main(int argc, char **argv)
84{
85 uint8_t status;
86 char name[16];
87 int i, rc;
88 int n_disks;
89
90 printf(NAME ": ATA disk driver\n");
91
92 printf("I/O address 0x%x\n", cmd_physical);
93
94 if (ata_bd_init() != EOK)
95 return -1;
96
97 /* Put drives to reset, disable interrupts. */
98 printf("Reset drives...\n");
99 pio_write_8(&ctl->device_control, DCR_SRST);
100 /* FIXME: Find out how to do this properly. */
101 async_usleep(100);
102 pio_write_8(&ctl->device_control, 0);
103
104 do {
105 status = pio_read_8(&cmd->status);
106 } while ((status & SR_BSY) != 0);
107 printf("Done\n");
108
109 printf("Status = 0x%x\n", pio_read_8(&cmd->status));
110
111 (void) drive_identify(0, &disk[0]);
112 (void) drive_identify(1, &disk[1]);
113
114 n_disks = 0;
115
116 for (i = 0; i < MAX_DISKS; i++) {
117 /* Skip unattached drives. */
118 if (disk[i].present == false)
119 continue;
120
121 snprintf(name, 16, "disk%d", i);
122 rc = devmap_device_register(name, &disk[i].dev_handle);
123 if (rc != EOK) {
124 devmap_hangup_phone(DEVMAP_DRIVER);
125 printf(NAME ": Unable to register device %s.\n",
126 name);
127 return rc;
128 }
129 ++n_disks;
130 }
131
132 if (n_disks == 0) {
133 printf("No disks detected.\n");
134 return -1;
135 }
136
137 printf(NAME ": Accepting connections\n");
138 async_manager();
139
140 /* Not reached */
141 return 0;
142}
143
144static int drive_identify(int disk_id, disk_t *d)
145{
146 uint16_t data;
147 uint8_t status;
148 size_t i;
149
150 printf("Identify drive %d\n", disk_id);
151 pio_write_8(&cmd->drive_head, ((disk_id != 0) ? DHR_DRV : 0));
152 async_usleep(100);
153 pio_write_8(&cmd->command, CMD_IDENTIFY_DRIVE);
154
155 status = pio_read_8(&cmd->status);
156 printf("Status = 0x%x\n", status);
157
158 d->present = false;
159
160 /*
161 * Detect if drive is present. This is Qemu only! Need to
162 * do the right thing to work with real drives.
163 */
164 if ((status & SR_DRDY) == 0) {
165 printf("None attached.\n");
166 return ENOENT;
167 }
168
169 for (i = 0; i < block_size / 2; i++) {
170 do {
171 status = pio_read_8(&cmd->status);
172 } while ((status & SR_DRDY) == 0);
173
174 data = pio_read_16(&cmd->data_port);
175
176 switch (i) {
177 case 1: d->cylinders = data; break;
178 case 3: d->heads = data; break;
179 case 6: d->sectors = data; break;
180 }
181 }
182
183 d->blocks = d->cylinders * d->heads * d->sectors;
184
185 printf("Geometry: %u cylinders, %u heads, %u sectors\n",
186 d->cylinders, d->heads, d->sectors);
187
188 d->present = true;
189 fibril_mutex_initialize(&d->lock);
190
191 return EOK;
192}
193
194static int ata_bd_init(void)
195{
196 void *vaddr;
197 int rc;
198
199 rc = devmap_driver_register(NAME, ata_bd_connection);
200 if (rc < 0) {
201 printf(NAME ": Unable to register driver.\n");
202 return rc;
203 }
204
205 rc = pio_enable((void *) cmd_physical, sizeof(ata_cmd_t), &vaddr);
206 if (rc != EOK) {
207 printf(NAME ": Could not initialize device I/O space.\n");
208 return rc;
209 }
210
211 cmd = vaddr;
212
213 rc = pio_enable((void *) ctl_physical, sizeof(ata_ctl_t), &vaddr);
214 if (rc != EOK) {
215 printf(NAME ": Could not initialize device I/O space.\n");
216 return rc;
217 }
218
219 ctl = vaddr;
220
221
222 return EOK;
223}
224
225static void ata_bd_connection(ipc_callid_t iid, ipc_call_t *icall)
226{
227 void *fs_va = NULL;
228 ipc_callid_t callid;
229 ipc_call_t call;
230 ipcarg_t method;
231 dev_handle_t dh;
232 int flags;
233 int retval;
234 off_t idx;
235 size_t size;
236 int disk_id, i;
237
238 /* Get the device handle. */
239 dh = IPC_GET_ARG1(*icall);
240
241 /* Determine which disk device is the client connecting to. */
242 disk_id = -1;
243 for (i = 0; i < MAX_DISKS; i++)
244 if (disk[i].dev_handle == dh)
245 disk_id = i;
246
247 if (disk_id < 0 || disk[disk_id].present == false) {
248 ipc_answer_0(iid, EINVAL);
249 return;
250 }
251
252 /* Answer the IPC_M_CONNECT_ME_TO call. */
253 ipc_answer_0(iid, EOK);
254
255 if (!ipc_share_out_receive(&callid, &comm_size, &flags)) {
256 ipc_answer_0(callid, EHANGUP);
257 return;
258 }
259
260 fs_va = as_get_mappable_page(comm_size);
261 if (fs_va == NULL) {
262 ipc_answer_0(callid, EHANGUP);
263 return;
264 }
265
266 (void) ipc_share_out_finalize(callid, fs_va);
267
268 while (1) {
269 callid = async_get_call(&call);
270 method = IPC_GET_METHOD(call);
271 switch (method) {
272 case IPC_M_PHONE_HUNGUP:
273 /* The other side has hung up. */
274 ipc_answer_0(callid, EOK);
275 return;
276 case BD_READ_BLOCK:
277 case BD_WRITE_BLOCK:
278 idx = IPC_GET_ARG1(call);
279 size = IPC_GET_ARG2(call);
280 if (size > comm_size) {
281 retval = EINVAL;
282 break;
283 }
284 retval = ata_bd_rdwr(disk_id, method, idx,
285 size, fs_va);
286 break;
287 default:
288 retval = EINVAL;
289 break;
290 }
291 ipc_answer_0(callid, retval);
292 }
293}
294
295static int ata_bd_rdwr(int disk_id, ipcarg_t method, off_t blk_idx, size_t size,
296 void *buf)
297{
298 int rc;
299 size_t now;
300
301 while (size > 0) {
302 now = size < block_size ? size : block_size;
303 if (now != block_size)
304 return EINVAL;
305
306 if (method == BD_READ_BLOCK)
307 rc = ata_bd_read_block(disk_id, blk_idx, 1, buf);
308 else
309 rc = ata_bd_write_block(disk_id, blk_idx, 1, buf);
310
311 if (rc != EOK)
312 return rc;
313
314 buf += block_size;
315 blk_idx++;
316
317 if (size > block_size)
318 size -= block_size;
319 else
320 size = 0;
321 }
322
323 return EOK;
324}
325
326
327static int ata_bd_read_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
328 void *buf)
329{
330 size_t i;
331 uint16_t data;
332 uint8_t status;
333 uint64_t c, h, s;
334 uint64_t idx;
335 uint8_t drv_head;
336 disk_t *d;
337
338 d = &disk[disk_id];
339
340 /* Check device bounds. */
341 if (blk_idx >= d->blocks)
342 return EINVAL;
343
344 /* Compute CHS. */
345 c = blk_idx / (d->heads * d->sectors);
346 idx = blk_idx % (d->heads * d->sectors);
347
348 h = idx / d->sectors;
349 s = 1 + (idx % d->sectors);
350
351 /* New value for Drive/Head register */
352 drv_head =
353 ((disk_id != 0) ? DHR_DRV : 0) |
354 (h & 0x0f);
355
356 fibril_mutex_lock(&d->lock);
357
358 /* Program a Read Sectors operation. */
359
360 pio_write_8(&cmd->drive_head, drv_head);
361 pio_write_8(&cmd->sector_count, 1);
362 pio_write_8(&cmd->sector_number, s);
363 pio_write_8(&cmd->cylinder_low, c & 0xff);
364 pio_write_8(&cmd->cylinder_high, c >> 16);
365 pio_write_8(&cmd->command, CMD_READ_SECTORS);
366
367 /* Read data from the disk buffer. */
368
369 for (i = 0; i < block_size / 2; i++) {
370 do {
371 status = pio_read_8(&cmd->status);
372 } while ((status & SR_DRDY) == 0);
373
374 data = pio_read_16(&cmd->data_port);
375 ((uint16_t *) buf)[i] = data;
376 }
377
378 fibril_mutex_unlock(&d->lock);
379 return EOK;
380}
381
382static int ata_bd_write_block(int disk_id, uint64_t blk_idx, size_t blk_cnt,
383 const void *buf)
384{
385 size_t i;
386 uint8_t status;
387 uint64_t c, h, s;
388 uint64_t idx;
389 uint8_t drv_head;
390 disk_t *d;
391
392 d = &disk[disk_id];
393
394 /* Check device bounds. */
395 if (blk_idx >= d->blocks)
396 return EINVAL;
397
398 /* Compute CHS. */
399 c = blk_idx / (d->heads * d->sectors);
400 idx = blk_idx % (d->heads * d->sectors);
401
402 h = idx / d->sectors;
403 s = 1 + (idx % d->sectors);
404
405 /* New value for Drive/Head register */
406 drv_head =
407 ((disk_id != 0) ? DHR_DRV : 0) |
408 (h & 0x0f);
409
410 fibril_mutex_lock(&d->lock);
411
412 /* Program a Read Sectors operation. */
413
414 pio_write_8(&cmd->drive_head, drv_head);
415 pio_write_8(&cmd->sector_count, 1);
416 pio_write_8(&cmd->sector_number, s);
417 pio_write_8(&cmd->cylinder_low, c & 0xff);
418 pio_write_8(&cmd->cylinder_high, c >> 16);
419 pio_write_8(&cmd->command, CMD_WRITE_SECTORS);
420
421 /* Write data to the disk buffer. */
422
423 for (i = 0; i < block_size / 2; i++) {
424 do {
425 status = pio_read_8(&cmd->status);
426 } while ((status & SR_DRDY) == 0);
427
428 pio_write_16(&cmd->data_port, ((uint16_t *) buf)[i]);
429 }
430
431 fibril_mutex_unlock(&d->lock);
432 return EOK;
433}
434
435
436/**
437 * @}
438 */
Note: See TracBrowser for help on using the repository browser.