550 likes | 4.95k Views
Kista, Sweden 26-27 March 2007 Porting Contiki Crash Course Porting Contiki Contiki is designed to be easy to port Porting the base system is instant No architecture-dependent code Stuff that needs porting: The clock module I/O (network drivers, serial driver, sensors, …) ELF loader
E N D
Kista, Sweden 26-27 March 2007 Porting ContikiCrash Course
Porting Contiki • Contiki is designed to be easy to port • Porting the base system is instant • No architecture-dependent code • Stuff that needs porting: • The clock module • I/O (network drivers, serial driver, sensors, …) • ELF loader • Multi-threading • Possible C compiler-quirks • Makefiles
This presentation • I will present the ideal Contiki port • Not all ports look exacly like this • Ports under development typically do not look like this • However, we should aim for it • Having this structure in mind during porting makes things easier
Overview • Directory structure • The native port • The clock module • contiki-conf.h • Network drivers • Sensors • Integrating into the Contiki build system • ELF loader • Multi-threading
Directory structure • Two directories are of importance • cpu/ • platform/ • cpu/ contains code common to all platforms with the same microcontroller • platform/ contains platform specific code • Example: platform/sky, platform/esb, and cpu/msp430
The native port • The simplest port; can be used as a template • ls platform/native • Makefile.native • clock.c • contiki-conf.h • contiki-main.c • dev/
The native port (contd) • Makefile.native • For integration into the Contiki build system, must have the name Makefile.(platform name) • clock.c • Architecture-specific clock code • contiki-conf.h • Configuration options • contiki-main.c • main(): boot-up code and the main loop • dev/ • Device drivers
Other subdirectories used in platform/ subdirs • apps/ • Platform-specific applications • Example: platform/sky/apps/burn-nodeid.c • net/ • Platform-specific network code/drivers • Example: platform/esb/net/tr1001-rime.c
The clock module • The clock module is hardware-specific • Generic header file in core/sys/clock.h • clock.c can be either in platform/ or in cpu/ • Example: platform/ for native, cpu/ for MSP430 ports • Three functions • clock_init() • clock_time() • clock_delay()
clock_init() • Initializes the clock module • Called by the boot-up code • Configures hardware timers, interrupts
clock_time() • Should return an increasing tick counter • The tick typically is increased in an interrupt handler • Implemented in clock.c • Interrupt handler must wake up CPU if there are etimers pending • etimer_request_poll()
Example: cpu/msp430/clock.c static volatile clock_time_t count = 0; interrupt(TIMERA1_VECTOR) timera1 (void) { if(TAIV == 2) { TACCR1 += INTERVAL; /* Set next hw interrupt */ ++count; /* Increase tick counter */ /* If there are pending etimers, poll etimer process, wake up CPU. */ if(etimer_pending() && (etimer_next_expiration_time() - count - 1) > MAX_TICKS) { etimer_request_poll(); LPM4_EXIT; } } } clock_time_t clock_time(void) { return count; /* Return tick count */ }
clock_delay() • Delay for a platform-specific amount of time • This function is only used by device drivers and other low-level code
Example: cpu/msp430/clock.c /** * Delay the CPU for a multiple * of 2.83 us. */ void clock_delay(unsigned int i) { asm("add #-1, r15"); asm("jnz $-2"); }
Workplan • Copy exiting clock.c (cpu/msp430/dev/clock.c) • Modify • Look at application notes for CPU to see how timer interrupt works
The future of the clock module • Periodic ticking is inefficient • The clock module might be replaced by the rtimer module in the future • The rtimer code will have to be backed by code similar to the current clock.c code
contiki-conf.h • Found in platform/(name of platform) • Example: platform/native/contiki-conf.h • Contains platform-specific configuration options • C compiler configuration • C types • uIP configuration • Clock configuration: clock_time_t, CLOCK_CONF_SECOND • Sometimes used as a kitchen sink • Workplan: copy platform/native/contiki-conf.h
Network device drivers • Two operations: • Send out packet • Receive packets • Driver design: • Split code into three files • Hardware-specific code • Interface between uIP and hw specific code • Interface between Rime and hw specific code
Example: core/dev/simple-cc2420.c /* Initialize driver */ void simple_cc2420_init(void); /* Setup a function to be called when a packet has arrived */ void simple_cc2420_set_receiver(void (* recv)(void)); /* Read arrived packet into a buffer */ int simple_cc2420_read(u8_t *buf, u8_t bufsize); /* Send a packet from a buffer */ int simple_cc2420_send(const u8_t *data, u8_t len);
Example: core/dev/simple-cc2420.c • Hardware driver does not add any headers • Only handles reception/sending of raw bytes • Any 802.15.4 headers (optional) could be handled by upper layer module • Add function for switching on CC2420 address decoding
Example: core/dev/simple-cc2420-rime.c • Uses the hardware-specific code in simple-cc2420.c to send and receive packets • No hardware-specific code outside of lowest level driver
core/dev/simple-cc2420-rime.c static void receiver(void) { u8_t len; rimebuf_clear(); len = simple_cc2420_read(rimebuf_dataptr(), RIMEBUF_SIZE); if(len > 0) { rimebuf_set_datalen(len); rime_input(); } } void simple_cc2420_rime_init(void) { simple_cc2420_set_receiver(receiver); } void rime_driver_send(void) { simple_cc2420_send(rimebuf_hdrptr(), rimebuf_totlen()); }
The equivalent uIP driver static void receiver(void) { u8_t len; len = simple_cc2420_read(uip_buf, UIP_BUFSIZE); if(len > 0) { uip_len = len; tcpip_input(); } } void simple_cc2420_rime_init(void) { simple_cc2420_set_receiver(receiver); } int simple_cc2420_uip_send(void) { simple_cc2420_send(&uip_buf[UIP_LLH_LEN], uip_len); }
Overall comments • Keep things simple • Keep low-level code in a seprate file • As little low-level code as possible • Create simple interfaces between the low-level code and Rime/uIP driver • Workplan: • Copy existing driver (simple-cc2420, tr1001) • Modify
Sensor drivers in Contiki • When a sensor changes an event is broadcast to all processes • Button press, PIR movement detected, … • Generic code for sending events in core/lib/sensors.c • Low-level code in separate file
Example: platform/sky/dev/button-sensor.c HWCONF_PIN(BUTTON, 2, 7); /* Button is on port 2, pin 7 */ HWCONF_IRQ(BUTTON, 2, 7); /* Connect this to an interrupt */ static void init(void) { timer_set(&debouncetimer, 0); BUTTON_IRQ_EDGE_SELECTD(); /* Trigger interrupt on down flank */ BUTTON_SELECT(); /* Select this pin */ BUTTON_MAKE_INPUT(); /* Make button pin an input pin */ } static int irq(void) { if(BUTTON_CHECK_IRQ()) { /* Is this why we got an interrupt? */ if(timer_expired(&debouncetimer)) { timer_set(&debouncetimer, CLOCK_SECOND / 4); sensors_changed(&button_sensor); /* Send event to all processes */ return 1; } } return 0; } /* … more stuff here … */ SENSORS_SENSOR(button_sensor, BUTTON_SENSOR, init, irq, activate, deactivate, active, value, configure, status);
In platform/sky/contiki-sky-main.c #include "dev/button-sensor.h" SENSORS(&button_sensor); /* … */ int main(void) { /* … */ process_start(&sensors_process, NULL); button_sensor.activate(); /* … */ }
Workplan • Copy platform/sky/dev/button-dev.c • Modify
The future of the sensor API • Works well for digital sensors • Does not work that well for analog sensors • May need to be updated in the future
Platform-specific Makefile • platform/name/Makefile.name • Included by top-level Makefile.include • Never invoked directly • Not supposed to run “make” in platform directory • Makefile specifies source files, directories, … • Usually includes CPU-specific Makefile • cpu/name/Makefile.name
Example: platform/sky/Makefile.sky # … CONTIKI_TARGET_DIRS = . dev apps net # … CONTIKI_TARGET_SOURCEFILES += # … include $(CONTIKI)/cpu/msp430/Makefile.msp430 # …
CPU-specific Makefile • cpu/name/Makefile.name • Definitions for CPU-specific source files • Definitions for rules for C compiler • CFLAGS, CC • Definition of vpath • Where (GNU) make looks for .c files
Example: cpu/x86/Makefile.x86 CONTIKI_SOURCEFILES += mtarch.c elfloader-stub.c ### Compiler definitions CC = gcc LD = gcc AS = as OBJCOPY = objcopy STRIP = strip CFLAGSNO = -I. -I$(CONTIKI) -I$(CONTIKI)/core \ -I$(CONTIKI_CPU) \ -I$(CONTIKI)/platform/$(TARGET) \ ${addprefix -I,$(APPDIRS)} $(APP_INCLUDES) \ -DWITH_UIP -DWITH_ASCII \ -Wall -g -I. -I/usr/local/include CFLAGS += $(CFLAGSNO) -O LDFLAGS = -Wl,-Map=contiki.map,-export-dynamic # …
Integrating into build system • Keep things simple • Copy entire platform/native directory • Modify • Copy entire cpu/x86 directory • Modify
ELF loader • Only needs porting for new CPUs • Designed for portability • Ported to MSP430, AVR, x86, ARM7
ELF loader architecture-specific interface /* Allocate RAM for data and bss segments */ void *elfloader_arch_allocate_ram(int size); /* Allocate ROM for text segment (code) */ void *elfloader_arch_allocate_rom(int size); /* Relocate one relocation entry. */ void elfloader_arch_relocate(int fd, unsigned int sectionoffset, char *sectionaddr, struct elf32_rela *rela, char *addr); /* Write code to previously allocated ROM */ void elfloader_arch_write_rom(int fd, unsigned short textoff, unsigned int size, char *mem);
Workplan • Copy core/loader/elfloader-stub.c • Modify • Look at core/loader/elfloader-msp430.c, elfloader-avr.c, elfloader-x86.c • Need knowledge of the ELF format for the CPU
The future of the ELF loader • The current interface cannot nicely hold multiple allocations • But Contiki itself supports it • API change will be needed • Current ELF loader does not properly process relocation entries • New ELF loader code on the way
Multi-threading library • Must be ported to new CPUs • But: not used very often • Porting often not required • Split into two parts (Contiki-style) • mt.c • mtarch.c
mtarch API /* Thread structure */ struct mtarch_thread; /* Initialize the architecture-specific code */ void mtarch_init(void); /* Remove & clean up */ void mtarch_remove(void); /* “Start” a thread (setup its stack) */ void mtarch_start(struct mtarch_thread *thread, void (* function)(void *data), void *data); /* Switch to the specified thread */ void mtarch_exec(struct mtarch_thread *thread); /* Yield the current thread */ void mtarch_yield(void); /* Stop preemption */ void mtarch_pstop(void); /* Start preemption */ void mtarch_pstart(void);
Example: cpu/msp430/mtarch.c void mtarch_start(struct mtarch_thread *t, void (*function)(void *), void *data) { t->sp = &t->stack[MTARCH_STACKSIZE - 1]; *t->sp = (unsigned short)mt_exit; --t->sp; *t->sp = (unsigned short)function; --t->sp; /* Space for registers. */ t->sp -= 11; } static struct mtarch_thread *running; void mtarch_exec(struct mtarch_thread *t) { running = t; sw(); running = NULL; } void mtarch_yield(void) { sw(); }