diff --git a/tests/drivers/smbus/smbus_emul/CMakeLists.txt b/tests/drivers/smbus/smbus_emul/CMakeLists.txt new file mode 100644 index 00000000000..b52ef0c4729 --- /dev/null +++ b/tests/drivers/smbus/smbus_emul/CMakeLists.txt @@ -0,0 +1,10 @@ +#SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(test_smbus_emul) + +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/drivers/smbus) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/smbus/smbus_emul/README.rst b/tests/drivers/smbus/smbus_emul/README.rst new file mode 100644 index 00000000000..f0ce42421db --- /dev/null +++ b/tests/drivers/smbus/smbus_emul/README.rst @@ -0,0 +1,8 @@ +.. _smbus_emul_tests: + +SMBUS unit tests +################ + +Unit tests emulate SMBus hardware controller and connected SMBus peripheral +device by redirecting I/O read / write to a memory buffer. This allows to +cover all SMBus protocol commands. diff --git a/tests/drivers/smbus/smbus_emul/prj.conf b/tests/drivers/smbus/smbus_emul/prj.conf new file mode 100644 index 00000000000..9dd20e84904 --- /dev/null +++ b/tests/drivers/smbus/smbus_emul/prj.conf @@ -0,0 +1,4 @@ +CONFIG_ZTEST=y +CONFIG_LOG=y +CONFIG_ZTEST_NEW_API=y +CONFIG_ENTROPY_GENERATOR=y diff --git a/tests/drivers/smbus/smbus_emul/src/emul.c b/tests/drivers/smbus/smbus_emul/src/emul.c new file mode 100644 index 00000000000..6ae27326bfb --- /dev/null +++ b/tests/drivers/smbus/smbus_emul/src/emul.c @@ -0,0 +1,451 @@ +/* + * Copyright (c) 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +#include + +#include +LOG_MODULE_REGISTER(emul, LOG_LEVEL_DBG); + +#include "emul.h" + +/** + * Emulate Intel PCH Host Controller hardware as PCI device with I/O access + */ + +/* PCI Configuration space */ +uint32_t pci_config_area[32] = { + [PCIE_CONF_CMDSTAT] = PCIE_CONF_CMDSTAT_INTERRUPT, /* Mark INT */ + [8] = 1, /* I/O BAR */ + [16] = 1, /* Enable SMBus */ +}; + +/* I/O and MMIO registers */ +uint8_t io_area[24] = { + 0, +}; + +struct e32_block { + uint8_t buf[SMBUS_BLOCK_BYTES_MAX]; + uint8_t offset; +} e32; + +/** + * Emulate SMBus peripheral device, connected to the bus as + * simple EEPROM device of size 256 bytes + */ + +/* List of peripheral devices registered */ +sys_slist_t peripherals; + +void emul_register_smbus_peripheral(struct smbus_peripheral *peripheral) +{ + sys_slist_prepend(&peripherals, &peripheral->node); +} + +static struct smbus_peripheral *emul_get_smbus_peripheral(uint8_t addr) +{ + struct smbus_peripheral *peripheral, *tmp; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&peripherals, peripheral, tmp, node) { + if (peripheral->addr == addr) { + return peripheral; + } + } + + return NULL; +} + +static bool peripheral_handle_smbalert(void) +{ + struct smbus_peripheral *peripheral, *tmp, *found = NULL; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&peripherals, peripheral, tmp, node) { + if (peripheral->smbalert && !peripheral->smbalert_handled) { + found = peripheral; + } + } + + if (found == NULL) { + LOG_WRN("No (more) smbalert handlers found"); + return false; + } + + LOG_DBG("Return own address: 0x%02x", found->addr); + + io_area[PCH_SMBUS_HD0] = found->addr; + found->smbalert_handled = true; + + return true; +} + +bool peripheral_handle_host_notify(void) +{ + struct smbus_peripheral *peripheral, *tmp; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&peripherals, peripheral, tmp, node) { + if (peripheral->host_notify) { + LOG_DBG("Save own peripheral address to NDA"); + io_area[PCH_SMBUS_NDA] = peripheral->addr << 1; + + return true; + } + } + + return false; +} + +static void peripheral_write(uint8_t reg, uint8_t value) +{ + uint8_t addr = PCH_SMBUS_TSA_ADDR_GET(io_area[PCH_SMBUS_TSA]); + struct smbus_peripheral *peripheral; + + peripheral = emul_get_smbus_peripheral(addr); + if (peripheral) { + peripheral->raw_data[reg] = value; + LOG_DBG("peripheral: [0x%02x] <= 0x%02x", reg, value); + } else { + LOG_ERR("Peripheral not found, addr 0x%02x", addr); + } +} + +static void peripheral_read(uint8_t reg, uint8_t *value) +{ + uint8_t addr = PCH_SMBUS_TSA_ADDR_GET(io_area[PCH_SMBUS_TSA]); + struct smbus_peripheral *peripheral; + + peripheral = emul_get_smbus_peripheral(addr); + if (peripheral) { + *value = peripheral->raw_data[reg]; + LOG_DBG("peripheral: [0x%02x] => 0x%02x", reg, *value); + } else { + LOG_ERR("Peripheral not found, addr 0x%02x", addr); + } +} + +static void emul_start_smbus_protocol(void) +{ + uint8_t smbus_cmd = PCH_SMBUS_HCTL_CMD_GET(io_area[PCH_SMBUS_HCTL]); + bool write = + (io_area[PCH_SMBUS_TSA] & PCH_SMBUS_TSA_RW) == SMBUS_MSG_WRITE; + uint8_t addr = PCH_SMBUS_TSA_ADDR_GET(io_area[PCH_SMBUS_TSA]); + struct smbus_peripheral *peripheral; + + LOG_DBG("Start SMBUS protocol"); + + if (unlikely(addr == SMBUS_ADDRESS_ARA)) { + if (peripheral_handle_smbalert()) { + goto isr; + } + } + + peripheral = emul_get_smbus_peripheral(addr); + if (peripheral == NULL) { + LOG_WRN("Set Device Error"); + emul_set_io(emul_get_io(PCH_SMBUS_HSTS) | + PCH_SMBUS_HSTS_DEV_ERROR, PCH_SMBUS_HSTS); + goto isr; + } + + switch (smbus_cmd) { + case PCH_SMBUS_HCTL_CMD_QUICK: + LOG_DBG("Quick command"); + break; + case PCH_SMBUS_HCTL_CMD_BYTE: + if (write) { + LOG_DBG("Byte Write command"); + peripheral_write(0, io_area[PCH_SMBUS_HCMD]); + } else { + LOG_DBG("Byte Read command"); + peripheral_read(0, &io_area[PCH_SMBUS_HD0]); + } + break; + case PCH_SMBUS_HCTL_CMD_BYTE_DATA: + if (write) { + LOG_DBG("Byte Data Write command"); + peripheral_write(io_area[PCH_SMBUS_HCMD], + io_area[PCH_SMBUS_HD0]); + } else { + LOG_DBG("Byte Data Read command"); + peripheral_read(io_area[PCH_SMBUS_HCMD], + &io_area[PCH_SMBUS_HD0]); + } + break; + case PCH_SMBUS_HCTL_CMD_WORD_DATA: + if (write) { + LOG_DBG("Word Data Write command"); + peripheral_write(io_area[PCH_SMBUS_HCMD], + io_area[PCH_SMBUS_HD0]); + peripheral_write(io_area[PCH_SMBUS_HCMD] + 1, + io_area[PCH_SMBUS_HD1]); + + } else { + LOG_DBG("Word Data Read command"); + peripheral_read(io_area[PCH_SMBUS_HCMD], + &io_area[PCH_SMBUS_HD0]); + peripheral_read(io_area[PCH_SMBUS_HCMD] + 1, + &io_area[PCH_SMBUS_HD1]); + } + break; + case PCH_SMBUS_HCTL_CMD_PROC_CALL: + if (!write) { + LOG_ERR("Incorrect operation flag"); + return; + } + + LOG_DBG("Process Call command"); + + peripheral_write(io_area[PCH_SMBUS_HCMD], + io_area[PCH_SMBUS_HD0]); + peripheral_write(io_area[PCH_SMBUS_HCMD] + 1, + io_area[PCH_SMBUS_HD1]); + + /** + * For the testing purposes implement data + * swap for the Proc Call, that would be + * easy for testing. + * + * Note: real device should have some other + * logic for Proc Call. + */ + peripheral_read(io_area[PCH_SMBUS_HCMD], + &io_area[PCH_SMBUS_HD1]); + peripheral_read(io_area[PCH_SMBUS_HCMD] + 1, + &io_area[PCH_SMBUS_HD0]); + break; + case PCH_SMBUS_HCTL_CMD_BLOCK: + if (write) { + uint8_t count = io_area[PCH_SMBUS_HD0]; + uint8_t reg = io_area[PCH_SMBUS_HCMD]; + + LOG_DBG("Block Write command"); + + if (count > SMBUS_BLOCK_BYTES_MAX) { + return; + } + + for (int i = 0; i < count; i++) { + peripheral_write(reg++, e32.buf[i]); + } + } else { + /** + * count is set by peripheral device, just + * assume it to be maximum block count + */ + uint8_t count = SMBUS_BLOCK_BYTES_MAX; + uint8_t reg = io_area[PCH_SMBUS_HCMD]; + + LOG_DBG("Block Read command"); + + for (int i = 0; i < count; i++) { + peripheral_read(reg++, &e32.buf[i]); + } + + /* Set count */ + io_area[PCH_SMBUS_HD0] = count; + } + break; + case PCH_SMBUS_HCTL_CMD_BLOCK_PROC: + if (!write) { + LOG_ERR("Incorrect operation flag"); + } else { + uint8_t snd_count = io_area[PCH_SMBUS_HD0]; + uint8_t reg = io_area[PCH_SMBUS_HCMD]; + uint8_t rcv_count; + + LOG_DBG("Block Process Call command"); + + if (snd_count > SMBUS_BLOCK_BYTES_MAX) { + return; + } + + /** + * Make Block Process Call swap block buffer bytes + * for testing purposes only, return the same "count" + * bytes + */ + for (int i = 0; i < snd_count; i++) { + peripheral_write(reg++, e32.buf[i]); + } + + rcv_count = snd_count; + if (snd_count + rcv_count > SMBUS_BLOCK_BYTES_MAX) { + return; + } + + for (int i = 0; i < rcv_count; i++) { + peripheral_read(--reg, &e32.buf[i]); + } + + /* Clear offset count */ + e32.offset = 0; + + /* Set count */ + io_area[PCH_SMBUS_HD0] = rcv_count; + } + break; + default: + LOG_ERR("Protocol is not implemented yet in emul"); + break; + } + +isr: + if (io_area[PCH_SMBUS_HCTL] & PCH_SMBUS_HCTL_INTR_EN) { + /* Fire emulated interrupt if enabled */ + run_isr(EMUL_SMBUS_INTR); + } +} + +static void emul_evaluate_write(uint8_t value, io_port_t addr) +{ + switch (addr) { + case PCH_SMBUS_HCTL: + if (value & PCH_SMBUS_HCTL_START) { + /* This is write only */ + io_area[PCH_SMBUS_HCTL] = value & ~PCH_SMBUS_HCTL_START; + + emul_start_smbus_protocol(); + } + break; + default: + break; + } +} + +static const char *pch_get_reg_name(uint8_t reg) +{ + switch (reg) { + case PCH_SMBUS_HSTS: + return "HSTS"; + case PCH_SMBUS_HCTL: + return "HCTL"; + case PCH_SMBUS_HCMD: + return "HCMD"; + case PCH_SMBUS_TSA: + return "TSA"; + case PCH_SMBUS_HD0: + return "HD0"; + case PCH_SMBUS_HD1: + return "HD1"; + case PCH_SMBUS_HBD: + return "HBD"; + case PCH_SMBUS_PEC: + return "PEC"; + case PCH_SMBUS_RSA: + return "RSA"; + case PCH_SMBUS_SD: + return "SD"; + case PCH_SMBUS_AUXS: + return "AUXS"; + case PCH_SMBUS_AUXC: + return "AUXC"; + case PCH_SMBUS_SMLC: + return "SMLC"; + case PCH_SMBUS_SMBC: + return "SMBC"; + case PCH_SMBUS_SSTS: + return "SSTS"; + case PCH_SMBUS_SCMD: + return "SCMD"; + case PCH_SMBUS_NDA: + return "NDA"; + case PCH_SMBUS_NDLB: + return "NDLB"; + case PCH_SMBUS_NDHB: + return "NDHB"; + default: + return "Unknown"; + } +} + +uint32_t emul_pci_read(unsigned int reg) +{ + LOG_DBG("PCI [%x] => 0x%x", reg, pci_config_area[reg]); + return pci_config_area[reg]; +} + +void emul_pci_write(pcie_bdf_t bdf, unsigned int reg, uint32_t value) +{ + LOG_DBG("PCI [%x] <= 0x%x", reg, value); + pci_config_area[reg] = value; +} + +/* This function is used to set registers for emulation purpose */ +void emul_set_io(uint8_t value, io_port_t addr) +{ + io_area[addr] = value; +} + +uint8_t emul_get_io(io_port_t addr) +{ + return io_area[addr]; +} + +void emul_out8(uint8_t value, io_port_t addr) +{ + switch (addr) { + case PCH_SMBUS_HSTS: + /* Writing clears status bits */ + io_area[addr] &= ~value; + break; + case PCH_SMBUS_SSTS: + /* Writing clears status bits */ + io_area[addr] &= ~value; + break; + case PCH_SMBUS_HBD: + /* Using internal E32 buffer offset */ + e32.buf[e32.offset++] = value; + break; + case PCH_SMBUS_AUXC: + if (value & PCH_SMBUS_AUXC_EN_32BUF) { + LOG_DBG("Enabled 32 bit buffer block mode"); + } + io_area[addr] = value; + break; + default: + io_area[addr] = value; + break; + } + + LOG_DBG("I/O [%s] <= 0x%x => 0x%x", pch_get_reg_name(addr), value, + io_area[addr]); + + /** + * Evaluate should decide about starting actual SMBus + * protocol transaction emulation + */ + emul_evaluate_write(value, addr); +} + +uint8_t emul_in8(io_port_t addr) +{ + uint8_t value; + + switch (addr) { + case PCH_SMBUS_HBD: + /* Using internal E32 buffer offset */ + value = e32.buf[e32.offset++]; + break; + case PCH_SMBUS_HCTL: + /* Clear e32 block buffer offset */ + e32.offset = 0; + LOG_WRN("E32 buffer offset is cleared"); + value = io_area[addr]; + break; + default: + value = io_area[addr]; + break; + } + + LOG_DBG("I/O [%s] => 0x%x", pch_get_reg_name(addr), value); + + return value; +} diff --git a/tests/drivers/smbus/smbus_emul/src/emul.h b/tests/drivers/smbus/smbus_emul/src/emul.h new file mode 100644 index 00000000000..520d81c6dca --- /dev/null +++ b/tests/drivers/smbus/smbus_emul/src/emul.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Read from PCI configuration space */ +uint32_t emul_pci_read(unsigned int reg); + +/* Write to PCI configuration space */ +void emul_pci_write(pcie_bdf_t bdf, unsigned int reg, uint32_t value); + +void emul_out8(uint8_t data, io_port_t addr); +uint8_t emul_in8(io_port_t addr); + +void emul_set_io(uint8_t value, io_port_t addr); +uint8_t emul_get_io(io_port_t addr); + +enum emul_isr_type { + EMUL_SMBUS_INTR, + EMUL_SMBUS_SMBALERT, + EMUL_SMBUS_HOST_NOTIFY, +}; + +void run_isr(enum emul_isr_type); + +struct smbus_peripheral { + sys_snode_t node; + uint8_t raw_data[256]; + uint8_t offset; + uint8_t addr; + bool smbalert; + bool smbalert_handled; + bool host_notify; +}; + +bool peripheral_handle_host_notify(void); + +static inline void peripheral_clear_smbalert(struct smbus_peripheral *periph) +{ + periph->smbalert_handled = false; +} + +void emul_register_smbus_peripheral(struct smbus_peripheral *peripheral); diff --git a/tests/drivers/smbus/smbus_emul/src/smbus.c b/tests/drivers/smbus/smbus_emul/src/smbus.c new file mode 100644 index 00000000000..4c62a1a9c55 --- /dev/null +++ b/tests/drivers/smbus/smbus_emul/src/smbus.c @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include + +#include "emul.h" + +#define CONFIG_SMBUS_LOG_LEVEL LOG_LEVEL_DBG + +#define PERIPH_ADDR 0x10 + +static uint8_t mock_sys_in8(io_port_t port) +{ + return emul_in8(port); +} + +static void mock_sys_out8(uint8_t data, io_port_t port) +{ + emul_out8(data, port); +} + +static uint32_t mock_conf_read(pcie_bdf_t bdf, unsigned int reg) +{ + return emul_pci_read(reg); +} + +#if defined(PCIE_CONF_WRITE) +static void mock_conf_write(pcie_bdf_t bdf, unsigned int reg, uint32_t data) +{ + emul_pci_write(bdf, reg, data); +} + +#define pcie_conf_write(bdf, reg, val) mock_conf_write(bdf, reg, val) +#endif /* PCIE_CONF_WRITE */ + +/* Redefine PCIE access */ +#define pcie_conf_read(bdf, reg) mock_conf_read(bdf, reg) + +/* Redefine sys_in function */ +#define sys_in8(port) mock_sys_in8(port) +#define sys_out8(data, port) mock_sys_out8(data, port) + +#define CONFIG_SMBUS_INTEL_PCH_ACCESS_IO +#define device_map(a, b, c, d) +#define pcie_probe(bdf, id) 1 +#define pcie_set_cmd(a, b, c) + +#define SMBUS_EMUL "smbus_emul" + +#ifdef PERIPHERAL_INT +#define CONFIG_SMBUS_INTEL_PCH_SMBALERT 1 +#define CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY 1 +#endif + +#include "intel_pch_smbus.c" + +void run_isr(enum emul_isr_type type) +{ + const struct device *const dev = device_get_binding(SMBUS_EMUL); + + switch (type) { + case EMUL_SMBUS_INTR: + emul_set_io(emul_get_io(PCH_SMBUS_HSTS) | + PCH_SMBUS_HSTS_INTERRUPT, PCH_SMBUS_HSTS); + break; + case EMUL_SMBUS_SMBALERT: + emul_set_io(emul_get_io(PCH_SMBUS_HSTS) | + PCH_SMBUS_HSTS_SMB_ALERT, PCH_SMBUS_HSTS); + break; + case EMUL_SMBUS_HOST_NOTIFY: + emul_set_io(emul_get_io(PCH_SMBUS_SSTS)| + PCH_SMBUS_SSTS_HNS, PCH_SMBUS_SSTS); + peripheral_handle_host_notify(); + break; + default: + break; + } + + smbus_isr(dev); +} + +static void config_function(const struct device *dev) +{ + TC_PRINT("Emulator device configuration\n"); +} +static struct pch_data smbus_data; +static struct pch_config pch_config_data = { + .config_func = config_function, +}; +DEVICE_DEFINE(dummy_driver, SMBUS_EMUL, &pch_smbus_init, + NULL, &smbus_data, &pch_config_data, APPLICATION, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &funcs); + +ZTEST(test_smbus_emul, test_byte) +{ + const struct device *const dev = device_get_binding(SMBUS_EMUL); + uint8_t snd_byte, rcv_byte; + int ret; + + zassert_not_null(dev, "Device not found"); + + ret = smbus_quick(dev, PERIPH_ADDR, 1); + zassert_ok(ret, "SMBus Quick failed"); + + snd_byte = (uint8_t)sys_rand32_get(); + + ret = smbus_byte_write(dev, PERIPH_ADDR, snd_byte); + zassert_ok(ret, "SMBus Byte Write failed"); + + ret = smbus_byte_read(dev, PERIPH_ADDR, &rcv_byte); + zassert_ok(ret, "SMBus Byte Read failed"); + + zassert_equal(snd_byte, rcv_byte, "Data mismatch"); + + ret = smbus_byte_data_write(dev, PERIPH_ADDR, 0, snd_byte); + zassert_ok(ret, "SMBus Byte Data Write failed"); + + ret = smbus_byte_data_read(dev, PERIPH_ADDR, 0, &rcv_byte); + zassert_ok(ret, "SMBus Byte Data Read failed"); + + zassert_equal(snd_byte, rcv_byte, "Data mismatch"); +} + +ZTEST(test_smbus_emul, test_word) +{ + const struct device *const dev = device_get_binding(SMBUS_EMUL); + uint16_t snd_word, rcv_word; + uint8_t snd_byte; + int ret; + + zassert_not_null(dev, "Device not found"); + + snd_word = (uint16_t)sys_rand32_get(); + + ret = smbus_word_data_write(dev, PERIPH_ADDR, 0, snd_word); + zassert_ok(ret, "SMBus Word Data Write failed"); + + ret = smbus_word_data_read(dev, PERIPH_ADDR, 0, &rcv_word); + zassert_ok(ret, "SMBus Byte Data Read failed"); + + zassert_equal(snd_word, rcv_word, "Data mismatch"); + + /* Test 2 byte writes following word read */ + + snd_byte = (uint8_t)sys_rand32_get(); + + ret = smbus_byte_data_write(dev, PERIPH_ADDR, 0, snd_byte); + zassert_ok(ret, "SMBus Byte Data Write failed"); + ret = smbus_byte_data_write(dev, PERIPH_ADDR, 1, snd_byte); + zassert_ok(ret, "SMBus Byte Data Write failed"); + + ret = smbus_word_data_read(dev, PERIPH_ADDR, 0, &rcv_word); + zassert_ok(ret, "SMBus Byte Data Read failed"); + + zassert_equal(snd_byte << 8 | snd_byte, rcv_word, "Data mismatch"); +} + +ZTEST(test_smbus_emul, test_proc_call) +{ + const struct device *const dev = device_get_binding(SMBUS_EMUL); + uint16_t snd_word, rcv_word; + int ret; + + zassert_not_null(dev, "Device not found"); + + snd_word = (uint16_t)sys_rand32_get(); + zassert_not_equal(snd_word, 0, "Random number generator misconfgured"); + + ret = smbus_pcall(dev, PERIPH_ADDR, 0x0, snd_word, &rcv_word); + zassert_ok(ret, "SMBus Proc Call failed"); + + /* Our emulated Proc Call swaps bytes */ + zassert_equal(snd_word, __bswap_16(rcv_word), "Data mismatch"); +} + +ZTEST(test_smbus_emul, test_block) +{ + const struct device *const dev = device_get_binding(SMBUS_EMUL); + uint8_t snd_block[SMBUS_BLOCK_BYTES_MAX]; + uint8_t rcv_block[SMBUS_BLOCK_BYTES_MAX]; + uint8_t snd_count, rcv_count; + int ret; + + zassert_not_null(dev, "Device not found"); + + for (int i = 0; i < sizeof(snd_block); i++) { + snd_block[i] = (uint8_t)sys_rand32_get(); + } + + snd_count = sizeof(snd_block); + + ret = smbus_block_write(dev, PERIPH_ADDR, 0, snd_count, snd_block); + zassert_ok(ret, "SMBUS write block failed, ret %d", ret); + + ret = smbus_block_read(dev, PERIPH_ADDR, 0, &rcv_count, rcv_block); + zassert_ok(ret, "SMBUS read block failed, ret %d", ret); + + zassert_equal(snd_count, rcv_count, "Block count differs"); + zassert_true(!memcmp(snd_block, rcv_block, rcv_count), + "Data mismatch"); +} + +ZTEST(test_smbus_emul, test_block_pcall) +{ + const struct device *const dev = device_get_binding(SMBUS_EMUL); + uint8_t snd_block[SMBUS_BLOCK_BYTES_MAX]; + uint8_t rcv_block[SMBUS_BLOCK_BYTES_MAX]; + uint8_t snd_count, rcv_count; + int ret; + + zassert_not_null(dev, "Device not found"); + + for (int i = 0; i < sizeof(snd_block); i++) { + snd_block[i] = (uint8_t)sys_rand32_get(); + } + + snd_count = SMBUS_BLOCK_BYTES_MAX / 2; + ret = smbus_block_pcall(dev, PERIPH_ADDR, 0, snd_count, snd_block, + &rcv_count, rcv_block); + zassert_ok(ret, "SMBUS block pcall failed, ret %d", ret); + zassert_equal(snd_count, rcv_count, "Block count differs"); + + /** + * Verify that our emulated peripheral swapped bytes in the block + * buffer + */ + for (int i = 0; i < rcv_count; i++) { + zassert_equal(snd_block[i], rcv_block[rcv_count - (i + 1)], + "Data mismatch, not swapped"); + + } +} + +/* SMBALERT handling */ + +/* False by default */ +bool smbalert_handled; + +static void smbalert_cb(const struct device *dev, struct smbus_callback *cb, + uint8_t addr) +{ + LOG_DBG("SMBALERT callback"); + + smbalert_handled = true; +} + +struct smbus_callback smbalert_callback = { + .handler = smbalert_cb, + .addr = PERIPH_ADDR, +}; + +/* Host Notify handling */ + +/* False by default */ +bool notify_handled; + +static void notify_cb(const struct device *dev, struct smbus_callback *cb, + uint8_t addr) +{ + LOG_DBG("Notify callback"); + + notify_handled = true; +} + +struct smbus_callback notify_callback = { + .handler = notify_cb, + .addr = PERIPH_ADDR, +}; + +/* Setup peripheral SMBus device on a bus */ + +struct smbus_peripheral peripheral = { + .addr = PERIPH_ADDR, + .smbalert = true, + .host_notify = true, +}; + +ZTEST(test_smbus_emul, test_alert) +{ + const struct device *const dev = device_get_binding(SMBUS_EMUL); + int ret; + + Z_TEST_SKIP_IFNDEF(CONFIG_SMBUS_INTEL_PCH_SMBALERT); + + zassert_not_null(dev, "Device not found"); + + /* Try to remove not existing callback */ + ret = smbus_manage_smbalert_cb(dev, &smbalert_callback, false); + zassert_equal(ret, -ENOENT, "Callback remove failed"); + + /* Set callback */ + ret = smbus_manage_smbalert_cb(dev, &smbalert_callback, true); + zassert_ok(ret, "Callback set failed"); + + /* Emulate SMBus alert from peripheral device */ + peripheral_clear_smbalert(&peripheral); + smbalert_handled = false; + + /* Run without configure smbalert */ + run_isr(EMUL_SMBUS_SMBALERT); + + /* Wait for delayed work handled */ + k_sleep(K_MSEC(100)); + + /* Verify that smbalert is NOT handled */ + zassert_false(smbalert_handled, "smbalert is not handled"); + + /* Now enable smbalert */ + ret = smbus_configure(dev, SMBUS_MODE_CONTROLLER | SMBUS_MODE_SMBALERT); + zassert_ok(ret, "Configure failed"); + + /* Emulate SMBus alert again */ + run_isr(EMUL_SMBUS_SMBALERT); + + /* Wait for delayed work handled */ + k_sleep(K_MSEC(100)); + + /* Verify that smbalert is not handled */ + zassert_true(smbalert_handled, "smbalert is not handled"); +} + +ZTEST(test_smbus_emul, test_host_notify) +{ + const struct device *const dev = device_get_binding(SMBUS_EMUL); + int ret; + + Z_TEST_SKIP_IFNDEF(CONFIG_SMBUS_INTEL_PCH_HOST_NOTIFY); + + zassert_not_null(dev, "Device not found"); + + /* Try to remove not existing callback */ + ret = smbus_manage_host_notify_cb(dev, ¬ify_callback, false); + zassert_equal(ret, -ENOENT, "Callback remove failed"); + + /* Set callback */ + ret = smbus_manage_host_notify_cb(dev, ¬ify_callback, true); + zassert_ok(ret, "Callback set failed"); + + /* Emulate SMBus alert from peripheral device */ + notify_handled = false; + + /* Run without configuring Host Notify */ + run_isr(EMUL_SMBUS_HOST_NOTIFY); + + /* Wait for delayed work handled */ + k_sleep(K_MSEC(100)); + + /* Verify that smbalert is NOT handled */ + zassert_false(notify_handled, "smbalert is not handled"); + + /* Now enable smbalert */ + ret = smbus_configure(dev, + SMBUS_MODE_CONTROLLER | SMBUS_MODE_HOST_NOTIFY); + zassert_ok(ret, "Configure failed"); + + /* Emulate SMBus alert again */ + run_isr(EMUL_SMBUS_HOST_NOTIFY); + + /* Wait for delayed work handled */ + k_sleep(K_MSEC(100)); + + /* Verify that smbalert is handled */ + zassert_true(notify_handled, "smbalert is not handled"); +} + +/* Test setup function */ +static void *smbus_emul_setup(void) +{ + emul_register_smbus_peripheral(&peripheral); + + return NULL; +} + +ZTEST_SUITE(test_smbus_emul, NULL, smbus_emul_setup, NULL, NULL, NULL); diff --git a/tests/drivers/smbus/smbus_emul/testcase.yaml b/tests/drivers/smbus/smbus_emul/testcase.yaml new file mode 100644 index 00000000000..26fc9254c55 --- /dev/null +++ b/tests/drivers/smbus/smbus_emul/testcase.yaml @@ -0,0 +1,8 @@ +common: + platform_allow: native_posix +tests: + drivers.smbus.emul: + tags: smbus + drivers.smbus.emul.peripheral.interrupt: + tags: smbus + extra_args: EXTRA_CFLAGS=-DPERIPHERAL_INT=1