Module: xenomai-forge Branch: next Commit: 6d8886c30d4b981d9a84380e289daa6325865516 URL: http://git.xenomai.org/?p=xenomai-forge.git;a=commit;h=6d8886c30d4b981d9a84380e289daa6325865516
Author: Philippe Gerum <r...@xenomai.org> Date: Fri Aug 15 13:07:18 2014 +0200 drivers/udd: expose file descriptor to ancillary I/O handlers The file descriptor may be needed for accessing context data, such as the device minor information. At this chance, the UDD core is also documented. --- include/cobalt/kernel/rtdm/udd.h | 245 ++++++++++++++++++++++++++++++++++++-- include/rtdm/uapi/udd.h | 60 +++++++++- kernel/cobalt/rtdm/device.c | 4 +- kernel/drivers/udd/udd.c | 159 ++++++++++++++++++++++--- 4 files changed, 436 insertions(+), 32 deletions(-) diff --git a/include/cobalt/kernel/rtdm/udd.h b/include/cobalt/kernel/rtdm/udd.h index 5aab27f..1222f9a 100644 --- a/include/cobalt/kernel/rtdm/udd.h +++ b/include/cobalt/kernel/rtdm/udd.h @@ -1,4 +1,5 @@ -/* +/** + * @file * Copyright (C) 2014 Philippe Gerum <r...@xenomai.org> * * Xenomai is free software; you can redistribute it and/or @@ -22,40 +23,262 @@ #include <rtdm/driver.h> #include <rtdm/uapi/udd.h> +/** + * @ingroup rtdm_profiles + * @defgroup rtdm_udd User-space driver core + * + * This profile includes all mini-drivers sitting on top of the + * User-space Device Driver framework (UDD). The generic UDD core + * driver enables interrupt control and I/O memory access interfaces + * to user-space device drivers, as defined by the mini-drivers when + * registering. + * + * A mini-driver supplements the UDD core with ancillary functions for + * dealing with @ref udd_memory_region "memory mappings" and @ref + * udd_irq_handler "interrupt control" for a particular I/O + * card/device. + * + * UDD-compliant mini-drivers only have to provide the basic support + * for dealing with the interrupt sources present in the device, so + * that most part of the device requests can be handled from a Xenomai + * application running in user-space. + * + * This profile is reminiscent of the UIO framework available with the + * Linux kernel, adapted to the dual kernel Cobalt environment. + * + * @{ + */ + +/** + * @anchor udd_irq_special + * Special IRQ values for udd_device.irq + * + * @{ + */ +/** + * No IRQ managed. Passing this code implicitly disables all + * interrupt-related services, including control (disable/enable) and + * notification. + */ #define UDD_IRQ_NONE 0 +/** + * IRQ directly managed from the mini-driver on top of the UDD + * core. The mini-driver is in charge of notifying the Cobalt threads + * waiting for IRQ events by calling the udd_notify_event() service. + */ #define UDD_IRQ_CUSTOM (-1) +/** @} */ +/** + * @anchor udd_memory_types @name Memory types for mapping + * Types of memory for mapping + * + * The UDD core implements a default ->mmap() handler which first + * attempts to hand over the request to the corresponding handler + * defined by the mini-driver. If not present, the UDD core + * establishes the mapping automatically, depending on the memory + * type defined for the region. + * + * @{ + */ +/** + * No memory region. Use this type code to disable an entry in the + * array of memory mappings, i.e. udd_device.mem_regions[]. + */ #define UDD_MEM_NONE 0 +/** + * Physical I/O memory region. By default, the UDD core maps such + * memory to a virtual user range by calling the rtdm_mmap_iomem() + * service. + */ #define UDD_MEM_PHYS 1 +/** + * Kernel logical memory region (e.g. kmalloc()). By default, the UDD + * core maps such memory to a virtual user range by calling the + * rtdm_mmap_kem() service. */ #define UDD_MEM_LOGICAL 2 +/** + * Virtual memory region with no direct physical mapping + * (e.g. vmalloc()). By default, the UDD core maps such memory to a + * virtual user range by calling the rtdm_mmap_vmem() service. + */ #define UDD_MEM_VIRTUAL 3 +/** @} */ #define UDD_NR_MAPS 5 +/** + * @anchor udd_memory_region + * UDD memory region descriptor. + * + * This descriptor defines the characteristics of a memory region + * declared to the UDD core by the mini-driver. All valid regions + * should be declared in the udd_device.mem_regions[] array, + * invalid/unassigned ones should bear the UDD_MEM_NONE type. + * + * The UDD core exposes each region via the mmap(2) interface to the + * application. To this end, a companion mapper device is created + * automatically when registering the mini-driver. + * + * The mapper device creates special files in the RTDM namespace to + * reach the individual regions, which the application can open, for + * mapping the corresponding region to their address space via the + * mmap(2) system call. + * + * For instance, declaring a region of physical memory at index #2 of + * the memory region array as follows: + * + * @code + * static int foocard_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) + * { + * struct udd_device udd; + * + * udd.device_name = "foocard"; + * ... + * udd.mem_regions[2].name = "ADC"; + * udd.mem_regions[2].addr = pci_resource_start(dev, 1); + * udd.mem_regions[2].len = pci_resource_len(dev, 1); + * udd.mem_regions[2].type = UDD_MEM_PHYS; + * ... + * return udd_register_device(&udd); + * } + * @endcode + * + * will make such region accessible via the mapper device using the + * following sequence of code, via the default ->mmap() handler from + * the UDD core: + * + * @code + * int fd, fdm; + * void *p; + * + * fd = open("/dev/foocard", O_RDWR); + * fdm = open("/dev/foocard,mapper@2", O_RDWR); + * p = mmap(NULL, 4096, PROT_READ|PROT_WRITE, 0, fdm, 0); + * @endcode + * + * @note No mapper device is created unless a valid region has been + * declared in the udd_device.mem_regions[] array. + */ struct udd_memregion { + /** Name of the region (informational but required) */ const char *name; - phys_addr_t addr; + /** + * Start address of the region. This may be a physical or + * virtual address, depending on the @ref udd_memory_types + * "memory type". + */ + unsigned long addr; + /** + * Length (in bytes) of the region. This value must be + * PAGE_SIZE aligned. + */ size_t len; + /** + * Type of the region. See the discussion about @ref + * udd_memory_types "UDD memory types" for possible values. + */ int type; }; +/** + * @anchor udd_device + * UDD device descriptor. + * + * This descriptor defines the characteristics of a UDD-based + * mini-driver when registering via a call to udd_register_device(). + */ struct udd_device { + /** Name of the device managed by the mini-driver. */ const char *device_name; + /** + * Textual description of the device managed by the + * mini-driver. + */ const char *device_description; + /** + * Subclass code of the device managed by the mini-driver (see + * RTDM_SUBCLASS_xxx definition in the @ref rtdm_profiles + * "Device Profiles"). The main class code is forced to + * RTDM_CLASS_UDD. + */ int device_subclass; + /** @ref drv_versioning "Driver version. */ int driver_version; + /** Driver author/provider (exposed via /proc/xenomai/rtdm) */ const char *driver_author; struct { - int (*open)(struct udd_device *dev, int oflags); - void (*close)(struct udd_device *dev); - int (*ioctl)(struct udd_device *dev, + /** + * Ancillary open() handler, optional. See + * rtdm_open_handler(). + */ + int (*open)(struct rtdm_fd *fd, int oflags); + /** + * Ancillary close() handler, optional. See + * rtdm_close_handler(). + */ + void (*close)(struct rtdm_fd *fd); + /** + * Ancillary ioctl() handler, optional. See + * rtdm_ioctl_handler(). + */ + int (*ioctl)(struct rtdm_fd *fd, unsigned int request, void *arg); - int (*mmap)(struct udd_device *dev, + /** + * Ancillary mmap() handler for the mapper device, + * optional. See rtdm_mmap_handler(). The mapper + * device operates on a valid region defined in the @a + * mem_regions[] array. A pointer to the region + * can be obtained by a call to udd_get_region(). + * + * If this handler is NULL, the UDD core establishes + * the mapping automatically, depending on the memory + * type defined for the region. + */ + int (*mmap)(struct rtdm_fd *fd, struct vm_area_struct *vma); - int (*interrupt)(struct udd_device *dev); + /** + * @anchor udd_irq_handler + * + * Ancillary handler for receiving interrupts. This + * handler must be provided if the mini-driver hands + * over IRQ handling to the UDD core, by setting the + * @a irq field to a valid value, different from + * UDD_IRQ_CUSTOM and UDD_IRQ_NONE. + * + * The ->interrupt() handler shall return one of the + * following status codes: + * + * - RTDM_IRQ_HANDLED, if the mini-driver successfully + * handled the IRQ. This flag can be combined with + * RTDM_IRQ_DISABLE to prevent the Cobalt kernel from + * re-enabling the interrupt line upon return, + * otherwise it is re-enabled automatically. + * + * - RTDM_IRQ_NONE, if the interrupt does not match + * any IRQ the mini-driver can handle. + * + * Once the ->interrupt() handler has returned, the + * UDD core notifies user-space Cobalt threads waiting + * for IRQ events (if any). + */ + int (*interrupt)(struct udd_device *udd); } ops; + /** + * IRQ number. If valid, the UDD core manages the + * corresponding interrupt line, installing a base handler. + * Otherwise, a special value can be passed for declaring + * @ref udd_irq_special "unmanaged IRQs". + */ int irq; + /** + * Array of memory regions defined by the device. The array + * can be sparse, with some entries bearing the UDD_MEM_NONE + * type interleaved with valid ones. See the discussion about + * @ref udd_memory_region "UDD memory regions". + */ struct udd_memregion mem_regions[UDD_NR_MAPS]; + /** Reserved to the UDD core. */ struct udd_reserved { rtdm_irq_t irqh; atomic_t event; @@ -68,15 +291,19 @@ struct udd_device { } __reserved; }; -int udd_register_device(struct udd_device *dev); +int udd_register_device(struct udd_device *udd); -int udd_unregister_device(struct udd_device *dev, +int udd_unregister_device(struct udd_device *udd, unsigned int poll_delay); +struct udd_device *udd_get_device(struct rtdm_fd *fd); + void udd_notify_event(struct udd_device *udd); void udd_post_irq_enable(int irq); void udd_post_irq_disable(int irq); +/** @} */ + #endif /* !_COBALT_RTDM_UDD_H */ diff --git a/include/rtdm/uapi/udd.h b/include/rtdm/uapi/udd.h index d5bafdd..210f0ce 100644 --- a/include/rtdm/uapi/udd.h +++ b/include/rtdm/uapi/udd.h @@ -1,7 +1,8 @@ -/* +/** + * @file * This file is part of the Xenomai project. * - * Copyright (C) 2014 Philippe Gerum <r...@xenomai.org> + * @author Copyright (C) 2014 Philippe Gerum <r...@xenomai.org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -20,13 +21,68 @@ #ifndef _RTDM_UAPI_UDD_H #define _RTDM_UAPI_UDD_H +/** + * @addtogroup rtdm_udd + * + * @{ + */ + +/** + * @anchor udd_signotify + * @brief UDD event notification descriptor + * + * This structure shall be used to pass the information required to + * enable/disable the notification by signal upon interrupt receipt. + * + * If PID is zero or negative, the notification is disabled. + * Otherwise, the Cobalt thread whose PID is given will receive the + * Cobalt signal also mentioned, along with the count of interrupts at + * the time of the receipt stored in siginfo.si_int. A Cobalt thread + * must explicitly wait for notifications using the sigwaitinfo() or + * sigtimedwait() services (no asynchronous mode available). + */ struct udd_signotify { + /** + * PID of the Cobalt thread to notify upon interrupt + * receipt. If @a pid is zero or negative, the notification is + * disabled. + */ pid_t pid; + /** + * Signal number to send to PID for notifying, which must be + * in the range [SIGRTMIN .. SIGRTMAX] inclusive. This value + * is not considered if @a pid is zero or negative. + */ int sig; }; +/** + * @anchor udd_ioctl_codes @name UDD_IOCTL + * IOCTL requests + * + * @{ + */ + +/** + * Enable the interrupt line. The UDD-class mini-driver in kernel + * space should act upon this request appropriately when received via + * its ->ioctl() handler. + */ #define UDD_RTIOC_IRQEN _IO(RTDM_CLASS_UDD, 0) +/** + * Disable the interrupt line. The UDD-class mini-driver in kernel + * should act upon this request appropriately when received via its + * ->ioctl() handler. + */ #define UDD_RTIOC_IRQDIS _IO(RTDM_CLASS_UDD, 1) +/** + * Enable/Disable signal notification upon interrupt event. A valid + * @ref udd_signotify "notification descriptor" must be passed along + * with this request, which is handled by the UDD core directly. + */ #define UDD_RTIOC_IRQSIG _IOW(RTDM_CLASS_UDD, 2, struct udd_signotify) +/** @} */ +/** @} */ + #endif /* !_RTDM_UAPI_UDD_H */ diff --git a/kernel/cobalt/rtdm/device.c b/kernel/cobalt/rtdm/device.c index 9c9ef5e..4c6f4c8 100644 --- a/kernel/cobalt/rtdm/device.c +++ b/kernel/cobalt/rtdm/device.c @@ -148,8 +148,8 @@ __rtdm_get_protocol_device(int protocol_family, int socket_type) * - -EINVAL is returned if the device structure contains invalid entries. * Check kernel log in this case. * - * - -ENOMEM is returned if the context for an exclusive device cannot be - * allocated. + * - -ENOMEM is returned if a memory allocation failed in the process + * of registering the device. * * - -EEXIST is returned if the specified device name of protocol ID is * already in use. diff --git a/kernel/drivers/udd/udd.c b/kernel/drivers/udd/udd.c index 332bd5a..37b4db9 100644 --- a/kernel/drivers/udd/udd.c +++ b/kernel/drivers/udd/udd.c @@ -37,7 +37,7 @@ static int udd_open(struct rtdm_fd *fd, int oflags) udd = container_of(rtdm_fd_device(fd), struct udd_device, __reserved.device); if (udd->ops.open) { - ret = udd->ops.open(udd, oflags); + ret = udd->ops.open(fd, oflags); if (ret) return ret; } @@ -54,7 +54,7 @@ static void udd_close(struct rtdm_fd *fd) udd = container_of(rtdm_fd_device(fd), struct udd_device, __reserved.device); if (udd->ops.close) - udd->ops.close(udd); + udd->ops.close(fd); } static int udd_ioctl_rt(struct rtdm_fd *fd, @@ -67,7 +67,7 @@ static int udd_ioctl_rt(struct rtdm_fd *fd, udd = container_of(rtdm_fd_device(fd), struct udd_device, __reserved.device); if (udd->ops.ioctl) { - ret = udd->ops.ioctl(udd, request, arg); + ret = udd->ops.ioctl(fd, request, arg); if (ret != -ENOSYS) return ret; } @@ -230,7 +230,7 @@ static int mapper_mmap(struct rtdm_fd *fd, struct vm_area_struct *vma) udd = container_of(rtdm_fd_device(fd), struct udd_device, __reserved.mapper); if (udd->ops.mmap) /* Offload to client driver if handler is present. */ - return udd->ops.mmap(udd, vma); + return udd->ops.mmap(fd, vma); /* Otherwise DIY using the RTDM helpers. */ @@ -257,21 +257,6 @@ static int mapper_mmap(struct rtdm_fd *fd, struct vm_area_struct *vma) return ret; } -void udd_notify_event(struct udd_device *udd) -{ - struct udd_reserved *ur = &udd->__reserved; - union sigval sival; - - atomic_inc(&ur->event); - rtdm_event_signal(&ur->pulse); - - if (ur->signfy.pid > 0) { - sival.sival_int = atomic_read(&ur->event); - cobalt_sigqueue(ur->signfy.pid, ur->signfy.sig, &sival); - } -} -EXPORT_SYMBOL_GPL(udd_notify_event); - static inline int check_memregion(struct udd_device *udd, struct udd_memregion *rn) { @@ -323,6 +308,32 @@ static inline int register_mapper(struct udd_device *udd) return rtdm_dev_register(dev); } +/** + * @brief Register a UDD device + * + * This routine registers a mini-driver at the UDD core. + * + * @param udd The @ref udd_device "UDD device descriptor" which should + * describe the new device properties. + * + * @return Zero is returned upon success, otherwise a negative error + * code is received, from the set of error codes defined by + * rtdm_dev_register(). In addition, the following error codes can be + * returned: + * + * - -EINVAL, some of the memory regions declared in the + * udd_device.mem_regions[] array have invalid properties, i.e. bad + * type, NULL name, zero length or address. Any undeclared region + * entry from the array must bear the UDD_MEM_NONE type. + * + * - -EINVAL, if udd_device.irq is different from UDD_IRQ_CUSTOM and + * UDD_IRQ_NONE but invalid, causing rtdm_irq_request() to fail. + * + * - -ENXIO can be received if this service is called while the Cobalt + * kernel is disabled. + * + * @coretags{secondary-only} + */ int udd_register_device(struct udd_device *udd) { struct rtdm_device *dev = &udd->__reserved.device; @@ -397,6 +408,23 @@ fail_irq_request: } EXPORT_SYMBOL_GPL(udd_register_device); +/** + * @brief Unregister a UDD device + * + * This routine unregisters a mini-driver from the UDD core. + * + * @param udd The UDD device descriptor + * + * @param poll_delay Polling delay in milliseconds to check repeatedly + * for open instances of @a udd, or 0 for non-blocking mode. + * + * @return Zero is returned upon success, otherwise a negative error + * code is received, from the set of error codes defined by + * rtdm_dev_unregister(). In addition, -ENXIO can be received if this + * service is called while the Cobalt kernel is disabled. + * + * @coretags{secondary-only} + */ int udd_unregister_device(struct udd_device *udd, unsigned int poll_delay) { @@ -419,6 +447,42 @@ int udd_unregister_device(struct udd_device *udd, } EXPORT_SYMBOL_GPL(udd_unregister_device); +/** + * @brief Notify an IRQ event for an unmanaged interrupt + * + * When the UDD core shall hand over the interrupt management for a + * device to the mini-driver (see UDD_IRQ_CUSTOM), the latter should + * notify the UDD core when IRQ events are received by calling this + * service. + * + * As a result, the UDD core wakes up any Cobalt thread waiting for + * interrupts on the device via a read(2) or select(2) call. + * + * @param udd The UDD device descriptor receiving the IRQ. + * + * @coretags{coreirq-only} + * + * @note In case the ref udd_irq_handler "IRQ handler" from the + * mini-driver requested the UDD core not to re-enable the interrupt + * line, the application may later request the unmasking by issuing + * the UDD_RTIOC_IRQEN ioctl(2) command. Writing a non-zero integer to + * the device via the write(2) system call has the same effect. + */ +void udd_notify_event(struct udd_device *udd) +{ + struct udd_reserved *ur = &udd->__reserved; + union sigval sival; + + atomic_inc(&ur->event); + rtdm_event_signal(&ur->pulse); + + if (ur->signfy.pid > 0) { + sival.sival_int = atomic_read(&ur->event); + cobalt_sigqueue(ur->signfy.pid, ur->signfy.sig, &sival); + } +} +EXPORT_SYMBOL_GPL(udd_notify_event); + struct irqswitch_work { struct ipipe_work_header work; /* Must be first. */ int irq; @@ -460,16 +524,73 @@ static void switch_irq_line(int irq, int enable) ipipe_post_work_root(&switchwork, work); } +/** + * @brief Post a request for enabling an IRQ line + * + * This service issues a request to the regular kernel for enabling + * the IRQ line mentioned. If the caller runs in primary mode, the + * request is scheduled but deferred until the current CPU leaves the + * real-time domain. Otherwise, the request is immediately handled. + * + * @param irq The IRQ line to enable. + * + * @coretags{unrestricted} + * + * @note The deferral is required as some interrupt management code + * involved in enabling interrupt lines may not be safely executed + * from primary mode. + */ void udd_post_irq_enable(int irq) { switch_irq_line(irq, 1); } EXPORT_SYMBOL_GPL(udd_post_irq_enable); +/** + * @brief Post a request for disabling an IRQ line + * + * This service issues a request to the regular kernel for disabling + * the IRQ line mentioned. If the caller runs in primary mode, the + * request is scheduled but deferred until the current CPU leaves the + * real-time domain. Otherwise, the request is immediately handled. + * + * @param irq The IRQ line to enable. + * + * @coretags{unrestricted} + * + * @note The deferral is required as some interrupt management code + * involved in disable interrupt lines may not be safely executed from + * primary mode. + */ void udd_post_irq_disable(int irq) { switch_irq_line(irq, 0); } EXPORT_SYMBOL_GPL(udd_post_irq_disable); +/** + * @brief Retrieve the UDD device descriptor from a file descriptor + * + * @param fd The file descriptor received by an ancillary I/O handler + * from a mini-driver based on the UDD core. + * + * @return A pointer to the UDD device to which @a fd refers to. + * + * @note This service is intended for use by mini-drivers based on the + * UDD core exclusively. Passing file descriptors referring to other + * RTDM devices will certainly lead to invalid results. + * + * @coretags{mode-unrestricted} + */ +struct udd_device *udd_get_device(struct rtdm_fd *fd) +{ + struct rtdm_device *dev = rtdm_fd_device(fd); + + if (dev->device_class == RTDM_CLASS_MEMORY) + return container_of(dev, struct udd_device, __reserved.mapper); + + return container_of(dev, struct udd_device, __reserved.device); +} +EXPORT_SYMBOL_GPL(udd_get_device); + MODULE_LICENSE("GPL"); _______________________________________________ Xenomai-git mailing list Xenomai-git@xenomai.org http://www.xenomai.org/mailman/listinfo/xenomai-git