source: mainline/uspace/lib/drv/generic/driver.c@ 45019865

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 45019865 was 6610565b, checked in by Vojtech Horky <vojtechhorky@…>, 14 years ago

Merge mainline changes

  • Property mode set to 100644
File size: 14.2 KB
Line 
1/*
2 * Copyright (c) 2010 Lenka Trochtova
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/**
30 * @defgroup libdrv generic device driver support.
31 * @brief HelenOS generic device driver support.
32 * @{
33 */
34
35/** @file
36 */
37
38#include <assert.h>
39#include <ipc/services.h>
40#include <ipc/ns.h>
41#include <async.h>
42#include <stdio.h>
43#include <errno.h>
44#include <bool.h>
45#include <fibril_synch.h>
46#include <stdlib.h>
47#include <str.h>
48#include <ctype.h>
49#include <errno.h>
50#include <inttypes.h>
51
52#include <ipc/driver.h>
53
54#include "dev_iface.h"
55#include "driver.h"
56
57/** Driver structure */
58static driver_t *driver;
59
60/** Devices */
61LIST_INITIALIZE(devices);
62FIBRIL_MUTEX_INITIALIZE(devices_mutex);
63
64/** Interrupts */
65static interrupt_context_list_t interrupt_contexts;
66
67static irq_cmd_t default_cmds[] = {
68 {
69 .cmd = CMD_ACCEPT
70 }
71};
72
73static irq_code_t default_pseudocode = {
74 sizeof(default_cmds) / sizeof(irq_cmd_t),
75 default_cmds
76};
77
78
79static void driver_irq_handler(ipc_callid_t iid, ipc_call_t *icall)
80{
81 int id = (int)IPC_GET_IMETHOD(*icall);
82 interrupt_context_t *ctx;
83
84 ctx = find_interrupt_context_by_id(&interrupt_contexts, id);
85 if (ctx != NULL && ctx->handler != NULL)
86 (*ctx->handler)(ctx->dev, iid, icall);
87}
88
89interrupt_context_t *create_interrupt_context(void)
90{
91 interrupt_context_t *ctx;
92
93 ctx = (interrupt_context_t *) malloc(sizeof(interrupt_context_t));
94 if (ctx != NULL)
95 memset(ctx, 0, sizeof(interrupt_context_t));
96
97 return ctx;
98}
99
100void delete_interrupt_context(interrupt_context_t *ctx)
101{
102 if (ctx != NULL)
103 free(ctx);
104}
105
106void init_interrupt_context_list(interrupt_context_list_t *list)
107{
108 memset(list, 0, sizeof(interrupt_context_list_t));
109 fibril_mutex_initialize(&list->mutex);
110 list_initialize(&list->contexts);
111}
112
113void
114add_interrupt_context(interrupt_context_list_t *list, interrupt_context_t *ctx)
115{
116 fibril_mutex_lock(&list->mutex);
117 ctx->id = list->curr_id++;
118 list_append(&ctx->link, &list->contexts);
119 fibril_mutex_unlock(&list->mutex);
120}
121
122void remove_interrupt_context(interrupt_context_list_t *list,
123 interrupt_context_t *ctx)
124{
125 fibril_mutex_lock(&list->mutex);
126 list_remove(&ctx->link);
127 fibril_mutex_unlock(&list->mutex);
128}
129
130interrupt_context_t *
131find_interrupt_context_by_id(interrupt_context_list_t *list, int id)
132{
133 fibril_mutex_lock(&list->mutex);
134
135 link_t *link = list->contexts.next;
136 interrupt_context_t *ctx;
137
138 while (link != &list->contexts) {
139 ctx = list_get_instance(link, interrupt_context_t, link);
140 if (ctx->id == id) {
141 fibril_mutex_unlock(&list->mutex);
142 return ctx;
143 }
144 link = link->next;
145 }
146
147 fibril_mutex_unlock(&list->mutex);
148 return NULL;
149}
150
151interrupt_context_t *
152find_interrupt_context(interrupt_context_list_t *list, device_t *dev, int irq)
153{
154 fibril_mutex_lock(&list->mutex);
155
156 link_t *link = list->contexts.next;
157 interrupt_context_t *ctx;
158
159 while (link != &list->contexts) {
160 ctx = list_get_instance(link, interrupt_context_t, link);
161 if (ctx->irq == irq && ctx->dev == dev) {
162 fibril_mutex_unlock(&list->mutex);
163 return ctx;
164 }
165 link = link->next;
166 }
167
168 fibril_mutex_unlock(&list->mutex);
169 return NULL;
170}
171
172
173int
174register_interrupt_handler(device_t *dev, int irq, interrupt_handler_t *handler,
175 irq_code_t *pseudocode)
176{
177 interrupt_context_t *ctx = create_interrupt_context();
178
179 ctx->dev = dev;
180 ctx->irq = irq;
181 ctx->handler = handler;
182
183 add_interrupt_context(&interrupt_contexts, ctx);
184
185 if (pseudocode == NULL)
186 pseudocode = &default_pseudocode;
187
188 int res = ipc_register_irq(irq, dev->handle, ctx->id, pseudocode);
189 if (res != EOK) {
190 remove_interrupt_context(&interrupt_contexts, ctx);
191 delete_interrupt_context(ctx);
192 }
193
194 return res;
195}
196
197int unregister_interrupt_handler(device_t *dev, int irq)
198{
199 interrupt_context_t *ctx = find_interrupt_context(&interrupt_contexts,
200 dev, irq);
201 int res = ipc_unregister_irq(irq, dev->handle);
202
203 if (ctx != NULL) {
204 remove_interrupt_context(&interrupt_contexts, ctx);
205 delete_interrupt_context(ctx);
206 }
207
208 return res;
209}
210
211static void add_to_devices_list(device_t *dev)
212{
213 fibril_mutex_lock(&devices_mutex);
214 list_append(&dev->link, &devices);
215 fibril_mutex_unlock(&devices_mutex);
216}
217
218static void remove_from_devices_list(device_t *dev)
219{
220 fibril_mutex_lock(&devices_mutex);
221 list_remove(&dev->link);
222 fibril_mutex_unlock(&devices_mutex);
223}
224
225static device_t *driver_get_device(link_t *devices, devman_handle_t handle)
226{
227 device_t *dev = NULL;
228
229 fibril_mutex_lock(&devices_mutex);
230 link_t *link = devices->next;
231
232 while (link != devices) {
233 dev = list_get_instance(link, device_t, link);
234 if (dev->handle == handle) {
235 fibril_mutex_unlock(&devices_mutex);
236 return dev;
237 }
238 link = link->next;
239 }
240
241 fibril_mutex_unlock(&devices_mutex);
242
243 return NULL;
244}
245
246static void driver_add_device(ipc_callid_t iid, ipc_call_t *icall)
247{
248 char *dev_name = NULL;
249 int res;
250
251 devman_handle_t dev_handle = IPC_GET_ARG1(*icall);
252 devman_handle_t parent_dev_handle = IPC_GET_ARG2(*icall);
253
254 device_t *dev = create_device();
255 dev->handle = dev_handle;
256
257 async_data_write_accept((void **) &dev_name, true, 0, 0, 0, 0);
258 dev->name = dev_name;
259
260 add_to_devices_list(dev);
261 dev->parent = driver_get_device(&devices, parent_dev_handle);
262
263 res = driver->driver_ops->add_device(dev);
264 if (res == EOK) {
265 printf("%s: new device with handle=%" PRIun " was added.\n",
266 driver->name, dev_handle);
267 } else {
268 printf("%s: failed to add a new device with handle = %" PRIun ".\n",
269 driver->name, dev_handle);
270 remove_from_devices_list(dev);
271 delete_device(dev);
272 }
273
274 ipc_answer_0(iid, res);
275}
276
277static void driver_connection_devman(ipc_callid_t iid, ipc_call_t *icall)
278{
279 /* Accept connection */
280 ipc_answer_0(iid, EOK);
281
282 bool cont = true;
283 while (cont) {
284 ipc_call_t call;
285 ipc_callid_t callid = async_get_call(&call);
286
287 switch (IPC_GET_IMETHOD(call)) {
288 case IPC_M_PHONE_HUNGUP:
289 cont = false;
290 continue;
291 case DRIVER_ADD_DEVICE:
292 driver_add_device(callid, &call);
293 break;
294 default:
295 ipc_answer_0(callid, ENOENT);
296 }
297 }
298}
299
300/**
301 * Generic client connection handler both for applications and drivers.
302 *
303 * @param drv True for driver client, false for other clients
304 * (applications, services etc.).
305 */
306static void driver_connection_gen(ipc_callid_t iid, ipc_call_t *icall, bool drv)
307{
308 /*
309 * Answer the first IPC_M_CONNECT_ME_TO call and remember the handle of
310 * the device to which the client connected.
311 */
312 devman_handle_t handle = IPC_GET_ARG2(*icall);
313 device_t *dev = driver_get_device(&devices, handle);
314
315 if (dev == NULL) {
316 printf("%s: driver_connection_gen error - no device with handle"
317 " %" PRIun " was found.\n", driver->name, handle);
318 ipc_answer_0(iid, ENOENT);
319 return;
320 }
321
322
323 /*
324 * TODO - if the client is not a driver, check whether it is allowed to
325 * use the device.
326 */
327
328 int ret = EOK;
329 /* open the device */
330 if (dev->ops != NULL && dev->ops->open != NULL)
331 ret = (*dev->ops->open)(dev);
332
333 ipc_answer_0(iid, ret);
334 if (ret != EOK)
335 return;
336
337 while (1) {
338 ipc_callid_t callid;
339 ipc_call_t call;
340 callid = async_get_call(&call);
341 sysarg_t method = IPC_GET_IMETHOD(call);
342 int iface_idx;
343
344 switch (method) {
345 case IPC_M_PHONE_HUNGUP:
346 /* close the device */
347 if (dev->ops != NULL && dev->ops->close != NULL)
348 (*dev->ops->close)(dev);
349 ipc_answer_0(callid, EOK);
350 return;
351 default:
352 /* convert ipc interface id to interface index */
353
354 iface_idx = DEV_IFACE_IDX(method);
355
356 if (!is_valid_iface_idx(iface_idx)) {
357 remote_handler_t *default_handler =
358 device_get_default_handler(dev);
359 if (default_handler != NULL) {
360 (*default_handler)(dev, callid, &call);
361 break;
362 }
363 /*
364 * This is not device's interface and the
365 * default handler is not provided.
366 */
367 printf("%s: driver_connection_gen error - "
368 "invalid interface id %d.",
369 driver->name, iface_idx);
370 ipc_answer_0(callid, ENOTSUP);
371 break;
372 }
373
374 /* calling one of the device's interfaces */
375
376 /* Get the interface ops structure. */
377 void *ops = device_get_ops(dev, iface_idx);
378 if (ops == NULL) {
379 printf("%s: driver_connection_gen error - ",
380 driver->name);
381 printf("device with handle %" PRIun " has no interface "
382 "with id %d.\n", handle, iface_idx);
383 ipc_answer_0(callid, ENOTSUP);
384 break;
385 }
386
387 /*
388 * Get the corresponding interface for remote request
389 * handling ("remote interface").
390 */
391 remote_iface_t *rem_iface = get_remote_iface(iface_idx);
392 assert(rem_iface != NULL);
393
394 /* get the method of the remote interface */
395 sysarg_t iface_method_idx = IPC_GET_ARG1(call);
396 remote_iface_func_ptr_t iface_method_ptr =
397 get_remote_method(rem_iface, iface_method_idx);
398 if (iface_method_ptr == NULL) {
399 // the interface has not such method
400 printf("%s: driver_connection_gen error - "
401 "invalid interface method.", driver->name);
402 ipc_answer_0(callid, ENOTSUP);
403 break;
404 }
405
406 /*
407 * Call the remote interface's method, which will
408 * receive parameters from the remote client and it will
409 * pass it to the corresponding local interface method
410 * associated with the device by its driver.
411 */
412 (*iface_method_ptr)(dev, ops, callid, &call);
413 break;
414 }
415 }
416}
417
418static void driver_connection_driver(ipc_callid_t iid, ipc_call_t *icall)
419{
420 driver_connection_gen(iid, icall, true);
421}
422
423static void driver_connection_client(ipc_callid_t iid, ipc_call_t *icall)
424{
425 driver_connection_gen(iid, icall, false);
426}
427
428
429/** Function for handling connections to device driver. */
430static void driver_connection(ipc_callid_t iid, ipc_call_t *icall)
431{
432 /* Select interface */
433 switch ((sysarg_t) (IPC_GET_ARG1(*icall))) {
434 case DRIVER_DEVMAN:
435 /* Handle request from device manager */
436 driver_connection_devman(iid, icall);
437 break;
438 case DRIVER_DRIVER:
439 /* Handle request from drivers of child devices */
440 driver_connection_driver(iid, icall);
441 break;
442 case DRIVER_CLIENT:
443 /* Handle request from client applications */
444 driver_connection_client(iid, icall);
445 break;
446 default:
447 /* No such interface */
448 ipc_answer_0(iid, ENOENT);
449 }
450}
451
452/** Create new device structure.
453 *
454 * @return The device structure.
455 */
456device_t *create_device(void)
457{
458 device_t *dev = malloc(sizeof(device_t));
459
460 if (dev != NULL) {
461 memset(dev, 0, sizeof(device_t));
462 init_match_ids(&dev->match_ids);
463 }
464
465 return dev;
466}
467
468/** Delete device structure.
469 *
470 * @param dev The device structure.
471 */
472void delete_device(device_t *dev)
473{
474 clean_match_ids(&dev->match_ids);
475 if (dev->name != NULL)
476 free(dev->name);
477 free(dev);
478}
479
480void *device_get_ops(device_t *dev, dev_inferface_idx_t idx)
481{
482 assert(is_valid_iface_idx(idx));
483 if (dev->ops == NULL)
484 return NULL;
485 return dev->ops->interfaces[idx];
486}
487
488int child_device_register(device_t *child, device_t *parent)
489{
490 assert(child->name != NULL);
491
492 int res;
493
494 add_to_devices_list(child);
495 res = devman_child_device_register(child->name, &child->match_ids,
496 parent->handle, &child->handle);
497 if (res != EOK) {
498 remove_from_devices_list(child);
499 return res;
500 }
501
502 return res;
503}
504
505/** Wrapper for child_device_register for devices with single match id.
506 *
507 * @param parent Parent device.
508 * @param child_name Child device name.
509 * @param child_match_id Child device match id.
510 * @param child_match_score Child device match score.
511 * @return Error code.
512 */
513int child_device_register_wrapper(device_t *parent, const char *child_name,
514 const char *child_match_id, int child_match_score,
515 devman_handle_t *child_handle)
516{
517 device_t *child = NULL;
518 match_id_t *match_id = NULL;
519 int rc;
520
521 child = create_device();
522 if (child == NULL) {
523 rc = ENOMEM;
524 goto failure;
525 }
526
527 child->name = child_name;
528
529 match_id = create_match_id();
530 if (match_id == NULL) {
531 rc = ENOMEM;
532 goto failure;
533 }
534
535 match_id->id = child_match_id;
536 match_id->score = child_match_score;
537 add_match_id(&child->match_ids, match_id);
538
539 rc = child_device_register(child, parent);
540 if (rc != EOK)
541 goto failure;
542
543 if (child_handle != NULL) {
544 *child_handle = child->handle;
545 }
546
547 return EOK;
548
549failure:
550 if (match_id != NULL) {
551 match_id->id = NULL;
552 delete_match_id(match_id);
553 }
554
555 if (child != NULL) {
556 child->name = NULL;
557 delete_device(child);
558 }
559
560 return rc;
561}
562
563/** Get default handler for client requests */
564remote_handler_t *device_get_default_handler(device_t *dev)
565{
566 if (dev->ops == NULL)
567 return NULL;
568 return dev->ops->default_handler;
569}
570
571int add_device_to_class(device_t *dev, const char *class_name)
572{
573 return devman_add_device_to_class(dev->handle, class_name);
574}
575
576int driver_main(driver_t *drv)
577{
578 /*
579 * Remember the driver structure - driver_ops will be called by generic
580 * handler for incoming connections.
581 */
582 driver = drv;
583
584 /* Initialize the list of interrupt contexts. */
585 init_interrupt_context_list(&interrupt_contexts);
586
587 /* Set generic interrupt handler. */
588 async_set_interrupt_received(driver_irq_handler);
589
590 /*
591 * Register driver by device manager with generic handler for incoming
592 * connections.
593 */
594 devman_driver_register(driver->name, driver_connection);
595
596 async_manager();
597
598 /* Never reached. */
599 return 0;
600}
601
602/**
603 * @}
604 */
Note: See TracBrowser for help on using the repository browser.