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:
parent
afc59112a9
commit
37e19451ec
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
10
drivers/misc/devmux/CMakeLists.txt
Normal file
10
drivers/misc/devmux/CMakeLists.txt
Normal 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)
|
23
drivers/misc/devmux/Kconfig
Normal file
23
drivers/misc/devmux/Kconfig
Normal 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
|
179
drivers/misc/devmux/devmux.c
Normal file
179
drivers/misc/devmux/devmux.c
Normal 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)};
|
33
dts/bindings/misc/zephyr,devmux.yaml
Normal file
33
dts/bindings/misc/zephyr,devmux.yaml
Normal 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.
|
90
include/zephyr/drivers/misc/devmux/devmux.h
Normal file
90
include/zephyr/drivers/misc/devmux/devmux.h
Normal 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_ */
|
Loading…
Reference in a new issue