The CoreModel API provides over-the-internet support to attach remote peripheral models to a virtual Machine(VM) bus interfaces. There are multiple standard device interfaces supported including UART, I2C, SPI, CAN, GPIO, and USB Host.
------------- ------------ -------------------
| CoreModel | <-> | Internet | <-> | Virtual Machine |
------------- ------------ -------------------
Not all machine types support CoreModel API interface as they were built before CoreModel was implemented. The following machines have been updated for CoreModel support.
| Interface | I.MX93 | I.MX8 | RD-1AE | RPI4B | RPI5 | STM32 | S32K3 |
|---|---|---|---|---|---|---|---|
| GPIO | x | x | x | x | x | x | |
| UART | x | x | x | x | x | x | x |
| I2C | x | x | x | x | x | x | |
| SPI | x | x | x | x | x | x | |
| CAN | x | x | x | ||||
| USBH | x | x | |||||
| ETH | x | x | |||||
| EVENT | x |
Additionally, the following models have support for CoreModel access to serial ports but do not have full CoreModel support: Corstone-1000, Cortex-R52 System, Cortex-R82 System
New machine types will support CoreModel API interface.
These API's and helper functions provide simple way to connect and interact with a VMs bus interfaces.
The coremodel_connect/disconnect functions provide a standard way to connect and disconnect over the network to the VM.
The coremodel_connect function takes <void **priv> to store an instance of coremodel state and a <target>string
formatted as "ip:port" for example "10.10.0.3:1900" and returns 0 on success.
The IP to use with CoreModel is the services IP of the VM and the port is 1900. You can find the services IP in the Connect tab of the VM.
The coremodel_disconnect requries the pointer <void *priv> to the state for a given instance of coremodel to disconnect from the network and free the state.
/* Connect to a VM. */intcoremodel_connect(void**priv, constchar*target);
/* Close connection to a VM. */voidcoremodel_disconnect(void*priv);The coremodel_mainloop helper function provides a simple implementation of the device model main loop.
Primarily the main loop handles processing the connection send and receive between the device model and the VM interface.
coremodel_mainloop takes the parameters <void *priv> which is the state of the coremodel instance and usec setting how much time to spend in the loop.
The value of usec is specified in microseconds and if set to -1 the main loop will run indefinitely.
The main loop will return an error flag of 0 on success. Helper function coremodel_mainloop wraps the usage of the file descriptor functions.
intcoremodel_mainloop(void*priv, long longusec);The file descriptor functions set and process the read and write buffers of the attached device model.
/* Prepare fd_sets for select(2). * nfds current index of maximum fd in sets + 1 * readfds readfds to update * writefds writefds to update * Returns new nfds. */intcoremodel_preparefds(intnfds, fd_set*readfds, fd_set*writefds);
/* Process fd_sets after select(2). * readfds readfds to process * writefds writefds to process * Returns error flag. */intcoremodel_processfds(fd_set*readfds, fd_set*writefds);Detach any device model by handle from the VM.
/* handle handle of UART/I2C/SPI/GPIO interface */voidcoremodel_detach(void*handle);Device list functions provide a way to enumerate available bus interfaces of the VM into an array and free that array.
The function coremodel_list returns an array of coremodel_device_list_t data structure and is terminated by the last element member type set to COREMODEL_INVALID.
/* Enumerates devices available in VM. * Returns invalid-terminated array of device structs. The array, as well as * names in it, is allocated by malloc(3). */#defineCOREMODEL_UART 0
#defineCOREMODEL_I2C 1
#defineCOREMODEL_SPI 2
#defineCOREMODEL_GPIO 3
#defineCOREMODEL_USBH 4
#defineCOREMODEL_CAN 5
#defineCOREMODEL_INVALID (-1)
typedefstruct {
inttype; /* one of COREMODEL_* constants */char*name; /* name used to attach to the device */unsignednum; /* number of chip selects (SPI) or pins (GPIO) */
} coremodel_device_list_t;
coremodel_device_list_t*coremodel_list(void*priv);
/* Frees a device list */voidcoremodel_free_list(coremodel_device_list_t*list);All attach functions require the pointer to the coremodel instance <priv> and the a correct <name> be provided to generate a handle for the VMs interface.
The correct name can be retrieved from device list.
A proper <func> data structure for the specific interface is required.
Any attach function will return NULL on failure.
If the device model being attached to one of the interfaces does not need any independent state structure or specific value for operation then NULL can be provided to <ifpriv>.
The CoreModel UART APIs provides the ability to attach a single device to any available virtual UART on the VM.
The following data structure represents required functions that need to be provided to coremodel_attach_uart for a device to interface with the VMs UART.
typedefstruct {
/* Called by CoreModel to transmit bytes. Return a >0 number to accept as * many bytes, or 0 to stall Tx interface (it will have to be un-stalled * with coremodel_uart_txrdy). */int (*tx)(void*priv, unsignedlen, uint8_t*data);
/* Called by CoreModel to signal a BREAK condition on UART line. */void (*brk)(void*priv);
/* Called by CoreModel to unstall Rx interface. */void (*rxrdy)(void*priv);
} coremodel_uart_func_t;Stub functions for brk and rxrdy can be provided if they are not required for the device model operation.
Attach to a virtual UART and returns the handle.
void*coremodel_attach_uart(void*priv, constchar*name, constcoremodel_uart_func_t*func, void*ifpriv);Attempt to send data over the virtual UART.
intcoremodel_uart_rx(void*uart, unsignedlen, uint8_t*data);Provide <uart> the attached handle of the UART interface.
The <data> and <len> of the array to send to the interface.
Returns a number >0 of how many bytes were accepted if 0 then the interface is stalled. CoreModel will call func->rxrdy to un-stall the device.
Unstall a stalled Tx interface of the <uart> handle.
voidcoremodel_uart_txrdy(void*uart);The CoreModel I2C APIs provides the ability to attach multiple devices to any available virtual I2C bus on the VM. The virtual I2C bus supports a maximum of 128 devices.
The data structure below is allocated and owned by the device model.
typedefstruct {
/* Called by CoreModel to notify of a START to a device; return -1 to NAK, * 0 to stall, 1 to accept. A stalled interface will have to be un-stalled * with coremodel_i2c_ready. */int (*start)(void*priv);
/* Called by CoreModel to WRITE bytes. Return a >0 number to accept as * many bytes, -1 to NAK, or 0 to stall interface (it will have to be * un-stalled with coremodel_i2c_ready). */int (*write)(void*priv, unsignedlen, uint8_t*data);
/* Called by CoreModel to READ bytes. Return a >0 number to produce as * many bytes, or 0 to stall interface (it will have to be un-stalled * with coremodel_i2c_ready). */int (*read)(void*priv, unsignedlen, uint8_t*data);
/* Called by CoreModel to notify of a STOP to a device. */void (*stop)(void*priv);
} coremodel_i2c_func_t;The virtual I2C bus only supports 7-bit addresses that are defined <addr> when the device is attached.
Depending on the VM there could already be other devices on the bus and those addresses can not be used.
<flags> can be set to notify the master of non standard behavior of the device.
#defineCOREMODEL_I2C_START_ACK 0x0001 /* device must ACK all starts */#defineCOREMODEL_I2C_WRITE_ACK 0x0002 /* device must ACK all writes */void*coremodel_attach_i2c(void*priv, constchar*name, uint8_taddr, constcoremodel_i2c_func_t*func, void*ifpriv, uint16_tflags);Provide unsolicited data for lower access latency.
intcoremodel_i2c_push_read(void*i2c, unsignedlen, uint8_t*data);Push unsolicited I2C READ <data> of <len> in bytes to the <i2c> handle.
Returns the number of accepted bytes by the host controller.
Signal CoreModel that the <i2c> handle of the interface is unstalled and can call func->start/write/read again.
voidcoremodel_i2c_ready(void*i2c);The CoreModel SPI APIs provides the ability to attach multiple devices to any available virtual SPI bus on the VM.
The maximum amount of devices the virtual SPI bus supports can be different between VMs or even instances of SPI masters.
To determine the maximum <num> of supported devices for the bus use device list. described above
typedefstruct {
/* Called by CoreModel to notify of a CS pin change. */void (*cs)(void*priv, unsignedcsel);
/* Called by CoreModel to write and read bytes. Return a >0 number to * accept (and produce) as many bytes, or 0 to stall interface (it will * have to be un-stalled with coremodel_spi_ready). */int (*xfr)(void*priv, unsignedlen, uint8_t*wrdata, uint8_t*rddata);
} coremodel_spi_func_t;Attach a device to the VMs virtual SPI bus. Since multiple devices can be attached to the same SPI interface the chip select index <csel> must be >= 0 and < <num> max devices.
VMs could have devices already on the SPI bus and their chip select index can not be used as they are hardwired inside the VM.
<flags> can be set to notify the master of non standard behavior of the device.
#defineCOREMODEL_SPI_BLOCK 0x0001 /* device must handle >1 byte transfers */void*coremodel_attach_spi(void*priv, constchar*name, unsignedcsel, constcoremodel_spi_func_t*func, void*ifpriv, uint16_tflags);Notify CoreModel that the <spi> handle of the interface is unstalled and can call func->xfr again.
voidcoremodel_spi_ready(void*spi);The CoreModel CAN APIs provides the ability to interface multiple devices to any available virtual CAN bus on the VM. CAN 2.0, CAN FD, and CAN XL protocols are supported by the API independent of controller support.
#defineCAN_CTRL1_SEC (1ul << 59)
#defineCAN_CTRL1_SDT_MASK (0xFFul << CAN_CTRL1_SDT_SHIFT)
#defineCAN_CTRL1_SDT_SHIFT 51
#defineCAN_CTRL1_VCID_MASK (0xFFul << CAN_CTRL1_VCID_SHIFT)
#defineCAN_CTRL1_VCID_SHIFT 43
#defineCAN_CTRL1_PRIO_MASK (0x7FFul << CAN_CTRL1_PRIO_SHIFT)
#defineCAN_CTRL1_PRIO_SHIFT 32
#defineCAN_CTRL1_AF_MASK (0xFFFFFFFFul << CAN_CTRL1_AF_SHIFT)
#defineCAN_CTRL1_AF_SHIFT 0
#defineCAN_CTRL_XLF (1ul << 49)
#defineCAN_CTRL_FDF (1ul << 48)
#defineCAN_CTRL_ID_MASK (0x7FFul << CAN_CTRL_ID_SHIFT)
#defineCAN_CTRL_ID_SHIFT 36
#defineCAN_CTRL_RTR (1ul << 35)
#defineCAN_CTRL_IDE (1ul << 34)
#defineCAN_CTRL_EID_MASK (0x3FFFFul << CAN_CTRL_EID_SHIFT)
#defineCAN_CTRL_EID_SHIFT 16
#defineCAN_CTRL_ERTR (1ul << 15)
#defineCAN_CTRL_EDL (1ul << 14)
#defineCAN_CTRL_BRS (1ul << 12)
#defineCAN_CTRL_ESI (1ul << 11)
#defineCAN_CTRL_DLC_MASK (0x7FFul << CAN_CTRL_DLC_SHIFT)
#defineCAN_CTRL_DLC_SHIFT 0
#defineCAN_ACK 0
#defineCAN_NAK 1
#defineCAN_STALL (-1)
typedefstruct {
int (*tx)(void*priv, uint64_t*ctrl, uint8_t*data); /* return one of CAN_ACK, CAN_NAK, CAN_STALL */void (*rxcomplete)(void*priv, intnak);
} coremodel_can_func_t;Attach a device to a virtual CAN bus.
void*coremodel_attach_can(void*priv, constchar*name, constcoremodel_can_func_t*func, void*ifpriv);Send CAN packet over the virtual <can> interface. The <data> portion of the packet is optional if control word <ctrl> DLC != 0.
coremodel_can_rx returns 0 on success if the bus is not available 1 will be returned.
intcoremodel_can_rx(void*can, uint64_t*ctrl, uint8_t*data);Unstall the stalled virtual interface <can> signaling CoreModel that func->tx can be called once again.
voidcoremodel_can_ready(void*can);The CoreModel GPIO APIs provides the ability to interact with the VMs GPIO pins logical or voltage values. Caution should be taken when manipulating GPIO pins that are already being used internally by the VM as this could cause undefined behavior. The count of GPIO pins can be determined using device list. described above
typedefstruct {
/* Called by CoreModel to update voltage on a GPIO pin. */void (*notify)(void*priv, intmvolt);
} coremodel_gpio_func_t;Attach to a specified <pin> index in the bank of GPIO pins.
void*coremodel_attach_gpio(void*priv, constchar*name, unsignedpin, constcoremodel_gpio_func_t*func, void*ifpriv);Set a tri-state driver on a GPIO <pin> interface, enabling or disabling the <drven><mvolt> value in millivolts.
voidcoremodel_gpio_set(void*pin, unsigneddrven, intmvolt);The CoreModel USBH APIs provides the ability to interface multiple devices to any available virtual USB Bus.
/* USB Host (connect a local USB Device to a Host inside VM) */#defineUSB_TKN_OUT 0
#defineUSB_TKN_IN 1
#defineUSB_TKN_SETUP 2
#defineUSB_XFR_NAK (-1)
#defineUSB_XFR_STALL (-2)
typedefstruct {
/* Called by CoreModel on USB bus reset. */void (*rst)(void*priv);
/* Called by CoreModel to perform a USB transfer. Return a >0 number to * accept / produce as many bytes, or USB_XFR_NAK to pause interface, or * USB_XFR_STALL to stall interface (create error condition). A paused * interface will have to be un-paused with coremodel_usbh_ready. */int (*xfr)(void*priv, uint8_tdev, uint8_tep, uint8_ttkn, uint8_t*buf, unsignedsize, uint8_tend);
} coremodel_usbh_func_t;Attach to a virtual USB host <port> index at a requested connection <speed>.
VMs could have devices already on the USB bus and the port index those device are attached to should not be used as they are hardwired inside the VM.
#defineUSB_SPEED_LOW 0
#defineUSB_SPEED_FULL 1
#defineUSB_SPEED_HIGH 2
#defineUSB_SPEED_SUPER 3
void*coremodel_attach_usbh(void*priv, constchar*name, unsignedport, constcoremodel_usbh_func_t*func, void*ifpriv, unsignedspeed);Unstall a stalled virtual USB interface end point <ep> with the token <tkn> notifying CoreModel can call func->xfr once again.
voidcoremodel_usbh_ready(void*usb, uint8_tep, uint8_ttkn);The CoreModel ETH APIs provides the ability to detach a interface from the virtual switch within a CHARM project, allowing local capture and injection of arbitrary ethernet packets.
The packet is a raw IEEE802.3 ethernet frame, without the trailing FCS.
typedefstruct {
/* Called by CoreModel when the remote side has transmitted a packet */int (*tx)(void*priv, unsignedlen, uint8_t*data);
/* Called by CoreModel to unstall the RX interface */void (*rxrdy)(void*priv);
} coremodel_eth_func_t;Attach to the virtual ethernet controller <name>.
void*coremodel_attach_eth(void*cm, constchar*name, constcoremodel_eth_func_t*func, void*priv);Call this function with the raw packet data the associated device will receive.
intcoremodel_eth_rx(void*eth, unsignedlen, uint8_t*data);The CoreModel Event Bus APIs provides the ability to listen for events from the VM, and send events to the VM. Please see the event bus documentation for additional details.
typedefstruct {
/* Called by CoreModel to update event state. */void (*update)(void*priv, uint64_tdata0, uint64_tdata1, unsignedinitial);
/* Called by CoreModel when atomic with response is requested. */void (*atresp)(void*priv, uint64_tdata0, uint64_tdata1);
} coremodel_event_func_t;Register interest in a specific named event. The update function will be invoked when the event state changes. The atresp function is called when a special "atomic" event is called.
void*coremodel_attach_event_name(void*cm, constchar*evtname, constcoremodel_event_func_t*func, void*priv);Send a signal to an event handle. data0 and data1 are opaque and must be properly formatted for the specific event type. chgonly will only signal an event if the data has changed from the last time it was signaled.
voidcoremodel_event_signal(void*evt, uint64_tdata0, uint64_tdata1, unsignedchgonly);Send an signal to an event handle that operates on the previous value. If EVENT_OP_RESP is specified then the atresp callback will be invoked with the event data prior to this operation.
#defineEVENT_OP_XCHG 0
#defineEVENT_OP_ADD 1
#defineEVENT_OP_SUB 2
#defineEVENT_OP_AND 3
#defineEVENT_OP_OR 4
#defineEVENT_OP_XOR 5
#defineEVENT_OP_MIN 6
#defineEVENT_OP_MAX 7
#defineEVENT_OP_SUBMIN 8
#defineEVENT_OP_RESP 0x40
voidcoremodel_event_atomic(void*evt, uint64_tdata0, uint64_tdata1, unsignedop);Send a wire-type signal to an event handle.
#defineEVENT_WIRE_VALUE_LOW 0x0000
#defineEVENT_WIRE_VALUE_HIGH 0x0001
#defineEVENT_WIRE_VALUE_PULSE 0x0002
#defineEVENT_WIRE_VALUE_TOGGLE 0x4000
#defineEVENT_WIRE_VALUE_FORCE 0x8000
voidevent_signal_wire(void*evt, unsignedval);These events specify a voltage that is acted upon (ie "converted") by an Analog-Digital Converter (ADC). The value is specified in microvolts, represented
using a signed 32-bit integer packed into the <data0> field passed into coremodel_event_signal().
#defineEVENT_ADC_DATA_MASK (0xffffffffu)The coremodel.py module uses the libcoremodel.so library that is built using the default Makefile. The python wrapper abstracts the c library and necessary type conversions to make it easier to use in a more pyhonic object oriented manner. The coremodel module allows each coremodel instance and device to be its own object.
The CoreModel class is the base class that manages a single network instance and all attached devices.
Each CoreModel instance requires a <name> for the instance this is purely for the user to help keep track of multiple instances.
Cormodel connection is handled on initialization and uses the ip <address> and <port> to establish a connection.
Disconnecting an instance will happen automatically from the CoreModel class __del__ method.
CoreModel uses python ctypes to load libcoremodel.so from <libpath> location.
cm=CoreModel(name, address, port, libpath)CoreModel class provides a single attach function that takes a device <obj> to be attached.
This attach function handles all coremodel device types.
The attached devices will automatically detach from the CoreModel class in __del__ method if they are not detached manually.
cm.attach(obj)
cm.detach(obj)CoreModel class has two ways to use the mainloop, the first would be it directly.
The cycle_time instance attribute is used as the <usec> parameter for the mainloop.
cm.cycle_time=100000cm.mainloop()The other way to use the main loop is to kick off an independent thread using the start method.
CoreModel class inherits threading.Thread and has a basic run method implemented.
Stopping the thread from running there is a stop_event instance attribute that holds a threading.Event to signal the thread to return allowing it to be joined.
cm.cycle_time=100000cm.start()
foriinrange(5):
time.sleep(1)
cm.stop_event.set()
cm.join()With the CoreModel module each supported virtual bus has their own device class that contains the base structure and methods for a given device.
These device classes will be used as a base class for a given device model. Because the CoreModel module wrapps the coremodel library and requires type conversions,
functions that will be called by coremodel must match their base class naming convention replacing their place holder methods.
This would include read/write or xfer functions, for example class I2C0(CoreModelI2C) will need to define start/stop write/read methods with the necessary device code.
The instance of the object is handled as the functions <ifpriv> in CoreModel that is passed on attach allowing the self of the object to be maintained,
this maintains that each object can handle the devices state normally through using self.
Coremodel functions that are provided by the library like coremodel_uart_rx have their names shortened to just rx and get their method populated during attach.
This allows each object to call the appropriate method directly from the object e.g. uart.rx(byte_data) to send data to the controller.
If a function is not needed like uart.rxrdy it does not need to be defined in the child class as the parent populates a stub function.
Python examples for coremodel can be found in their respective example folder along side the c examples.
The following are basic device classes using the CoreModel device class as the base class.
classI2C(CoreModelI2C):
def__init__(self, busname, address, devname):
super().__init__(busname=busname, address=address)
defstart(self):
print("start")
return1defwrite(self, data):
returnlen(data)
defread(self, data):
returnlen(data)
defstop(self):
print("stop")
# added by coremodel during attachi2c.ready()classUart(CoreModelUart):
def__init__(self, busname):
super().__init__(busname=busname)
deftx(self, data):
print(data.decode("utf-8"), end="")
returnlen(data)
defrxrdy(self):
passdefbrk(self):
pass# added by coremodel during attachuart.rx(data)classSpi(CoreModelSpi):
def__init__(self, busname, cs, devname):
super().__init__(busname=busname, cs=cs)
self.devname=devnamedefcs(self, csel):
print("cs", csel)
defxfr(self, wrdata, rddata):
returnlen(rddata)
# added by coremodel during attachspi.ready()classGpio(CoreModelGpio):
def__init__(self, busname, pin):
super().__init__(busname=busname, pin=pin)
defnotify(self, mvolt):
pass# added by coremodel during attachgpio.set(driven, mvolt)classUsbh(CoreModelUsbh):
def__init__(self, busname, port, speed):
super().__init__(busname=busname, port=port, speed=speed)
defrst(self):
passdefxfr(self, dev, ep, tkn, buf, size, end):
pass# added by coremodel during attachusbh.readyclass Can(CoreModelCan):
def __init__(self, busname):
super().__init__(busname = busname)
def tx(self, ctrl, data):
dlen = CAN_DATALEN[(ctrl[0] & CAN_CTRL_DLC_MASK) >> CAN_CTRL_DLC_SHIFT]
return CAN_ACK
# added by coremodel during attach
can.ready
can.rx
Class Eth(CoreModelEth):
def __init__(self, busname):
super().__init__(busname = busname)
def tx(self, length, data):
pass
# added by coremodel during attach
eth.rxrdy
eth.rx
class Event(CoreModelEvent):
def __init__(self,eventname):
super().__init__(eventname=eventname)
def update(self, data0, data1, initial):
print("event[{}] data0 {} data1 {} initial {}".format(self.name, data0, data1, initial))
def atresp(self, data0, data1):
print("atevent[{}] data0 {} data1 {} ".format(self.name, data0, data1))
# added by coremodel during attach
event.signal