Qual è la differenza tra un driver di piattaforma Linux e un normale driver di dispositivo?

In precedenza avevo pensato al driver di piattaforma e al normale driver di dispositivo come:

Per favore qualcuno lo spieghi.

I tuoi riferimenti sono buoni ma mancano di una definizione di cosa sia un dispositivo di piattaforma . Ce n’è uno su LWN . Cosa possiamo imparare da questa pagina:

  1. I dispositivi della piattaforma non sono intrinsecamente individuabili , ovvero l’hardware non può dire “Ehi, sono presente!” al software. Esempi tipici sono i dispositivi i2c, kernel/Documentation/i2c/instantiating-devices stati dei kernel/Documentation/i2c/instantiating-devices :

    A differenza dei dispositivi PCI o USB, i dispositivi I2C non sono enumerati a livello hardware (in fase di esecuzione). Invece, il software deve sapere (al momento della compilazione) quali dispositivi sono connessi su ciascun segmento di bus I2C. Quindi USB e PCI non sono dispositivi di piattaforma.

  2. I dispositivi della piattaforma sono associati ai driver mediante nomi corrispondenti ,

  3. I dispositivi della piattaforma devono essere registrati molto presto durante l’avvio del sistema. Perché sono spesso critici per il resto del sistema (piattaforma) e dei suoi driver.

Quindi, in sostanza, la domanda ” è un dispositivo di piattaforma o un dispositivo standard? ” È più una questione di quale bus utilizza . Per lavorare con un particolare dispositivo con piattaforma, devi:

  1. registrare un driver di piattaforma che gestirà questo dispositivo. Dovrebbe definire un nome univoco ,
  2. registrare il dispositivo della piattaforma , definendo lo stesso nome del driver.

Il driver di piattaforma è per quei dispositivi che sono su chip.

Non vero (in teoria, ma vero nella pratica). I dispositivi i2c non sono onChip, ma sono dispositivi di piattaforma perché non sono rilevabili. Inoltre possiamo pensare ai dispositivi onChip che sono normali dispositivi. Esempio: un chip PCI GPU integrato su un moderno processore x86. È rilevabile, quindi non un dispositivo di piattaforma.

I normali driver di dispositivo sono per quelli che sono interfacciati al chip del processore. prima di imbattersi in un driver i2c.

Non vero. Molti dispositivi normali sono interfacciati al processore, ma non tramite un bus i2c. Esempio: un mouse USB.

[EDIT] Nel tuo caso, dai un’occhiata ai drivers/usb/host/ohci-pnx4008.c , che è un dispositivo con piattaforma host controller USB (qui il controller host USB non è individuabile, mentre i dispositivi USB, che si collegheranno ad esso , siamo). È un dispositivo di piattaforma registrato dal file di bordo ( arch/arm/mach-pnx4008/core.c:pnx4008_init ). E all’interno della sua funzione probe, registra il suo dispositivo i2c sul bus con i2c_register_driver . Possiamo dedurre che il chipset del controller Host USB comunica con la CPU tramite un bus i2c.

Perché quell’architettura? Perché da un lato questo dispositivo può essere considerato un dispositivo i2c spoglio che fornisce alcune funzionalità al sistema. D’altra parte, è un dispositivo capace di ospitare USB. È necessario registrarsi allo stack USB ( usb_create_hcd ). Quindi sondare solo i2c sarà insufficiente. Documentation/i2c/instantiating-devices un’occhiata a Documentation/i2c/instantiating-devices .

Esempi di codice del modulo minimo

Forse la differenza diventerà anche più chiara con alcuni esempi concreti.

Esempio di dispositivo platform

Codice:

  • driver upstream
  • Minimo dispositivo virtuale QEMU guidato .
  • Modifiche alle voci DTS sul kernel Linux

Ulteriori note di integrazione su: https://stackoverflow.com/a/44612957/895245

Vedi come:

  • gli indirizzi di registrazione e interrupt sono hardcoded nell’albero del dispositivo e corrispondono alla descrizione della macchina -M versatilepb QEMU -M versatilepb , che rappresenta il SoC
  • non c’è modo di rimuovere l’hardware del dispositivo (poiché fa parte del SoC)
  • il driver corretto è selezionato dalla proprietà dell’albero del dispositivo compatible che corrisponde a platform_driver.name nel driver
  • platform_driver_register è l’interfaccia di registro principale
 #include  #include  #include  #include  #include  #include  #include  #include  #include  MODULE_LICENSE("GPL"); static struct resource res; static unsigned int irq; static void __iomem *map; static irqreturn_t lkmc_irq_handler(int irq, void *dev) { /* TODO this 34 and not 18 as in the DTS, likely the interrupt controller moves it around. * Understand precisely. 34 = 18 + 16. */ pr_info("lkmc_irq_handler irq = %d dev = %llx\n", irq, *(unsigned long long *)dev); /* ACK the IRQ. */ iowrite32(0x9ABCDEF0, map + 4); return IRQ_HANDLED; } static int lkmc_platform_device_probe(struct platform_device *pdev) { int asdf; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; dev_info(dev, "probe\n"); /* Play with our custom poperty. */ if (of_property_read_u32(np, "lkmc-asdf", &asdf) ) { dev_err(dev, "of_property_read_u32\n"); return -EINVAL; } if (asdf != 0x12345678) { dev_err(dev, "asdf = %llx\n", (unsigned long long)asdf); return -EINVAL; } /* IRQ. */ irq = irq_of_parse_and_map(dev->of_node, 0); if (request_irq(irq, lkmc_irq_handler, 0, "lkmc_platform_device", dev) < 0) { dev_err(dev, "request_irq"); return -EINVAL; } dev_info(dev, "irq = %u\n", irq); /* MMIO. */ if (of_address_to_resource(pdev->dev.of_node, 0, &res)) { dev_err(dev, "of_address_to_resource"); return -EINVAL; } if (!request_mem_region(res.start, resource_size(&res), "lkmc_platform_device")) { dev_err(dev, "request_mem_region"); return -EINVAL; } map = of_iomap(pdev->dev.of_node, 0); if (!map) { dev_err(dev, "of_iomap"); return -EINVAL; } dev_info(dev, "res.start = %llx resource_size = %llx\n", (unsigned long long)res.start, (unsigned long long)resource_size(&res)); /* Test MMIO and IRQ. */ iowrite32(0x12345678, map); return 0; } static int lkmc_platform_device_remove(struct platform_device *pdev) { dev_info(&pdev->dev, "remove\n"); free_irq(irq, &pdev->dev); iounmap(map); release_mem_region(res.start, resource_size(&res)); return 0; } static const struct of_device_id of_lkmc_platform_device_match[] = { { .compatible = "lkmc_platform_device", }, {}, }; MODULE_DEVICE_TABLE(of, of_lkmc_platform_device_match); static struct platform_driver lkmc_plaform_driver = { .probe = lkmc_platform_device_probe, .remove = lkmc_platform_device_remove, .driver = { .name = "lkmc_platform_device", .of_match_table = of_lkmc_platform_device_match, .owner = THIS_MODULE, }, }; static int lkmc_platform_device_init(void) { pr_info("lkmc_platform_device_init\n"); return platform_driver_register(&lkmc_plaform_driver); } static void lkmc_platform_device_exit(void) { pr_info("lkmc_platform_device_exit\n"); platform_driver_unregister(&lkmc_plaform_driver); } module_init(lkmc_platform_device_init) module_exit(lkmc_platform_device_exit) 

Esempio di dispositivo PCI non su piattaforma

  • driver upstream
  • Minimo dispositivo virtuale QEMU guidato

Vedi come:

  • gli indirizzi di registrazione e di interruzione vengono allocati dynamicmente dal sistema PCI, non viene utilizzata alcuna struttura del dispositivo
  • il driver corretto viene selezionato dal vendor:device PCI vendor:device ID vendor:device ( QEMU_VENDOR_ID, EDU_DEVICE_ID sull’esempio). Questo viene inserito in ogni dispositivo e i fornitori devono garantire l’unicità.
  • possiamo inserire e rimuovere il dispositivo PCI con device_add edu e device_del edu come possiamo nella vita reale. L’analisi non è automatica, ma può essere eseguita dopo l’avvio con echo 1 > /sys/bus/pci/rescan . Vedi anche: Perché il metodo probe è necessario nei driver di dispositivo Linux oltre a init?
 #include  #include  #include  #include  #include  #include  #include  #include  #define BAR 0 #define CDEV_NAME "lkmc_hw_pci_min" #define EDU_DEVICE_ID 0x11e9 #define QEMU_VENDOR_ID 0x1234 MODULE_LICENSE("GPL"); static struct pci_device_id id_table[] = { { PCI_DEVICE(QEMU_VENDOR_ID, EDU_DEVICE_ID), }, { 0, } }; MODULE_DEVICE_TABLE(pci, id_table); static int major; static struct pci_dev *pdev; static void __iomem *mmio; static struct file_operations fops = { .owner = THIS_MODULE, }; static irqreturn_t irq_handler(int irq, void *dev) { pr_info("irq_handler irq = %d dev = %d\n", irq, *(int *)dev); iowrite32(0, mmio + 4); return IRQ_HANDLED; } static int probe(struct pci_dev *dev, const struct pci_device_id *id) { pr_info("probe\n"); major = register_chrdev(0, CDEV_NAME, &fops); pdev = dev; if (pci_enable_device(dev) < 0) { dev_err(&(pdev->dev), "pci_enable_device\n"); goto error; } if (pci_request_region(dev, BAR, "myregion0")) { dev_err(&(pdev->dev), "pci_request_region\n"); goto error; } mmio = pci_iomap(pdev, BAR, pci_resource_len(pdev, BAR)); pr_info("dev->irq = %u\n", dev->irq); if (request_irq(dev->irq, irq_handler, IRQF_SHARED, "pci_irq_handler0", &major) < 0) { dev_err(&(dev->dev), "request_irq\n"); goto error; } iowrite32(0x12345678, mmio); return 0; error: return 1; } static void remove(struct pci_dev *dev) { pr_info("remove\n"); free_irq(dev->irq, &major); pci_release_region(dev, BAR); unregister_chrdev(major, CDEV_NAME); } static struct pci_driver pci_driver = { .name = CDEV_NAME, .id_table = id_table, .probe = probe, .remove = remove, }; static int myinit(void) { if (pci_register_driver(&pci_driver) < 0) { return 1; } return 0; } static void myexit(void) { pci_unregister_driver(&pci_driver); } module_init(myinit); module_exit(myexit);