source: mainline/uspace/drv/block/ddisk/ddisk.c@ 0eacc32

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 0eacc32 was 0eacc32, checked in by Jakub Jermar <jakub@…>, 11 years ago

Register and handle ddisk interrupt.
Implement ddisk_bd_read/write_blocks().

  • Property mode set to 100644
File size: 13.8 KB
Line 
1/*
2 * Copyright (c) 2015 Jakub Jermar
3 * Copyright (c) 2013 Jiri Svoboda
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @file
31 * MSIM ddisk block device driver
32 */
33
34#include <assert.h>
35#include <stdio.h>
36#include <errno.h>
37#include <str_error.h>
38#include <ddf/driver.h>
39#include <ddf/interrupt.h>
40#include <ddf/log.h>
41#include <device/hw_res_parsed.h>
42#include <ddi.h>
43#include <bd_srv.h>
44#include <fibril_synch.h>
45#include <as.h>
46
47#define NAME "ddisk"
48
49#define DDISK_FUN_NAME "a"
50
51#define DDISK_BLOCK_SIZE 512
52
53#define DDISK_STAT_IRQ_PENDING 0x4
54#define DDISK_CMD_READ 0x1
55#define DDISK_CMD_WRITE 0x2
56#define DDISK_CMD_IRQ_DEASSERT 0x4
57
58static int ddisk_dev_add(ddf_dev_t *);
59static int ddisk_dev_remove(ddf_dev_t *);
60static int ddisk_dev_gone(ddf_dev_t *);
61static int ddisk_fun_online(ddf_fun_t *);
62static int ddisk_fun_offline(ddf_fun_t *);
63
64static void ddisk_bd_connection(ipc_callid_t, ipc_call_t *, void *);
65
66static void ddisk_irq_handler(ipc_callid_t, ipc_call_t *, ddf_dev_t *);
67
68static driver_ops_t driver_ops = {
69 .dev_add = ddisk_dev_add,
70 .dev_remove = ddisk_dev_remove,
71 .dev_gone = ddisk_dev_gone,
72 .fun_online = ddisk_fun_online,
73 .fun_offline = ddisk_fun_offline
74};
75
76static driver_t ddisk_driver = {
77 .name = NAME,
78 .driver_ops = &driver_ops
79};
80
81typedef struct {
82 int irq;
83 uintptr_t base;
84} ddisk_res_t;
85
86typedef struct {
87 ioport32_t dma_buffer;
88 ioport32_t block;
89 union {
90 ioport32_t status;
91 ioport32_t command;
92 };
93 ioport32_t size;
94} ddisk_regs_t;
95
96struct ddisk_dev;
97
98typedef struct ddisk_fun {
99 ddf_fun_t *fun;
100 struct ddisk_dev *ddisk_dev;
101
102 size_t blocks;
103
104 bd_srvs_t bds;
105} ddisk_fun_t;
106
107typedef struct ddisk_dev {
108 fibril_mutex_t lock;
109 fibril_condvar_t io_cv;
110 bool io_busy;
111 ddf_dev_t *dev;
112 ddisk_res_t ddisk_res;
113 ddisk_fun_t *ddisk_fun;
114 ddisk_regs_t *ddisk_regs;
115 uintptr_t dma_buffer_phys;
116 void *dma_buffer;
117} ddisk_dev_t;
118
119static int ddisk_bd_open(bd_srvs_t *, bd_srv_t *);
120static int ddisk_bd_close(bd_srv_t *);
121static int ddisk_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
122static int ddisk_bd_write_blocks(bd_srv_t *, aoff64_t, size_t, const void *,
123 size_t);
124static int ddisk_bd_get_block_size(bd_srv_t *, size_t *);
125static int ddisk_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
126
127bd_ops_t ddisk_bd_ops = {
128 .open = ddisk_bd_open,
129 .close = ddisk_bd_close,
130 .read_blocks = ddisk_bd_read_blocks,
131 .write_blocks = ddisk_bd_write_blocks,
132 .get_block_size = ddisk_bd_get_block_size,
133 .get_num_blocks = ddisk_bd_get_num_blocks,
134};
135
136irq_pio_range_t ddisk_irq_pio_ranges[] = {
137 {
138 .base = 0,
139 .size = sizeof(ddisk_regs_t)
140 }
141};
142
143irq_cmd_t ddisk_irq_commands[] = {
144 {
145 .cmd = CMD_PIO_READ_32,
146 .addr = NULL,
147 .dstarg = 1
148 },
149 {
150 .cmd = CMD_AND,
151 .srcarg = 1,
152 .value = DDISK_STAT_IRQ_PENDING,
153 .dstarg = 2
154 },
155 {
156 .cmd = CMD_PREDICATE,
157 .srcarg = 2,
158 .value = 2
159 },
160 {
161 /* Deassert the DMA interrupt. */
162 .cmd = CMD_PIO_WRITE_32,
163 .value = DDISK_CMD_IRQ_DEASSERT,
164 .addr = NULL
165 },
166 {
167 .cmd = CMD_ACCEPT
168 }
169};
170
171irq_code_t ddisk_irq_code = {
172 .rangecount = 1,
173 .ranges = ddisk_irq_pio_ranges,
174 .cmdcount = sizeof(ddisk_irq_commands) / sizeof(irq_cmd_t),
175 .cmds = ddisk_irq_commands,
176};
177
178void ddisk_irq_handler(ipc_callid_t iid, ipc_call_t *icall, ddf_dev_t *dev)
179{
180 ddf_msg(LVL_NOTE, "ddisk_irq_handler(), status=%" PRIx32,
181 (uint32_t) IPC_GET_ARG1(*icall));
182
183 ddisk_dev_t *ddisk_dev = (ddisk_dev_t *) ddf_dev_data_get(dev);
184
185 fibril_mutex_lock(&ddisk_dev->lock);
186 fibril_condvar_broadcast(&ddisk_dev->io_cv);
187 fibril_mutex_unlock(&ddisk_dev->lock);
188}
189
190int ddisk_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
191{
192 return EOK;
193}
194
195int ddisk_bd_close(bd_srv_t *bd)
196{
197 return EOK;
198}
199
200static
201int ddisk_rw_block(ddisk_dev_t *ddisk_dev, bool read, aoff64_t ba, void *buf)
202{
203 fibril_mutex_lock(&ddisk_dev->lock);
204
205 ddf_msg(LVL_NOTE, "ddisk_rw_block(): read=%d, ba=%" PRId64 ", buf=%p",
206 read, ba, buf);
207
208 if (ba >= ddisk_dev->ddisk_fun->blocks)
209 return ELIMIT;
210
211 while (ddisk_dev->io_busy)
212 fibril_condvar_wait(&ddisk_dev->io_cv, &ddisk_dev->lock);
213
214 ddisk_dev->io_busy = true;
215
216 if (!read)
217 memcpy(ddisk_dev->dma_buffer, buf, DDISK_BLOCK_SIZE);
218
219 pio_write_32(&ddisk_dev->ddisk_regs->dma_buffer,
220 ddisk_dev->dma_buffer_phys);
221 pio_write_32(&ddisk_dev->ddisk_regs->block, (uint32_t) ba);
222 pio_write_32(&ddisk_dev->ddisk_regs->command,
223 read ? DDISK_CMD_READ : DDISK_CMD_WRITE);
224
225 fibril_condvar_wait(&ddisk_dev->io_cv, &ddisk_dev->lock);
226
227 if (read)
228 memcpy(buf, ddisk_dev->dma_buffer, DDISK_BLOCK_SIZE);
229
230 ddisk_dev->io_busy = false;
231 fibril_condvar_signal(&ddisk_dev->io_cv);
232 fibril_mutex_unlock(&ddisk_dev->lock);
233
234 return EOK;
235}
236
237static
238int ddisk_bd_rw_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt, void *buf,
239 size_t size, bool is_read)
240{
241 ddisk_fun_t *ddisk_fun = (ddisk_fun_t *) bd->srvs->sarg;
242 aoff64_t i;
243 int rc;
244
245 if (size < cnt * DDISK_BLOCK_SIZE)
246 return EINVAL;
247
248 for (i = 0; i < cnt; i++) {
249 rc = ddisk_rw_block(ddisk_fun->ddisk_dev, is_read, ba + i,
250 buf + i * DDISK_BLOCK_SIZE);
251 if (rc != EOK)
252 return rc;
253 }
254
255 return EOK;
256}
257
258int ddisk_bd_read_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt, void *buf,
259 size_t size)
260{
261 return ddisk_bd_rw_blocks(bd, ba, cnt, buf, size, true);
262}
263
264int ddisk_bd_write_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
265 const void *buf, size_t size)
266{
267 return ddisk_bd_rw_blocks(bd, ba, cnt, (void *) buf, size, false);
268}
269
270int ddisk_bd_get_block_size(bd_srv_t *bd, size_t *rsize)
271{
272 *rsize = DDISK_BLOCK_SIZE;
273 return EOK;
274}
275
276int ddisk_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *rnb)
277{
278 ddisk_fun_t *ddisk_fun = (ddisk_fun_t *) bd->srvs->sarg;
279
280 *rnb = ddisk_fun->blocks;
281 return EOK;
282}
283
284static int ddisk_get_res(ddf_dev_t *dev, ddisk_res_t *ddisk_res)
285{
286 async_sess_t *parent_sess;
287 hw_res_list_parsed_t hw_res;
288 int rc;
289
290 parent_sess = ddf_dev_parent_sess_create(dev, EXCHANGE_SERIALIZE);
291 if (parent_sess == NULL)
292 return ENOMEM;
293
294 hw_res_list_parsed_init(&hw_res);
295 rc = hw_res_get_list_parsed(parent_sess, &hw_res, 0);
296 if (rc != EOK)
297 return rc;
298
299 if ((hw_res.mem_ranges.count != 1) || (hw_res.irqs.count != 1)) {
300 rc = EINVAL;
301 goto error;
302 }
303
304 addr_range_t *regs = &hw_res.mem_ranges.ranges[0];
305 ddisk_res->base = RNGABS(*regs);
306 ddisk_res->irq = hw_res.irqs.irqs[0];
307
308 if (RNGSZ(*regs) < sizeof(ddisk_regs_t)) {
309 rc = EINVAL;
310 goto error;
311 }
312
313 rc = EOK;
314error:
315 hw_res_list_parsed_clean(&hw_res);
316 return rc;
317}
318
319static int ddisk_fun_create(ddisk_dev_t *ddisk_dev)
320{
321 int rc;
322 ddf_fun_t *fun = NULL;
323 ddisk_fun_t *ddisk_fun = NULL;
324
325 fun = ddf_fun_create(ddisk_dev->dev, fun_exposed, DDISK_FUN_NAME);
326 if (fun == NULL) {
327 ddf_msg(LVL_ERROR, "Failed creating DDF function.");
328 rc = ENOMEM;
329 goto error;
330 }
331
332 /* Allocate soft state */
333 ddisk_fun = ddf_fun_data_alloc(fun, sizeof(ddisk_fun_t));
334 if (!ddisk_fun) {
335 ddf_msg(LVL_ERROR, "Failed allocating softstate.");
336 rc = ENOMEM;
337 goto error;
338 }
339
340 ddisk_fun->fun = fun;
341 ddisk_fun->ddisk_dev = ddisk_dev;
342
343 bd_srvs_init(&ddisk_fun->bds);
344 ddisk_fun->bds.ops = &ddisk_bd_ops;
345 ddisk_fun->bds.sarg = ddisk_fun;
346
347 /* Set up a connection handler. */
348 ddf_fun_set_conn_handler(fun, ddisk_bd_connection);
349
350 ddisk_fun->blocks = pio_read_32(&ddisk_dev->ddisk_regs->size) /
351 DDISK_BLOCK_SIZE;
352
353 rc = ddf_fun_bind(fun);
354 if (rc != EOK) {
355 ddf_msg(LVL_ERROR, "Failed binding DDF function %s: %s",
356 DDISK_FUN_NAME, str_error(rc));
357 goto error;
358 }
359
360 ddf_fun_add_to_category(fun, "bd");
361
362 ddisk_dev->ddisk_fun = ddisk_fun;
363 return EOK;
364error:
365 if (fun != NULL)
366 ddf_fun_destroy(fun);
367
368 return rc;
369}
370
371static int ddisk_fun_remove(ddisk_fun_t *ddisk_fun)
372{
373 int rc;
374
375 if (ddisk_fun->fun == NULL)
376 return EOK;
377
378 ddf_msg(LVL_DEBUG, "ddisk_fun_remove(%p, '%s')", ddisk_fun,
379 DDISK_FUN_NAME);
380 rc = ddf_fun_offline(ddisk_fun->fun);
381 if (rc != EOK) {
382 ddf_msg(LVL_ERROR, "Error offlining function '%s'.",
383 DDISK_FUN_NAME);
384 goto error;
385 }
386
387 rc = ddf_fun_unbind(ddisk_fun->fun);
388 if (rc != EOK) {
389 ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.",
390 DDISK_FUN_NAME);
391 goto error;
392 }
393
394 ddf_fun_destroy(ddisk_fun->fun);
395 ddisk_fun->fun = NULL;
396 rc = EOK;
397error:
398 return rc;
399}
400
401static int ddisk_fun_unbind(ddisk_fun_t *ddisk_fun)
402{
403 int rc;
404
405 if (ddisk_fun->fun == NULL)
406 return EOK;
407
408 ddf_msg(LVL_DEBUG, "ddisk_fun_unbind(%p, '%s')", ddisk_fun,
409 DDISK_FUN_NAME);
410 rc = ddf_fun_unbind(ddisk_fun->fun);
411 if (rc != EOK) {
412 ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.",
413 DDISK_FUN_NAME);
414 goto error;
415 }
416
417 ddf_fun_destroy(ddisk_fun->fun);
418 ddisk_fun->fun = NULL;
419 rc = EOK;
420error:
421 return rc;
422}
423
424/** Add new device
425 *
426 * @param dev New device
427 * @return EOK on success or negative error code.
428 */
429static int ddisk_dev_add(ddf_dev_t *dev)
430{
431 ddisk_dev_t *ddisk_dev;
432 ddisk_res_t res;
433 int rc;
434
435 rc = ddisk_get_res(dev, &res);
436 if (rc != EOK) {
437 ddf_msg(LVL_ERROR, "Invalid HW resource configuration.");
438 return EINVAL;
439 }
440
441 ddisk_dev = ddf_dev_data_alloc(dev, sizeof(ddisk_dev_t));
442 if (!ddisk_dev) {
443 ddf_msg(LVL_ERROR, "Failed allocating soft state.");
444 rc = ENOMEM;
445 goto error;
446 }
447
448 fibril_mutex_initialize(&ddisk_dev->lock);
449 fibril_condvar_initialize(&ddisk_dev->io_cv);
450 ddisk_dev->io_busy = false;
451 ddisk_dev->dev = dev;
452 ddisk_dev->ddisk_res = res;
453
454 /*
455 * Enable access to ddisk's PIO registers.
456 */
457 void *vaddr;
458 rc = pio_enable((void *) res.base, sizeof(ddisk_regs_t), &vaddr);
459 if (rc != EOK) {
460 ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
461 goto error;
462 }
463 ddisk_dev->ddisk_regs = vaddr;
464
465 if ((int32_t) pio_read_32(&ddisk_dev->ddisk_regs->size) <= 0) {
466 ddf_msg(LVL_WARN, "No disk detected.");
467 rc = EIO;
468 goto error;
469 }
470
471 /*
472 * Allocate DMA buffer.
473 */
474 ddisk_dev->dma_buffer = AS_AREA_ANY;
475 rc = dmamem_map_anonymous(DDISK_BLOCK_SIZE, DMAMEM_4GiB,
476 AS_AREA_READ | AS_AREA_WRITE, 0, &ddisk_dev->dma_buffer_phys,
477 &ddisk_dev->dma_buffer);
478 if (rc != EOK) {
479 ddf_msg(LVL_ERROR, "Cannot allocate DMA memory.");
480 goto error;
481 }
482
483 ddf_msg(LVL_NOTE, "Allocated DMA buffer at %p virtual and %p physical.",
484 ddisk_dev->dma_buffer, (void *) ddisk_dev->dma_buffer_phys);
485
486 rc = ddisk_fun_create(ddisk_dev);
487 if (rc != EOK) {
488 ddf_msg(LVL_ERROR, "Failed initializing ddisk controller.");
489 rc = EIO;
490 goto error;
491 }
492
493 /*
494 * Register IRQ handler.
495 */
496 ddisk_regs_t *res_phys = (ddisk_regs_t *) res.base;
497 ddisk_irq_pio_ranges[0].base = res.base;
498 ddisk_irq_commands[0].addr = (void *) &res_phys->status;
499 ddisk_irq_commands[3].addr = (void *) &res_phys->command;
500 rc = register_interrupt_handler(dev, ddisk_dev->ddisk_res.irq,
501 ddisk_irq_handler, &ddisk_irq_code);
502 if (rc != EOK) {
503 ddf_msg(LVL_ERROR, "Failed to register interrupt handler.");
504 goto error;
505 }
506
507 /*
508 * Success, report what we have found.
509 */
510 ddf_msg(LVL_NOTE,
511 "Device at %p with %zu blocks (%zuB) using interrupt %d",
512 (void *) ddisk_dev->ddisk_res.base, ddisk_dev->ddisk_fun->blocks,
513 ddisk_dev->ddisk_fun->blocks * DDISK_BLOCK_SIZE,
514 ddisk_dev->ddisk_res.irq);
515
516 return EOK;
517error:
518 if (ddisk_dev->ddisk_regs)
519 pio_disable(ddisk_dev->ddisk_regs, sizeof(ddisk_regs_t));
520 if (ddisk_dev->dma_buffer)
521 dmamem_unmap_anonymous(ddisk_dev->dma_buffer);
522 return rc;
523}
524
525
526static int ddisk_dev_remove_common(ddisk_dev_t *ddisk_dev, bool surprise)
527{
528 int rc;
529
530 if (!surprise)
531 rc = ddisk_fun_remove(ddisk_dev->ddisk_fun);
532 else
533 rc = ddisk_fun_unbind(ddisk_dev->ddisk_fun);
534 if (rc != EOK) {
535 ddf_msg(LVL_ERROR, "Unable to cleanup function '%s'.",
536 DDISK_FUN_NAME);
537 return rc;
538 }
539
540 rc = pio_disable(ddisk_dev->ddisk_regs, sizeof(ddisk_regs_t));
541 if (rc != EOK) {
542 ddf_msg(LVL_ERROR, "Unable to disable PIO.");
543 return rc;
544 }
545
546 dmamem_unmap_anonymous(ddisk_dev->dma_buffer);
547
548 return EOK;
549}
550
551static int ddisk_dev_remove(ddf_dev_t *dev)
552{
553 ddisk_dev_t *ddisk_dev = (ddisk_dev_t *) ddf_dev_data_get(dev);
554
555 ddf_msg(LVL_DEBUG, "ddisk_dev_remove(%p)", dev);
556
557 return ddisk_dev_remove_common(ddisk_dev, false);
558}
559
560static int ddisk_dev_gone(ddf_dev_t *dev)
561{
562 ddisk_dev_t *ddisk_dev = (ddisk_dev_t *) ddf_dev_data_get(dev);
563
564 ddf_msg(LVL_DEBUG, "ddisk_dev_gone(%p)", dev);
565
566 return ddisk_dev_remove_common(ddisk_dev, true);
567}
568
569static int ddisk_fun_online(ddf_fun_t *fun)
570{
571 ddf_msg(LVL_DEBUG, "ddisk_fun_online()");
572 return ddf_fun_online(fun);
573}
574
575static int ddisk_fun_offline(ddf_fun_t *fun)
576{
577 ddf_msg(LVL_DEBUG, "ddisk_fun_offline()");
578 return ddf_fun_offline(fun);
579}
580
581/** Block device connection handler */
582static void ddisk_bd_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
583{
584 ddisk_fun_t *ddfun;
585
586 ddfun = (ddisk_fun_t *) ddf_fun_data_get((ddf_fun_t *)arg);
587 bd_conn(iid, icall, &ddfun->bds);
588}
589
590int main(int argc, char *argv[])
591{
592 printf(NAME ": HelenOS MSIM ddisk device driver\n");
593 ddf_log_init(NAME);
594 return ddf_driver_main(&ddisk_driver);
595}
Note: See TracBrowser for help on using the repository browser.