tests: smbus: Add SMBus unit tests
Add SMBus unit tests which emulate SMBus controller and connected test peripheral device. This allows to test also rare SMBus protocols like Process Call and Block Process Call. Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
This commit is contained in:
parent
a242e572ce
commit
a35c889687
7 changed files with 908 additions and 0 deletions
10
tests/drivers/smbus/smbus_emul/CMakeLists.txt
Normal file
10
tests/drivers/smbus/smbus_emul/CMakeLists.txt
Normal file
|
@ -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})
|
8
tests/drivers/smbus/smbus_emul/README.rst
Normal file
8
tests/drivers/smbus/smbus_emul/README.rst
Normal file
|
@ -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.
|
4
tests/drivers/smbus/smbus_emul/prj.conf
Normal file
4
tests/drivers/smbus/smbus_emul/prj.conf
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
CONFIG_ZTEST=y
|
||||||
|
CONFIG_LOG=y
|
||||||
|
CONFIG_ZTEST_NEW_API=y
|
||||||
|
CONFIG_ENTROPY_GENERATOR=y
|
451
tests/drivers/smbus/smbus_emul/src/emul.c
Normal file
451
tests/drivers/smbus/smbus_emul/src/emul.c
Normal file
|
@ -0,0 +1,451 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 Intel Corporation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/types.h>
|
||||||
|
#include <zephyr/ztest.h>
|
||||||
|
|
||||||
|
#include <zephyr/drivers/pcie/pcie.h>
|
||||||
|
#include <zephyr/drivers/smbus.h>
|
||||||
|
|
||||||
|
#include <intel_pch_smbus.h>
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
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;
|
||||||
|
}
|
44
tests/drivers/smbus/smbus_emul/src/emul.h
Normal file
44
tests/drivers/smbus/smbus_emul/src/emul.h
Normal file
|
@ -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);
|
383
tests/drivers/smbus/smbus_emul/src/smbus.c
Normal file
383
tests/drivers/smbus/smbus_emul/src/smbus.c
Normal file
|
@ -0,0 +1,383 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 Intel Corporation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/ztest.h>
|
||||||
|
#include <zephyr/tc_util.h>
|
||||||
|
#include <zephyr/sys/byteorder.h>
|
||||||
|
#include <zephyr/random/rand32.h>
|
||||||
|
|
||||||
|
#include <zephyr/drivers/pcie/pcie.h>
|
||||||
|
#include <zephyr/drivers/smbus.h>
|
||||||
|
|
||||||
|
#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);
|
8
tests/drivers/smbus/smbus_emul/testcase.yaml
Normal file
8
tests/drivers/smbus/smbus_emul/testcase.yaml
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue