drivers: misc: devmux: a device multiplexer pseudo-device

The Device Multiplexer (devmux) is a pseudo-device that can
be used to select between multiple included sub-devices.

It is experimental, but its current use is in system
remediation. Take for example, the scenario where the
system console and log subsystem both have the uart backend
enabled. The case may arise, where the chosen backing uart
could be an abstraction of another very high-bandwidth bus
- such as a PCIe BAR, a UDP socket, or even even just memory.

If the "service" (for lack of a better term) that backs this
abstract "uart" experiences an error, it is of critical
importance to be able to switch the system console, uart log
backend, or whatever to another uart (semi-transparently) in
order to bring up a shell, continue to view system logs, or
even just support user console I/O.

Signed-off-by: Christopher Friedt <cfriedt@meta.com>
This commit is contained in:
Christopher Friedt 2023-09-22 15:26:11 -04:00 committed by Carles Cufí
parent afc59112a9
commit 37e19451ec
7 changed files with 337 additions and 0 deletions

View file

@ -6,3 +6,4 @@ add_subdirectory_ifdef(CONFIG_GROVE_LCD_RGB grove_lcd_rgb)
add_subdirectory_ifdef(CONFIG_PIO_RPI_PICO pio_rpi_pico)
add_subdirectory_ifdef(CONFIG_NXP_S32_EMIOS nxp_s32_emios)
add_subdirectory_ifdef(CONFIG_TIMEAWARE_GPIO timeaware_gpio)
add_subdirectory_ifdef(CONFIG_DEVMUX devmux)

View file

@ -10,5 +10,6 @@ source "drivers/misc/grove_lcd_rgb/Kconfig"
source "drivers/misc/pio_rpi_pico/Kconfig"
source "drivers/misc/nxp_s32_emios/Kconfig"
source "drivers/misc/timeaware_gpio/Kconfig"
source "drivers/misc/devmux/Kconfig"
endmenu

View file

@ -0,0 +1,10 @@
# Copyright (c) 2023, Meta
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_syscall_header(
${ZEPHYR_BASE}/include/zephyr/drivers/misc/devmux/devmux.h
)
zephyr_library_sources(devmux.c)

View file

@ -0,0 +1,23 @@
# Copyright (c) 2023, Meta
# SPDX-License-Identifier: Apache-2.0
config DEVMUX
bool "Device Multiplexer (devmux) [EXPERIMENTAL]"
depends on DT_HAS_ZEPHYR_DEVMUX_ENABLED
depends on DEVICE_MUTABLE
select EXPERIMENTAL
help
Devmux is a pseudo-device that operates as a device switch. It allows
software to select the data, config, and api from a number of linked
devices.
if DEVMUX
config DEVMUX_INIT_PRIORITY
int "Devmux init priority"
default 51
help
Init priority for the devmux driver. It must be
greater than the priority of the initially selected muxed device.
endif

View file

@ -0,0 +1,179 @@
/*
* Copyright (c) 2023, Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT zephyr_devmux
#include <zephyr/device.h>
#include <zephyr/drivers/misc/devmux/devmux.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
struct devmux_config {
const struct device **devs;
const size_t n_devs;
};
struct devmux_data {
struct k_spinlock lock;
size_t selected;
};
/* The number of devmux devices */
#define N DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT)
static const struct device *devmux_devices[N];
static const struct devmux_config *devmux_configs[N];
static struct devmux_data *devmux_datas[N];
static bool devmux_device_is_valid(const struct device *dev)
{
for (size_t i = 0; i < N; ++i) {
if (dev == devmux_devices[i]) {
return true;
}
}
return false;
}
static size_t devmux_inst_get(const struct device *dev)
{
for (size_t i = 0; i < N; i++) {
if (dev == devmux_devices[i]) {
return i;
}
}
return SIZE_MAX;
}
const struct devmux_config *devmux_config_get(const struct device *dev)
{
for (size_t i = 0; i < N; i++) {
if (dev == devmux_devices[i]) {
return devmux_configs[i];
}
}
return NULL;
}
struct devmux_data *devmux_data_get(const struct device *dev)
{
for (size_t i = 0; i < N; i++) {
if (dev == devmux_devices[i]) {
return devmux_datas[i];
}
}
return NULL;
}
ssize_t z_impl_devmux_select_get(const struct device *dev)
{
ssize_t index;
struct devmux_data *const data = devmux_data_get(dev);
if (!devmux_device_is_valid(dev)) {
return -EINVAL;
}
K_SPINLOCK(&data->lock)
{
index = data->selected;
}
return index;
}
#ifdef CONFIG_USERSPACE
ssize_t z_vrfy_devmux_select_get(const struct device *dev)
{
return z_impl_devmux_select_get(dev);
}
#include <syscalls/devmux_select_get_mrsh.c>
#endif
int z_impl_devmux_select_set(struct device *dev, size_t index)
{
struct devmux_data *const data = devmux_data_get(dev);
const struct devmux_config *config = devmux_config_get(dev);
if (!devmux_device_is_valid(dev) || index >= config->n_devs) {
return -EINVAL;
}
if (!device_is_ready(config->devs[index])) {
return -ENODEV;
}
K_SPINLOCK(&data->lock)
{
*dev = *config->devs[index];
data->selected = index;
}
return 0;
}
#ifdef CONFIG_USERSPACE
int z_vrfy_devmux_select_set(struct device *dev, size_t index)
{
return z_impl_devmux_select_set(dev, index);
}
#include <syscalls/devmux_select_set_mrsh.c>
#endif
static int devmux_init(struct device *const dev)
{
size_t inst = devmux_inst_get(dev);
struct devmux_data *const data = dev->data;
const struct devmux_config *config = dev->config;
size_t sel = data->selected;
devmux_configs[inst] = config;
devmux_datas[inst] = data;
if (!device_is_ready(config->devs[sel])) {
return -ENODEV;
}
*dev = *config->devs[sel];
return 0;
}
#define DEVMUX_PHANDLE_TO_DEVICE(node_id, prop, idx) \
DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx))
#define DEVMUX_PHANDLE_DEVICES(_n) \
DT_INST_FOREACH_PROP_ELEM_SEP(_n, devices, DEVMUX_PHANDLE_TO_DEVICE, (,))
#define DEVMUX_SELECTED(_n) DT_INST_PROP(_n, selected)
#define DEVMUX_DEFINE(_n) \
BUILD_ASSERT(DT_INST_PROP_OR(_n, zephyr_mutable, 0), \
"devmux nodes must contain the 'zephyr,mutable' property"); \
BUILD_ASSERT(DT_INST_PROP_LEN(_n, devices) > 0, "devices array must have non-zero size"); \
BUILD_ASSERT(DEVMUX_SELECTED(_n) >= 0, "selected must be > 0"); \
BUILD_ASSERT(DEVMUX_SELECTED(_n) < DT_INST_PROP_LEN(_n, devices), \
"selected must be within bounds of devices phandle array"); \
static const struct device *demux_devs_##_n[] = {DEVMUX_PHANDLE_DEVICES(_n)}; \
static const struct devmux_config devmux_config_##_n = { \
.devs = demux_devs_##_n, \
.n_devs = DT_INST_PROP_LEN(_n, devices), \
}; \
static struct devmux_data devmux_data_##_n = { \
.selected = DEVMUX_SELECTED(_n), \
}; \
\
DEVICE_DT_INST_DEFINE(_n, devmux_init, NULL, &devmux_data_##_n, &devmux_config_##_n, \
PRE_KERNEL_1, CONFIG_DEVMUX_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(DEVMUX_DEFINE)
#define DEVMUX_DEVICE_GET(_n) DEVICE_DT_INST_GET(_n),
static const struct device *devmux_devices[] = {DT_INST_FOREACH_STATUS_OKAY(DEVMUX_DEVICE_GET)};

View file

@ -0,0 +1,33 @@
# Copyright (c) 2023, Meta
# SPDX-License-Identifier: Apache-2.0
description: Generic Device Multiplexer
compatible: "zephyr,devmux"
include: [base.yaml, mutable.yaml]
properties:
devices:
type: phandles
required: true
description: |
Devices to be multiplexed.
selected:
type: int
default: 0
description: |
Initial multiplexer selection.
This must be in the range [0, N-1], where N is the length of the
'devices' phandle list.
If unspecified, the default selection is zero in order to ensure that
the multiplexer is ready for use (i.e. one of the [0, N-1] multiplexed
devices is selected). Zero is, necessarily, the only possible valid
default value since the phandle list must have length >= 1.
Note: Specifying a value of 'selected' outside the range [0, N-1]
results in a compile-time error.

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2023, Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Public APIs for the Device Multiplexer driver
*/
#ifndef INCLUDE_ZEPHYR_DRIVERS_MISC_DEVMUX_H_
#define INCLUDE_ZEPHYR_DRIVERS_MISC_DEVMUX_H_
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Devmux Driver APIs
* @defgroup demux_interface Devmux Driver APIs
* @ingroup misc_interfaces
*
* @details
* Devmux operates as a device multiplexer, forwarding the characteristics of
* the selected device.
*
* ```
* +----------+ +----------+
* | devmux | | devmux |
* | | | |
* dev0 | | dev0 | |
* +----------> \ | +----------> |
* | \ | | |
* dev1 | \ | dev0 dev1 | | dev2
* +----------> O +----------> +----------> O +---------->
* | | | / |
* dev2 | | dev2 | / |
* +----------> | +----------> / |
* | | | |
* | | | |
* | | | |
* +-----^----+ +-----^----+
* | |
* select == 0 | select == 2 |
* +--------------+ +---------------+
* ```
* @{
*/
/**
* @brief Get the current selection of a devmux device.
*
* Return the index of the currently selected device.
*
* @param dev the devmux device
* @return The index (>= 0) of the currently active multiplexed device on success
* @retval -EINVAL If @p dev is invalid
*/
__syscall ssize_t devmux_select_get(const struct device *dev);
/**
* @brief Set the selection of a devmux device.
*
* Select the device at @p index.
*
* @param[in] dev the devmux device
* @param index the index representing the desired selection
* @retval 0 On success
* @retval -EINVAL If @p dev is invalid
* @retval -ENODEV If the multiplexed device at @p index is not ready
*/
__syscall int devmux_select_set(struct device *dev, size_t index);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#include <syscalls/devmux.h>
#endif /* INCLUDE_ZEPHYR_DRIVERS_MISC_DEVMUX_H_ */