emul: espi: Add support for eSPI emulators

Add an emulation controller which routes eSPI traffic to attached
emulators depending on the selected chip(mostly host).
This allows drivers for eSPI peripherals to be tested on systems
that don't have that peripheral attached, with the emulator handling
the eSPI traffic.

Signed-off-by: Dawid Niedzwiecki <dn@semihalf.com>
This commit is contained in:
Dawid Niedzwiecki 2020-12-16 15:07:27 +01:00 committed by Anas Nashif
commit d1948dc164
8 changed files with 387 additions and 0 deletions

View file

@ -112,6 +112,15 @@
label = "SPI_0";
};
espi0: espi@300 {
status = "okay";
compatible = "zephyr,espi-emul-controller";
reg = <0x300 4>;
#address-cells = <1>;
#size-cells = <0>;
label = "ESPI_0";
};
uart0: uart {
status = "okay";
compatible = "zephyr,native-posix-uart";

View file

@ -85,6 +85,9 @@ Zephyr includes the following emulators:
* SPI emulator driver, which does the same for SPI
* eSPI emulator driver, which does the same for eSPI. The emulator is being
developed to support more functionalities.
A GPIO emulator is planned but is not yet complete.
Samples

View file

@ -6,3 +6,4 @@ zephyr_library_sources_ifdef(CONFIG_ESPI_XEC espi_mchp_xec.c)
zephyr_library_sources_ifdef(CONFIG_ESPI_NPCX espi_npcx.c)
zephyr_library_sources_ifdef(CONFIG_ESPI_NPCX host_subs_npcx.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE espi_handlers.c)
zephyr_library_sources_ifdef(CONFIG_ESPI_EMUL espi_emul.c)

View file

@ -14,6 +14,8 @@ source "drivers/espi/Kconfig.xec"
source "drivers/espi/Kconfig.npcx"
source "drivers/espi/Kconfig.espi_emul"
module = ESPI
module-str = espi
source "subsys/logging/Kconfig.template.log_config"

View file

@ -0,0 +1,16 @@
# Copyright 2020 Google LLC
# SPDX-License-Identifier: Apache-2.0
config ESPI_EMUL
bool "eSPI emulator"
help
Enable the eSPI emulator driver. This is a fake driver,
it does not talk to real hardware. Instead it talks to emulation
drivers that pretend to be devices on the emulated eSPI bus. It is
used for testing drivers for eSPI devices.
eSPI is an interface using SPI wires, whose main goal is to reduce the
number of required pins. It includes the functionality of LPC, SMB, SPI
itself (flash access) and GPIO (virtual wires). Please refer to the
specification for more details (it is good for the introduction as well)
https://www.intel.com/content/dam/support/us/en/documents/software/chipset-software/327432-004_espi_base_specification_rev1.0_cb.pdf

207
drivers/espi/espi_emul.c Normal file
View file

@ -0,0 +1,207 @@
/*
* Copyright 2020 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*
* This driver creates fake eSPI buses which can contain emulated devices
* (mainly host), implemented by a separate emulation driver.
* The API between this driver/controller and device emulators attached
* to its bus is defined by struct emul_espi_device_api.
*/
#define DT_DRV_COMPAT zephyr_espi_emul_controller
#define LOG_LEVEL CONFIG_ESPI_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(espi_emul_ctlr);
#include <device.h>
#include <emul.h>
#include <drivers/espi.h>
#include <drivers/espi_emul.h>
#include "espi_utils.h"
/** Working data for the controller */
struct espi_emul_data {
/* List of struct espi_emul associated with the device */
sys_slist_t emuls;
/* eSPI host configuration */
struct espi_cfg cfg;
/** List of eSPI callbacks */
sys_slist_t callbacks;
};
static struct espi_emul *espi_emul_find(const struct device *dev,
unsigned int chipsel)
{
struct espi_emul_data *data = dev->data;
sys_snode_t *node;
SYS_SLIST_FOR_EACH_NODE(&data->emuls, node) {
struct espi_emul *emul;
emul = CONTAINER_OF(node, struct espi_emul, node);
if (emul->chipsel == chipsel) {
return emul;
}
}
return NULL;
}
static int espi_emul_config(const struct device *dev, struct espi_cfg *cfg)
{
struct espi_emul_data *data = dev->data;
__ASSERT_NO_MSG(cfg);
data->cfg = *cfg;
return 0;
}
static int emul_espi_trigger_event(const struct device *dev,
struct espi_event *evt)
{
struct espi_emul_data *data = dev->data;
if (((evt->evt_type & ESPI_BUS_EVENT_VWIRE_RECEIVED) &&
!(data->cfg.channel_caps & ESPI_CHANNEL_VWIRE)) ||
((evt->evt_type & ESPI_BUS_EVENT_OOB_RECEIVED) &&
!(data->cfg.channel_caps & ESPI_CHANNEL_OOB)) ||
((evt->evt_type & ESPI_BUS_PERIPHERAL_NOTIFICATION)
&& !(data->cfg.channel_caps & ESPI_CHANNEL_PERIPHERAL))) {
return -EIO;
}
espi_send_callbacks(&data->callbacks, dev, *evt);
return 0;
}
static bool espi_emul_get_channel_status(const struct device *dev, enum espi_channel ch)
{
struct espi_emul_data *data = dev->data;
return (data->cfg.channel_caps & ch);
}
static int espi_emul_send_vwire(const struct device *dev, enum espi_vwire_signal vw, uint8_t level)
{
const struct emul_espi_device_api *api;
struct espi_emul *emul;
struct espi_emul_data *data = dev->data;
if (!(data->cfg.channel_caps & ESPI_CHANNEL_VWIRE)) {
return -EIO;
}
emul = espi_emul_find(dev, EMUL_ESPI_HOST_CHIPSEL);
if (!emul) {
LOG_DBG("espi_emul not found");
return -EIO;
}
__ASSERT_NO_MSG(emul->api);
__ASSERT_NO_MSG(emul->api->set_vw);
api = emul->api;
return api->set_vw(emul, vw, level);
}
static int espi_emul_receive_vwire(const struct device *dev, enum espi_vwire_signal vw, uint8_t *level)
{
const struct emul_espi_device_api *api;
struct espi_emul *emul;
struct espi_emul_data *data = dev->data;
if (!(data->cfg.channel_caps & ESPI_CHANNEL_VWIRE)) {
return -EIO;
}
emul = espi_emul_find(dev, EMUL_ESPI_HOST_CHIPSEL);
if (!emul) {
LOG_INF("espi_emul not found");
return -EIO;
}
__ASSERT_NO_MSG(emul->api);
__ASSERT_NO_MSG(emul->api->get_vw);
api = emul->api;
return api->get_vw(emul, vw, level);
}
static int espi_emul_manage_callback(const struct device *dev, struct espi_callback *callback, bool set)
{
struct espi_emul_data *data = dev->data;
return espi_manage_callback(&data->callbacks, callback, set);
}
/**
* Set up a new emulator and add it to the list
*
* @param dev eSPI emulation controller device
*/
static int espi_emul_init(const struct device *dev)
{
struct espi_emul_data *data = dev->data;
const struct emul_list_for_bus *list = dev->config;
sys_slist_init(&data->emuls);
return emul_init_for_bus_from_list(dev, list);
}
int espi_emul_register(const struct device *dev, const char *name,
struct espi_emul *emul)
{
struct espi_emul_data *data = dev->data;
sys_slist_append(&data->emuls, &emul->node);
LOG_INF("Register emulator '%s' at cs %u\n", name, emul->chipsel);
return 0;
}
/* Device instantiation */
static struct emul_espi_driver_api emul_espi_driver_api = {
.espi_api = {
.config = espi_emul_config,
.get_channel_status = espi_emul_get_channel_status,
.send_vwire = espi_emul_send_vwire,
.receive_vwire = espi_emul_receive_vwire,
.manage_callback = espi_emul_manage_callback
},
.trigger_event = emul_espi_trigger_event,
.find_emul = espi_emul_find,
};
#define EMUL_LINK_AND_COMMA(node_id) { \
.label = DT_LABEL(node_id), \
},
#define ESPI_EMUL_INIT(n) \
static const struct emul_link_for_bus emuls_##n[] = { \
DT_FOREACH_CHILD(DT_DRV_INST(n), EMUL_LINK_AND_COMMA) \
}; \
static struct emul_list_for_bus espi_emul_cfg_##n = { \
.children = emuls_##n, \
.num_children = ARRAY_SIZE(emuls_##n), \
}; \
static struct espi_emul_data espi_emul_data_##n; \
DEVICE_DT_INST_DEFINE(n, \
&espi_emul_init, \
device_pm_control_nop, \
&espi_emul_data_##n, \
&espi_emul_cfg_##n, \
POST_KERNEL, \
CONFIG_ESPI_INIT_PRIORITY, \
&emul_espi_driver_api);
DT_INST_FOREACH_STATUS_OKAY(ESPI_EMUL_INIT)

View file

@ -0,0 +1,12 @@
# Copyright 2020 Google LLC
# SPDX-License-Identifier: Apache-2.0
description: Zephyr eSPI Emulation controller
compatible: "zephyr,espi-emul-controller"
include: espi-controller.yaml
properties:
reg:
required: true

137
include/drivers/espi_emul.h Normal file
View file

@ -0,0 +1,137 @@
/*
* Copyright 2020 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_ESPI_SPI_EMUL_H_
#define ZEPHYR_INCLUDE_DRIVERS_ESPI_SPI_EMUL_H_
/**
* @file
*
* @brief Public APIs for the eSPI emulation drivers.
*/
#include <zephyr/types.h>
#include <device.h>
/**
* @brief eSPI Emulation Interface
* @defgroup espi_emul_interface eSPI Emulation Interface
* @ingroup io_emulators
* @{
*/
#ifdef __cplusplus
extern "C" {
#endif
#define EMUL_ESPI_HOST_CHIPSEL 0
struct espi_emul;
/**
* Passes eSPI virtual wires set request (virtual wire packet) to the emulator.
* The emulator updates the state (level) of its virtual wire.
*
* @param emul Emulator instance
* @param vw The signal to be set.
* @param level The level of signal requested LOW(0) or HIGH(1).
*
* @retval 0 If successful.
* @retval -EIO General input / output error.
*/
typedef int (*emul_espi_api_set_vw)(struct espi_emul *emul,
enum espi_vwire_signal vw,
uint8_t level);
/**
* Passes eSPI virtual wires get request (virtual wire packet) to the emulator.
* The emulator returns the state (level) of its virtual wire.
*
* @param emul Emulator instance
* @param vw The signal to be get.
* @param level The level of the signal to be get.
*
* @retval 0 If successful.
* @retval -EIO General input / output error.
*/
typedef int (*emul_espi_api_get_vw)(struct espi_emul *emul,
enum espi_vwire_signal vw,
uint8_t *level);
/**
* Find an emulator present on a eSPI bus
*
* At present the function is used only to find an emulator of the host
* device. It may be useful in systems with the SPI flash chips.
*
* @param dev eSPI emulation controller device
* @param chipsel Chip-select value
* @return emulator to use
* @return NULL if not found
*/
typedef struct espi_emul *(*emul_find_emul)(const struct device *dev,
unsigned int chipsel);
/**
* Triggers an event on the emulator of eSPI controller side which causes
* calling specific callbacks.
*
* @param dev Device instance of emulated eSPI controller
* @param evt Event to be triggered
*
* @retval 0 If successful.
* @retval -EIO General input / output error.
*/
typedef int (*emul_trigger_event)(const struct device *dev,
struct espi_event *evt);
/** Definition of the eSPI device emulator API */
struct emul_espi_device_api {
emul_espi_api_set_vw set_vw;
emul_espi_api_get_vw get_vw;
};
/** Node in a linked list of emulators for eSPI devices */
struct espi_emul {
sys_snode_t node;
/* API provided for this device */
const struct emul_espi_device_api *api;
/* eSPI chip-select of the emulated device */
uint16_t chipsel;
};
/** Definition of the eSPI controller emulator API */
struct emul_espi_driver_api {
/* The struct espi_driver_api has to be first in
* struct emul_espi_driver_api to make pointer casting working
*/
struct espi_driver_api espi_api;
/* The rest, emulator specific functions */
emul_trigger_event trigger_event;
emul_find_emul find_emul;
};
/**
* Register an emulated device on the controller
*
* @param dev Device that will use the emulator
* @param name User-friendly name for this emulator
* @param emul eSPI emulator to use
* @return 0 indicating success (always)
*/
int espi_emul_register(const struct device *dev, const char *name,
struct espi_emul *emul);
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#endif /* ZEPHYR_INCLUDE_DRIVERS_ESPI_SPI_EMUL_H_ */