diff --git a/examples/zephyr/virtio_i2c_ram/CMakeLists.txt b/examples/zephyr/virtio_i2c_ram/CMakeLists.txt new file mode 100644 index 0000000..f36d836 --- /dev/null +++ b/examples/zephyr/virtio_i2c_ram/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.13.1) +# Copyright (c) 2023 STMicroelectronics +# +# SPDX-License-Identifier: Apache-2.0 +# + +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) + +project(virtio_i2c_ram) + +zephyr_compile_definitions( + -DMETAL_MAX_DEVICE_REGIONS=2 + -DVIRTIO_SLAVE_ONLY + ) + +target_include_directories(app PRIVATE $ENV{ZEPHYR_BASE}/drivers) +target_include_directories(app PRIVATE ${LIBMETAL_INCLUDE_DIR} ${OPENAMP_INCLUDE_DIR} ${PLATFORM_DIR}) + +target_sources(app PRIVATE src/main_remote.c) +target_sources(app PRIVATE src/mmio_table.c) + +target_include_directories(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/examples/zephyr/virtio_i2c_ram/README.rst b/examples/zephyr/virtio_i2c_ram/README.rst new file mode 100644 index 0000000..7344f16 --- /dev/null +++ b/examples/zephyr/virtio_i2c_ram/README.rst @@ -0,0 +1,89 @@ +.. _openAMP_sample: + +OpenAMP Sample Application +########################## + +Overview +******** + +This application demonstrates how to use of virtio mmio to share an I2C bus between +Zephyr and a remote processor. It is designed to implement an memory device +connected on an I2C virtio bus. + +Currently this implementation don't use the OpenAMP virtio as not ready. + +requested Hardware +************************* + +- compatible with: + - STM32MP115c-dk2 board + +Zephyr +====== + +.. code-block:: console + + west build -b openamp-system-reference/examples/zephyr/virtio_i2c_ram + +Copy the binary file on the SDCard + +Linux console +============= + +Open a serial Linux terminal (minicom, putty, etc.) and connect the board with the +following settings: + +- Speed: 115200 +- Data: 8 bits +- Parity: odd +- Stop bits: 1 + +Load and start the firmware: + +.. code-block:: console + + echo -n > /sys/class/remoteproc/remoteproc0/firmware + echo start >/sys/class/remoteproc/remoteproc0/state + + +This is the Linux console: + +1. Verify that the virtio I2C bus is preent + +.. code-block:: console + + root@stm32mp1-disco-oss:~# i2cdetect -l + i2c-0 i2c STM32F7 I2C(0x40012000) I2C adapter + i2c-1 i2c STM32F7 I2C(0x5c002000) I2C adapter + i2c-2 i2c i2c-0-mux (chan_id 0) I2C adapter + i2c-3 i2c i2c_virtio at virtio bus 0 I2C adapter + +2. list devices on the virtio bus + +Two memory devices should be connected: +- one 20 bytes memory at address 0x54 initialized with "123456789abcdefghij" +- one 20 bytes memory at address 0x56 initialized with "klmnopqrstuvwxyz!:;" +Verify that the virtio I2C bus is present + +.. code-block:: console + + root@stm32mp1-disco-oss:~# i2cdetect -y 3 + 0 1 2 3 4 5 6 7 8 9 a b c d e f + 00: -- -- -- -- -- -- -- -- + 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 50: -- -- -- -- 54 -- 56 -- -- -- -- -- -- -- -- -- + 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 70: -- -- -- -- -- -- -- -- + +3. Update RAM device 0x54 at address 0x14 with value 4 + +.. code-block:: console + + root@stm32mp1-disco-oss:~# i2cget -y 3 0x54 14 + 0x66 + root@stm32mp1-disco-oss:~# i2cset -y 3 0x54 14 0x25 + root@stm32mp1-disco-oss:~# i2cget -y 3 0x54 14 + 0x25 diff --git a/examples/zephyr/virtio_i2c_ram/boards/stm32mp157c_dk2.overlay b/examples/zephyr/virtio_i2c_ram/boards/stm32mp157c_dk2.overlay new file mode 100644 index 0000000..05f33e3 --- /dev/null +++ b/examples/zephyr/virtio_i2c_ram/boards/stm32mp157c_dk2.overlay @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023, STMICROLECTRONICS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + chosen { + /* + * shared memory reserved for the inter-processor communication + */ + zephyr,ipc_shm = &mcusram3; + zephyr,ipc = &mailbox; + }; + + mcusram3: memory1@10050000 { + compatible = "mmio-sram"; + reg = <0x10050000 DT_SIZE_K(64)>; + }; +}; + +&mcusram { + reg = <0x10000000 DT_SIZE_K(256)>; +}; diff --git a/examples/zephyr/virtio_i2c_ram/prj.conf b/examples/zephyr/virtio_i2c_ram/prj.conf new file mode 100644 index 0000000..052b96f --- /dev/null +++ b/examples/zephyr/virtio_i2c_ram/prj.conf @@ -0,0 +1,9 @@ +CONFIG_KERNEL_BIN_NAME="zephyr_virtio_i2c_eeprom" +CONFIG_PRINTK=n +CONFIG_IPM=y +CONFIG_PLATFORM_SPECIFIC_INIT=n +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_HEAP_MEM_POOL_SIZE=16384 +CONFIG_OPENAMP=y +CONFIG_OPENAMP_MASTER=n +CONFIG_OPENAMP_RSC_TABLE=y diff --git a/examples/zephyr/virtio_i2c_ram/src/main_remote.c b/examples/zephyr/virtio_i2c_ram/src/main_remote.c new file mode 100644 index 0000000..e2c9f51 --- /dev/null +++ b/examples/zephyr/virtio_i2c_ram/src/main_remote.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* Copyright (c) 2023, STMicroelectronics */ + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "mmio_table.h" + +#include +LOG_MODULE_REGISTER(virtio_i2c_ram, LOG_LEVEL_DBG); + +#define I2C_REMOTE_DISP_ADDR 0x3D + +/* constant derivated from linker symbols */ + +#define APP_TASK_STACK_SIZE (4096) +K_THREAD_STACK_DEFINE(thread_stack, APP_TASK_STACK_SIZE); +static struct k_thread thread_data; + +static const struct device *const ipm_handle = + DEVICE_DT_GET(DT_CHOSEN(zephyr_ipc)); + +static K_SEM_DEFINE(data_sem, 0, 1); + +#define RAM_0_ADDR 0x54 +#define RAM_1_ADDR 0x56 + +#define I2C_RAM_SIZE 20 +#define I2C_NUM_RAM 2 + +/**** MMIO ****/ + +/* This marks a buffer as continuing via the next field. */ +#define VIRTQ_DESC_F_NEXT 0 +/* This marks a buffer as device write-only (otherwise device read-only). */ +#define VIRTQ_DESC_F_WRITE 1 + +#define VIRTIO_I2C_MSG_OK 0 +#define VIRTIO_I2C_MSG_ERR 1 + +#define BitSet(BIT, VALUE) (VALUE & (1 << BIT)) + +uint64_t features = 1 | ((uint64_t)1 << 32) | ((uint64_t)1 << 33); +uint64_t driver_features; + +K_SEM_DEFINE(my_sem, 0, 1); + +struct virtio_i2c_out_hdr { + uint16_t addr; + uint16_t padding; + uint32_t flags; +}; + +typedef struct { + uint8_t i2c_addr; + uint8_t padding; + uint8_t offset; + uint8_t buff[I2C_RAM_SIZE]; + +} i2c_ram_t; + +static i2c_ram_t rams[I2C_NUM_RAM] = { + { + .i2c_addr = 0x54, + .buff = "123456789abcdefghij", + }, + { + .i2c_addr = 0x56, + .buff = "klmnopqrstuvwxyz!:;", + } +}; + +void mmio_interrupt(void) +{ + static struct fw_mmio_table *mmio; + static uint32_t last_status; + + if (!mmio) + mmio_table_get(&mmio); + + if (last_status != mmio->status) { + LOG_DBG("MMIO status: %d\n", mmio->status); + last_status = mmio->status; + } + + if (mmio->status == 0) { + driver_features = 0; + mmio->queueReady = 0; + } else if (mmio->status == 3) { + mmio->deviceFeatures = (uint32_t)(features >> (mmio->deviceFeaturesSel * 32)); + driver_features |= + ((uint64_t)mmio->driverFeatures << (mmio->driverFeaturesSel * 32)); + } else if (mmio->status == 11) { + if (driver_features != features) { + LOG_ERR("DriverFeatures does not match our features :(\n"); + mmio->status &= ~(uint32_t)8; + return; + } + + mmio->queueNumMax = 16; + + } else if (mmio->status == 15) { + printk("%s:%d\n", __func__, __LINE__); + k_sem_give(&my_sem); + } +} + +static int mmio_i2c_read(struct virtio_i2c_out_hdr *header, struct virtq_desc *secdesc, + struct virtq_desc *lastdesc) +{ + int i, len = -1; + + for (i = 0; i < I2C_NUM_RAM; i++) { + if ((header->addr >> 1) == rams[i].i2c_addr) { + if (secdesc->len + rams[i].offset > I2C_RAM_SIZE) + continue; + + printk("read RAM %#x, offset %x length %d\n", + rams[i].i2c_addr, rams[i].offset, + secdesc->len); + memcpy((uint8_t *)(uintptr_t)secdesc->addr, + &rams[i].buff[rams[i].offset], + secdesc->len); + len = secdesc->len + 1; + break; + } + } + + return len; +} + +static int mmio_i2c_write(struct virtio_i2c_out_hdr *header, struct virtq_desc *secdesc, + struct virtq_desc *lastdesc) +{ + int i, len = -1; + + printk("ping I2C add %#x\n", header->addr >> 1); + for (i = 0; i < I2C_NUM_RAM; i++) { + if ((header->addr >> 1) == rams[i].i2c_addr) { + if (!len) { + printk("discover RAM %#x\n", rams[i].i2c_addr); + /* Just return ack */ + } else if (len == 1) { + /* update read ram offset*/ + rams[i].offset = *(uint8_t *)(uintptr_t)secdesc->addr; + printk("set RAM %#x offset at %#x\n", + rams[i].i2c_addr, rams[i].offset); + } else { + /* write in memory */ + if (secdesc->len + rams[i].offset > I2C_RAM_SIZE) + break; + rams[i].offset = *(uint8_t *)(uintptr_t)secdesc->addr; + printk("write RAM %#x, offset %#x length %d\n", + rams[i].i2c_addr, rams[i].offset, + secdesc->len - 1); + memcpy(&rams[i].buff[rams[i].offset], + ((uint8_t *)(uintptr_t)secdesc->addr) + 1, secdesc->len - 1); + } + len = 1; + break; + } + } + + return len; +} + +void mmio_task(void *u1, void *u2, void *u3) +{ + struct virtq_desc *descs; + struct virtq_avail *avails; + struct virtq_used *useds; + + static struct fw_mmio_table *mmio; + struct virtio_i2c_out_hdr *header; + struct virtq_desc *secdesc; + struct virtq_desc *lastdesc; + int len = 0; + uint8_t *returnCode; + + mmio_table_get(&mmio); + +init: + + while (true) { + uint16_t used = 0; + + if (mmio->status != 15) + goto init; + + descs = (struct virtq_desc *)(uintptr_t)mmio->queueDesc; + avails = (struct virtq_avail *)(uintptr_t)mmio->queueDriver; + useds = (struct virtq_used *)(uintptr_t)mmio->queueDevice; + + + while (avails->idx != (uint16_t)(useds->idx + used)) { + uint16_t currentIDX = (uint16_t)(useds->idx + used) % mmio->queueNum; + struct virtq_desc *headerDesc = &descs[avails->ring[currentIDX]]; + + if (!headerDesc->addr || BitSet(VIRTQ_DESC_F_WRITE, headerDesc->flags) + || headerDesc->len != sizeof(struct virtio_i2c_out_hdr) + || !BitSet(VIRTQ_DESC_F_NEXT, headerDesc->flags)) { + LOG_ERR("First buffer is not a valid i2c_out_hdr\n"); + mmio->status |= 64; + break; + } + + header = (struct virtio_i2c_out_hdr *)(uintptr_t)headerDesc->addr; + secdesc = &descs[headerDesc->next]; + lastdesc = BitSet(VIRTQ_DESC_F_NEXT, secdesc->flags) ? + &descs[secdesc->next] : NULL; + + if (BitSet(1, header->flags)) { /* read */ + + returnCode = (uint8_t *)(uintptr_t)lastdesc->addr; + *returnCode = VIRTIO_I2C_MSG_ERR; + + if (!lastdesc || + !BitSet(VIRTQ_DESC_F_WRITE, secdesc->flags) || + !BitSet(VIRTQ_DESC_F_WRITE, lastdesc->flags)) { + LOG_ERR("Read with invalid buffer\n"); + mmio->status |= 64; + len = 1; + break; + } + + len = mmio_i2c_read(header, secdesc, lastdesc); + if (len < 0) + len = 1; + else + *returnCode = VIRTIO_I2C_MSG_OK; + } else { /* write */ + if (!lastdesc) { + /* Zero length write */ + if (!BitSet(VIRTQ_DESC_F_WRITE, secdesc->flags)) { + LOG_ERR("Zero length write with invalid buffer\n"); + mmio->status |= 64; + break; + } + returnCode = (uint8_t *)(uintptr_t)secdesc->addr; + len = 0; + } else { + if (BitSet(VIRTQ_DESC_F_WRITE, secdesc->flags) + || !BitSet(VIRTQ_DESC_F_WRITE, lastdesc->flags)) { + LOG_ERR("Write with invalid buffer\n"); + mmio->status |= 64; + break; + } + returnCode = (uint8_t *)(uintptr_t)lastdesc->addr; + len = secdesc->len; + } + + + len = mmio_i2c_write(header, secdesc, lastdesc); + if (len < 0) { + *returnCode = VIRTIO_I2C_MSG_ERR; + len = 1; + } else { + *returnCode = VIRTIO_I2C_MSG_OK; + } + } + useds->ring[currentIDX].id = avails->ring[currentIDX]; + useds->ring[currentIDX].len = len; + + used++; + } + if (used) { + useds->idx = (uint16_t)(useds->idx + used); + + /* Used buffer notification */ + mmio->interruptStatus = 1; + ipm_send(ipm_handle, 0, 4, NULL, 0); + } + + k_sem_take(&my_sem, K_FOREVER); + } +} + +/**** ENDMMIO ****/ + +static void platform_ipm_callback(const struct device *dev, + void *context, uint32_t id, volatile void *data) +{ + if (id == 5) { + mmio_interrupt(); + return; + } + + LOG_ERR("IPM IRQ\n"); + k_sem_give(&data_sem); +} + +static void cleanup_system(void) +{ + ipm_set_enabled(ipm_handle, 0); +} + +int platform_init(void) +{ + int status; + + /* setup IPM */ + if (!device_is_ready(ipm_handle)) { + LOG_ERR("IPM device is not ready\n"); + return -1; + } + + ipm_register_callback(ipm_handle, platform_ipm_callback, NULL); + + status = ipm_set_enabled(ipm_handle, 1); + if (status) { + LOG_ERR("ipm_set_enabled failed\n"); + return -1; + } + + return 0; +} + +static int openamp_init(void) +{ + int ret; + + /* Initialize platform */ + ret = platform_init(); + if (ret) { + LOG_ERR("Failed to initialize platform\n"); + goto error_case; + } + + return 0; + +error_case: + cleanup_system(); + + return 1; +} + +void main(void) +{ + k_thread_create(&thread_data, thread_stack, APP_TASK_STACK_SIZE, + (k_thread_entry_t)mmio_task, + NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); +} + +SYS_INIT(openamp_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE); diff --git a/examples/zephyr/virtio_i2c_ram/src/mmio_table.c b/examples/zephyr/virtio_i2c_ram/src/mmio_table.c new file mode 100644 index 0000000..e950f93 --- /dev/null +++ b/examples/zephyr/virtio_i2c_ram/src/mmio_table.c @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* Copyright (c) 2023, STMicroelectronics */ + +#include +#include "mmio_table.h" + +#define __resource Z_GENERIC_SECTION(.mmio_table) + +static struct fw_mmio_table __resource mmio_table = { + .magic = 0x74726976, + .version = 2, + .deviceType = 0x22, +}; + +void mmio_table_get(struct fw_mmio_table **table_ptr) +{ + *table_ptr = &mmio_table; +} diff --git a/examples/zephyr/virtio_i2c_ram/src/mmio_table.h b/examples/zephyr/virtio_i2c_ram/src/mmio_table.h new file mode 100644 index 0000000..4640ea0 --- /dev/null +++ b/examples/zephyr/virtio_i2c_ram/src/mmio_table.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2023, STMicroelectronics + */ + +#include + +#define STATUS_ACK 1 +#define STATUS_DRIVER 2 +#define STATUS_DRIVER_OK 4 +#define STATUS_FEATURES_OK 8 +#define STATUS_RESET_DEVICE 64 +#define STATUS_FAILED 128 + +/* Virtqueue descriptors: 16 bytes. + * These can chain together via "next". + */ +struct virtq_desc { + /* Address (guest-physical). */ + volatile uint64_t addr; + /* Length. */ + volatile uint32_t len; + /* The flags as indicated above. */ + volatile uint16_t flags; + /* We chain unused descriptors via this, too */ + volatile uint16_t next; +}; + +struct virtq_avail { + volatile uint16_t flags; + volatile uint16_t idx; + volatile uint16_t ring[]; + /* Only if VIRTIO_F_EVENT_IDX: le16 used_event; */ +}; + +/* le32 is used here for ids for padding reasons. */ +struct virtq_used_elem { + /* Index of start of used descriptor chain. */ + volatile uint32_t id; + /* Total length of the descriptor chain which was written to. */ + volatile uint32_t len; +}; + +struct virtq_used { + volatile uint16_t flags; + volatile uint16_t idx; + volatile struct virtq_used_elem ring[]; + /* Only if VIRTIO_F_EVENT_IDX: le16 avail_event; */ +}; + +struct fw_mmio_table { + + /* 0x00 R should be 0x74726976 */ + uint32_t magic; + + /* 0x04 R */ + uint32_t version; + + /* 0x08 R 34 for i2c */ + uint32_t deviceType; + + /* 0x0c R */ + uint32_t vendoId; + + /* 0x10 R flags to represent features supported by the device */ + uint32_t deviceFeatures; + + /* 0x14 W selected features from the device */ + volatile uint32_t deviceFeaturesSel; + + uint32_t reserved[2]; + + /* 0x20 W flags to represent features supported by the driver */ + volatile uint32_t driverFeatures; + + /* 0x24 W selected features from the driver */ + volatile uint32_t driverFeaturesSel; + + uint32_t reserved2[2]; + + /* 0x30 W virtual queue index */ + volatile uint32_t queueSel; + + /* 0x34 R maximum virtual queue size */ + uint32_t queueNumMax; + + /* 0x38 W virtual queue size */ + volatile uint32_t queueNum; + + uint32_t reserved3[2]; + + /* 0x44 RW virtual queue ready bit */ + volatile uint32_t queueReady; + + uint32_t reserved4[2]; + + /* 0x50 W queue notifier */ + volatile uint32_t queueNotify; + + uint32_t reserved5[3]; + + /* 0x60 R bit mask of events that caused the device interrupt to be asserted */ + volatile uint32_t interruptStatus; + + /* 0x64 W bit mask notifies the device that events causing the interrupt, + * have been handled. + */ + volatile uint32_t interruptACK; + + uint32_t reserved6[2]; + + /* 0x70 RW Writing non-zero values to this register sets the status flags, + * indicating the driver progress. + * Writing zero (0x0) to this register triggers a device reset. + */ + volatile uint32_t status; + + uint32_t reserved7[3]; + + /* 0x80 W notifies the device about location of the Descriptor Area of the queue, + * selected by writing to QueueSel register + */ + volatile uint64_t queueDesc; + + uint32_t reserved8[2]; + + /* 0x90 W notifies the device about location of the Driver Area of the queue selected, + * by writing to QueueSel. + */ + volatile uint64_t queueDriver; + + uint32_t reserved9[2]; + + /* 0xa0 W notifies the device about location of the Device Area of the queue selected, + * by writing to QueueSel. + */ + volatile uint64_t queueDevice; + + uint32_t reserved10[23]; + + /* 0xfc R value describing a version of the device-specific configuration space */ + uint32_t configGeneration; +}; + +void mmio_table_get(struct fw_mmio_table **table_ptr); diff --git a/west.yml b/west.yml index 4101a85..6705637 100644 --- a/west.yml +++ b/west.yml @@ -14,5 +14,5 @@ manifest: projects: - name: openamp-zephyr-staging path: zephyr - revision: v3.4-branch + revision: main import: true