source: mainline/uspace/drv/block/ddisk/ddisk.c@ ebb1489

Last change on this file since ebb1489 was 60744cb, checked in by Jiri Svoboda <jiri@…>, 17 months ago

Let driver specify any argument to IRQ handler

This allows the driver to register a single handler for multiple
interrupts and still distinguish between them. It also removes
the extra step of having to get softstate from ddf_dev_t.

  • Property mode set to 100644
File size: 13.6 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 errno_t ddisk_dev_add(ddf_dev_t *);
59static errno_t ddisk_dev_remove(ddf_dev_t *);
60static errno_t ddisk_dev_gone(ddf_dev_t *);
61static errno_t ddisk_fun_online(ddf_fun_t *);
62static errno_t ddisk_fun_offline(ddf_fun_t *);
63
64static void ddisk_bd_connection(ipc_call_t *, void *);
65
66static void ddisk_irq_handler(ipc_call_t *, void *);
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
96typedef struct {
97 fibril_mutex_t lock;
98
99 fibril_condvar_t io_cv;
100 bool io_busy;
101
102 ssize_t size;
103 size_t blocks;
104
105 uintptr_t dma_buffer_phys;
106 void *dma_buffer;
107
108 ddf_dev_t *dev;
109 ddf_fun_t *fun;
110
111 ddisk_res_t ddisk_res;
112 ddisk_regs_t *ddisk_regs;
113
114 cap_irq_handle_t irq_cap;
115
116 bd_srvs_t bds;
117} ddisk_t;
118
119static errno_t ddisk_bd_open(bd_srvs_t *, bd_srv_t *);
120static errno_t ddisk_bd_close(bd_srv_t *);
121static errno_t ddisk_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
122static errno_t ddisk_bd_write_blocks(bd_srv_t *, aoff64_t, size_t, const void *,
123 size_t);
124static errno_t ddisk_bd_get_block_size(bd_srv_t *, size_t *);
125static errno_t 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
178/** Ddisk IRQ handler.
179 *
180 * @param icall IRQ event notification
181 * @param arg Argument (ddisk_t *)
182 */
183void ddisk_irq_handler(ipc_call_t *icall, void *arg)
184{
185 ddf_msg(LVL_DEBUG, "ddisk_irq_handler(), status=%" PRIx32,
186 (uint32_t) ipc_get_arg1(icall));
187
188 ddisk_t *ddisk = (ddisk_t *)arg;
189
190 fibril_mutex_lock(&ddisk->lock);
191 fibril_condvar_broadcast(&ddisk->io_cv);
192 fibril_mutex_unlock(&ddisk->lock);
193}
194
195errno_t ddisk_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
196{
197 return EOK;
198}
199
200errno_t ddisk_bd_close(bd_srv_t *bd)
201{
202 return EOK;
203}
204
205static errno_t ddisk_rw_block(ddisk_t *ddisk, bool read, aoff64_t ba, void *buf)
206{
207 fibril_mutex_lock(&ddisk->lock);
208
209 ddf_msg(LVL_DEBUG, "ddisk_rw_block(): read=%d, ba=%" PRId64 ", buf=%p",
210 read, ba, buf);
211
212 if (ba >= ddisk->blocks)
213 return ELIMIT;
214
215 while (ddisk->io_busy)
216 fibril_condvar_wait(&ddisk->io_cv, &ddisk->lock);
217
218 ddisk->io_busy = true;
219
220 if (!read)
221 memcpy(ddisk->dma_buffer, buf, DDISK_BLOCK_SIZE);
222
223 pio_write_32(&ddisk->ddisk_regs->dma_buffer,
224 ddisk->dma_buffer_phys);
225 pio_write_32(&ddisk->ddisk_regs->block, (uint32_t) ba);
226 pio_write_32(&ddisk->ddisk_regs->command,
227 read ? DDISK_CMD_READ : DDISK_CMD_WRITE);
228
229 fibril_condvar_wait(&ddisk->io_cv, &ddisk->lock);
230
231 if (read)
232 memcpy(buf, ddisk->dma_buffer, DDISK_BLOCK_SIZE);
233
234 ddisk->io_busy = false;
235 fibril_condvar_signal(&ddisk->io_cv);
236 fibril_mutex_unlock(&ddisk->lock);
237
238 return EOK;
239}
240
241static errno_t ddisk_bd_rw_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
242 void *buf, size_t size, bool is_read)
243{
244 ddisk_t *ddisk = (ddisk_t *) bd->srvs->sarg;
245 aoff64_t i;
246 errno_t rc;
247
248 if (size < cnt * DDISK_BLOCK_SIZE)
249 return EINVAL;
250
251 for (i = 0; i < cnt; i++) {
252 rc = ddisk_rw_block(ddisk, is_read, ba + i,
253 buf + i * DDISK_BLOCK_SIZE);
254 if (rc != EOK)
255 return rc;
256 }
257
258 return EOK;
259}
260
261errno_t ddisk_bd_read_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt, void *buf,
262 size_t size)
263{
264 return ddisk_bd_rw_blocks(bd, ba, cnt, buf, size, true);
265}
266
267errno_t ddisk_bd_write_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
268 const void *buf, size_t size)
269{
270 return ddisk_bd_rw_blocks(bd, ba, cnt, (void *) buf, size, false);
271}
272
273errno_t ddisk_bd_get_block_size(bd_srv_t *bd, size_t *rsize)
274{
275 *rsize = DDISK_BLOCK_SIZE;
276 return EOK;
277}
278
279errno_t ddisk_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *rnb)
280{
281 ddisk_t *ddisk = (ddisk_t *) bd->srvs->sarg;
282
283 *rnb = ddisk->blocks;
284 return EOK;
285}
286
287static errno_t ddisk_get_res(ddf_dev_t *dev, ddisk_res_t *ddisk_res)
288{
289 async_sess_t *parent_sess;
290 hw_res_list_parsed_t hw_res;
291 errno_t rc;
292
293 parent_sess = ddf_dev_parent_sess_get(dev);
294 if (parent_sess == NULL)
295 return ENOMEM;
296
297 hw_res_list_parsed_init(&hw_res);
298 rc = hw_res_get_list_parsed(parent_sess, &hw_res, 0);
299 if (rc != EOK)
300 return rc;
301
302 if ((hw_res.mem_ranges.count != 1) || (hw_res.irqs.count != 1)) {
303 rc = EINVAL;
304 goto error;
305 }
306
307 addr_range_t *regs = &hw_res.mem_ranges.ranges[0];
308 ddisk_res->base = RNGABS(*regs);
309 ddisk_res->irq = hw_res.irqs.irqs[0];
310
311 if (RNGSZ(*regs) < sizeof(ddisk_regs_t)) {
312 rc = EINVAL;
313 goto error;
314 }
315
316 rc = EOK;
317error:
318 hw_res_list_parsed_clean(&hw_res);
319 return rc;
320}
321
322static errno_t ddisk_fun_create(ddisk_t *ddisk)
323{
324 errno_t rc;
325 ddf_fun_t *fun = NULL;
326 bool bound = false;
327
328 fun = ddf_fun_create(ddisk->dev, fun_exposed, DDISK_FUN_NAME);
329 if (fun == NULL) {
330 ddf_msg(LVL_ERROR, "Failed creating DDF function.");
331 rc = ENOMEM;
332 goto error;
333 }
334
335 /* Set up a connection handler. */
336 ddf_fun_set_conn_handler(fun, ddisk_bd_connection);
337
338 rc = ddf_fun_bind(fun);
339 if (rc != EOK) {
340 ddf_msg(LVL_ERROR, "Failed binding DDF function %s: %s",
341 DDISK_FUN_NAME, str_error(rc));
342 goto error;
343 }
344
345 bound = true;
346
347 rc = ddf_fun_add_to_category(fun, "disk");
348 if (rc != EOK) {
349 ddf_msg(LVL_ERROR, "Failed adding function %s to category "
350 "'disk': %s.\n", DDISK_FUN_NAME, str_error(rc));
351 goto error;
352 }
353 ddisk->fun = fun;
354
355 return EOK;
356error:
357 if (bound)
358 ddf_fun_unbind(fun);
359 if (fun != NULL)
360 ddf_fun_destroy(fun);
361
362 return rc;
363}
364
365static errno_t ddisk_fun_remove(ddisk_t *ddisk)
366{
367 errno_t rc;
368
369 if (ddisk->fun == NULL)
370 return EOK;
371
372 ddf_msg(LVL_DEBUG, "ddisk_fun_remove(%p, '%s')", ddisk,
373 DDISK_FUN_NAME);
374 rc = ddf_fun_offline(ddisk->fun);
375 if (rc != EOK) {
376 ddf_msg(LVL_ERROR, "Error offlining function '%s'.",
377 DDISK_FUN_NAME);
378 goto error;
379 }
380
381 rc = ddf_fun_unbind(ddisk->fun);
382 if (rc != EOK) {
383 ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.",
384 DDISK_FUN_NAME);
385 goto error;
386 }
387
388 ddf_fun_destroy(ddisk->fun);
389 ddisk->fun = NULL;
390 rc = EOK;
391
392error:
393 return rc;
394}
395
396static errno_t ddisk_fun_unbind(ddisk_t *ddisk)
397{
398 errno_t rc;
399
400 if (ddisk->fun == NULL)
401 return EOK;
402
403 ddf_msg(LVL_DEBUG, "ddisk_fun_unbind(%p, '%s')", ddisk,
404 DDISK_FUN_NAME);
405 rc = ddf_fun_unbind(ddisk->fun);
406 if (rc != EOK) {
407 ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.",
408 DDISK_FUN_NAME);
409 goto error;
410 }
411
412 ddf_fun_destroy(ddisk->fun);
413 ddisk->fun = NULL;
414 rc = EOK;
415
416error:
417 return rc;
418}
419
420/** Add new device
421 *
422 * @param dev New device
423 * @return EOK on success or an error code.
424 */
425static errno_t ddisk_dev_add(ddf_dev_t *dev)
426{
427 ddisk_t *ddisk;
428 ddisk_res_t res;
429 errno_t rc;
430
431 /*
432 * Get our resources.
433 */
434 rc = ddisk_get_res(dev, &res);
435 if (rc != EOK) {
436 ddf_msg(LVL_ERROR, "Invalid HW resource configuration.");
437 return EINVAL;
438 }
439
440 /*
441 * Allocate soft state.
442 */
443 ddisk = ddf_dev_data_alloc(dev, sizeof(ddisk_t));
444 if (!ddisk) {
445 ddf_msg(LVL_ERROR, "Failed allocating soft state.");
446 rc = ENOMEM;
447 goto error;
448 }
449
450 /*
451 * Initialize soft state.
452 */
453 fibril_mutex_initialize(&ddisk->lock);
454 ddisk->dev = dev;
455 ddisk->ddisk_res = res;
456
457 fibril_condvar_initialize(&ddisk->io_cv);
458 ddisk->io_busy = false;
459
460 bd_srvs_init(&ddisk->bds);
461 ddisk->bds.ops = &ddisk_bd_ops;
462 ddisk->bds.sarg = ddisk;
463
464 ddisk->irq_cap = CAP_NIL;
465
466 /*
467 * Enable access to ddisk's PIO registers.
468 */
469 void *vaddr;
470 rc = pio_enable((void *) res.base, sizeof(ddisk_regs_t), &vaddr);
471 if (rc != EOK) {
472 ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
473 goto error;
474 }
475 ddisk->ddisk_regs = vaddr;
476
477 ddisk->size = (int32_t) pio_read_32(&ddisk->ddisk_regs->size);
478 ddisk->blocks = ddisk->size / DDISK_BLOCK_SIZE;
479
480 if (ddisk->size <= 0) {
481 ddf_msg(LVL_WARN, "No disk detected.");
482 rc = EIO;
483 goto error;
484 }
485
486 /*
487 * Allocate DMA buffer.
488 */
489 ddisk->dma_buffer = AS_AREA_ANY;
490 rc = dmamem_map_anonymous(DDISK_BLOCK_SIZE, DMAMEM_4GiB,
491 AS_AREA_READ | AS_AREA_WRITE, 0, &ddisk->dma_buffer_phys,
492 &ddisk->dma_buffer);
493 if (rc != EOK) {
494 ddf_msg(LVL_ERROR, "Cannot allocate DMA memory.");
495 goto error;
496 }
497
498 ddf_msg(LVL_NOTE, "Allocated DMA buffer at %p virtual and %p physical.",
499 ddisk->dma_buffer, (void *) ddisk->dma_buffer_phys);
500
501 /*
502 * Create an exposed function.
503 */
504 rc = ddisk_fun_create(ddisk);
505 if (rc != EOK) {
506 ddf_msg(LVL_ERROR, "Failed initializing ddisk controller.");
507 rc = EIO;
508 goto error;
509 }
510
511 /*
512 * Register IRQ handler.
513 */
514 ddisk_regs_t *res_phys = (ddisk_regs_t *) res.base;
515 ddisk_irq_pio_ranges[0].base = res.base;
516 ddisk_irq_commands[0].addr = (void *) &res_phys->status;
517 ddisk_irq_commands[3].addr = (void *) &res_phys->command;
518 rc = register_interrupt_handler(dev, ddisk->ddisk_res.irq,
519 ddisk_irq_handler, (void *)ddisk, &ddisk_irq_code, &ddisk->irq_cap);
520 if (rc != EOK) {
521 ddf_msg(LVL_ERROR, "Failed to register interrupt handler.");
522 goto error;
523 }
524
525 /*
526 * Success, report what we have found.
527 */
528 ddf_msg(LVL_NOTE,
529 "Device at %p with %zd blocks (%zuB) using interrupt %d",
530 (void *) ddisk->ddisk_res.base, ddisk->blocks,
531 ddisk->size, ddisk->ddisk_res.irq);
532
533 return EOK;
534
535error:
536 if (ddisk->ddisk_regs)
537 pio_disable(ddisk->ddisk_regs, sizeof(ddisk_regs_t));
538 if (ddisk->dma_buffer)
539 dmamem_unmap_anonymous(ddisk->dma_buffer);
540
541 return rc;
542}
543
544static errno_t ddisk_dev_remove_common(ddisk_t *ddisk, bool surprise)
545{
546 errno_t rc;
547
548 if (!surprise)
549 rc = ddisk_fun_remove(ddisk);
550 else
551 rc = ddisk_fun_unbind(ddisk);
552
553 if (rc != EOK) {
554 ddf_msg(LVL_ERROR, "Unable to cleanup function '%s'.",
555 DDISK_FUN_NAME);
556 return rc;
557 }
558
559 unregister_interrupt_handler(ddisk->dev, ddisk->irq_cap);
560
561 rc = pio_disable(ddisk->ddisk_regs, sizeof(ddisk_regs_t));
562 if (rc != EOK) {
563 ddf_msg(LVL_ERROR, "Unable to disable PIO.");
564 return rc;
565 }
566
567 dmamem_unmap_anonymous(ddisk->dma_buffer);
568
569 return EOK;
570}
571
572static errno_t ddisk_dev_remove(ddf_dev_t *dev)
573{
574 ddisk_t *ddisk = (ddisk_t *) ddf_dev_data_get(dev);
575
576 ddf_msg(LVL_DEBUG, "ddisk_dev_remove(%p)", dev);
577 return ddisk_dev_remove_common(ddisk, false);
578}
579
580static errno_t ddisk_dev_gone(ddf_dev_t *dev)
581{
582 ddisk_t *ddisk = (ddisk_t *) ddf_dev_data_get(dev);
583
584 ddf_msg(LVL_DEBUG, "ddisk_dev_gone(%p)", dev);
585 return ddisk_dev_remove_common(ddisk, true);
586}
587
588static errno_t ddisk_fun_online(ddf_fun_t *fun)
589{
590 ddf_msg(LVL_DEBUG, "ddisk_fun_online()");
591 return ddf_fun_online(fun);
592}
593
594static errno_t ddisk_fun_offline(ddf_fun_t *fun)
595{
596 ddf_msg(LVL_DEBUG, "ddisk_fun_offline()");
597 return ddf_fun_offline(fun);
598}
599
600/** Block device connection handler */
601static void ddisk_bd_connection(ipc_call_t *icall, void *arg)
602{
603 ddisk_t *ddisk;
604 ddf_fun_t *fun = (ddf_fun_t *) arg;
605
606 ddisk = (ddisk_t *) ddf_dev_data_get(ddf_fun_get_dev(fun));
607 bd_conn(icall, &ddisk->bds);
608}
609
610int main(int argc, char *argv[])
611{
612 printf(NAME ": HelenOS MSIM ddisk device driver\n");
613 ddf_log_init(NAME);
614 return ddf_driver_main(&ddisk_driver);
615}
Note: See TracBrowser for help on using the repository browser.