source: mainline/uspace/drv/bus/isa/isa.c@ cccd60c3

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since cccd60c3 was cccd60c3, checked in by Jiri Svoboda <jiri@…>, 8 years ago

hw_res_enable_interrupt should allow enabling individual interrupts.

  • Property mode set to 100644
File size: 17.1 KB
Line 
1/*
2 * Copyright (c) 2010 Lenka Trochtova
3 * Copyright (c) 2011 Jiri Svoboda
4 * Copyright (c) 2011 Jan Vesely
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * - The name of the author may not be used to endorse or promote products
17 * derived from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @defgroup isa ISA bus driver.
33 * @brief HelenOS ISA bus driver.
34 * @{
35 */
36
37/** @file
38 */
39
40#include <adt/list.h>
41#include <assert.h>
42#include <stdio.h>
43#include <errno.h>
44#include <stdbool.h>
45#include <fibril_synch.h>
46#include <stdlib.h>
47#include <str.h>
48#include <str_error.h>
49#include <ctype.h>
50#include <macros.h>
51#include <malloc.h>
52#include <dirent.h>
53#include <ipc/irc.h>
54#include <ipc/services.h>
55#include <vfs/vfs.h>
56#include <irc.h>
57#include <ns.h>
58
59#include <ddf/driver.h>
60#include <ddf/log.h>
61#include <ops/hw_res.h>
62#include <ops/pio_window.h>
63
64#include <device/hw_res.h>
65#include <device/pio_window.h>
66
67#include <pci_dev_iface.h>
68
69#include "i8237.h"
70
71#define NAME "isa"
72#define ISA_CHILD_FUN_CONF_PATH "/drv/isa/isa.dev"
73#define EBUS_CHILD_FUN_CONF_PATH "/drv/isa/ebus.dev"
74
75#define ISA_MAX_HW_RES 5
76
77typedef struct {
78 fibril_mutex_t mutex;
79 uint16_t pci_vendor_id;
80 uint16_t pci_device_id;
81 uint8_t pci_class;
82 uint8_t pci_subclass;
83 ddf_dev_t *dev;
84 ddf_fun_t *fctl;
85 pio_window_t pio_win;
86 list_t functions;
87} isa_bus_t;
88
89typedef struct isa_fun {
90 fibril_mutex_t mutex;
91 ddf_fun_t *fnode;
92 hw_resource_t resources[ISA_MAX_HW_RES];
93 hw_resource_list_t hw_resources;
94 link_t bus_link;
95} isa_fun_t;
96
97/** Obtain soft-state from device node */
98static isa_bus_t *isa_bus(ddf_dev_t *dev)
99{
100 return ddf_dev_data_get(dev);
101}
102
103/** Obtain soft-state from function node */
104static isa_fun_t *isa_fun(ddf_fun_t *fun)
105{
106 return ddf_fun_data_get(fun);
107}
108
109static hw_resource_list_t *isa_fun_get_resources(ddf_fun_t *fnode)
110{
111 isa_fun_t *fun = isa_fun(fnode);
112 assert(fun);
113
114 return &fun->hw_resources;
115}
116
117static int isa_fun_enable_interrupt(ddf_fun_t *fnode, int irq)
118{
119 isa_fun_t *fun = isa_fun(fnode);
120 const hw_resource_list_t *res = &fun->hw_resources;
121 bool found;
122
123 /* Check that specified irq really belongs to the function */
124 found = false;
125 for (size_t i = 0; i < res->count; ++i) {
126 if (res->resources[i].type == INTERRUPT &&
127 res->resources[i].res.interrupt.irq == irq) {
128 found = true;
129 break;
130 }
131 }
132
133 if (!found)
134 return EINVAL;
135
136 return irc_enable_interrupt(irq);
137}
138
139static int isa_fun_setup_dma(ddf_fun_t *fnode,
140 unsigned int channel, uint32_t pa, uint32_t size, uint8_t mode)
141{
142 assert(fnode);
143 isa_fun_t *fun = isa_fun(fnode);
144 assert(fun);
145 const hw_resource_list_t *res = &fun->hw_resources;
146 assert(res);
147
148 for (size_t i = 0; i < res->count; ++i) {
149 /* Check for assigned channel */
150 if (((res->resources[i].type == DMA_CHANNEL_16) &&
151 (res->resources[i].res.dma_channel.dma16 == channel)) ||
152 ((res->resources[i].type == DMA_CHANNEL_8) &&
153 (res->resources[i].res.dma_channel.dma8 == channel))) {
154 return dma_channel_setup(channel, pa, size, mode);
155 }
156 }
157
158 return EINVAL;
159}
160
161static int isa_fun_remain_dma(ddf_fun_t *fnode,
162 unsigned channel, size_t *size)
163{
164 assert(size);
165 assert(fnode);
166 isa_fun_t *fun = isa_fun(fnode);
167 assert(fun);
168 const hw_resource_list_t *res = &fun->hw_resources;
169 assert(res);
170
171 for (size_t i = 0; i < res->count; ++i) {
172 /* Check for assigned channel */
173 if (((res->resources[i].type == DMA_CHANNEL_16) &&
174 (res->resources[i].res.dma_channel.dma16 == channel)) ||
175 ((res->resources[i].type == DMA_CHANNEL_8) &&
176 (res->resources[i].res.dma_channel.dma8 == channel))) {
177 return dma_channel_remain(channel, size);
178 }
179 }
180
181 return EINVAL;
182}
183
184static hw_res_ops_t isa_fun_hw_res_ops = {
185 .get_resource_list = isa_fun_get_resources,
186 .enable_interrupt = isa_fun_enable_interrupt,
187 .dma_channel_setup = isa_fun_setup_dma,
188 .dma_channel_remain = isa_fun_remain_dma,
189};
190
191static pio_window_t *isa_fun_get_pio_window(ddf_fun_t *fnode)
192{
193 ddf_dev_t *dev = ddf_fun_get_dev(fnode);
194 isa_bus_t *isa = isa_bus(dev);
195 assert(isa);
196
197 return &isa->pio_win;
198}
199
200static pio_window_ops_t isa_fun_pio_window_ops = {
201 .get_pio_window = isa_fun_get_pio_window
202};
203
204static ddf_dev_ops_t isa_fun_ops= {
205 .interfaces[HW_RES_DEV_IFACE] = &isa_fun_hw_res_ops,
206 .interfaces[PIO_WINDOW_DEV_IFACE] = &isa_fun_pio_window_ops,
207};
208
209static int isa_dev_add(ddf_dev_t *dev);
210static int isa_dev_remove(ddf_dev_t *dev);
211static int isa_fun_online(ddf_fun_t *fun);
212static int isa_fun_offline(ddf_fun_t *fun);
213
214/** The isa device driver's standard operations */
215static driver_ops_t isa_ops = {
216 .dev_add = &isa_dev_add,
217 .dev_remove = &isa_dev_remove,
218 .fun_online = &isa_fun_online,
219 .fun_offline = &isa_fun_offline
220};
221
222/** The isa device driver structure. */
223static driver_t isa_driver = {
224 .name = NAME,
225 .driver_ops = &isa_ops
226};
227
228static isa_fun_t *isa_fun_create(isa_bus_t *isa, const char *name)
229{
230 ddf_fun_t *fnode = ddf_fun_create(isa->dev, fun_inner, name);
231 if (fnode == NULL)
232 return NULL;
233
234 isa_fun_t *fun = ddf_fun_data_alloc(fnode, sizeof(isa_fun_t));
235 if (fun == NULL) {
236 ddf_fun_destroy(fnode);
237 return NULL;
238 }
239
240 fibril_mutex_initialize(&fun->mutex);
241 fun->hw_resources.resources = fun->resources;
242
243 fun->fnode = fnode;
244 return fun;
245}
246
247static char *fun_conf_read(const char *conf_path)
248{
249 bool suc = false;
250 char *buf = NULL;
251 bool opened = false;
252 int fd;
253 size_t len;
254 ssize_t r;
255 struct stat st;
256
257 fd = vfs_lookup_open(conf_path, WALK_REGULAR, MODE_READ);
258 if (fd < 0) {
259 ddf_msg(LVL_ERROR, "Unable to open %s", conf_path);
260 goto cleanup;
261 }
262
263 opened = true;
264
265 if (vfs_stat(fd, &st) != EOK) {
266 ddf_msg(LVL_ERROR, "Unable to vfs_stat %d", fd);
267 goto cleanup;
268 }
269
270 len = st.size;
271 if (len == 0) {
272 ddf_msg(LVL_ERROR, "Configuration file '%s' is empty.",
273 conf_path);
274 goto cleanup;
275 }
276
277 buf = malloc(len + 1);
278 if (buf == NULL) {
279 ddf_msg(LVL_ERROR, "Memory allocation failed.");
280 goto cleanup;
281 }
282
283 r = vfs_read(fd, (aoff64_t []) {0}, buf, len);
284 if (r < 0) {
285 ddf_msg(LVL_ERROR, "Unable to read file '%s'.", conf_path);
286 goto cleanup;
287 }
288
289 buf[len] = 0;
290
291 suc = true;
292
293cleanup:
294 if (!suc && buf != NULL) {
295 free(buf);
296 buf = NULL;
297 }
298
299 if (opened)
300 vfs_put(fd);
301
302 return buf;
303}
304
305static char *str_get_line(char *str, char **next)
306{
307 char *line = str;
308 *next = NULL;
309
310 if (str == NULL) {
311 return NULL;
312 }
313
314 while (*str != '\0' && *str != '\n') {
315 str++;
316 }
317
318 if (*str != '\0') {
319 *next = str + 1;
320 }
321
322 *str = '\0';
323 return line;
324}
325
326static bool line_empty(const char *line)
327{
328 while (line != NULL && *line != 0) {
329 if (!isspace(*line))
330 return false;
331 line++;
332 }
333
334 return true;
335}
336
337static char *get_device_name(char *line)
338{
339 /* Skip leading spaces. */
340 while (*line != '\0' && isspace(*line)) {
341 line++;
342 }
343
344 /* Get the name part of the rest of the line. */
345 str_tok(line, ":", NULL);
346 return line;
347}
348
349static inline const char *skip_spaces(const char *line)
350{
351 /* Skip leading spaces. */
352 while (*line != '\0' && isspace(*line))
353 line++;
354
355 return line;
356}
357
358static void isa_fun_add_irq(isa_fun_t *fun, int irq)
359{
360 size_t count = fun->hw_resources.count;
361 hw_resource_t *resources = fun->hw_resources.resources;
362
363 if (count < ISA_MAX_HW_RES) {
364 resources[count].type = INTERRUPT;
365 resources[count].res.interrupt.irq = irq;
366
367 fun->hw_resources.count++;
368
369 ddf_msg(LVL_NOTE, "Added irq 0x%x to function %s", irq,
370 ddf_fun_get_name(fun->fnode));
371 }
372}
373
374static void isa_fun_add_dma(isa_fun_t *fun, int dma)
375{
376 size_t count = fun->hw_resources.count;
377 hw_resource_t *resources = fun->hw_resources.resources;
378
379 if (count < ISA_MAX_HW_RES) {
380 if ((dma > 0) && (dma < 4)) {
381 resources[count].type = DMA_CHANNEL_8;
382 resources[count].res.dma_channel.dma8 = dma;
383
384 fun->hw_resources.count++;
385 ddf_msg(LVL_NOTE, "Added dma 0x%x to function %s", dma,
386 ddf_fun_get_name(fun->fnode));
387
388 return;
389 }
390
391 if ((dma > 4) && (dma < 8)) {
392 resources[count].type = DMA_CHANNEL_16;
393 resources[count].res.dma_channel.dma16 = dma;
394
395 fun->hw_resources.count++;
396 ddf_msg(LVL_NOTE, "Added dma 0x%x to function %s", dma,
397 ddf_fun_get_name(fun->fnode));
398
399 return;
400 }
401
402 ddf_msg(LVL_WARN, "Skipped dma 0x%x for function %s", dma,
403 ddf_fun_get_name(fun->fnode));
404 }
405}
406
407static void isa_fun_add_io_range(isa_fun_t *fun, size_t addr, size_t len)
408{
409 size_t count = fun->hw_resources.count;
410 hw_resource_t *resources = fun->hw_resources.resources;
411
412 isa_bus_t *isa = isa_bus(ddf_fun_get_dev(fun->fnode));
413
414 if (count < ISA_MAX_HW_RES) {
415 resources[count].type = IO_RANGE;
416 resources[count].res.io_range.address = addr;
417 resources[count].res.io_range.address += isa->pio_win.io.base;
418 resources[count].res.io_range.size = len;
419 resources[count].res.io_range.relative = false;
420 resources[count].res.io_range.endianness = LITTLE_ENDIAN;
421
422 fun->hw_resources.count++;
423
424 ddf_msg(LVL_NOTE, "Added io range (addr=0x%x, size=0x%x) to "
425 "function %s", (unsigned int) addr, (unsigned int) len,
426 ddf_fun_get_name(fun->fnode));
427 }
428}
429
430static void fun_parse_irq(isa_fun_t *fun, const char *val)
431{
432 int irq = 0;
433 char *end = NULL;
434
435 val = skip_spaces(val);
436 irq = (int) strtol(val, &end, 10);
437
438 if (val != end)
439 isa_fun_add_irq(fun, irq);
440}
441
442static void fun_parse_dma(isa_fun_t *fun, const char *val)
443{
444 char *end = NULL;
445
446 val = skip_spaces(val);
447 const int dma = strtol(val, &end, 10);
448
449 if (val != end)
450 isa_fun_add_dma(fun, dma);
451}
452
453static void fun_parse_io_range(isa_fun_t *fun, const char *val)
454{
455 size_t addr, len;
456 char *end = NULL;
457
458 val = skip_spaces(val);
459 addr = strtol(val, &end, 0x10);
460
461 if (val == end)
462 return;
463
464 val = skip_spaces(end);
465 len = strtol(val, &end, 0x10);
466
467 if (val == end)
468 return;
469
470 isa_fun_add_io_range(fun, addr, len);
471}
472
473static void get_match_id(char **id, const char *val)
474{
475 const char *end = val;
476
477 while (!isspace(*end))
478 end++;
479
480 size_t size = end - val + 1;
481 *id = (char *)malloc(size);
482 str_cpy(*id, size, val);
483}
484
485static void fun_parse_match_id(isa_fun_t *fun, const char *val)
486{
487 char *id = NULL;
488 char *end = NULL;
489
490 val = skip_spaces(val);
491
492 int score = (int)strtol(val, &end, 10);
493 if (val == end) {
494 ddf_msg(LVL_ERROR, "Cannot read match score for function "
495 "%s.", ddf_fun_get_name(fun->fnode));
496 return;
497 }
498
499 val = skip_spaces(end);
500 get_match_id(&id, val);
501 if (id == NULL) {
502 ddf_msg(LVL_ERROR, "Cannot read match ID for function %s.",
503 ddf_fun_get_name(fun->fnode));
504 return;
505 }
506
507 ddf_msg(LVL_DEBUG, "Adding match id '%s' with score %d to "
508 "function %s", id, score, ddf_fun_get_name(fun->fnode));
509
510 int rc = ddf_fun_add_match_id(fun->fnode, id, score);
511 if (rc != EOK) {
512 ddf_msg(LVL_ERROR, "Failed adding match ID: %s",
513 str_error(rc));
514 }
515
516 free(id);
517}
518
519static bool prop_parse(isa_fun_t *fun, const char *line, const char *prop,
520 void (*read_fn)(isa_fun_t *, const char *))
521{
522 size_t proplen = str_size(prop);
523
524 if (str_lcmp(line, prop, proplen) == 0) {
525 line += proplen;
526 line = skip_spaces(line);
527 (*read_fn)(fun, line);
528
529 return true;
530 }
531
532 return false;
533}
534
535static void fun_prop_parse(isa_fun_t *fun, const char *line)
536{
537 /* Skip leading spaces. */
538 line = skip_spaces(line);
539
540 if (!prop_parse(fun, line, "io_range", &fun_parse_io_range) &&
541 !prop_parse(fun, line, "irq", &fun_parse_irq) &&
542 !prop_parse(fun, line, "dma", &fun_parse_dma) &&
543 !prop_parse(fun, line, "match", &fun_parse_match_id)) {
544
545 ddf_msg(LVL_ERROR, "Undefined device property at line '%s'",
546 line);
547 }
548}
549
550static char *isa_fun_read_info(char *fun_conf, isa_bus_t *isa)
551{
552 char *line;
553
554 /* Skip empty lines. */
555 do {
556 line = str_get_line(fun_conf, &fun_conf);
557
558 if (line == NULL) {
559 /* no more lines */
560 return NULL;
561 }
562
563 } while (line_empty(line));
564
565 /* Get device name. */
566 const char *fun_name = get_device_name(line);
567 if (fun_name == NULL)
568 return NULL;
569
570 isa_fun_t *fun = isa_fun_create(isa, fun_name);
571 if (fun == NULL) {
572 return NULL;
573 }
574
575 /* Get properties of the device (match ids, irq and io range). */
576 while (true) {
577 line = str_get_line(fun_conf, &fun_conf);
578
579 if (line_empty(line)) {
580 /* no more device properties */
581 break;
582 }
583
584 /*
585 * Get the device's property from the configuration line
586 * and store it in the device structure.
587 */
588 fun_prop_parse(fun, line);
589 }
590
591 /* Set device operations to the device. */
592 ddf_fun_set_ops(fun->fnode, &isa_fun_ops);
593
594 ddf_msg(LVL_DEBUG, "Binding function %s.", ddf_fun_get_name(fun->fnode));
595
596 /* XXX Handle error */
597 (void) ddf_fun_bind(fun->fnode);
598
599 list_append(&fun->bus_link, &isa->functions);
600
601 return fun_conf;
602}
603
604static void isa_functions_add(isa_bus_t *isa)
605{
606#define BASE_CLASS_BRIDGE 0x06
607#define SUB_CLASS_BRIDGE_ISA 0x01
608 bool isa_bridge = ((isa->pci_class == BASE_CLASS_BRIDGE) &&
609 (isa->pci_subclass == SUB_CLASS_BRIDGE_ISA));
610
611#define VENDOR_ID_SUN 0x108e
612#define DEVICE_ID_EBUS 0x1000
613 bool ebus = ((isa->pci_vendor_id == VENDOR_ID_SUN) &&
614 (isa->pci_device_id == DEVICE_ID_EBUS));
615
616 const char *conf_path = NULL;
617 if (isa_bridge)
618 conf_path = ISA_CHILD_FUN_CONF_PATH;
619 else if (ebus)
620 conf_path = EBUS_CHILD_FUN_CONF_PATH;
621
622 char *conf = fun_conf_read(conf_path);
623 while (conf != NULL && *conf != '\0') {
624 conf = isa_fun_read_info(conf, isa);
625 }
626 free(conf);
627}
628
629static int isa_dev_add(ddf_dev_t *dev)
630{
631 async_sess_t *sess;
632 int rc;
633
634 ddf_msg(LVL_DEBUG, "isa_dev_add, device handle = %d",
635 (int) ddf_dev_get_handle(dev));
636
637 isa_bus_t *isa = ddf_dev_data_alloc(dev, sizeof(isa_bus_t));
638 if (isa == NULL)
639 return ENOMEM;
640
641 fibril_mutex_initialize(&isa->mutex);
642 isa->dev = dev;
643 list_initialize(&isa->functions);
644
645 sess = ddf_dev_parent_sess_get(dev);
646 if (sess == NULL) {
647 ddf_msg(LVL_ERROR, "isa_dev_add failed to connect to the "
648 "parent driver.");
649 return ENOENT;
650 }
651
652 rc = pci_config_space_read_16(sess, PCI_VENDOR_ID, &isa->pci_vendor_id);
653 if (rc != EOK)
654 return rc;
655 rc = pci_config_space_read_16(sess, PCI_DEVICE_ID, &isa->pci_device_id);
656 if (rc != EOK)
657 return rc;
658 rc = pci_config_space_read_8(sess, PCI_BASE_CLASS, &isa->pci_class);
659 if (rc != EOK)
660 return rc;
661 rc = pci_config_space_read_8(sess, PCI_SUB_CLASS, &isa->pci_subclass);
662 if (rc != EOK)
663 return rc;
664
665 rc = pio_window_get(sess, &isa->pio_win);
666 if (rc != EOK) {
667 ddf_msg(LVL_ERROR, "isa_dev_add failed to get PIO window "
668 "for the device.");
669 return rc;
670 }
671
672 /* Make the bus device more visible. Does not do anything. */
673 ddf_msg(LVL_DEBUG, "Adding a 'ctl' function");
674
675 fibril_mutex_lock(&isa->mutex);
676
677 isa->fctl = ddf_fun_create(dev, fun_exposed, "ctl");
678 if (isa->fctl == NULL) {
679 ddf_msg(LVL_ERROR, "Failed creating control function.");
680 return EXDEV;
681 }
682
683 if (ddf_fun_bind(isa->fctl) != EOK) {
684 ddf_fun_destroy(isa->fctl);
685 ddf_msg(LVL_ERROR, "Failed binding control function.");
686 return EXDEV;
687 }
688
689 /* Add functions as specified in the configuration file. */
690 isa_functions_add(isa);
691 ddf_msg(LVL_NOTE, "Finished enumerating legacy functions");
692
693 fibril_mutex_unlock(&isa->mutex);
694
695 return EOK;
696}
697
698static int isa_dev_remove(ddf_dev_t *dev)
699{
700 isa_bus_t *isa = isa_bus(dev);
701
702 fibril_mutex_lock(&isa->mutex);
703
704 while (!list_empty(&isa->functions)) {
705 isa_fun_t *fun = list_get_instance(list_first(&isa->functions),
706 isa_fun_t, bus_link);
707
708 int rc = ddf_fun_offline(fun->fnode);
709 if (rc != EOK) {
710 fibril_mutex_unlock(&isa->mutex);
711 ddf_msg(LVL_ERROR, "Failed offlining %s", ddf_fun_get_name(fun->fnode));
712 return rc;
713 }
714
715 rc = ddf_fun_unbind(fun->fnode);
716 if (rc != EOK) {
717 fibril_mutex_unlock(&isa->mutex);
718 ddf_msg(LVL_ERROR, "Failed unbinding %s", ddf_fun_get_name(fun->fnode));
719 return rc;
720 }
721
722 list_remove(&fun->bus_link);
723
724 ddf_fun_destroy(fun->fnode);
725 }
726
727 if (ddf_fun_unbind(isa->fctl) != EOK) {
728 fibril_mutex_unlock(&isa->mutex);
729 ddf_msg(LVL_ERROR, "Failed unbinding control function.");
730 return EXDEV;
731 }
732
733 fibril_mutex_unlock(&isa->mutex);
734
735 return EOK;
736}
737
738static int isa_fun_online(ddf_fun_t *fun)
739{
740 ddf_msg(LVL_DEBUG, "isa_fun_online()");
741 return ddf_fun_online(fun);
742}
743
744static int isa_fun_offline(ddf_fun_t *fun)
745{
746 ddf_msg(LVL_DEBUG, "isa_fun_offline()");
747 return ddf_fun_offline(fun);
748}
749
750int main(int argc, char *argv[])
751{
752 printf(NAME ": HelenOS ISA bus driver\n");
753 ddf_log_init(NAME);
754 return ddf_driver_main(&isa_driver);
755}
756
757/**
758 * @}
759 */
Note: See TracBrowser for help on using the repository browser.