Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Programowanie sterowników w Linuksie.

1,061 views

Published on

Wiele portów w jednym kontrolerze Gigabit Ethernet - jak to oprogramować w Linuksie? Powiązanie działania sprzętu ze stosem sieciowym systemu operacyjnego na przykładzie nowego sterownika dodanego do Linuksa v3.17.

Published in: Software
  • Login to see the comments

  • Be the first to like this

Programowanie sterowników w Linuksie.

  1. 1. Programowanie sterowników sieciowych w Linuksie Czy bliskie spotkanie ze sprzętem musi boleć? Marcin Wojtas
  2. 2. Kto mówi? •  Inżynier systemów wbudowanych •  Linux, U-Boot, ARMv7
  3. 3. Kto mówi? •  Inżynier systemów wbudowanych •  Linux, U-Boot, ARMv7 •  PlaJorm device drivers (Ethernet, AHCI, SD/ MMC, USB, Audio i inne)
  4. 4. Kto mówi? •  Inżynier systemów wbudowanych •  Linux, U-Boot, ARMv7 •  PlaJorm device drivers (Ethernet, AHCI, SD/ MMC, USB, Audio i inne) •  … a wcześniej
  5. 5. Plan •  Rzut oka na sprzęt •  Od czego zacząć? •  Wiele portów w jednej instancji sterownika? •  Inicjalizacja •  Obsługa PHY •  Ścieżka TX •  Ścieżka RX •  Wieloprocesorowa pułapka •  Uff, skończone – jak opublikować?
  6. 6. Marvell Armada 375
  7. 7. Marvell Armada 375 •  NAS – np. DS215j
  8. 8. Kontroler sieci GbE – Packet Processor v2
  9. 9. Plan •  Rzut oka na sprzęt •  Od czego zacząć? •  Wiele portów w jednej instancji sterownika? •  Inicjalizacja •  Obsługa PHY •  Ścieżka TX •  Ścieżka RX •  Wieloprocesorowa pułapka •  Uff, skończone – jak opublikować?
  10. 10. Device Tree •  Struktura danych opisująca sprzęt •  Każdemu urządzeniu odpowiada węzeł (node) •  Właściwości (properhes) •  Jeden obraz jądra – wiele maszyn
  11. 11. Device Tree ethernet@f0000 { compatible = "marvell,armada-375-pp2"; reg = <0xf0000 0xa000>, /* Packet Processor regs */ <0xc0000 0x3060>, /* LMS regs */ <0xc4000 0x100>, /* eth0 regs */ <0xc5000 0x100>; /* eth1 regs */ clocks = <&gateclk 3>, <&gateclk 19>; clock-names = "pp_clk", "gop_clk"; status = "okay"; eth0: eth0@c4000 { interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>; port-id = <0>; status = "okay"; }; eth1: eth1@c5000 { interrupts = <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>; port-id = <1>; status = "okay"; }; };
  12. 12. Szkielet •  Ścieżka - drivers/net/ethernet/marvell/mvpp2.c •  Makefile, Kconfig •  Struktury inicjalizacyjne: static const struct of_device_id mvpp2_match[] = { { .compatible = "marvell,armada-375-pp2" }, { } }; MODULE_DEVICE_TABLE(of, mvpp2_match); static struct platform_driver mvpp2_driver = { .probe = mvpp2_probe, .remove = mvpp2_remove, .driver = { .name = MVPP2_DRIVER_NAME, .of_match_table = mvpp2_match, }, };
  13. 13. Plan •  Rzut oka na sprzęt •  Od czego zacząć? •  Wiele portów w jednej instancji sterownika? •  Inicjalizacja •  Obsługa PHY •  Ścieżka TX •  Ścieżka RX •  Wieloprocesorowa pułapka •  Uff, skończone – jak opublikować?
  14. 14. struct net_device •  Reprezentacja portu sieciowego w linuksie •  struct net_device_ops static const struct net_device_ops mvpp2_netdev_ops = { .ndo_open = mvpp2_open, .ndo_stop = mvpp2_stop, .ndo_start_xmit = mvpp2_tx, .ndo_set_rx_mode = mvpp2_set_rx_mode, .ndo_set_mac_address = mvpp2_set_mac_address, .ndo_change_mtu = mvpp2_change_mtu, .ndo_get_stats64 = mvpp2_get_stats64, .ndo_do_ioctl = mvpp2_ioctl, }; •  struct ethtool_ops
  15. 15. Dwa podejścia •  Instancja sterownika osobna dla każdego portu – Częściowo wspólna inicjalizacja – Przekazywanie informacji między instancjami – Dzielenie zasobów •  Jeden sterownik rejestrujący kilka net_device – Bardziej skomplikowana inicjalizacja – Brak globalnych zmiennych
  16. 16. Plan •  Rzut oka na sprzęt •  Od czego zacząć? •  Wiele portów w jednej instancji sterownika? •  Inicjalizacja •  Obsługa PHY •  Ścieżka TX •  Ścieżka RX •  Wieloprocesorowa pułapka •  Uff, skończone – jak opublikować?
  17. 17. Inicjalizacja
  18. 18. mvpp2_probe •  Główna funkcja inicjalizująca sterownik •  Pobranie danych z device tree •  Mapowanie adresów w przestrzeni wirtualnej jądra •  Alokacja pamięci dla urządzenia, uruchomienie zegarów, itp. •  Zasoby zwalniane automatycznie lub w mvpp2_remove struct mvpp2 *priv; [...] res = platform_get_resource(pdev,IORESOURCE_MEM, 1); priv->lms_base = devm_ioremap_resource(&pdev->dev,res); if (IS_ERR(priv->lms_base)) return PTR_ERR(priv->lms_base); priv->pp_clk = devm_clk_get(&pdev->dev, "pp_clk"); if (IS_ERR(priv->pp_clk)) return PTR_ERR(priv->pp_clk); err = clk_prepare_enable(priv->pp_clk); if (err < 0) return err; [...] platform_set_drvdata(pdev, priv);
  19. 19. mvpp2_init •  Niskopoziomowa inicjalizacja wspólnej części HW •  Konfiguracja rejestrów •  Wspólne kolejki deskryptorów TX (aggregated queues) •  Nadanie początkowych ustawień dla Buffer managera, Parsera i Classifiera
  20. 20. Uruchomienie portów i alokacja ich listy struct device_node *dn = pdev->dev.of_node; struct device_node *port_node; struct mvpp2 *priv; int port_count, first_rxq; [...] port_count = of_get_available_child_count(dn); if (port_count == 0) { dev_err(&pdev->dev, "no ports enabledn"); err = -ENODEV; goto err_gop_clk; } priv->port_list = devm_kcalloc(&pdev->dev, port_count, sizeof(struct mvpp2_port *), GFP_KERNEL); if (!priv->port_list) { err = -ENOMEM; goto err_gop_clk; } /* Initialize ports */ first_rxq = 0; for_each_available_child_of_node(dn, port_node) { err = mvpp2_port_probe(pdev, port_node, priv, &first_rxq); if (err < 0) goto err_gop_clk; }
  21. 21. mvpp2_port_probe •  uzupełnienie struktury portu •  alokacja i zarejestrowanie net_device •  inicjalizacja NAPI
  22. 22. Uzupełnienie struktury portu •  Dane z Device Tree (przerwania, informacje o PHY, sprzętowe ID) •  Wskaźnik do wspólnych zasobów (*priv) •  Wskaźnik do net_device
  23. 23. Alokacja i zarejestrowanie net_device struct net_device *dev; struct mvpp2_port *port; [...] dev = alloc_etherdev_mqs(sizeof(struct mvpp2_port), txq_number, rxq_number); if (!dev) return -ENOMEM; [...] dev->netdev_ops = &mvpp2_netdev_ops; dev->ethtool_ops = &mvpp2_eth_tool_ops; [...] SET_NETDEV_DEV(dev, &pdev->dev); [...] port = netdev_priv(dev); [...] err = register_netdev(dev); if (err < 0) { dev_err(&pdev->dev, "failed to register netdevn"); goto err_free_txq_pcpu; } netdev_info(dev, "Using %s mac address %pMn“, mac_from, dev->dev_addr);
  24. 24. Inicjalizacja NAPI •  Przełączanie pomiędzy mechanizmami odpytywania i przerwań sprzętowych •  Włączany jest przy dużym ruchu •  Funkcja inicjalizująca netif_napi_add(dev, &port->napi, mvpp2_poll, NAPI_POLL_WEIGHT); static irqreturn_t mvpp2_isr(int irq, void *dev_id) { struct mvpp2_port *port = (struct mvpp2_port *)dev_id; mvpp2_interrupts_disable(port); napi_schedule(&port->napi); return IRQ_HANDLED; }
  25. 25. mvpp2_port_init •  konfiguracja i uruchomienie dla portu Parsera i Classifiera; •  przypisanie portowi puli buforów Buffer managera; •  niskopoziomowa konfiguracja rejestrów; •  mapowanie i przyporządkowanie portowi kolejek deskryptorów dla RX oraz TX;
  26. 26. Deskryptory i kolejki •  Struktury danych definiowane przez sprzęt •  Nośnik informacji (np. adres bufora pamięci, ID kolejki, komendy, etc.) •  DMA •  Kolejka – bufor cykliczny deskryptorów
  27. 27. Mapowanie kolejek TX w PPv2
  28. 28. Mapowanie kolejek TX w PPv2 for (queue = 0; queue < txq_number; queue++) { int queue_phy_id = mvpp2_txq_phys(port->id, queue); struct mvpp2_tx_queue *txq; txq = devm_kzalloc(dev, sizeof(*txq), GFP_KERNEL); if (!txq) return -ENOMEM; txq->pcpu = alloc_percpu(struct mvpp2_txq_pcpu); if (!txq->pcpu) { err = -ENOMEM; goto err_free_percpu; } txq->id = queue_phy_id; txq->log_id = queue; txq->done_pkts_coal = MVPP2_TXDONE_COAL_PKTS_THRESH; for_each_present_cpu(cpu) { txq_pcpu = per_cpu_ptr(txq->pcpu, cpu); txq_pcpu->cpu = cpu; } port->txqs[queue] = txq; }
  29. 29. Plan •  Rzut oka na sprzęt •  Od czego zacząć? •  Wiele portów w jednej instancji sterownika? •  Inicjalizacja •  Obsługa PHY •  Ścieżka TX •  Ścieżka RX •  Wieloprocesorowa pułapka •  Uff, skończone – jak opublikować?
  30. 30. Obsługa PHY •  Warstwa fizyczna – (R/S)(G)MII (Media Independent Interface) •  Sterowanie PHY przez MDIO (Management Data Input/Output) •  Phylib
  31. 31. Podłączenie sterownika MDIO od strony Device Tree mdio { #address-cells = <1>; #size-cells = <0>; compatible = "marvell,orion-mdio"; reg = <0xc0054 0x4>; clocks = <&gateclk 19>; mdio { phy0: ethernet-phy@0 { reg = <0>; }; phy3: ethernet-phy@3 { reg = <3>; }; }; }; [...] eth0@c4000 { phy = <&phy0>; phy-mode = "rgmii-id"; }; eth1@c5000 { phy = <&phy3>; phy-mode = "gmii"; };
  32. 32. Podłączenie portu do infrastruktury phylib static int mvpp2_phy_connect(struct mvpp2_port *port) { struct phy_device *phy_dev; phy_dev = of_phy_connect(port->dev, port->phy_node, mvpp2_link_event, 0, port->phy_interface); if (!phy_dev) { netdev_err(port->dev, "cannot connect to phyn"); return -ENODEV; } phy_dev->supported &= PHY_GBIT_FEATURES; phy_dev->advertising = phy_dev->supported; port->phy_dev = phy_dev; port->link = 0; port->duplex = 0; port->speed = 0; return 0; }
  33. 33. Plan •  Rzut oka na sprzęt •  Od czego zacząć? •  Wiele portów w jednej instancji sterownika? •  Inicjalizacja •  Obsługa PHY •  Ścieżka TX •  Ścieżka RX •  Wieloprocesorowa pułapka •  Uff, skończone – jak opublikować?
  34. 34. struct sk_buff •  Reprezentacja pakietu sieciowego •  Przechowuje dane (payload) •  Budowa: – head - początek pakietu – data - wskaźnik na początek danych – tail - wskaźnik na koniec danych – end - koniec pakietu – len - wielkość danych
  35. 35. Alokowanie pamięci na deskryptory i inicjalizacja kolejek per-CPU txq->descs = dma_alloc_coherent(port->dev->dev.parent, txq->size * MVPP2_DESC_ALIGNED_SIZE, &txq->descs_phys, GFP_KERNEL); if (!txq->descs) return -ENOMEM; [...] for_each_present_cpu(cpu) { txq_pcpu = per_cpu_ptr(txq->pcpu, cpu); txq_pcpu->size = txq->size; txq_pcpu->tx_skb = kmalloc(txq_pcpu->size * sizeof(*txq_pcpu->tx_skb), GFP_KERNEL); [...] txq_pcpu->count = 0; txq_pcpu->reserved_num = 0; txq_pcpu->txq_put_index = 0; txq_pcpu->txq_get_index = 0; }
  36. 36. Ścieżka TX
  37. 37. Ścieżka TX static int mvpp2_tx(struct sk_buff *skb, struct net_device *dev) { [...] txq_id = skb_get_queue_mapping(skb); txq = port->txqs[txq_id]; txq_pcpu = this_cpu_ptr(txq->pcpu); aggr_txq = &port->priv->aggr_txqs[smp_processor_id()]; frags = skb_shinfo(skb)->nr_frags + 1; tx_desc = mvpp2_txq_next_desc_get(aggr_txq); tx_desc->phys_txq = txq->id; tx_desc->data_size = skb_headlen(skb); buf_phys_addr = dma_map_single(dev->dev.parent, skb->data, tx_desc->data_size, DMA_TO_DEVICE); [...] tx_desc->packet_offset = buf_phys_addr & MVPP2_TX_DESC_ALIGN; tx_desc->buf_phys_addr = buf_phys_addr & ~MVPP2_TX_DESC_ALIGN; [...] /* Enable transmit */ wmb(); mvpp2_aggr_txq_pend_desc_add(port, frags);
  38. 38. Łzy szczęścia…
  39. 39. Plan •  Rzut oka na sprzęt •  Od czego zacząć? •  Wiele portów w jednej instancji sterownika? •  Inicjalizacja •  Obsługa PHY •  Ścieżka TX •  Ścieżka RX •  Wieloprocesorowa pułapka •  Uff, skończone – jak opublikować?
  40. 40. Ścieżka RX
  41. 41. Parser i Classifier •  Classifier – Klasyfikacja na podstawie metadanych – Przepuszczamy wszystko •  Parser – Dwie tablice pomocnicze (SRAM i TCAM) – Iteracyjne przeszukiwanie – Każdy przypadek musi być wspierany (np. TCP over IPv6 do portu 1 o danym MAC adresie)
  42. 42. Buffer manager •  Sprzętowe zarządzanie rezerwacją i zwalnianiem buforów •  8 puli wskaźników do buforów •  Inicjalizacja i alokacja buforów jednorazowa •  Po odebraniu pakietów „napełnianie puli”
  43. 43. Odbiór pakietu w SW •  Kontekst sow_irq •  Pobranie deskryptora z pamięci •  Informacja o puli BM i sk_buff
  44. 44. Odbiór pakietu w SW •  Kontekst sow_irq •  Pobranie deskryptora z pamięci •  Informacja o puli BM i sk_buff •  Aktualizacja nagłówka (skb_reserve) •  Skopiowanie danych do sk_buff (skb_put)
  45. 45. Odbiór pakietu w SW •  Kontekst sow_irq •  Pobranie deskryptora z pamięci •  Informacja o puli BM i sk_buff •  Aktualizacja nagłówka (skb_reserve) •  Skopiowanie danych do sk_buff (skb_put) •  Przekazanie pakietu wyższym warstwom OS (napi_gro_receive) •  Uzupełnienie puli wskaźników
  46. 46. Plan •  Rzut oka na sprzęt •  Od czego zacząć? •  Wiele portów w jednej instancji sterownika? •  Inicjalizacja •  Obsługa PHY •  Ścieżka TX •  Ścieżka RX •  Wieloprocesorowa pułapka •  Uff, skończone – jak opublikować?
  47. 47. O co chodzi? •  Na którym CPU zostało wywołane przerwanie? •  RX czy TX (coalescing)? •  Na którym CPU wykonuje się obsługa przerwania?
  48. 48. Dowody zbrodni •  Są jeszcze na kernel.org •  Odczytywanie rejestrów na obu CPU w poszukiwaniu przyczyny przy użyciu on_each_cpu() w kontekście soft_irq
  49. 49. Dowody zbrodni •  Są jeszcze na kernel.org •  Odczytywanie rejestrów na obu CPU w poszukiwaniu przyczyny przy użyciu on_each_cpu() w kontekście soft_irq JEST ZAKAZANE!!!
  50. 50. Rozwiązanie •  Wyłączyć wieloprocesorową transmisję (A375- Z1) Lub •  Zastosować przerwania HRTIMER – SoAware’owy coalescing w mvpp2_tx – Nie wszystkie pakiety gotowe? – włączamy hmer –  Gwarancja wykonania operacji na właściwym CPU
  51. 51. Plan •  Rzut oka na sprzęt •  Od czego zacząć? •  Wiele portów w jednej instancji sterownika? •  Inicjalizacja •  Obsługa PHY •  Ścieżka TX •  Ścieżka RX •  Wieloprocesorowa pułapka •  Uff, skończone – jak opublikować?
  52. 52. Jak opublikować? •  Kod ma działać i się kompilować •  Styl - scripts/checkpatch.pl •  Dobry commit log
  53. 53. Jak opublikować? •  Kod ma działać i się kompilować •  Styl - scripts/checkpatch.pl •  Dobry commit log •  Wysyłka na listy – MAINTAINTERS przy użyciu git send •  Mnóstwo nowego kodu? – im szybciej, tym lepiej
  54. 54. Jak opublikować? •  Kod ma działać i się kompilować •  Styl - scripts/checkpatch.pl •  Dobry commit log •  Wysyłka na listy – MAINTAINTERS przy użyciu git send •  Mnóstwo nowego kodu? – im szybciej, tym lepiej •  Szczegółowe howto - Documentahon/ SubmizngPatches
  55. 55. Podsumowanie •  Iperf – line rate ~950Mbps •  4 miesiące (ok. 6000 linii, efektywnie 4500 SLOC) •  Dziurawa dokumentacja •  Zmiana wersji procesora w trakcie prac – dużo debugu
  56. 56. Co czytać? •  h•p://elinux.org/images/a/a3/Elce2013-petazzoni-devicetree-for-dummies.pdf - szczegółowe omówienie konceptu Device Tree •  h•ps://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentahon/ devicetree/bindings/net/marvell-pp2.txt •  - dokumentacja opisu w Device Tree dla utworzonego sterownika •  h•ps://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/log/drivers/net/ ethernet/marvell/mvpp2.c - dotychczasowe commity związane ze sterownikiem •  h•ps://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentahon/ networking/phy.txt - opis warstwy abstrakcji PHY dla sterowników sieciowych •  h•p://free-electrons.com/doc/network-drivers.pdf - wprowadzenie do sterowników sieciowych w Linuksie •  h•p://lwn.net/Kernel/LDD3/ - nie do końca aktualne, ale znakomite kompendium wiedzy o sterownikach w Linuksie
  57. 57. Pytania?

×