..
  Copyright (C)  2023  Jeremie Salvi.
  Permission is granted to copy, distribute and/or modify this document
  under the terms of the GNU Free Documentation License, Version 1.3
  or any later version published by the Free Software Foundation;
  with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
  A copy of the license is included in the section entitled "GNU
  Free Documentation License".

Interface ethernet avec QEMU
============================

Y a pas à dire, j'ai bien galéré pour cette partie 😅. Bon, faut dire q'il y avait des choses qui
ne fonctionnent pas. J'y refient à la fin de cette section.

Après pas mal de tatonnements entre la doc et les exemples fournis par espressif, j'ai finalement
réussi à interagir entre firefox et la machine QEMU. Il y a plusieures choses à bien comprendre
dans la façon dont fonctionne l'IDF, notament comment sont gérées les 
`différentes couches du modèle OSI <https://fr.wikipedia.org/wiki/Mod%C3%A8le_OSI>`_.

Principes
---------

dans l'ESP, il y à trois étapes principales pour le réseau :

#. La partie physique (couches physique + mac),
#. La partie network (couches network + transport),
#. La partie link entre physique et network.

La partie physique
~~~~~~~~~~~~~~~~~~

C'est la couche physique plus la couche mac. On la configure, on la démare, et ensuite on
la link à la partie network. Côté wifi, comme le driver de la carte est toujours le même,
on à la fonction ``esp_netif_create_default_wifi_sta()`` qui gère tout, elle créer tout.
Elle gère la partie physique, la link à la partie network, et nous retourne un handle de la 
partie network.

Pour l'ethernet, c'est différent, comme plusieurs extensions ethernet sont disponible pour
l'esp32, il y a plusieurs drivers disponibles, il nous faut donc choisir le bon, et configurer ça à la main.

Dans notre cas, pour QEMU, le driver à utiliser est le driver openeth. (`cf wiki qemu <https://github.com/espressif/qemu/wiki>`_)
La configuration de la partie physique se fait à l'aide des fonctions suivantes :

.. code-block:: cpp
  :linenos:

  // Pysical layer configuration
  eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
  phy_config.phy_addr = -1;
  phy_config.reset_gpio_num = -1;
  phy_config.autonego_timeout_ms = 4000;
  s_phy = esp_eth_phy_new_dp83848(&phy_config);
  // Data link layer configuration
  eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
  mac_config.rx_task_stack_size = 4096;
  mac_config.rx_task_prio = 20;
  s_mac = esp_eth_mac_new_openeth(&mac_config);
  // Install Ethernet driver
  esp_eth_config_t config = ETH_DEFAULT_CONFIG(s_mac, s_phy);
  esp_eth_driver_install(&config, &esp_eth_handle);

La partie network
~~~~~~~~~~~~~~~~~

Can cette partie, on gère la couche ip et la couche transport (TCP/UDP/ICMP). Espressif nous donne pour ça tout un tas
de fonctions, qui sont un wrapper des librairies opensource `lwIP <https://fr.wikipedia.org/wiki/LwIP>`_ (lightweight IP).

Elle nous permet d'initialiser ces couches, de gérer l'attribution de l'adressage ip (statique ou DHCP.)

Cette couche s'implémente avec les fonctions suivantes :

.. code-block:: cpp
  :linenos:

  // Initialize TCP/IP layers
  esp_netif_init();
  esp_event_loop_create_default();
  esp_netif_config_t esp_netif_config = ESP_NETIF_DEFAULT_ETH();
  esp_netif_t *eth_netif = esp_netif_new(&esp_netif_config);

  // Set static ip
  // esp_netif_dhcpc_stop(eth_netif);
  // esp_netif_ip_info_t esp_netif_ip_info;
  // esp_netif_ip_info.ip.addr = ESP_IP4TOUINT32(11,2,168,192);
  // esp_netif_ip_info.netmask.addr = ESP_IP4TOUINT32(0,255,255,255);
  // esp_netif_ip_info.gw.addr = ESP_IP4TOUINT32(1,2,168,192);
  // esp_err_t ret2 = esp_netif_set_ip_info(eth_netif, &esp_netif_ip_info);
  // ESP_ERROR_CHECK(ret2);

Lien entre les deux premières couches
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

On peut configurer les deux parties précédentes dans l'ordre de notre choix, mais c'est seulement une fois que 
ces deux étapes sont réalisées qu'on peut attacher la couche TCP/IP à la couche physique/MAC.

Assez simple à faire, il suffit d'utiliser le code suivant :

.. code-block:: cpp
  :linenos:

  // Bind TCP/IP to Phy+MAC
  esp_eth_netif_glue_handle = esp_eth_new_netif_glue(esp_eth_handle);
  esp_netif_attach(eth_netif, esp_eth_netif_glue_handle);

On peut ensuite démarrer ethernet avec la fonction suivante :

.. code-block:: cpp
  :linenos:

  // Start ethernet
  esp_eth_start(esp_eth_handle);

La partie applicative
~~~~~~~~~~~~~~~~~~~~~

Ici, on aura l'implémentation de notre serveur web. Ça se fait à partir de la librairie ``esp_http_server``
Rien de bien complexe ici quand on connait bien le fonctionnement de ce protocole.

Le code est le suivant :

.. code-block:: cpp
  :linenos:

  esp_err_t get_handler(httpd_req_t *req)
  {
      /* Send a simple response */
      const char resp[] = "URI GET Response\n";
      httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
      ESP_LOGI(TAG, "good");
      return ESP_OK;
  }

  httpd_uri_t uri_get = {
      .uri      = "/uri",
      .method   = HTTP_GET,
      .handler  = get_handler,
      .user_ctx = NULL
  };

Une fois ces trois étapes franchies, on peut démarrer QEMU avec la commande suivante :

.. code-block:: bash
  :linenos:

  /opt/qemu/build/qemu-system-xtensa -m 128 -nographic -machine esp32 \
    -drive file=flash_img.bin,if=mtd,format=raw \
    -net nic,model=open_eth \
    -net user,host=192.168.2.1,net=192.168.2.0/24,hostfwd=tcp::80-:80 \
    -net tap,script=no,downscript=no

On teste avec un simple ``curl http://127.0.0.1/uri`` et on voit que ça marche.

.. image:: ../images/esp32/QEMU_http_response.png
  :width: 600

Ce qui ne marche pas
--------------------

L'IPv6 
~~~~~~

Ne marche pas ni en wifi avec SLAAC, en wifi avec une ip statique,
ni avec QEMU. C'est ce qui m'a le plus fait perdre de temps.
En m'acharnant, je suis tombé sur ça 😤 🤬:

.. code-block:: bash
  :linenos:

  esp_err_t esp_netif_add_ip6_address(esp_netif_t *esp_netif, const esp_ip6_addr_t *addr, uint8_t preference)
  {
      return ESP_ERR_NOT_SUPPORTED;
  }

Il manque clairement du travail du côté d'espressif pour implémenter la couche IPv6. C'est embêtant, ça nous
oblige à faire pleins de routes dans notre box, alors qu'on pourrait tout simplement pinger notre domotique de
l'extérieur avec du v6

.. note:: Il n'y a que la librairie loobback.c qui manque d'implémentation, dans
  components/lwip/lwip/src/core/netif.c on trouve 
  ``netif_add_ip6_address(struct netif *netif, const ip6_addr_t *ip6addr, s8_t *chosen_idx)``
  qui est bel et bien implémentée. J'ai perdu du temps dessus, ça sera un NAT v4 pour l'instant.
  Si à l'avenir j'arrive à faire marcher l'IPv6, je le documenterai.

L'interface tun0 et le bridging
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

auand je lance ma commande QEMU, j'ai deux moyens d'accéder à mon ``http_server`` :
Soit via le ``hostfwd=tcp::80-:80``, soit via ``-net tap,script=no,downscript=no``.

Je fait un ``ip link set tap0 up et brctl addif br0 tap0`` pour que mon tap0 soit
pingable depuis le bridge en 192.168.2.15.

Et bien, un ``curl http://127.0.0.1/uri`` marche très bien, un ``curl http://192.168.2.15/uri``
ne parche pas, on à une erreur ``W (476492) opencores.emac: emac_opencores_isr_handler: RX frame dropped (0x14)``
J'ai essayé de mettre au max les buffers RX, testé toutes les configurations possible et imaginables,
impossible de passer par le bridge sans avoir un frame_drop. Grace au mod débug, j'arrive ici :

``components/esp_eth_src/esp_eth_mac_openeth.c``

.. image:: ../images/esp32/openeth_busy.png
  :width: 600

