Userpace I/O et le noyau Linux

Depuis peu, des développeurs du Kernel ont intégré un système simplifié pour la création de pilotes en mode Utilisateur.

Les pilotes matériels sont en grand majorité (sauf exections comme l’USB et ceux sur Ports série ou parallelle) inclus dans le noyau et s’exécutent avec des droits privilégiés en terme d’accés mémoire et d’instructions autorisées (Ring0 sur les intel-like et mode superviseur sur ARM).
Mes ce code doit être absolument distribué sous une license similaire au code du noyau, ce qui limite le développement de pilotes trés spécifiques pour l’embarqué par exemple.

Le domaine de l’embarqué nécessite d’avoir une meilleure flexibilité dans le dévelopement des pilotes, ceci car les blocs matériels sont trés souvent trés spécifiques et ne respectent jamais des normes ou conventions que l’on trouve dans des plateformes grand public.

Le système se compose d’un micro pilote noyau trés simple qui réponds à une interruption et indique les zones de mémoire physique (zone IO, mémoire vive non utilisée, …) à remapper dans la zone de mémoire virtuelle associée au contexte de l’application en mode utilisateur.
Un exemple de code pour un pilote de plateforme embarquée :

/*
 * Copyright (C) Na-Prod.com 2008
 *
 * Mon pilote UIO simple
 *
 */
#include
 
#include
 
#include
 
#include
 
#include
 
#include
 
#include
 
#include
 
/*
 * Interrupt Handler
 */
static irqreturn_t myuio_irq(int irq, struct uio_info *dev_info)
{
    // write values to registers, ack irq, ...
    return IRQ_HANDLED;
}
static int myuio_probe(struct platform_device *pdev)
{
    struct resource *res;
    struct uio_info *info;
     info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
     if (!info)
         return -ENOMEM;
     res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
     if (!res)
         return -ENODEV;
     info->irq = res->start;
     info->irq_flags = 0;
     info->handler = myuio_irq;
     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
     if (!res)
         return -ENODEV;
     info->mem[0].addr = res->start;
     info->mem[0].size = res->end - res->start;
     info->mem[0].memtype = UIO_MEM_PHYS;
     info->name = "myuio";
     info->version = "0.0.1";
     platform_set_drvdata(pdev, info);
     if(uio_register_device(&pdev->dev, info)){
         printk(KERN_ERR "myuio Unable to register UIO device\n");
         return -ENOMEM;
     }
     printk(KERN_ALERT "myuio device loaded\n");
     return 0;
}
static int myuio_remove(struct platform_device *pdev)
{
    printk(KERN_ALERT "myuio: device unloaded\n");
    platform_set_drvdata(pdev, NULL);
    return 0;
}
static struct platform_driver myuio_platform_driver = {
    .probe = myuio_probe,
    .remove = myuio_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "myuio",
    },
};
static int __init myuio_init(void)
{
     int ret;
     ret = platform_driver_register(&myuio_platform_driver);
     if(!ret)
         printk(KERN_ALERT "myuio: uio driver loaded\n");
     return ret;
}
static void __exit myuio_exit(void)
{
    platform_driver_unregister(&myuio_platform_driver);
    printk(KERN_ALERT "myuio: uio driver unloaded\n");
}
module_init(myuio_init);
module_exit(myuio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Neil Armstrong ");
MODULE_DESCRIPTION("MyUIO Driver");

Le modèle de l’application en mode utilisateur utilise les commandes open/read/mmap/close POSIX pour effectuer les opérations de : ouverture du périphérique, attente d’une interruption avec read, mappage de la mémoire IO avec mmp et fermeture du périphérique.

L’exemple le plus classique est :

uint8_t * iomem;
uint32_t pending;
int fd = open("/dev/uio0", O_RDONLY);
iomem = mmap(0, IO_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
while(1) {
    read(fd, &pending, sizeof(pending));
    // Write to some registers....
    iomem[MY_REG0] = 0xFF;
}
close(fd); //memory is automatically unmapped

Les principaux avantages sont :

  • Pas de distribution de code qui sont spécifiques à une plateforme particulière
  • Pas de « overhead » de copie mémoire entre l’application et le pilote, on peut écrire directement dans les buffers mémoire par exemple
  • le code est trés simple, portable et utilise des commandes POSIX

Les desavantages sont :

  • Peut être utilisé à tort pour distribuer des pilotes sans sources sur PC par exemple
  • Pas de possibilité de faire des pilotes réseau ou block (bien que non nécessaire)
  • Temps de latence bien plus élevés

Enfin, voila une bonne nouvelle pour le monde de l’embarqué !

Des liens !