Xenomai RTDM
introduction Real Time Driver Model une des « peaux » de Xenomai issue des développements autour des approches « co- noyau » pour développer un Linux Temps Réel RTLinux RTAI Fusion va au delà des approches « traditionnelles » (POSIX I/O) qui est limitée quand il s'agit de gérer des communications basées sur des échange de messages (e.g. interfaces réseau)
introduction Application Application Optionnel Wrapper Library RTDM Hardware Abstraction Layer Hardware Driver Hardware Driver
modèles de périphériques tous les types de périphériques ne sont pas abordés par RTDM (seulement ceux susceptibles d'entrer dans une application de type temps réel) : périphériques « à protocole » utilisent le modèle des sockets POSIX échange de messages primitives socket(), close(), sendmsg(), recvmsg(), ... identifiés par le protocole le type de socket périphériques « nommés » identifiés par un « nom » (chaîne de caractères) support d'échanges analogue à celui des périphériques caractères open(), close() entrées/sorties orientées stream read(), write(), ioctl() ou seulement ioctl()
pilotes RTDM RTDM permet de gérer des périphériques à l'aide de pilotes qui ne sont pas des « pilotes » au sens classique de Linux, mais qui rendent des services équivalents n'apparaissent pas dans /proc/devices arborescence dédiée /proc/xenomai/rtdm pas de fichier spécial associé dans /dev les pilotes RTDM peuvent avoir un comportement différent selon qu'ils sont appelés d'un contexte « temps réel » ou non RTDM n'est pas fondamentalement lié à Xenomai Xenomai est le support actuel de développement (et le seul environnement qui supporte RTDM) organisation du pilote analogue à celle des pilotes Linux classiques simplifiée (2 classes de périphériques seulement) nous n'aborderons que les périphériques nommés
l'API de RTDM des services élémentaires sont proposés pour augmenter la portabilité des pilotes indépendants de l'OS sous-jacent l'API se divise en 2 grandes parties (groupes de modules) l'API utilisateur, pour les applications faisant appel aux services du pilote les API génériques pour le développement portable des pilotes, avec les services suivants : enregistrement des pilotes horloges et timers gestion des tâches synchronisation gestion des interruptions gestion des signaux non temps réels utilitaires le détail des fonctions de l'API se trouve dans la documentation (http://www.xenomai.org)
enregistrement des pilotes structures utilisées par RTDM pour gérer les pilotes rtdm_device contient les champs spécifiant les propriétés du périphérique susceptible d'être modifié par RTDM pendant l'exécution (ne doit pas être dans une zone mémoire protégée en écriture) champs : device flags : type du périphérique (nommé/protocole) device_name : le nom du périphérique nommé protocol_family/socket_type context_size : taille de la structure associée au périphérique open_rt : handler pour la création ou l'ouverture de périphériques nommés socket_rt/socket_nrt : idem pour les périphériques protocole (dépendant du contexte) ops : handlers pour les opérations (les suffixes _rt et _nrt spécifient le contexte) device_class/device_subclass : catégorie du périphérique plus d'autres éléments de description...
enregistrement des pilotes rtdm_operations donne les handlers qui seront appelés en cas de read, write, ioctl, ou close le contexte (rt ou nrt) est spécifié (sauf pour close où seul nrt est valide) inclus dans rtdm_device (ops) rtdm_dev_context associée à chaque instance du périphérique ouverte gérée par RTDM (création, destruction) passée aux handlers dans les arguments des données peuvent être arbitrairement attachées à cette structure. Leur taille doit être spécifiée dans le champ context_size de la structure de type rtdm_device toutes ces structures sont définies dans <rtdm/rtdm_driver.h>
enregistrement des pilotes int rtdm_dev_register(struct rtdm_device *dev); int rtdm_dev_unregister( struct rtdm_device *dev, unsigned int poll_delay); poll_delay indique le delai (en millisecondes) avec lequel RTDM vérifie si des instances du pilote sont encore ouvertes
API utilisateur l'API utilisateur peut être appelée par des tâches qui s'exécutent dans l'espace noyau (kernel threads) en désuétude des tâches qui s'exécutent dans l'espace utilisateur (threads Xenomai) contrôlés par l'ordonnanceur Xenomai (mode primaire) contrôlés par l'ordonnanceur Linux (mode secondaire) permet d'accéder aux fonctionnalités des pilotes RTDM
périphériques nommés int rt_dev_open(const char *path, int oflag, ...); ouvre un périphérique int rt_dev_close(int fd); ferme le périphérique ssize_t rt_dev_read(int fd, void *buf, size_t nbytes); lit des données sur le périphérique ssize_t rt_dev_write(int fd, void *buf, size_t nbytes); écrit des données sur le périphérique int rt_dev_ioctl(int fd, int request, ...); effectue une opération de contrôle sur le périphérique
périphériques à protocole int rt_dev_socket (int protocol_family, int socket_type, int protocol) crée une socket. ssize_t rt_dev_recvmsg (int fd, struct msghdr *msg, int flags) reçoit un message d'une socket ssize_t rt_dev_recvfrom (int fd, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) reçoit un message d'une socket ssize_t rt_dev_recv (int fd, void *buf, size_t len, int flags) reçoit un message d'une socket. ssize_t rt_dev_sendmsg (int fd, const struct msghdr *msg, int flags) envoie un message à une socket
périphériques à protocole ssize_t rt_dev_sendto (int fd, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) envoie un message à une socket ssize_t rt_dev_send (int fd, const void *buf, size_t len, int flags) envoie un message à une socket int rt_dev_bind (int fd, const struct sockaddr *my_addr, socklen_t addrlen) lie à une adresse locale int rt_dev_connect (int fd, const struct sockaddr *serv_addr, socklen_t addrlen) connecte à une adresse distante int rt_dev_listen (int fd, int backlog) écoute des demandes de transmission
périphériques à protocole int rt_dev_accept (int fd, struct sockaddr *addr, socklen_t *addrlen) accepte une demande de connection int rt_dev_shutdown (int fd, int how) déconnecte int rt_dev_getsockopt (int fd, int level, int optname, void *optval, socklen_t *optlen) retourne les option de la socket int rt_dev_setsockopt (int fd, int level, int optname, const void *optval, socklen_t optlen) établit les options de la socket. int rt_dev_getsockname (int fd, struct sockaddr *name, socklen_t *namelen) retourne l'adresse de la socket locale int rt_dev_getpeername (int fd, struct sockaddr *name, socklen_t *namelen) retourne l'adresse de la destination
horloges uniquement des fonctions pour lire l'heure courante : nanosecs_abs_t rtdm_clock_read(void); lit l'heure du système nanosecs_abs_t rtdm_clock_read_monotonic(void); lit l'heure monotonique
gestion des tâches gère des tâches entre les niveaux de priorité RTDM_LOWEST_PRIORITY et RTDM_HIGHEST_PRIORITY int rtdm_task_init (rtdm_task_t *task, const char *name, rtdm_task_proc_t task_proc, void *arg, int priority, nanosecs_rel_t period) initialise et démarre une tâche temps réel void rtdm_task_destroy (rtdm_task_t *task) détruit une tâche temps réel. void rtdm_task_set_priority (rtdm_task_t *task, int priority) ajuste la priorité int rtdm_task_set_period (rtdm_task_t *task, nanosecs_rel_t period) ajuste la période int rtdm_task_wait_period (void) attend la période suivante
gestion des tâches int rtdm_task_unblock (rtdm_task_t *task) active une tâche temps réel bloquée rtdm_task_t * rtdm_task_current (void) retourne l'identificateur de la tâche temps réel courante int rtdm_task_sleep (nanosecs_rel_t delay) se met en sommeil pendant le temps spécifié int rtdm_task_sleep_abs ( nanosecs_abs_t wakeup_time, enum rtdm_timer_mode mode) se met en sommeil jusqu'à une date absolue void rtdm_task_join_nrt (rtdm_task_t *task, unsigned int poll_delay) attend la fin d'une tâche temps réel void rtdm_task_busy_sleep (nanosecs_rel_t delay) attente active pendant un laps de temps spécifié
timers rtdm_timer_init(rtdm_timer_t *timer, rtdm_timer_handler_t handler, constant char *name); initialisation le prototype du handler qui sera appelé à l'expiration est void(* rtdm_timer_handler_t) rtdm_timer_t *timer) rtdm_timer_destroy(rtdm_timer_t *timer); destruction rtdm_timer_start(rtdm_timer_t *timer, nanosecs_abs_t expiry, nanosecs_rel_t interval, enum rtdm_timer_mode mode); démarrage rtdm_timer_stop(rtdm_timer_t *timer) arrêt
timers rtdm_timer_start_in_handler(rtdm_timer_t *timer, nanosecs_abs_t expiry, nanosecs_rel_t interval, enum rtdm_timer_mode mode); démarrage d'un timer dans un handler de timer rtdm_timer_stop_in_handler(rtdm_timer_t *timer); arrêt d'un timer dans un handler de timer modes pour le timer : RTDM_TIMERMODE_RELATIVE timer monotonique avec un timeout relatif RTDM_TIMERMODE_ABSOLUTE timer monotonique avec un timeout absolu RTDM_TIMERMODE_REALTIME timer ajustable avec un timeout absolu
synchronisation les mécanismes de synchronisation sont nombreux : sémaphores mutexes événements spinlock avec désactivation de la préemption séquences de gestion des timeouts
synchronisation : sémaphores void rtdm_sem_init (rtdm_sem_t *sem, unsigned long value) initialise un sémaphore void rtdm_sem_destroy (rtdm_sem_t *sem) détruit un sémaphore. int rtdm_sem_down (rtdm_sem_t *sem) décrémente un sémaphore int rtdm_sem_timeddown (rtdm_sem_t *sem, nanosecs_rel_t timeout, rtdm_toseq_t *timeout_seq) décrémente un sémaphore avec timeout. void rtdm_sem_up (rtdm_sem_t *sem) incrémente un sémaphore
synchronisation : mutexes void rtdm_mutex_init (rtdm_mutex_t *mutex) initialise un mutex. void rtdm_mutex_destroy (rtdm_mutex_t *mutex) détruit un mutex. void rtdm_mutex_unlock (rtdm_mutex_t *mutex) libère un mutex. int rtdm_mutex_lock (rtdm_mutex_t *mutex) demande un mutex. int rtdm_mutex_timedlock (rtdm_mutex_t *mutex, nanosecs_rel_t timeout, rtdm_toseq_t *timeout_seq) demande un mutex avec timeout.
synchronisation : événements void rtdm_event_init (rtdm_event_t *event, unsigned long pending) initialise un événement. void rtdm_event_destroy (rtdm_event_t *event) détruit un événement void rtdm_event_pulse (rtdm_event_t *event) signale l'occurence d'un événement à toutes les tâches en attente void rtdm_event_signal (rtdm_event_t *event) signale l'occurence d'un événement int rtdm_event_wait (rtdm_event_t *event) attend l'occurence d'un événement
synchronisation : événements int rtdm_event_timedwait (rtdm_event_t *event, nanosecs_rel_t timeout, rtdm_toseq_t *timeout_seq) attend l'occurence d'un événement avec timeout void rtdm_event_clear (rtdm_event_t *event) efface l'état d'un événement
synchronisation : spinlocks les primitives pour la gestion des spinlocks sont reprises directement de la couche matérielle abstraite hal : #define rtdm_lock_xxx rthal_spin_xxx RTDM_LOCK_UNLOCKED initialisation statique d'un spinlock rtdm_lock_init(lock) initialisation dynamique d'un spinlock rtdm_lock_get(lock) acquisition d'un spinlock dans un contexte non préemptible rtdm_lock_put(lock) libération d'un spinlock sans restauration de la préemption rtdm_lock_get_irqsave(lock, context) acquisition d'un spinlock avec désactivation de la préemption rtdm_lock_put_irqrestore(lock, context) libération d'un spinlock avec restauration de la préemption
synchronisation : spinlocks rtdm_lock_irqsave(context) désactivation locale de la préemption rtdm_lock_irqrestore(context) restauration de l'état de préemption
synchronisation : gestion des timeouts void rtdm_toseq_init (rtdm_toseq_t *timeout_seq, nanosecs_rel_t timeout) initialisation d'une séquence de timeout utilisé pour toutes les fonctions qui font appel à un timeout (attente d'un sémaphore, d'un mutex, d'un événement) évite d'avoir à réinitialiser le mécanisme de timeout quand on utilise l'attente dans une boucle ce mécanisme de réinitialisation peut être plus long que le délai du timeout !
synchronisation : gestion des timeouts exemple int device_service_routine(...) { rtdm_toseq_t timeout_seq; ... rtdm_toseq_init(&timeout_seq, timeout); ... while (received < requested) { ret = rtdm_event_timedwait(&data_available, timeout, &timeout_seq); if (ret < 0) break; // including -ETIMEDOUT // receive some data ... } ... }
gestion des interruptions int rtdm_irq_request (rtdm_irq_t *irq_handle, unsigned int irq_no, rtdm_irq_handler_t handler, unsigned long flags, const char *device_name, void *arg) enregistre un handler d'interruption pour la ligne d'interruption irq_no int rtdm_irq_free (rtdm_irq_t *irq_handle) libère un handler int rtdm_irq_enable (rtdm_irq_t *irq_handle) autorise la ligne d'interruption associée à irq_handle int rtdm_irq_disable (rtdm_irq_t *irq_handle) désactive la ligne d'interruption associée à irq_handle
gestion des signaux non temps réels ces services donnent un moyen pour exécuter un handler donné dans un contexte non temps réel. Le déclenchement du mécanisme peut être effectué dans un environnement temps réel. L'exécution du handler sera retardée jusqu'à la fin des threads temps réels (quand Linux reprend la CPU) int rtdm_nrtsig_init (rtdm_nrtsig_t *nrt_sig, rtdm_nrtsig_handler_t handler, void *arg) enregistre un handler void rtdm_nrtsig_destroy (rtdm_nrtsig_t *nrt_sig) libère le handler void rtdm_nrtsig_pend (rtdm_nrtsig_t *nrt_sig) envoie un signal prototype du handler : void(* rtdm_nrtsig_handler_t)( rtdm_nrtsig_t nrt_sig, void *arg)
utilitaires int rtdm_mmap_to_user (rtdm_user_info_t *user_info, void *src_addr, size_t len, int prot, void **pptr, struct vm_operations_struct *vm_ops, void *vm_private_data) Mappe une zone mémoire noyau dans l'espace utilisateur int rtdm_iomap_to_user (rtdm_user_info_t *user_info, phys_addr_t src_addr, size_t len, int prot, void **pptr, struct vm_operations_struct *vm_ops, void *vm_private_data) Mappe une zone mémoire d'entrée/sortie dans l'espace utilisateur int rtdm_munmap (rtdm_user_info_t *user_info, void *ptr, size_t len) Unmappe une zone mémoire utilisateur void rtdm_printk (const char *format,...) impression « sûre » sur la console à partir de l'espace noyau
utilitaires void * rtdm_malloc (size_t size) alloue une zone mémoire dans un contexte temps réel void rtdm_free (void *ptr) libère la zone mémoire int rtdm_read_user_ok (rtdm_user_info_t *user_info, const void __user *ptr, size_t size) vérifie si un accès en lecture de la zone mémoire est sûr int rtdm_rw_user_ok (rtdm_user_info_t *user_info, const void __user *ptr, size_t size) vérifie si un accès en lecture/écriture de la zone mémoire est sûr int rtdm_copy_from_user(rtdm_user_info_t *user_info, void *dst, const void __user *src, size_t size) copie une zone mémoire utilisateur dans l'espace noyau
utilitaires int rtdm_safe_copy_from_user (rtdm_user_info_t *user_info, void *dst, const void __user *src, size_t size) vérifie si l'accès mémoire est OK en lecture et effectue la copie vers l'espace noyau int rtdm_copy_to_user (rtdm_user_info_t *user_info, void __user *dst, const void *src, size_t size) copie le buffer vers l'espace utilisateur int rtdm_safe_copy_to_user (rtdm_user_info_t *user_info, void __user *dst, const void *src, size_t size) vérifie si l'accès mémoire est OK en lecture/écriture et effectue la copie vers l'espace noyau
utilitaires int rtdm_strncpy_from_user (rtdm_user_info_t *user_info, char *dst, const char __user *src, size_t count) copie une chaîne de caractères de l'espace utilisateur vers l'espace noyau int rtdm_in_rt_context (void) vérifie que la tâche appelante est exécutée dans un contexte temps réel int rtdm_rt_capable (rtdm_user_info_t *user_info) vérifie que la tâche appelante est capable d'être exécutée dans un contexte temps réel
exemples tut01-skeleton-drv.c tut01-skeleton-app.c