diff --git a/examples/echo_server/board/imx8mm_evk/echo_server.system b/examples/echo_server/board/imx8mm_evk/echo_server.system
index 264e4e867..f4c9dd681 100644
--- a/examples/echo_server/board/imx8mm_evk/echo_server.system
+++ b/examples/echo_server/board/imx8mm_evk/echo_server.system
@@ -164,7 +164,7 @@
-
+
@@ -181,7 +181,7 @@
-
+
diff --git a/examples/echo_server/board/maaxboard/echo_server.system b/examples/echo_server/board/maaxboard/echo_server.system
index 05e1d7684..a26891f98 100644
--- a/examples/echo_server/board/maaxboard/echo_server.system
+++ b/examples/echo_server/board/maaxboard/echo_server.system
@@ -164,7 +164,7 @@
-
+
@@ -181,7 +181,7 @@
-
+
diff --git a/examples/echo_server/board/odroidc4/echo_server.system b/examples/echo_server/board/odroidc4/echo_server.system
index 74998d981..001b4f9c8 100644
--- a/examples/echo_server/board/odroidc4/echo_server.system
+++ b/examples/echo_server/board/odroidc4/echo_server.system
@@ -164,7 +164,7 @@
-
+
@@ -181,7 +181,7 @@
-
+
diff --git a/examples/echo_server/board/qemu_virt_aarch64/echo_server.system b/examples/echo_server/board/qemu_virt_aarch64/echo_server.system
index 1cacbb3c9..8a859f46a 100644
--- a/examples/echo_server/board/qemu_virt_aarch64/echo_server.system
+++ b/examples/echo_server/board/qemu_virt_aarch64/echo_server.system
@@ -163,7 +163,7 @@
-
+
@@ -180,7 +180,7 @@
-
+
diff --git a/examples/echo_server/echo.c b/examples/echo_server/echo.c
new file mode 100644
index 000000000..6f9b3ffa0
--- /dev/null
+++ b/examples/echo_server/echo.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2022, UNSW
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "lwip/pbuf.h"
+
+#include "echo.h"
+
+#define SERIAL_TX_CH 0
+#define TIMER_CH 1
+#define RX_CH 2
+#define TX_CH 3
+
+char *serial_tx_data;
+serial_queue_t *serial_tx_queue;
+serial_queue_handle_t serial_tx_queue_handle;
+
+net_queue_t *rx_free;
+net_queue_t *rx_active;
+net_queue_t *tx_free;
+net_queue_t *tx_active;
+uintptr_t rx_buffer_data_region;
+uintptr_t tx_buffer_data_region;
+
+net_queue_handle_t net_rx_handle;
+net_queue_handle_t net_tx_handle;
+
+#define LWIP_TICK_MS 100
+
+struct pbuf *head;
+struct pbuf *tail;
+
+/**
+ * Netif status callback function that output's client's Microkit name and
+ * obtained IP address.
+ *
+ * @param ip_addr ip address of the client.
+ */
+void netif_status_callback(char *ip_addr)
+{
+ sddf_printf("DHCP request finished, IP address for netif %s is: %s\n", microkit_name, ip_addr);
+}
+
+/**
+ * Sets a timeout for the next lwip tick.
+ */
+void set_timeout(void)
+{
+ sddf_timer_set_timeout(TIMER_CH, LWIP_TICK_MS * NS_IN_MS);
+}
+
+/**
+ * Stores a pbuf to be transmitted upon available transmit buffers.
+ *
+ * @param p pbuf to be stored.
+ */
+net_sddf_err_t enqueue_pbufs(struct pbuf *p)
+{
+ /* Indicate to the tx virt that we wish to be notified about free tx buffers */
+ net_request_signal_free(&net_tx_handle);
+
+ if (head == NULL) {
+ head = p;
+ } else {
+ tail->next_chain = p;
+ }
+ tail = p;
+
+ /* Increment reference count to ensure this pbuf is not freed by lwip */
+ pbuf_ref(p);
+
+ return SDDF_LWIP_ERR_OK;
+}
+
+void transmit(void)
+{
+ bool reprocess = true;
+ while (reprocess) {
+ while (head != NULL && !net_queue_empty_free(&net_tx_handle)) {
+ net_sddf_err_t err = sddf_lwip_transmit_pbuf(head);
+ if (err == SDDF_LWIP_ERR_PBUF) {
+ sddf_dprintf("LWIP|ERROR: attempted to send a packet of size %u > BUFFER SIZE %u\n", head->tot_len,
+ NET_BUFFER_SIZE);
+ } else if (err != SDDF_LWIP_ERR_OK) {
+ sddf_dprintf("LWIP|ERROR: unkown error when trying to send pbuf %p\n", head);
+ }
+
+ struct pbuf *temp = head;
+ head = temp->next_chain;
+ if (head == NULL) {
+ tail = NULL;
+ }
+ pbuf_free(temp);
+ }
+
+ /* Only request a signal if there are more pending pbufs to send */
+ if (head == NULL || !net_queue_empty_free(&net_tx_handle)) {
+ net_cancel_signal_free(&net_tx_handle);
+ } else {
+ net_request_signal_free(&net_tx_handle);
+ }
+ reprocess = false;
+
+ if (head != NULL && !net_queue_empty_free(&net_tx_handle)) {
+ net_cancel_signal_free(&net_tx_handle);
+ reprocess = true;
+ }
+ }
+}
+
+void init(void)
+{
+ serial_cli_queue_init_sys(microkit_name, NULL, NULL, NULL, &serial_tx_queue_handle, serial_tx_queue,
+ serial_tx_data);
+ serial_putchar_init(SERIAL_TX_CH, &serial_tx_queue_handle);
+
+ size_t rx_size, tx_size;
+ net_cli_queue_size(microkit_name, &rx_size, &tx_size);
+ net_queue_init(&net_rx_handle, rx_free, rx_active, rx_size);
+ net_queue_init(&net_tx_handle, tx_free, tx_active, tx_size);
+ net_buffers_init(&net_tx_handle, 0);
+
+ sddf_lwip_init(net_rx_handle, net_tx_handle, RX_CH, TX_CH, rx_buffer_data_region, tx_buffer_data_region, TIMER_CH,
+ net_cli_mac_addr(microkit_name), NULL, netif_status_callback, enqueue_pbufs);
+ set_timeout();
+
+ setup_udp_socket();
+ setup_utilization_socket();
+ setup_tcp_socket();
+
+ sddf_lwip_maybe_notify();
+}
+
+void notified(microkit_channel ch)
+{
+ switch (ch) {
+ case RX_CH:
+ sddf_lwip_process_rx();
+ break;
+ case TIMER_CH:
+ sddf_lwip_process_timeout();
+ set_timeout();
+ break;
+ case TX_CH:
+ transmit();
+ break;
+ case SERIAL_TX_CH:
+ break;
+ default:
+ sddf_dprintf("LWIP|LOG: received notification on unexpected channel: %u\n", ch);
+ break;
+ }
+
+ sddf_lwip_maybe_notify();
+}
diff --git a/examples/echo_server/echo.h b/examples/echo_server/echo.h
index f514c979c..066e22365 100644
--- a/examples/echo_server/echo.h
+++ b/examples/echo_server/echo.h
@@ -9,9 +9,6 @@
#define UTILIZATION_PORT 1236
#define TCP_ECHO_PORT 1237
-#define LINK_SPEED 1000000000 // Gigabit
-#define ETHER_MTU 1500
-
int setup_udp_socket(void);
int setup_utilization_socket(void);
int setup_tcp_socket(void);
diff --git a/examples/echo_server/echo.mk b/examples/echo_server/echo.mk
index bf5e3a67c..7240089b8 100644
--- a/examples/echo_server/echo.mk
+++ b/examples/echo_server/echo.mk
@@ -7,17 +7,17 @@
QEMU := qemu-system-aarch64
MICROKIT_TOOL ?= $(MICROKIT_SDK)/bin/microkit
-ECHO_SERVER:=${SDDF}/examples/echo_server
-LWIPDIR:=network/ipstacks/lwip/src
-BENCHMARK:=$(SDDF)/benchmark
-UTIL:=$(SDDF)/util
-ETHERNET_DRIVER:=$(SDDF)/drivers/network/$(DRIV_DIR)
-ETHERNET_CONFIG_INCLUDE:=${ECHO_SERVER}/include/ethernet_config
+ECHO_SERVER := ${SDDF}/examples/echo_server
+LWIPDIR := network/ipstacks/lwip/src
+BENCHMARK := $(SDDF)/benchmark
+UTIL := $(SDDF)/util
+ETHERNET_DRIVER := $(SDDF)/drivers/network/$(DRIV_DIR)
+ETHERNET_CONFIG_INCLUDE := ${ECHO_SERVER}/include/ethernet_config
SERIAL_COMPONENTS := $(SDDF)/serial/components
UART_DRIVER := $(SDDF)/drivers/serial/$(UART_DRIV_DIR)
-SERIAL_CONFIG_INCLUDE:=${ECHO_SERVER}/include/serial_config
+SERIAL_CONFIG_INCLUDE := ${ECHO_SERVER}/include/serial_config
TIMER_DRIVER:=$(SDDF)/drivers/timer/$(TIMER_DRV_DIR)
-NETWORK_COMPONENTS:=$(SDDF)/network/components
+NETWORK_COMPONENTS := $(SDDF)/network/components
BOARD_DIR := $(MICROKIT_SDK)/board/$(MICROKIT_BOARD)/$(MICROKIT_CONFIG)
SYSTEM_FILE := ${ECHO_SERVER}/board/$(MICROKIT_BOARD)/echo_server.system
@@ -26,7 +26,7 @@ REPORT_FILE := report.txt
vpath %.c ${SDDF} ${ECHO_SERVER}
-IMAGES := eth_driver.elf lwip.elf benchmark.elf idle.elf network_virt_rx.elf\
+IMAGES := eth_driver.elf echo.elf benchmark.elf idle.elf network_virt_rx.elf\
network_virt_tx.elf copy.elf timer_driver.elf uart_driver.elf serial_virt_tx.elf
CFLAGS := -mcpu=$(CPU) \
@@ -41,14 +41,13 @@ CFLAGS := -mcpu=$(CPU) \
-I${ETHERNET_CONFIG_INCLUDE} \
-I$(SERIAL_CONFIG_INCLUDE) \
-I${SDDF}/$(LWIPDIR)/include \
- -I${SDDF}/$(LWIPDIR)/include/ipv4 \
-MD \
-MP
LDFLAGS := -L$(BOARD_DIR)/lib -L${LIBC}
LIBS := --start-group -lmicrokit -Tmicrokit.ld -lc libsddf_util_debug.a --end-group
-CHECK_FLAGS_BOARD_MD5:=.board_cflags-$(shell echo -- ${CFLAGS} ${BOARD} ${MICROKIT_CONFIG} | shasum | sed 's/ *-//')
+CHECK_FLAGS_BOARD_MD5 := .board_cflags-$(shell echo -- ${CFLAGS} ${BOARD} ${MICROKIT_CONFIG} | shasum | sed 's/ *-//')
${CHECK_FLAGS_BOARD_MD5}:
-rm -f .board_cflags-*
@@ -64,21 +63,20 @@ include ${SDDF}/${LWIPDIR}/Filelists.mk
NETIFFILES:=$(LWIPDIR)/netif/ethernet.c
# LWIPFILES: All the above.
-LWIPFILES=lwip.c $(COREFILES) $(CORE4FILES) $(NETIFFILES)
-LWIP_OBJS := $(LWIPFILES:.c=.o) lwip.o utilization_socket.o \
+LWIPFILES := $(COREFILES) $(CORE4FILES) $(NETIFFILES)
+ECHO_OBJS := $(LWIPFILES:.c=.o) echo.o utilization_socket.o \
udp_echo_socket.o tcp_echo_socket.o
-OBJS := $(LWIP_OBJS)
-DEPS := $(filter %.d,$(OBJS:.o=.d))
+DEPS := $(ECHO_OBJS:.o=.d)
all: loader.img
-${LWIP_OBJS}: ${CHECK_FLAGS_BOARD_MD5}
-lwip.elf: $(LWIP_OBJS) libsddf_util.a
+${ECHO_OBJS}: ${CHECK_FLAGS_BOARD_MD5}
+echo.elf: $(ECHO_OBJS) lib_sddf_lwip.a libsddf_util.a
$(LD) $(LDFLAGS) $^ $(LIBS) -o $@
LWIPDIRS := $(addprefix ${LWIPDIR}/, core/ipv4 netif api)
-${LWIP_OBJS}: |${BUILD_DIR}/${LWIPDIRS}
+${ECHO_OBJS}: |${BUILD_DIR}/${LWIPDIRS}
${BUILD_DIR}/${LWIPDIRS}:
mkdir -p $@
@@ -92,6 +90,9 @@ ${IMAGE_FILE} $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE)
include ${SDDF}/util/util.mk
include ${SDDF}/network/components/network_components.mk
+# Specify how many pbufs sDDF LWIP library requires for all clients
+SDDF_LWIP_NUM_BUFS=512
+include ${SDDF}/network/lib_sddf_lwip/lib_sddf_lwip.mk
include ${ETHERNET_DRIVER}/eth_driver.mk
include ${BENCHMARK}/benchmark.mk
include ${TIMER_DRIVER}/timer_driver.mk
diff --git a/examples/echo_server/include/ethernet_config/ethernet_config.h b/examples/echo_server/include/ethernet_config/ethernet_config.h
index 2d810c381..7a5913a80 100644
--- a/examples/echo_server/include/ethernet_config/ethernet_config.h
+++ b/examples/echo_server/include/ethernet_config/ethernet_config.h
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#define NUM_NETWORK_CLIENTS 2
diff --git a/examples/echo_server/lwip.c b/examples/echo_server/lwip.c
deleted file mode 100644
index 7f21022cd..000000000
--- a/examples/echo_server/lwip.c
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright 2022, UNSW
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include "lwip/init.h"
-#include "netif/etharp.h"
-#include "lwip/pbuf.h"
-#include "lwip/netif.h"
-#include "lwip/stats.h"
-#include "lwip/snmp.h"
-#include "lwip/sys.h"
-#include "lwip/timeouts.h"
-#include "lwip/dhcp.h"
-
-#include "echo.h"
-
-#define SERIAL_TX_CH 0
-#define TIMER 1
-#define RX_CH 2
-#define TX_CH 3
-
-char *serial_tx_data;
-serial_queue_t *serial_tx_queue;
-serial_queue_handle_t serial_tx_queue_handle;
-
-#define LWIP_TICK_MS 100
-#define NUM_PBUFFS NET_MAX_CLIENT_QUEUE_SIZE
-
-net_queue_t *rx_free;
-net_queue_t *rx_active;
-net_queue_t *tx_free;
-net_queue_t *tx_active;
-uintptr_t rx_buffer_data_region;
-uintptr_t tx_buffer_data_region;
-
-/* Booleans to indicate whether packets have been enqueued during notification handling */
-static bool notify_tx;
-static bool notify_rx;
-
-/* Wrapper over custom_pbuf structure to keep track of buffer offset */
-typedef struct pbuf_custom_offset {
- struct pbuf_custom custom;
- uint64_t offset;
-} pbuf_custom_offset_t;
-
-LWIP_MEMPOOL_DECLARE(
- RX_POOL,
- NUM_PBUFFS * 2,
- sizeof(struct pbuf_custom_offset),
- "Zero-copy RX pool"
-);
-
-typedef struct state {
- struct netif netif;
- uint8_t mac[ETH_HWADDR_LEN];
- net_queue_handle_t rx_queue;
- net_queue_handle_t tx_queue;
- struct pbuf *head;
- struct pbuf *tail;
-} state_t;
-
-state_t state;
-
-void set_timeout(void)
-{
- sddf_timer_set_timeout(TIMER, LWIP_TICK_MS * NS_IN_MS);
-}
-
-uint32_t sys_now(void)
-{
- return sddf_timer_time_now(TIMER) / NS_IN_MS;
-}
-
-/**
- * Free a pbuf. This also returns the underlying buffer to the receive free ring.
- *
- * @param p pbuf to free.
- */
-static void interface_free_buffer(struct pbuf *p)
-{
- SYS_ARCH_DECL_PROTECT(old_level);
- pbuf_custom_offset_t *custom_pbuf_offset = (pbuf_custom_offset_t *)p;
- SYS_ARCH_PROTECT(old_level);
- net_buff_desc_t buffer = {custom_pbuf_offset->offset, 0};
- int err = net_enqueue_free(&(state.rx_queue), buffer);
- assert(!err);
- notify_rx = true;
- LWIP_MEMPOOL_FREE(RX_POOL, custom_pbuf_offset);
- SYS_ARCH_UNPROTECT(old_level);
-}
-
-/**
- * Create a pbuf structure to pass to the network interface.
- *
- * @param state client state data.
- * @param buffer shared buffer containing the data.
- * @param length length of data.
- *
- * @return the newly created pbuf. Can be cast to pbuf_custom.
- */
-static struct pbuf *create_interface_buffer(uint64_t offset, size_t length)
-{
- pbuf_custom_offset_t *custom_pbuf_offset = (pbuf_custom_offset_t *) LWIP_MEMPOOL_ALLOC(RX_POOL);
- custom_pbuf_offset->offset = offset;
- custom_pbuf_offset->custom.custom_free_function = interface_free_buffer;
-
- return pbuf_alloced_custom(
- PBUF_RAW,
- length,
- PBUF_REF,
- &custom_pbuf_offset->custom,
- (void *)(offset + rx_buffer_data_region),
- NET_BUFFER_SIZE
- );
-}
-
-/**
- * Stores a pbuf to be transmitted upon available transmit buffers.
- *
- * @param p pbuf to be stored.
- */
-void enqueue_pbufs(struct pbuf *p)
-{
- /* Indicate to the multiplexer that we require transmit free buffers */
- net_request_signal_free(&state.tx_queue);
-
- if (state.head == NULL) {
- state.head = p;
- } else {
- state.tail->next_chain = p;
- }
- state.tail = p;
-
- /* Increment refernce count to ensure this pbuf is not freed by lwip */
- pbuf_ref(p);
-}
-
-/**
- * Insert pbuf into transmit active queue. If no free buffers available or transmit active queue is full,
- * stores pbuf to be sent upon buffers becoming available.
- * */
-static err_t lwip_eth_send(struct netif *netif, struct pbuf *p)
-{
- if (p->tot_len > NET_BUFFER_SIZE) {
- sddf_dprintf("LWIP|ERROR: attempted to send a packet of size %u > BUFFER SIZE %u\n", p->tot_len, NET_BUFFER_SIZE);
- return ERR_MEM;
- }
-
- if (net_queue_empty_free(&state.tx_queue)) {
- enqueue_pbufs(p);
- return ERR_OK;
- }
-
- net_buff_desc_t buffer;
- int err = net_dequeue_free(&state.tx_queue, &buffer);
- assert(!err);
-
- uintptr_t frame = buffer.io_or_offset + tx_buffer_data_region;
- uint16_t copied = 0;
- for (struct pbuf *curr = p; curr != NULL; curr = curr->next) {
- memcpy((void *)(frame + copied), curr->payload, curr->len);
- copied += curr->len;
- }
-
- buffer.len = copied;
- err = net_enqueue_active(&state.tx_queue, buffer);
- assert(!err);
-
- notify_tx = true;
-
- return ERR_OK;
-}
-
-void transmit(void)
-{
- bool reprocess = true;
- while (reprocess) {
- while (state.head != NULL && !net_queue_empty_free(&state.tx_queue)) {
- err_t err = lwip_eth_send(&state.netif, state.head);
- if (err == ERR_MEM) {
- sddf_dprintf("LWIP|ERROR: attempted to send a packet of size %u > BUFFER SIZE %u\n", state.head->tot_len,
- NET_BUFFER_SIZE);
- } else if (err != ERR_OK) {
- sddf_dprintf("LWIP|ERROR: unkown error when trying to send pbuf %p\n", state.head);
- }
-
- struct pbuf *temp = state.head;
- state.head = temp->next_chain;
- if (state.head == NULL) {
- state.tail = NULL;
- }
- pbuf_free(temp);
- }
-
- /* Only request a signal if no more pbufs enqueud to send */
- if (state.head == NULL || !net_queue_empty_free(&state.tx_queue)) {
- net_cancel_signal_free(&state.tx_queue);
- } else {
- net_request_signal_free(&state.tx_queue);
- }
- reprocess = false;
-
- if (state.head != NULL && !net_queue_empty_free(&state.tx_queue)) {
- net_cancel_signal_free(&state.tx_queue);
- reprocess = true;
- }
- }
-}
-
-void receive(void)
-{
- bool reprocess = true;
- while (reprocess) {
- while (!net_queue_empty_active(&state.rx_queue)) {
- net_buff_desc_t buffer;
- int err = net_dequeue_active(&state.rx_queue, &buffer);
- assert(!err);
-
- struct pbuf *p = create_interface_buffer(buffer.io_or_offset, buffer.len);
- assert(p != NULL);
- if (state.netif.input(p, &state.netif) != ERR_OK) {
- sddf_dprintf("LWIP|ERROR: unkown error inputting pbuf into network stack\n");
- pbuf_free(p);
- }
- }
-
- net_request_signal_active(&state.rx_queue);
- reprocess = false;
-
- if (!net_queue_empty_active(&state.rx_queue)) {
- net_cancel_signal_active(&state.rx_queue);
- reprocess = true;
- }
- }
-}
-
-/**
- * Initialise the network interface data structure.
- *
- * @param netif network interface data structuer.
- */
-static err_t ethernet_init(struct netif *netif)
-{
- if (netif->state == NULL) {
- return ERR_ARG;
- }
- state_t *data = netif->state;
-
- netif->hwaddr[0] = data->mac[0];
- netif->hwaddr[1] = data->mac[1];
- netif->hwaddr[2] = data->mac[2];
- netif->hwaddr[3] = data->mac[3];
- netif->hwaddr[4] = data->mac[4];
- netif->hwaddr[5] = data->mac[5];
- netif->mtu = ETHER_MTU;
- netif->hwaddr_len = ETHARP_HWADDR_LEN;
- netif->output = etharp_output;
- netif->linkoutput = lwip_eth_send;
- NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED);
- netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_IGMP;
- return ERR_OK;
-}
-
-/* Callback function that prints DHCP supplied IP address. */
-static void netif_status_callback(struct netif *netif)
-{
- if (dhcp_supplied_address(netif)) {
- sddf_printf("LWIP|NOTICE: DHCP request for %s returned IP address: %s\n", microkit_name,
- ip4addr_ntoa(netif_ip4_addr(netif)));
- }
-}
-
-void init(void)
-{
- serial_cli_queue_init_sys(microkit_name, NULL, NULL, NULL, &serial_tx_queue_handle, serial_tx_queue, serial_tx_data);
- serial_putchar_init(SERIAL_TX_CH, &serial_tx_queue_handle);
-
- size_t rx_size, tx_size;
- net_cli_queue_size(microkit_name, &rx_size, &tx_size);
- net_queue_init(&state.rx_queue, rx_free, rx_active, rx_size);
- net_queue_init(&state.tx_queue, tx_free, tx_active, tx_size);
- net_buffers_init(&state.tx_queue, 0);
-
- lwip_init();
- set_timeout();
-
- LWIP_MEMPOOL_INIT(RX_POOL);
-
- uint64_t mac_addr = net_cli_mac_addr(microkit_name);
- net_set_mac_addr(state.mac, mac_addr);
-
- /* Set dummy IP configuration values to get lwIP bootstrapped */
- struct ip4_addr netmask, ipaddr, gw, multicast;
- ipaddr_aton("0.0.0.0", &gw);
- ipaddr_aton("0.0.0.0", &ipaddr);
- ipaddr_aton("0.0.0.0", &multicast);
- ipaddr_aton("255.255.255.0", &netmask);
-
- state.netif.name[0] = 'e';
- state.netif.name[1] = '0';
-
- if (!netif_add(&(state.netif), &ipaddr, &netmask, &gw, (void *)&state,
- ethernet_init, ethernet_input)) {
- sddf_dprintf("LWIP|ERROR: Netif add returned NULL\n");
- }
-
- netif_set_default(&(state.netif));
- netif_set_status_callback(&(state.netif), netif_status_callback);
- netif_set_up(&(state.netif));
-
- if (dhcp_start(&(state.netif))) {
- sddf_dprintf("LWIP|ERROR: failed to start DHCP negotiation\n");
- }
-
- setup_udp_socket();
- setup_utilization_socket();
- setup_tcp_socket();
-
- if (notify_rx && net_require_signal_free(&state.rx_queue)) {
- net_cancel_signal_free(&state.rx_queue);
- notify_rx = false;
- if (!microkit_have_signal) {
- microkit_deferred_notify(RX_CH);
- } else if (microkit_signal_cap != BASE_OUTPUT_NOTIFICATION_CAP + RX_CH) {
- microkit_notify(RX_CH);
- }
- }
-
- if (notify_tx && net_require_signal_active(&state.tx_queue)) {
- net_cancel_signal_active(&state.tx_queue);
- notify_tx = false;
- if (!microkit_have_signal) {
- microkit_deferred_notify(TX_CH);
- } else if (microkit_signal_cap != BASE_OUTPUT_NOTIFICATION_CAP + TX_CH) {
- microkit_notify(TX_CH);
- }
- }
-}
-
-void notified(microkit_channel ch)
-{
- switch (ch) {
- case RX_CH:
- receive();
- break;
- case TIMER:
- sys_check_timeouts();
- set_timeout();
- break;
- case TX_CH:
- transmit();
- receive();
- break;
- case SERIAL_TX_CH:
- // Nothing to do
- break;
- default:
- sddf_dprintf("LWIP|LOG: received notification on unexpected channel: %u\n", ch);
- break;
- }
-
- if (notify_rx && net_require_signal_free(&state.rx_queue)) {
- net_cancel_signal_free(&state.rx_queue);
- notify_rx = false;
- if (!microkit_have_signal) {
- microkit_deferred_notify(RX_CH);
- } else if (microkit_signal_cap != BASE_OUTPUT_NOTIFICATION_CAP + RX_CH) {
- microkit_notify(RX_CH);
- }
- }
-
- if (notify_tx && net_require_signal_active(&state.tx_queue)) {
- net_cancel_signal_active(&state.tx_queue);
- notify_tx = false;
- if (!microkit_have_signal) {
- microkit_deferred_notify(TX_CH);
- } else if (microkit_signal_cap != BASE_OUTPUT_NOTIFICATION_CAP + TX_CH) {
- microkit_notify(TX_CH);
- }
- }
-}
diff --git a/include/sddf/network/lib_sddf_lwip.h b/include/sddf/network/lib_sddf_lwip.h
new file mode 100644
index 000000000..68e7a1b6c
--- /dev/null
+++ b/include/sddf/network/lib_sddf_lwip.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2022, UNSW
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include
+#include
+#include "lwip/pbuf.h"
+
+/* Default gigabit link speed. */
+#define SDDF_LWIP_LINK_SPEED 1000000000
+
+/* Default ethernet MTU. */
+#define SDDF_LWIP_ETHER_MTU 1500
+
+/* Definitions for sDDF error constants. */
+typedef enum {
+ /* No error, everything OK. */
+ SDDF_LWIP_ERR_OK = 0,
+ /* Pbuf too large for sDDF buffer. */
+ SDDF_LWIP_ERR_PBUF = -1,
+ /* No buffers available. */
+ SDDF_LWIP_ERR_NO_BUF = -2,
+ /* Pbuf successfully enqueued to be sent later. */
+ SDDF_LWIP_ERR_ENQUEUED = -3,
+ /* Could not resolve error. */
+ SDDF_LWIP_ERR_UNHANDLED = -4
+} net_sddf_err_t;
+
+/**
+ * Function type for output of sDDF LWIP errors.
+ */
+typedef int (*sddf_lwip_err_output_fn)(const char *format, ...) __attribute__((format(__printf__, 1, 2)));
+
+/**
+ * Function type for netif status callback. Invoked by LWIP upon
+ * successfully obtaining an IP address for the network interface.
+ */
+typedef void (*sddf_lwip_netif_status_callback_fn)(char *ip_addr);
+
+/**
+ * Function type for handling function which is optionally invoked
+ * when a pbuf is unable to be sent due to no available sDDF tx buffers.
+ * Can be used to store pbuf until more buffers are available.
+ */
+typedef net_sddf_err_t (*sddf_lwip_handle_empty_tx_free_fn)(struct pbuf *p);
+
+/**
+ * Checks LWIP system timeouts. Should be invoked after every LWIP tick.
+ */
+void sddf_lwip_process_timeout(void);
+
+/**
+ * Transmits the provided pbuf through the sddf network system.
+ *
+ * @param p pbuf to be transmitted.
+ *
+ * @return If the pbuf is sent successfully, SDDF_LWIP_ERR_OK is returned and the
+ * pbuf can safely be freed. If the pbuf is too large, SDDF_LWIP_ERR_PBUF is
+ * returned. If there are no free sDDF buffers available,
+ * handle_empty_tx_free will be called with the pbuf, and the return value
+ * will be returned.
+ */
+net_sddf_err_t sddf_lwip_transmit_pbuf(struct pbuf *p);
+
+/**
+ * Handles the passing of incoming packets in sDDF buffers to LWIP. Must be
+ * called to process the sDDF RX queue each time a notification is received
+ * from the network virtualiser.
+ */
+void sddf_lwip_process_rx(void);
+
+/**
+ * Handles the sending of notifications to the network RX and TX virtualisers.
+ * Must be invoked at the end of each event handling loop and initialisation
+ * to ensure outgoing buffers are processed by the virtualisers.
+ */
+void sddf_lwip_maybe_notify(void);
+
+/**
+ * Initialisation function for the sDDF LWIP library. Must be called prior
+ * to using any other library functions.
+ *
+ * @param rx_queue RX net queue handle data structure. Must be initialised
+ * prior to being passed to this function.
+ * @param tx_queue TX net queue handle data structure. Must be initialised
+ * prior to being passed to this function.
+ * @param rx_ch RX notification channel to the net RX virtualiser.
+ * @param tx_ch TX notification channel to the net TX virtualiser.
+ * @param rx_buffer_data_region virtual address of the start of the RX
+ * buffer region.
+ * @param tx_buffer_data_region virtual address of the start of the TX
+ * buffer region.
+ * @param timer_ch timer notification channel to the timer driver.
+ * @param mac mac address of the client.
+ * @param err_output function pointer to optional user provided error
+ * output function. Provide NULL to use default sddf_printf_.
+ * @param netif_callback function pointer to optional user provided netif
+ * status callback function. Provide NULL to use err_output to print client
+ * MAC address and obtained IP address.
+ * @param handle_empty_tx_free function pointer to optional user provided
+ * handling function for no available sDDF TX buffers during sending of LWIP
+ * pbuf. Provide NULL to leave unhandled.
+ */
+void sddf_lwip_init(net_queue_handle_t rx_queue, net_queue_handle_t tx_queue, microkit_channel rx_ch,
+ microkit_channel tx_ch, uintptr_t rx_buffer_data_region, uintptr_t tx_buffer_data_region,
+ microkit_channel timer_ch, uint64_t mac, sddf_lwip_err_output_fn err_output,
+ sddf_lwip_netif_status_callback_fn netif_callback,
+ sddf_lwip_handle_empty_tx_free_fn handle_empty_tx_free);
diff --git a/include/sddf/network/util.h b/include/sddf/network/util.h
index 916a56322..1c45f8d33 100644
--- a/include/sddf/network/util.h
+++ b/include/sddf/network/util.h
@@ -21,4 +21,4 @@ static void net_set_mac_addr(uint8_t *mac, uint64_t val)
mac[3] = val >> 16 & 0xff;
mac[4] = val >> 8 & 0xff;
mac[5] = val & 0xff;
-}
\ No newline at end of file
+}
diff --git a/network/components/network_components.mk b/network/components/network_components.mk
index 9ed9b3fd4..d681a79c7 100644
--- a/network/components/network_components.mk
+++ b/network/components/network_components.mk
@@ -11,7 +11,6 @@
# Generates network_virt_rx.elf network_virt_tx.elf arp.elf copy.elf
# Requires ${SDDF}/util/util.mk to build the utility library for debug output
-NETWORK_COMPONENTS_DIR := $(abspath $(dir $(lastword ${MAKEFILE_LIST})))
NETWORK_IMAGES:= network_virt_rx.elf network_virt_tx.elf arp.elf copy.elf
network/components/%.o: ${SDDF}/network/components/%.c
${CC} ${CFLAGS} -c -o $@ $<
@@ -40,10 +39,11 @@ network/components/network_virt_%.o: ${SDDF}/network/components/virt_%.c
${LD} ${LDFLAGS} -o $@ $< ${LIBS}
clean::
- rm -f network_virt_[rt]x.[od] copy.[od] arp.[od]
+ ${RM} -f network_virt_[rt]x.[od] copy.[od] arp.[od]
clobber::
- rm -f ${IMAGES}
+ ${RM} -f ${NETWORK_IMAGES}
+ rmdir network/components
network/components:
mkdir -p $@
diff --git a/network/lib_sddf_lwip/lib_sddf_lwip.c b/network/lib_sddf_lwip/lib_sddf_lwip.c
new file mode 100644
index 000000000..349e3eea4
--- /dev/null
+++ b/network/lib_sddf_lwip/lib_sddf_lwip.c
@@ -0,0 +1,368 @@
+/*
+ * Copyright 2022, UNSW
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "lwip/err.h"
+#include "lwip/init.h"
+#include "lwip/ip4_addr.h"
+#include "netif/etharp.h"
+#include "lwip/pbuf.h"
+#include "lwip/netif.h"
+#include "lwip/snmp.h"
+#include "lwip/sys.h"
+#include "lwip/timeouts.h"
+#include "lwip/dhcp.h"
+
+typedef struct lwip_state {
+ /* LWIP network interface struct. */
+ struct netif netif;
+ /* MAC address of client. */
+ uint64_t mac;
+ /* Output function used to print error messages. */
+ sddf_lwip_err_output_fn err_output;
+ /* Callback function to be invoked when ip address is obtained. */
+ sddf_lwip_netif_status_callback_fn netif_callback;
+ /* Function that optionally handles when no free tx buffers available. */
+ sddf_lwip_handle_empty_tx_free_fn handle_empty_tx_free;
+} lwip_state_t;
+
+typedef struct sddf_state {
+ /* sddf net rx queue handle. */
+ net_queue_handle_t rx_queue;
+ /* sddf net tx queue handle. */
+ net_queue_handle_t tx_queue;
+ /* sddf channel for net rx virt. */
+ microkit_channel rx_ch;
+ /* sddf channel for net tx virt. */
+ microkit_channel tx_ch;
+ /* Base address of data region containing rx buffers. */
+ uintptr_t rx_buffer_data_region;
+ /* Base address of data region containing tx buffers. */
+ uintptr_t tx_buffer_data_region;
+ /* Boolean indicating whether buffers have been given to rx virt. */
+ bool notify_rx;
+ /* Boolean indicating whether buffers have been given to tx virt. */
+ bool notify_tx;
+ /* sddf channel for timer. */
+ microkit_channel timer_ch;
+} sddf_state_t;
+
+/* Wrapper over custom_pbuf structure to keep track of buffer's offset into data region. */
+typedef struct pbuf_custom_offset {
+ struct pbuf_custom custom;
+ uint64_t offset;
+} pbuf_custom_offset_t;
+
+LWIP_MEMPOOL_DECLARE(RX_POOL, SDDF_LWIP_NUM_BUFS * 2, sizeof(struct pbuf_custom_offset), "Zero-copy RX pool");
+
+lwip_state_t lwip_state;
+sddf_state_t sddf_state;
+
+/**
+ * Helper function to convert sddf errors to lwip errors.
+ *
+ * @param sddf_err sddf error.
+ *
+ * @return Equivalent lwip error.
+ */
+static err_t sddf_err_to_lwip_err(net_sddf_err_t sddf_err)
+{
+ switch (sddf_err) {
+ case SDDF_LWIP_ERR_OK:
+ return ERR_OK;
+ case SDDF_LWIP_ERR_PBUF:
+ return ERR_BUF;
+ case SDDF_LWIP_ERR_NO_BUF:
+ return ERR_MEM;
+ case SDDF_LWIP_ERR_ENQUEUED:
+ return ERR_OK;
+ case SDDF_LWIP_ERR_UNHANDLED:
+ return ERR_MEM;
+ }
+ return ERR_ARG;
+}
+
+/**
+ * Default netif status callback function. Prints client MAC address and
+ * obtained ip address.
+ *
+ * @param ip_addr Obtained ip address as a string.
+ */
+static void netif_status_callback_default(char *ip_addr)
+{
+ uint8_t *mac = lwip_state.netif.hwaddr;
+ lwip_state.err_output("LWIP|NOTICE: DHCP request for mac "
+ "%02x:%02x:%02x:%02x:%02x:%02x "
+ "returned ip address: %s\n",
+ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], ip_addr);
+}
+
+/**
+ * Default handling function to be called during transmission if tx free
+ * queue is empty.
+ *
+ * @param p pbuf that could not be sent due to queue being empty.
+ *
+ * @return Simply returns the sddf error indicating nothing was done.
+ */
+static net_sddf_err_t handle_empty_tx_free_default(struct pbuf *p)
+{
+ return SDDF_LWIP_ERR_UNHANDLED;
+}
+
+/**
+ * Returns current time from the timer.
+ */
+uint32_t sys_now(void)
+{
+ return sddf_timer_time_now(sddf_state.timer_ch) / NS_IN_MS;
+}
+
+void sddf_lwip_process_timeout()
+{
+ sys_check_timeouts();
+}
+
+/**
+ * Free a pbuf. This also returns the underlying sddf buffer to the receive free ring.
+ *
+ * @param p pbuf to free.
+ */
+static void interface_free_buffer(struct pbuf *p)
+{
+ SYS_ARCH_DECL_PROTECT(old_level);
+ pbuf_custom_offset_t *custom_pbuf_offset = (pbuf_custom_offset_t *)p;
+ SYS_ARCH_PROTECT(old_level);
+ net_buff_desc_t buffer = { custom_pbuf_offset->offset, 0 };
+ int err = net_enqueue_free(&(sddf_state.rx_queue), buffer);
+ assert(!err);
+ sddf_state.notify_rx = true;
+ LWIP_MEMPOOL_FREE(RX_POOL, custom_pbuf_offset);
+ SYS_ARCH_UNPROTECT(old_level);
+}
+
+/**
+ * Create a pbuf structure to pass to the network interface.
+ *
+ * @param offset offset into the data region of the buffer to be passed.
+ * @param length length of data.
+ *
+ * @return the newly created pbuf. Can be cast to pbuf_custom.
+ */
+static struct pbuf *create_interface_buffer(uint64_t offset, size_t length)
+{
+ pbuf_custom_offset_t *custom_pbuf_offset = (pbuf_custom_offset_t *)LWIP_MEMPOOL_ALLOC(RX_POOL);
+ custom_pbuf_offset->offset = offset;
+ custom_pbuf_offset->custom.custom_free_function = interface_free_buffer;
+
+ return pbuf_alloced_custom(PBUF_RAW, length, PBUF_REF, &custom_pbuf_offset->custom,
+ (void *)(offset + sddf_state.rx_buffer_data_region), NET_BUFFER_SIZE);
+}
+
+/**
+ * Copy a pbuf into an sddf buffer and insert it into the transmit active queue.
+ *
+ * @param netif lwip network interface state.
+ * @param p pbuf to be transmitted.
+ *
+ * @return If the pbuf is sent, ERR_OK is returned and the pbuf can safely be
+ * freed. If the pbuf is too large ERR_MEM is returned. If there are no free
+ * sddf buffers available, handle_empty_tx_free will be called with the pbuf,
+ * and the equivalent lwip error will be returned.
+ */
+static err_t lwip_eth_send(struct netif *netif, struct pbuf *p)
+{
+ if (p->tot_len > NET_BUFFER_SIZE) {
+ lwip_state.err_output("LWIP|ERROR: attempted to send a packet of size %u > BUFFER SIZE %u\n", p->tot_len,
+ NET_BUFFER_SIZE);
+ return ERR_MEM;
+ }
+
+ if (net_queue_empty_free(&sddf_state.tx_queue)) {
+ return sddf_err_to_lwip_err(lwip_state.handle_empty_tx_free(p));
+ }
+
+ net_buff_desc_t buffer;
+ int err = net_dequeue_free(&sddf_state.tx_queue, &buffer);
+ assert(!err);
+
+ uintptr_t frame = buffer.io_or_offset + sddf_state.tx_buffer_data_region;
+ uint16_t copied = 0;
+ for (struct pbuf *curr = p; curr != NULL; curr = curr->next) {
+ memcpy((void *)(frame + copied), curr->payload, curr->len);
+ copied += curr->len;
+ }
+
+ buffer.len = copied;
+ err = net_enqueue_active(&sddf_state.tx_queue, buffer);
+ assert(!err);
+
+ sddf_state.notify_tx = true;
+
+ return ERR_OK;
+}
+
+net_sddf_err_t sddf_lwip_transmit_pbuf(struct pbuf *p)
+{
+ if (p->tot_len > NET_BUFFER_SIZE) {
+ lwip_state.err_output("LWIP|ERROR: attempted to send a packet of size %u > BUFFER SIZE %u\n", p->tot_len,
+ NET_BUFFER_SIZE);
+ return SDDF_LWIP_ERR_PBUF;
+ }
+
+ if (net_queue_empty_free(&sddf_state.tx_queue)) {
+ return lwip_state.handle_empty_tx_free(p);
+ }
+
+ err_t err = lwip_eth_send(&lwip_state.netif, p);
+ assert(!err);
+
+ return SDDF_LWIP_ERR_OK;
+}
+
+void sddf_lwip_process_rx(void)
+{
+ bool reprocess = true;
+ while (reprocess) {
+ while (!net_queue_empty_active(&sddf_state.rx_queue)) {
+ net_buff_desc_t buffer;
+ int err = net_dequeue_active(&sddf_state.rx_queue, &buffer);
+ assert(!err);
+
+ struct pbuf *p = create_interface_buffer(buffer.io_or_offset, buffer.len);
+ assert(p != NULL);
+ if (lwip_state.netif.input(p, &lwip_state.netif) != ERR_OK) {
+ lwip_state.err_output("LWIP|ERROR: unkown error inputting pbuf into network stack\n");
+ pbuf_free(p);
+ }
+ }
+
+ net_request_signal_active(&sddf_state.rx_queue);
+ reprocess = false;
+
+ if (!net_queue_empty_active(&sddf_state.rx_queue)) {
+ net_cancel_signal_active(&sddf_state.rx_queue);
+ reprocess = true;
+ }
+ }
+}
+
+/**
+ * Initialise the network interface data structure.
+ *
+ * @param netif network interface data structure.
+ */
+static err_t ethernet_init(struct netif *netif)
+{
+ if (netif->state == NULL) {
+ return ERR_ARG;
+ }
+
+ net_set_mac_addr(netif->hwaddr, lwip_state.mac);
+ netif->mtu = SDDF_LWIP_ETHER_MTU;
+ netif->hwaddr_len = ETHARP_HWADDR_LEN;
+ netif->output = etharp_output;
+ netif->linkoutput = lwip_eth_send;
+ NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, SDDF_LWIP_LINK_SPEED);
+ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_IGMP;
+
+ return ERR_OK;
+}
+
+/**
+ * Network interface callback function invoked when DHCP packets are received.
+ * If an ip address is successfully obtained, the provided netif_callback
+ * function will be invoked with the ip address as a string.
+ *
+ * @param netif network interface data structure.
+ */
+static void netif_status_callback(struct netif *netif)
+{
+ if (dhcp_supplied_address(netif)) {
+ char ip4_str[IP4ADDR_STRLEN_MAX];
+ lwip_state.netif_callback(ip4addr_ntoa_r(netif_ip4_addr(netif), ip4_str, IP4ADDR_STRLEN_MAX));
+ }
+}
+
+void sddf_lwip_init(net_queue_handle_t rx_queue, net_queue_handle_t tx_queue, microkit_channel rx_ch,
+ microkit_channel tx_ch, uintptr_t rx_buffer_data_region, uintptr_t tx_buffer_data_region,
+ microkit_channel timer_ch, uint64_t mac, sddf_lwip_err_output_fn err_output,
+ sddf_lwip_netif_status_callback_fn netif_callback,
+ sddf_lwip_handle_empty_tx_free_fn handle_empty_tx_free)
+{
+ /* Initialise sddf state */
+ sddf_state.rx_queue = rx_queue;
+ sddf_state.tx_queue = tx_queue;
+ sddf_state.rx_ch = rx_ch;
+ sddf_state.tx_ch = tx_ch;
+ sddf_state.rx_buffer_data_region = rx_buffer_data_region;
+ sddf_state.tx_buffer_data_region = tx_buffer_data_region;
+ sddf_state.timer_ch = timer_ch;
+
+ /* Initialise lwip state */
+ lwip_state.mac = mac;
+ lwip_state.err_output = (err_output == NULL) ? sddf_printf_ : err_output;
+ lwip_state.netif_callback = (netif_callback == NULL) ? netif_status_callback_default : netif_callback;
+ lwip_state.handle_empty_tx_free = (handle_empty_tx_free == NULL) ? handle_empty_tx_free_default
+ : handle_empty_tx_free;
+
+ lwip_init();
+
+ LWIP_MEMPOOL_INIT(RX_POOL);
+
+ /* Set dummy IP configuration values to get lwIP bootstrapped */
+ struct ip4_addr netmask, ipaddr, gw, multicast;
+ ipaddr_aton("0.0.0.0", &gw);
+ ipaddr_aton("0.0.0.0", &ipaddr);
+ ipaddr_aton("0.0.0.0", &multicast);
+ ipaddr_aton("255.255.255.0", &netmask);
+
+ lwip_state.netif.name[0] = 'e';
+ lwip_state.netif.name[1] = '0';
+
+ if (!netif_add(&(lwip_state.netif), &ipaddr, &netmask, &gw, (void *)&lwip_state, ethernet_init, ethernet_input)) {
+ lwip_state.err_output("LWIP|ERROR: Netif add returned NULL\n");
+ }
+
+ netif_set_default(&(lwip_state.netif));
+ netif_set_status_callback(&(lwip_state.netif), netif_status_callback);
+ netif_set_up(&(lwip_state.netif));
+
+ if (dhcp_start(&(lwip_state.netif))) {
+ lwip_state.err_output("LWIP|ERROR: failed to start DHCP negotiation\n");
+ }
+}
+
+void sddf_lwip_maybe_notify()
+{
+ if (sddf_state.notify_rx && net_require_signal_free(&sddf_state.rx_queue)) {
+ net_cancel_signal_free(&sddf_state.rx_queue);
+ sddf_state.notify_rx = false;
+ if (!microkit_have_signal) {
+ microkit_deferred_notify(sddf_state.rx_ch);
+ } else if (microkit_signal_cap != BASE_OUTPUT_NOTIFICATION_CAP + sddf_state.rx_ch) {
+ microkit_notify(sddf_state.rx_ch);
+ }
+ }
+
+ if (sddf_state.notify_tx && net_require_signal_active(&sddf_state.tx_queue)) {
+ net_cancel_signal_active(&sddf_state.tx_queue);
+ sddf_state.notify_tx = false;
+ if (!microkit_have_signal) {
+ microkit_deferred_notify(sddf_state.tx_ch);
+ } else if (microkit_signal_cap != BASE_OUTPUT_NOTIFICATION_CAP + sddf_state.tx_ch) {
+ microkit_notify(sddf_state.tx_ch);
+ }
+ }
+}
diff --git a/network/lib_sddf_lwip/lib_sddf_lwip.mk b/network/lib_sddf_lwip/lib_sddf_lwip.mk
new file mode 100644
index 000000000..17506e141
--- /dev/null
+++ b/network/lib_sddf_lwip/lib_sddf_lwip.mk
@@ -0,0 +1,32 @@
+#
+# Copyright 2022, UNSW
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+ifeq ($(strip $(SDDF_LWIP_NUM_BUFS)),)
+$(error SDDF_LWIP_NUM_BUFS must be specified)
+endif
+
+LIB_SDDF_LWIP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+LIB_SDDF_LWIP_FILES := $(addprefix ${LIB_SDDF_LWIP_DIR}/, lib_sddf_lwip.c)
+LIB_SDDF_LWIP_OBJS := $(LIB_SDDF_LWIP_FILES:.c=.o)
+
+${LIB_SDDF_LWIP_OBJS}: CFLAGS += -DSDDF_LWIP_NUM_BUFS=$(SDDF_LWIP_NUM_BUFS)
+
+${LIB_SDDF_LWIP_OBJS}: ${CHECK_FLAGS_BOARD_MD5} | ${LIB_SDDF_LWIP_DIR}
+${LIB_SDDF_LWIP_DIR}:
+ mkdir -p $@
+
+lib_sddf_lwip.a: ${LIB_SDDF_LWIP_OBJS}
+ ${AR} rv $@ $^
+ ${RANLIB} $@
+
+clean::
+ ${RM} -f ${LIB_SDDF_LWIP_OBJS} ${LIB_SDDF_LWIP_OBJS:.o=.d}
+
+clobber:: clean
+ ${RM} -f lib_sddf_lwip.a
+ rmdir ${LIB_SDDF_LWIP_DIR}
+
+-include ${LIB_SDDF_LWIP_OBJS:.o=.d}