Skip to content

Bare metal application development

Carlos Moratelli edited this page Jan 25, 2017 · 7 revisions

To develop new applications, go to the ~/prplHypervisor/bare-metal-apps/apps folder and see the blink, ping, pong and other folders. Each folder contains the implementation of a bare-metal application. These applications became guests during the hypervisor execution.

The hypervisor can perform up to 6 bare-metal applications (virtual machines). Each virtual machine has configurable SRAM and FLASH sizes, as demostraded here. To compile different bare-metal applications, modify the input configuration file in platform/&ltplatform_directory>/Makefile, as in the following example:

#Input CFG file for VMs configuration
CFG_FILE = cfg/sample-1VM.cfg

The easiest way to implement a new bare-metal app is to copy the folder of an existing application, for example blink, to a new folder and modify its implementation. Additionally, to compile different applications see the explanation here.

Timer

The hypervisor scheduler quantum can be configured in the scheduler_quantum_ms property (see here). Thus, each VM will perform for scheduler_quantum_ms milliseconds before it is preempted. During its execution, the guest will receive timer interrupts each millisecond. To handle the timer interrupts, the guest must register an interrupt handler calling:

interrupt_register(irq_timer, GUEST_TIMER_INT);

In this case, irq_timer is the interrupt handler.

Inter-VM Communication

The prplHypervisor supports Inter-VM communication through message copies from sender to the target VM. The API is simple and consists of the following calls:

int32_t ReceiveMessage(uint32_t *source, char* buffer, uint32_t bufsz, uint32_t block);
uint32_t SendMessage(uint32_t target, char* buffer, uint32_t size);

Each VM has an identification number (ID) generated by the hypervisor during its initialization. The ID is attributed based on the VM's initialization order. The first VM’s ID is 1, the second is 2, and so on. The ID is used to identify the VMs during communication. A VM can discover its own ID calling the hypercall get_guestid().

The ReiceveMessage() hypercall returns greater than 0 if a message was received. The data is copied to the buffer limited to bufsz bytes. Zero means no data was received. The SendMessage() hypercall returns the number of bytes sent. Zero or less means an error. The following table shows the possible error codes.

Error Code Meaning
MESSAGE_VCPU_NOT_FOUND Destination not found.
MESSAGE_FULL Destination queue full.
MESSAGE_TOO_BIG Message is too big.
MESSAGE_VCPU_NOT_INIT The target VPCU is not ready for communication.

If non-blocking receive is desired, use block equal to zero when calling ReceiveMessage(), otherwise, it will block the application until a message is received.

The application Ping-Pong is an example of inter-VM communication. A message is sent from ping (VM ID 1) to pong (VM ID 2) and back. A cfg file is provided with a configuration sample for inter-VM communication. This cfg file can be found at prplHypervisor/platform/&ltplatform_board>/cfg/ping-pong-sample.cfg. To compile the Ping-Pong example, you need to set the CFG_FILE variable in the Makefile platform to:

#Input CFG file for VMs configuration
CFG_FILE = ../samples_cfg/ping-pong-sample.cfg

Unix-like API

The bare-metal application's execution environment supports a minimal Unix-like API. The available functions are listed below:

int8_t *strcpy(int8_t *dst, const int8_t *src);
int8_t *strncpy(int8_t *s1, int8_t *s2, int32_t n);
int8_t *strcat(int8_t *dst, const int8_t *src);
int8_t *strncat(int8_t *s1, int8_t *s2, int32_t n);
int32_t strcmp(const int8_t *s1, const int8_t *s2);
int32_t strncmp(int8_t *s1, int8_t *s2, int32_t n);
int8_t *strstr(const int8_t *string, const int8_t *find);
int32_t strlen(const int8_t *s);
int8_t *strchr(const int8_t *s, int32_t c);
int8_t *strpbrk(int8_t *str, int8_t *set);
int8_t *strsep(int8_t **pp, int8_t *delim);
int8_t *strtok(int8_t *s, const int8_t *delim);
void *memcpy(void *dst, const void *src, uint32_t n);
void *memmove(void *dst, const void *src, uint32_t n);
int32_t memcmp(const void *cs, const void *ct, uint32_t n);
void *memset(void *s, int32_t c, uint32_t n);
int32_t strtol(const int8_t *s, int8_t **end, int32_t base);
int32_t atoi(const int8_t *s);
int8_t *itoa(int32_t i, int8_t *s, int32_t base);
int32_t puts(const int8_t *str);
int8_t *gets(int8_t *s);
int32_t abs(int32_t n);
int32_t random(void);
void srand(uint32_t seed);
int printf(const int8_t *fmt, ...);
int sprintf(int8_t *out, const int8_t *fmt, ...);
void udelay(uint32_t usec);
void putchar(int32_t value);
uint32_t getchar(void);

UART access

Some platforms may have a secondary UART port. Use the following call to select between the available UART ports:

int32_t serial_select(uint32_t serial_number);

Verify the available UART ports in your platform in the following file:

prplHypervisor/bare-metal-app/platform/<especific platform directory>/include/uart.h

Virtualized I/O

For a complete guest memory space separation use the property device_mapping to describe which peripherals the guest can access through virtualized I/O. On the guest, use read()/write() calls for peripheral access.

For example:

uint32_t v;
write(TRISHCLR, 1);
v = read(LATD);

Implementing device drivers at guest level

Some hypervisor features are useful when implementing device drivers at guest level. Combining device_mapping and interrupt_redirect properties it is possible to keep a complete isolation between guests but still allowing for them to have access to hardware controllers.

Use the interrupt_redirect to redirect hardware interrupt events to guests. On the guest, it must to register an interrupt handler to deal with the event, also called virtual interrupt. Once an interrupt is handled by the hypervisor, it disables it (cleaning the IEC bit) and inject a virtual interrupt to the guest. In order to receive further interrupts, the guest must re-enable the interrupt calling the hypercall reenable_interrupt().

The bare-metal application int_redirect implements an example of an UART driver that receives interrupts when characters are ready to be read from the input. Additionally, this driver uses device_mapping property for a complete isolation between guests. The redirection.cfg file has the setup configuration and the redirection.c on the int_redirection application directory has the instructions to use it.

Available hypercalls

This is the list of all hypercalls available to the guests.

/* Read from privileged address  */
int32_t read(uint32_t addr);

/* Write to privileged address */
int32_t write(uint32_t reg, uint32_t value);

/* InterVM send message */
int32_t ipc_send(uint32_t targed_id, uint8_t* msg, uint32_t size);
 
 /* interVM recv message  */
uint32_t ipc_recv(uint32_t source_id, uint8_t msg);
 
/* Get own guest ID  */
uint32_t get_guestid();
 
 /* Ethernet link checker */
uint32_t eth_watch();

/* Ethernet get mac */
eth_mac(uint8_t *msg)
 
/* Ethernet send message  */
int32_t eth_send_frame(uint8_t *msg,  uint32_t size);
 
 /* Ethernet recv message  */
int32_t eth_recv_frame(uint8_t *msg);
 
/* USB get device descriptor   */
int32_t usb_device_descriptor(uint8_t *descriptor, uint32_t size);
 
/* USB polling. Updates the USB state machines.   */
int32_t usb_polling() 
 
/* USB send data  */
int32_t usb_send(uint8_t *msg, uint32_t size)

/* Re-enable an interrupt */
int32_t reenable_interrupt(uint32_t irq);