| /* | 
 |  * Copyright 2017, Data61 | 
 |  * Commonwealth Scientific and Industrial Research Organisation (CSIRO) | 
 |  * ABN 41 687 119 230. | 
 |  * | 
 |  * This software may be distributed and modified according to the terms of | 
 |  * the GNU General Public License version 2. Note that NO WARRANTY is provided. | 
 |  * See "LICENSE_GPLv2.txt" for details. | 
 |  * | 
 |  * @TAG(DATA61_GPL) | 
 |  */ | 
 |  | 
 | #include <autoconf.h> | 
 | #include <ethdrivers/gen_config.h> | 
 | #include <lwip/gen_config.h> | 
 |  | 
 | #ifdef CONFIG_LIB_LWIP | 
 |  | 
 | #include <ethdrivers/lwip.h> | 
 | #include <ethdrivers/helpers.h> | 
 | #include <string.h> | 
 | #include <lwip/netif.h> | 
 | #include <netif/etharp.h> | 
 | #include <lwip/stats.h> | 
 | #include <lwip/snmp.h> | 
 | #include "debug.h" | 
 |  | 
 | static void initialize_free_bufs(lwip_iface_t *iface) | 
 | { | 
 |     dma_addr_t *dma_bufs = NULL; | 
 |     dma_bufs = malloc(sizeof(dma_addr_t) * CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS); | 
 |     if (!dma_bufs) { | 
 |         goto error; | 
 |     } | 
 |     memset(dma_bufs, 0, sizeof(dma_addr_t) * CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS); | 
 |     iface->bufs = malloc(sizeof(dma_addr_t *) * CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS); | 
 |     if (!iface->bufs) { | 
 |         goto error; | 
 |     } | 
 |     for (int i = 0; i < CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS; i++) { | 
 |         dma_bufs[i] = dma_alloc_pin(&iface->dma_man, CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE, 1, | 
 |                                     iface->driver.dma_alignment); | 
 |         if (!dma_bufs[i].phys) { | 
 |             goto error; | 
 |         } | 
 |         ps_dma_cache_clean_invalidate(&iface->dma_man, dma_bufs[i].virt, CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE); | 
 |         iface->bufs[i] = &dma_bufs[i]; | 
 |     } | 
 |     iface->num_free_bufs = CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS; | 
 |     return; | 
 | error: | 
 |     if (iface->bufs) { | 
 |         free(iface->bufs); | 
 |     } | 
 |     if (dma_bufs) { | 
 |         for (int i = 0; i < CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS; i++) { | 
 |             if (dma_bufs[i].virt) { | 
 |                 dma_unpin_free(&iface->dma_man, dma_bufs[i].virt, CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE); | 
 |             } | 
 |         } | 
 |         free(dma_bufs); | 
 |     } | 
 |     iface->bufs = NULL; | 
 | } | 
 |  | 
 | static uintptr_t lwip_allocate_rx_buf(void *iface, size_t buf_size, void **cookie) | 
 | { | 
 |     lwip_iface_t *lwip_iface = (lwip_iface_t *)iface; | 
 |     if (buf_size > CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE) { | 
 |         LOG_ERROR("Requested RX buffer of size %zu which can never be fullfilled by preallocated buffers of size %d", buf_size, | 
 |                   CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE); | 
 |         return 0; | 
 |     } | 
 |     if (lwip_iface->num_free_bufs == 0) { | 
 |         if (!lwip_iface->bufs) { | 
 |             initialize_free_bufs(lwip_iface); | 
 |             if (!lwip_iface->bufs) { | 
 |                 LOG_ERROR("Failed lazy initialization of preallocated free buffers"); | 
 |                 return 0; | 
 |             } | 
 |         } else { | 
 |             return 0; | 
 |         } | 
 |     } | 
 |     lwip_iface->num_free_bufs--; | 
 |     dma_addr_t *buf = lwip_iface->bufs[lwip_iface->num_free_bufs]; | 
 |     ps_dma_cache_invalidate(&lwip_iface->dma_man, buf->virt, buf_size); | 
 |     *cookie = (void *)buf; | 
 |     return buf->phys; | 
 | } | 
 |  | 
 | static void lwip_tx_complete(void *iface, void *cookie) | 
 | { | 
 |     lwip_iface_t *lwip_iface = (lwip_iface_t *)iface; | 
 |     lwip_iface->bufs[lwip_iface->num_free_bufs] = cookie; | 
 |     lwip_iface->num_free_bufs++; | 
 | } | 
 |  | 
 | static void lwip_rx_complete(void *iface, unsigned int num_bufs, void **cookies, unsigned int *lens) | 
 | { | 
 |     struct pbuf *p; | 
 |     int len; | 
 |     lwip_iface_t *lwip_iface = (lwip_iface_t *)iface; | 
 |     int i; | 
 |     len = 0; | 
 |     for (i = 0; i < num_bufs; i++) { | 
 |         ps_dma_cache_invalidate(&lwip_iface->dma_man, ((dma_addr_t *)cookies[i])->virt, lens[i]); | 
 |         len += lens[i]; | 
 |     } | 
 | #if ETH_PAD_SIZE | 
 |     len += ETH_PAD_SIZE; /* allow room for Ethernet padding */ | 
 | #endif | 
 |     /* Get a buffer from the pool */ | 
 |     p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); | 
 |     if (p == NULL) { | 
 |         for (i = 0; i < num_bufs; i++) { | 
 |             lwip_tx_complete(iface, cookies[i]); | 
 |         } | 
 |         return; | 
 |     } | 
 |  | 
 | #if ETH_PAD_SIZE | 
 |     pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */ | 
 |     len -= ETH_PAD_SIZE; | 
 | #endif | 
 |  | 
 |     /* fill the pbuf chain */ | 
 |     struct pbuf *q = p; | 
 |     unsigned int copied = 0; | 
 |     unsigned int buf = 0; | 
 |     unsigned int buf_done = 0; | 
 |     unsigned int pbuf_done = 0; | 
 |     while (copied < len) { | 
 |         unsigned int next = MIN(q->len - pbuf_done, lens[buf] - buf_done); | 
 |         memcpy(q->payload + pbuf_done, ((dma_addr_t *)cookies[buf])->virt + buf_done, next); | 
 |         buf_done += next; | 
 |         pbuf_done += next; | 
 |         copied += next; | 
 |         if (buf_done == lens[buf]) { | 
 |             buf++; | 
 |             buf_done = 0; | 
 |         } | 
 |         if (pbuf_done == q->len) { | 
 |             q = q->next; | 
 |             pbuf_done = 0; | 
 |         } | 
 |     } | 
 |  | 
 | //    PKT_DEBUG(printf("Receiving packet\n")); | 
 | //    PKT_DEBUG(print_packet(COL_RX, p->payload, len)); | 
 |  | 
 | #if ETH_PAD_SIZE | 
 |     pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ | 
 | #endif | 
 |     LINK_STATS_INC(link.recv); | 
 |  | 
 |     for (i = 0; i < num_bufs; i++) { | 
 |         lwip_tx_complete(iface, cookies[i]); | 
 |     } | 
 |  | 
 |     struct eth_hdr *ethhdr; | 
 |     ethhdr = p->payload; | 
 |  | 
 |     switch (htons(ethhdr->type)) { | 
 |     /* IP or ARP packet? */ | 
 |     case ETHTYPE_IP: | 
 |     case ETHTYPE_ARP: | 
 | #if PPPOE_SUPPORT | 
 |     /* PPPoE packet? */ | 
 |     case ETHTYPE_PPPOEDISC: | 
 |     case ETHTYPE_PPPOE: | 
 | #endif /* PPPOE_SUPPORT */ | 
 |         /* full packet send to tcpip_thread to process */ | 
 |         if (lwip_iface->netif->input(p, lwip_iface->netif) != ERR_OK) { | 
 |             LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n")); | 
 |             pbuf_free(p); | 
 |             p = NULL; | 
 |         } | 
 |         break; | 
 |  | 
 |     default: | 
 |         pbuf_free(p); | 
 |         break; | 
 |     } | 
 | } | 
 |  | 
 | static err_t ethif_link_output(struct netif *netif, struct pbuf *p) | 
 | { | 
 |     lwip_iface_t *iface = (lwip_iface_t *)netif->state; | 
 |     dma_addr_t buf; | 
 |     struct pbuf *q; | 
 |     int status; | 
 |  | 
 | #if ETH_PAD_SIZE | 
 |     pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */ | 
 | #endif | 
 |  | 
 |     if (p->tot_len > CONFIG_LIB_ETHDRIVER_PREALLOCATED_BUF_SIZE) { | 
 |         return ERR_MEM; | 
 |     } | 
 |     if (iface->num_free_bufs == 0) { | 
 |         return ERR_MEM; | 
 |     } | 
 |     iface->num_free_bufs--; | 
 |     dma_addr_t *orig_buf = iface->bufs[iface->num_free_bufs]; | 
 |     buf = *orig_buf; | 
 |  | 
 |     char *pkt_pos = (char *)buf.virt; | 
 |     for (q = p; q != NULL; q = q->next) { | 
 |         memcpy(pkt_pos, q->payload, q->len); | 
 |         pkt_pos += q->len; | 
 |     } | 
 |     ps_dma_cache_clean(&iface->dma_man, buf.virt, p->tot_len); | 
 | //    PKT_DEBUG(cprintf(COL_TX, "Sending packet")); | 
 | //    PKT_DEBUG(print_packet(COL_TX, (void*)buf.virt, p->tot_len)); | 
 |  | 
 | #if ETH_PAD_SIZE | 
 |     pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ | 
 | #endif | 
 |  | 
 |     unsigned int length = p->tot_len; | 
 |     status = iface->driver.i_fn.raw_tx(&iface->driver, 1, &buf.phys, &length, orig_buf); | 
 |     switch (status) { | 
 |     case ETHIF_TX_FAILED: | 
 |         lwip_tx_complete(iface, orig_buf); | 
 |         return ERR_WOULDBLOCK; | 
 |     case ETHIF_TX_COMPLETE: | 
 |         lwip_tx_complete(iface, orig_buf); | 
 |     case ETHIF_TX_ENQUEUED: | 
 |         break; | 
 |     } | 
 |  | 
 |     LINK_STATS_INC(link.xmit); | 
 |  | 
 |     return ERR_OK; | 
 | } | 
 |  | 
 | static uintptr_t lwip_pbuf_allocate_rx_buf(void *iface, size_t buf_size, void **cookie) | 
 | { | 
 |     lwip_iface_t *lwip_iface = (lwip_iface_t *)iface; | 
 | #if ETH_PAD_SIZE | 
 |     buf_size += ETH_PAD_SIZE; /* allow room for Ethernet padding */ | 
 | #endif | 
 |     /* add space for alignment */ | 
 |     buf_size += lwip_iface->driver.dma_alignment; | 
 |     struct pbuf *p = pbuf_alloc(PBUF_RAW, buf_size, PBUF_RAM); | 
 |     if (!p) { | 
 |         return 0; | 
 |     } | 
 |     /* we cannot support chained pbufs when doing this */ | 
 |     if (p->next) { | 
 |         pbuf_free(p); | 
 |         return 0; | 
 |     } | 
 | #if ETH_PAD_SIZE | 
 |     pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */ | 
 | #endif | 
 |     uintptr_t new_payload = (uintptr_t)p->payload; | 
 |     /* round up to dma_alignment */ | 
 |     new_payload = ROUND_UP(new_payload, lwip_iface->driver.dma_alignment); | 
 |     pbuf_header(p, -(new_payload - (uintptr_t)p->payload)); | 
 |     uintptr_t phys = ps_dma_pin(&lwip_iface->dma_man, p->payload, buf_size); | 
 |     if (!phys) { | 
 |         pbuf_free(p); | 
 |         return 0; | 
 |     } | 
 |     ps_dma_cache_invalidate(&lwip_iface->dma_man, p->payload, buf_size); | 
 |     *cookie = p; | 
 |     return phys; | 
 | } | 
 |  | 
 | static void lwip_pbuf_tx_complete(void *iface, void *cookie) | 
 | { | 
 |     lwip_iface_t *lwip_iface = (lwip_iface_t *)iface; | 
 |     struct pbuf *p = (struct pbuf *)cookie; | 
 |     for (; p; p = p->next) { | 
 |         uintptr_t loc = (uintptr_t)p->payload; | 
 |         uintptr_t end = (uintptr_t)p->payload + p->len; | 
 |         while (loc < end) { | 
 |             uintptr_t next = ROUND_UP(loc + 1, PAGE_SIZE_4K); | 
 |             if (next > end) { | 
 |                 next = end; | 
 |             } | 
 |             ps_dma_unpin(&lwip_iface->dma_man, (void *)loc, next - loc); | 
 |             loc = next; | 
 |         } | 
 |     } | 
 |     pbuf_free(cookie); | 
 | } | 
 |  | 
 | static void lwip_pbuf_rx_complete(void *iface, unsigned int num_bufs, void **cookies, unsigned int *lens) | 
 | { | 
 |     struct pbuf *p = NULL; | 
 |     int i; | 
 |     lwip_iface_t *lwip_iface = (lwip_iface_t *)iface; | 
 |  | 
 |     assert(num_bufs > 0); | 
 |     /* staple all the bufs together, do it in reverse order for efficiency | 
 |      * of traversing pbuf chains */ | 
 |     for (i = num_bufs - 1; i >= 0; i--) { | 
 |         struct pbuf *q = (struct pbuf *)cookies[i]; | 
 |         ps_dma_cache_invalidate(&lwip_iface->dma_man, q->payload, lens[i]); | 
 |         pbuf_realloc(q, lens[i]); | 
 |         if (p) { | 
 |             pbuf_cat(q, p); | 
 |         } | 
 |         p = q; | 
 |     } | 
 |  | 
 | #if ETH_PAD_SIZE | 
 |     pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ | 
 | #endif | 
 |  | 
 |     LINK_STATS_INC(link.recv); | 
 |  | 
 |     struct eth_hdr *ethhdr; | 
 |     ethhdr = p->payload; | 
 |  | 
 |     switch (htons(ethhdr->type)) { | 
 |     /* IP or ARP packet? */ | 
 |     case ETHTYPE_IP: | 
 |     case ETHTYPE_ARP: | 
 | #if PPPOE_SUPPORT | 
 |     /* PPPoE packet? */ | 
 |     case ETHTYPE_PPPOEDISC: | 
 |     case ETHTYPE_PPPOE: | 
 | #endif /* PPPOE_SUPPORT */ | 
 |         /* full packet send to tcpip_thread to process */ | 
 |         if (lwip_iface->netif->input(p, lwip_iface->netif) != ERR_OK) { | 
 |             LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n")); | 
 |             LOG_INFO("failed to input\n"); | 
 |             pbuf_free(p); | 
 |         } | 
 |         break; | 
 |  | 
 |     default: | 
 |         pbuf_free(p); | 
 |         break; | 
 |     } | 
 | } | 
 |  | 
 | static err_t ethif_pbuf_link_output(struct netif *netif, struct pbuf *p) | 
 | { | 
 |     lwip_iface_t *iface = (lwip_iface_t *)netif->state; | 
 |     struct pbuf *q; | 
 |     int status; | 
 |  | 
 |     /* grab a reference to the pbuf */ | 
 |     pbuf_ref(p); | 
 |  | 
 | #if ETH_PAD_SIZE | 
 |     pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */ | 
 | #endif | 
 |     int max_frames = 0; | 
 |  | 
 |     /* work out how many pieces this buffer could potentially take up */ | 
 |     for (q = p; q; q = q->next) { | 
 |         uintptr_t base = PAGE_ALIGN_4K((uintptr_t)q->payload); | 
 |         uintptr_t top = PAGE_ALIGN_4K((uintptr_t)q->payload + q->len - 1); | 
 |         max_frames += ((top - base) / PAGE_SIZE_4K) + 1; | 
 |     } | 
 |     int num_frames = 0; | 
 |     unsigned int lengths[max_frames]; | 
 |     uintptr_t phys[max_frames]; | 
 |     for (q = p; q; q = q->next) { | 
 |         uintptr_t loc = (uintptr_t)q->payload; | 
 |         uintptr_t end = (uintptr_t)q->payload + q->len; | 
 |         while (loc < end) { | 
 |             uintptr_t next = ROUND_UP(loc + 1, PAGE_SIZE_4K); | 
 |             if (next > end) { | 
 |                 next = end; | 
 |             } | 
 |             lengths[num_frames] = next - loc; | 
 |             phys[num_frames] = ps_dma_pin(&iface->dma_man, (void *)loc, lengths[num_frames]); | 
 |             ps_dma_cache_clean(&iface->dma_man, (void *)loc, lengths[num_frames]); | 
 |             assert(phys[num_frames]); | 
 |             num_frames++; | 
 |             loc = next; | 
 |         } | 
 |     } | 
 |  | 
 | #if ETH_PAD_SIZE | 
 |     pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ | 
 | #endif | 
 |  | 
 |     status = iface->driver.i_fn.raw_tx(&iface->driver, num_frames, phys, lengths, p); | 
 |     switch (status) { | 
 |     case ETHIF_TX_FAILED: | 
 |         lwip_pbuf_tx_complete(iface, p); | 
 |         return ERR_WOULDBLOCK; | 
 |     case ETHIF_TX_COMPLETE: | 
 |         lwip_pbuf_tx_complete(iface, p); | 
 |     case ETHIF_TX_ENQUEUED: | 
 |         break; | 
 |     } | 
 |  | 
 |     LINK_STATS_INC(link.xmit); | 
 |  | 
 |     return ERR_OK; | 
 | } | 
 |  | 
 | static struct raw_iface_callbacks lwip_prealloc_callbacks = { | 
 |     .tx_complete = lwip_tx_complete, | 
 |     .rx_complete = lwip_rx_complete, | 
 |     .allocate_rx_buf = lwip_allocate_rx_buf | 
 | }; | 
 |  | 
 | static struct raw_iface_callbacks lwip_pbuf_callbacks = { | 
 |     .tx_complete = lwip_pbuf_tx_complete, | 
 |     .rx_complete = lwip_pbuf_rx_complete, | 
 |     .allocate_rx_buf = lwip_pbuf_allocate_rx_buf | 
 | }; | 
 |  | 
 | static err_t ethif_init(struct netif *netif) | 
 | { | 
 |     if (netif -> state == NULL) { | 
 |         return ERR_ARG; | 
 |     } | 
 |  | 
 |     lwip_iface_t *iface = (lwip_iface_t *)netif->state; | 
 |     int mtu; | 
 |     iface->driver.i_fn.low_level_init(&iface->driver, netif->hwaddr, &mtu); | 
 |     netif->mtu = mtu; | 
 |  | 
 |     netif->hwaddr_len = ETHARP_HWADDR_LEN; | 
 |     netif->output = etharp_output; | 
 |     if (iface->bufs == NULL) { | 
 |         netif->linkoutput = ethif_pbuf_link_output; | 
 |     } else { | 
 |         netif->linkoutput = ethif_link_output; | 
 |     } | 
 |  | 
 |     NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, | 
 |                     LINK_SPEED_OF_YOUR_NETIF_IN_BPS); | 
 |  | 
 |     netif -> flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | | 
 |                      NETIF_FLAG_LINK_UP | NETIF_FLAG_IGMP; | 
 |  | 
 |     iface->netif = netif; | 
 |     return ERR_OK; | 
 | } | 
 |  | 
 | lwip_iface_t *ethif_new_lwip_driver_no_malloc(ps_io_ops_t io_ops, ps_dma_man_t *pbuf_dma, ethif_driver_init driver, | 
 |                                               void *driver_config, lwip_iface_t *iface) | 
 | { | 
 |     memset(iface, 0, sizeof(*iface)); | 
 |     iface->driver.cb_cookie = iface; | 
 |     if (pbuf_dma) { | 
 |         iface->driver.i_cb = lwip_pbuf_callbacks; | 
 |         iface->dma_man = *pbuf_dma; | 
 |     } else { | 
 |         iface->driver.i_cb = lwip_prealloc_callbacks; | 
 |         iface->dma_man = io_ops.dma_manager; | 
 |     } | 
 |     int err; | 
 |     err = driver(&iface->driver, io_ops, driver_config); | 
 |     if (err) { | 
 |         goto error; | 
 |     } | 
 |     /* if the driver did not already cause it to happen, allocate the preallocated buffers */ | 
 |     if (!pbuf_dma && !iface->bufs) { | 
 |         initialize_free_bufs(iface); | 
 |         if (iface->bufs == NULL) { | 
 |             LOG_ERROR("Fault preallocating bufs"); | 
 |             goto error; | 
 |         } | 
 |     } | 
 |     iface->ethif_init = ethif_init; | 
 |     return iface; | 
 | error: | 
 |     return NULL; | 
 | } | 
 |  | 
 | lwip_iface_t *ethif_new_lwip_driver(ps_io_ops_t io_ops, ps_dma_man_t *pbuf_dma, ethif_driver_init driver, | 
 |                                     void *driver_config) | 
 | { | 
 |     lwip_iface_t *ret; | 
 |     lwip_iface_t *iface = malloc(sizeof(*iface)); | 
 |     if (!iface) { | 
 |         LOG_ERROR("Failed to malloc"); | 
 |         return NULL; | 
 |     } | 
 |     ret = ethif_new_lwip_driver_no_malloc(io_ops, pbuf_dma, driver, driver_config, iface); | 
 |     if (!ret) { | 
 |         free(iface); | 
 |     } | 
 |     return ret; | 
 | } | 
 |  | 
 | #endif /* CONFIG_LIB_LWIP */ |