lwIP Wiki

Porting For Bare Metal

91pages on
this wiki
Add New Page
Talk0 Share

One way to use lwIP is to run without an operating system (aka "bare metal") -- see LwIP_with_or_without_an_operating_system. Running in the no-operating-system mode is also often referred to as a NOSYS environment from the lwIP symbol that controls it.

In NOSYS mode, the netconn and socket APIs are not available. All applications must be written using the raw (or native) API. As far as lwIP is concerned, there is only a single thread of execution in the entire system. Because operating system emulation layer is not needed, a port to this environment is slightly simpler.

The intent of this page is to collect in one place a list of the steps needed to port lwIP for this environment.

Basic StepsEdit

  1. Create cc.h
    This file contains settings needed to adapt lwIP for your compiler and machine architecture. Rather than duplicate the description of this file, please see Porting_for_an_OS.
  2. Create sys_arch.h
    For the NO_SYS environment, no operating system adaptation layer is required, so this file merely contains a handful of typedefs and preprocessor definitions.
  3. Create the lwipopts.h for your port.
    Getting this file right can be tricky. You will probably want to find an existing version of this file from the lwIP contrib directory, copy it and then start modifying. The number one setting is to define the preprocessor symbol NO_SYS to 1. There are a multitude of settings that can be specified in this file that determine which optional portions of lwIP will be included (e.g. IP reassembly, handling of out-of-order TCP segments, and so forth), which protocols will be enabled (TCP, UDP, ARP, DHCP, etc.), and a number of settings that help to determine how much memory will be used by the stack. The stack's source code is organized so that your settings in this file will override the built-in defaults in lwIP's lwip/opts.h file.
  4. Create your network driver.
    This process is the same whether or not you are using an operating system. See "Writing a device driver" for more info.
  5. Create your sys_now function to obtain a timestamp.
    In order to operate, the stack needs to have certain functions called at regular intervals to perform house-keeping tasks, such as handling TCP timeouts, retransmissions and so forth.
  6. Create your main function.
    Here, you'll initialize lwIP, initialize your timer, install your driver into lwip with netif_add (see Network Interfaces Management), and enter your main loop. With respect to lwIP, your main loop has two main jobs: poll your device driver to process received frames (the driver will pass them up to the lwIP stack using the input function you specified in netif_add) and periodically invoke the lwIP timer processing functions (etharp_tmr(), ip_reass_tmr(), tcp_tmr(), etc. -- the exact list depends on which protocols you have enabled or as of 1.4.0 sys_check_timeouts()).
  7. Create a makefile that compiles your driver, the lwIP files and the rest of your application.
    Getting the include paths right (and ordering of the include paths) can be tricky. You need to make sure that your cc.h and sys_arch.h are in a sub-directory named arch as all the include directives in lwIP expect this. You also need to make sure that this directory as well as the directory containing your lwipopts.h come before the lwIP-provided include directories. Finally, you'll need to identify the required lwIP source files (again, this depends on which features of lwIP you've enabled).


Below are some sample files for a bare-metal port that may provide a starting point. These have been sanitized somewhat but come from a real port done with lwIP version 1.3.2. References to any function beginning with "mch_" are specific to the machine and its devices. The compiler was gcc, the architecture was MIPS-32 (big-endian).


#ifndef __ARCH_CC_H__
#define __ARCH_CC_H__

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/time.h>

// Includes definition of mch_printf macro to do printf
#include "mch.h"


typedef uint8_t     u8_t;
typedef int8_t      s8_t;
typedef uint16_t    u16_t;
typedef int16_t     s16_t;
typedef uint32_t    u32_t;
typedef int32_t     s32_t;

typedef uintptr_t   mem_ptr_t;

#define LWIP_ERR_T  int

/* Define (sn)printf formatters for these lwIP types */
#define U16_F "hu"
#define S16_F "hd"
#define X16_F "hx"
#define U32_F "u"
#define S32_F "d"
#define X32_F "x"

/* Compiler hints for packing structures */
#define PACK_STRUCT_FIELD(x)    x
#define PACK_STRUCT_STRUCT  __attribute__((packed))

/* Plaform specific diagnostic output */
#define LWIP_PLATFORM_DIAG(x)   do {                \
        mch_printf x;                   \
    } while (0)

#define LWIP_PLATFORM_ASSERT(x) do {                \
        mch_printf("Assert \"%s\" failed at line %d in %s\n",   \
                x, __LINE__, __FILE__);             \
        mch_abort();                        \
    } while (0)

#endif /* __ARCH_CC_H__ */


#ifndef __ARCH_SYS_ARCH_H__
#define __ARCH_SYS_ARCH_H__

#define SYS_SEM_NULL    NULL

typedef void * sys_prot_t;

typedef void * sys_sem_t;

typedef void * sys_mbox_t;

typedef void * sys_thread_t;

#endif /* __ARCH_SYS_ARCH_H__ */


Note that this port was for a machine that — unlike many embedded systems on which lwIP runs — was not memory constrained. Hence, many of the settings here are much higher than will be the case for other lwIP ports. Unfortunately, these settings are highly specific to the particular machine and configuration. Your best bet is to read the comments in the main lwIP opts.h file, take a stab at settings appropriate to your system, and ask on the lwip-users mailing list if you need help in understanding what they all mean.

#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__

#define NO_SYS                      1
#define MEM_LIBC_MALLOC             1
#define MEMP_MEM_MALLOC             1
#define MEM_ALIGNMENT               4
#define MEM_SIZE                    (4 * 1024 * 1024)
#define MEMP_NUM_PBUF               1024
#define MEMP_NUM_UDP_PCB            20
#define MEMP_NUM_TCP_PCB            20
#define MEMP_NUM_TCP_PCB_LISTEN     16
#define MEMP_NUM_TCP_SEG            128
#define MEMP_NUM_REASSDATA          32
#define MEMP_NUM_ARP_QUEUE          10
#define PBUF_POOL_SIZE              512
#define LWIP_ARP                    1
#define IP_REASS_MAX_PBUFS          64
#define IP_DEFAULT_TTL              255
#define IP_SOF_BROADCAST            1
#define IP_SOF_BROADCAST_RECV       1
#define LWIP_ICMP                   1
#define LWIP_BROADCAST_PING         1
#define LWIP_MULTICAST_PING         1
#define LWIP_RAW                    1
#define TCP_WND                     (4 * TCP_MSS)
#define TCP_MSS                     1460
#define TCP_SND_BUF                 (8 * TCP_MSS)
#define TCP_LISTEN_BACKLOG          1
#define LWIP_NETCONN                0
#define LWIP_SOCKET                 0
#define LWIP_STATS_DISPLAY          1
#define MEM_STATS                   0
#define SYS_STATS                   0
#define MEMP_STATS                  0
#define LINK_STATS                  0
#define ETHARP_TRUST_IP_MAC         0
#define ETH_PAD_SIZE                2

#define LWIP_TCP_KEEPALIVE          1

// Keepalive values, compliant with RFC 1122. Don't change this unless you know what you're doing
#define TCP_KEEPIDLE_DEFAULT        10000UL // Default KEEPALIVE timer in milliseconds
#define TCP_KEEPINTVL_DEFAULT       2000UL  // Default Time between KEEPALIVE probes in milliseconds
#define TCP_KEEPCNT_DEFAULT         9U      // Default Counter for KEEPALIVE probes

#include "mch.h"

#define mem_init()
#define mem_free                    mch_free
#define mem_malloc                  mch_malloc
#define mem_calloc(c, n)            mch_zalloc((c) * (n))
#define mem_realloc(p, sz)          (p)

#define LWIP_DEBUG                  0

#define ETHARP_DEBUG                LWIP_DBG_OFF
#define NETIF_DEBUG                 LWIP_DBG_OFF
#define PBUF_DEBUG                  LWIP_DBG_OFF
#define API_LIB_DEBUG               LWIP_DBG_OFF
#define API_MSG_DEBUG               LWIP_DBG_OFF
#define SOCKETS_DEBUG               LWIP_DBG_OFF
#define ICMP_DEBUG                  LWIP_DBG_OFF
#define INET_DEBUG                  LWIP_DBG_OFF
#define IP_DEBUG                    LWIP_DBG_OFF
#define IP_REASS_DEBUG              LWIP_DBG_OFF
#define RAW_DEBUG                   LWIP_DBG_OFF
#define MEM_DEBUG                   LWIP_DBG_OFF
#define MEMP_DEBUG                  LWIP_DBG_OFF
#define SYS_DEBUG                   LWIP_DBG_OFF
#define TCP_DEBUG                   LWIP_DBG_OFF
#define TCP_INPUT_DEBUG             LWIP_DBG_OFF
#define TCP_OUTPUT_DEBUG            LWIP_DBG_OFF
#define TCP_RTO_DEBUG               LWIP_DBG_OFF
#define TCP_CWND_DEBUG              LWIP_DBG_OFF
#define TCP_WND_DEBUG               LWIP_DBG_OFF
#define TCP_FR_DEBUG                LWIP_DBG_OFF
#define TCP_QLEN_DEBUG              LWIP_DBG_OFF
#define TCP_RST_DEBUG               LWIP_DBG_OFF
#define UDP_DEBUG                   LWIP_DBG_OFF
#define TCPIP_DEBUG                 LWIP_DBG_OFF
#define PPP_DEBUG                   LWIP_DBG_OFF
#define SLIP_DEBUG                  LWIP_DBG_OFF
#define DHCP_DEBUG                  LWIP_DBG_OFF

#endif /* __LWIPOPTS_H__ */

Main function, etc. (mch_main.c)Edit

#include "mch.h"
#include "lwip/inet.h"
#include "lwip/tcp.h"
#include "lwip/ip_frag.h"
#include "lwip/netif.h"
#include "lwip/init.h"
#include "lwip/stats.h"
#include "netif/etharp.h"

struct ip_addr mch_myip_addr;


static mch_timestamp ts_etharp;
static mch_timestamp ts_tcp;
static mch_timestamp ts_ipreass;

// Our network interface structure
static struct netif mchdrv_netif;

// Functions from my netif driver
// Probe function (find the device, return driver private data)
extern int mchdrv_probe(struct mch_pci_dev *, void **, uint8_t *);
// Init function
extern int mchdrv_attach(struct netif *);
// Poll for received frames
extern void mchdrv_poll(struct netif *);

int mch_net_init(void)
    struct ip_addr gw_addr, netmask;
    struct mch_pci_dev * mchdrv_pcidev;
    void * mchdrvnet_priv;
    uint8_t mac_addr[6];
    int err = -1;

    // Hard-coded IP for my address, gateway and netmask
    if (mch_net_aton(MCH_IPADDR_BASE, &mch_myip_addr))
        return -1;
    if (mch_net_aton(MCH_IPADDR_GW, &gw_addr))
        return -1;
    if (mch_net_aton(MCH_IPADDR_NETMASK, &netmask))
        return -1;

    // Initialize LWIP

    // Initialize PCI bus structure

    // Search through the list of PCI devices until we find our NIC
    mchdrv_pcidev = NULL;
    while ((mchdrv_pcidev = mch_pci_next(mchdrv_pcidev)) != NULL) {
        if ((err = mchdrv_probe(mchdrv_pcidev, &mchdrvnet_priv, mac_addr)) == 0)

    if (mchdrv_pcidev == NULL) {
        mch_printf("mch_net_init: network adapter not found\n");
        return -1;

    // Add our netif to LWIP (netif_add calls our driver initialization function)
    if (netif_add(&mchdrv_netif, &mch_myip_addr, &netmask, &gw_addr, mchdrvnet_priv,
                mchdrv_init, ethernet_input) == NULL) {
        mch_printf("mch_net_init: netif_add (mchdrv_init) failed\n");
        return -1;


    // Initialize timer values

    return 0;

// Regular polling mechanism.  This should be called each time through
// the main application loop (after each interrupt, regardless of source).
// It handles any received packets, permits NIC device driver house-keeping
// and invokes timer-based TCP/IP functions (TCP retransmissions, delayed
// acks, IP reassembly timeouts, ARP timeouts, etc.)
void mch_net_poll(void)
    mch_timestamp now;

    // Call network interface to process incoming packets and do housekeeping

    // Process lwip network-related timers.
    if (mch_timestamp_diff(&ts_etharp, &now) >= MCH_ARP_TIMER_INTERVAL) {
        ts_etharp = now;
    if (mch_timestamp_diff(&ts_tcp, &now) >= MCH_TCP_TIMER_INTERVAL) {
        ts_tcp = now;
    if (mch_timestamp_diff(&ts_ipreass, &now) >= MCH_IPREASS_TIMER_INTERVAL) {
        ts_ipreass = now;

// Convert address from string to internal format.
// Return 0 on success; else non-zero
int mch_net_aton(char * str_addr, struct ip_addr * net_addr)
    struct in_addr a;
    int i = inet_aton(str_addr, &net_addr->addr);
    if (!i)
        return -1;
    return 0;

// Main entry point
int main(void)
    [snip other non-lwip initializations]
    mch_timestamp_init();       // Initialize timestamp generator
    while (1) {
        [snip other non-lwip functions]
        mch_wait_for_interrupt();   // Awakened by network, timer or other interrupt
        mch_net_poll();             // Poll network stack


(Please note that the rule separators are 4 spaces rather than a tab, and must be changed to tabs in your implementation)




#Set this to where you have the lwip core module checked out from CVS
#default assumes it's a dir named lwip at the same level as the contrib module

CFLAGS += $(CPPFLAGS) -I$(LWIPDIR)/include -I.              \
    -I$(LWIPARCH)/include -I$(LWIPARCH)/include/arch        \
    -I$(LWIPDIR)/include/ipv4 -I$(MCHINCDIR)

# COREFILES, CORE4FILES: The minimum set of files needed for lwIP.
COREFILES=$(LWIPDIR)/core/mem.c             \
    $(LWIPDIR)/core/memp.c              \
    $(LWIPDIR)/core/netif.c             \
    $(LWIPDIR)/core/pbuf.c              \
    $(LWIPDIR)/core/raw.c               \
    $(LWIPDIR)/core/stats.c             \
    $(LWIPDIR)/core/sys.c               \
    $(LWIPDIR)/core/tcp.c               \
    $(LWIPDIR)/core/tcp_in.c            \
    $(LWIPDIR)/core/tcp_out.c           \
    $(LWIPDIR)/core/udp.c               \
    $(LWIPDIR)/core/dhcp.c              \

CORE4FILES=$(LWIPDIR)/core/ipv4/icmp.c          \
    $(LWIPDIR)/core/ipv4/ip.c           \
    $(LWIPDIR)/core/ipv4/inet.c         \
    $(LWIPDIR)/core/ipv4/ip_addr.c          \
    $(LWIPDIR)/core/ipv4/ip_frag.c          \

# NETIFFILES: Files implementing various generic network interface functions.'
NETIFFILES=$(LWIPDIR)/netif/etharp.c            \
    $(LWIPARCH)/netif/mchdrv.c          \

# LWIPFILES: All the above.
OBJS=$(notdir $(LWIPFILES:.c=.o))


all compile: $(LWIPLIB)
    mkdir -p $(TARGETDIR)
    install $(LWIPLIB) $(TARGETDIR)

.PHONY: all depend compile clean

    $(CC) $(CFLAGS) -c $(@:.o=.c)

    rm -f *.o $(LWIPLIB) .depend*

    $(AR) $(ARFLAGS) $(LWIPLIB) $?

depend dep: .depend

include .depend

.depend: $(LWIPFILES)
    $(CCDEP) $(CFLAGS) -MM $^ > .depend || rm -f .depend

Ad blocker interference detected!

Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.