From 06c23f157ffbaf3086b9141fc34e02ff032d67dc Mon Sep 17 00:00:00 2001 From: Andrew Boie Date: Thu, 9 Aug 2018 12:38:18 -0700 Subject: [PATCH] i2s: expose i2s APIs to user mode User mode may now access the read, write, and trigger APIs. Unlike supervisor mode, memory slabs are not dealt with directly, the data is always copied. A new driver API added to fetch the current channel configuration, used by the system call handlers. The i2s_sam_ssc driver updated for the new API. CAVS driver not modified as there is no user mode port to Xtensa yet. Signed-off-by: Andrew Boie --- drivers/i2s/CMakeLists.txt | 2 + drivers/i2s/i2s_common.c | 52 +++++++++++++++++++++++++ drivers/i2s/i2s_handlers.c | 77 ++++++++++++++++++++++++++++++++++++++ drivers/i2s/i2s_sam_ssc.c | 20 ++++++++++ include/i2s.h | 77 ++++++++++++++++++++++++++++++++++++-- 5 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 drivers/i2s/i2s_common.c create mode 100644 drivers/i2s/i2s_handlers.c diff --git a/drivers/i2s/CMakeLists.txt b/drivers/i2s/CMakeLists.txt index a37c8c6c7a2..55dfbe20ef9 100644 --- a/drivers/i2s/CMakeLists.txt +++ b/drivers/i2s/CMakeLists.txt @@ -1,4 +1,6 @@ zephyr_library() +zephyr_library_sources(i2s_common.c) zephyr_library_sources_ifdef(CONFIG_I2S_SAM_SSC i2s_sam_ssc.c) zephyr_library_sources_ifdef(CONFIG_I2S_CAVS i2s_cavs.c) +zephyr_library_sources_ifdef(CONFIG_USERSPACE i2s_handlers.c) diff --git a/drivers/i2s/i2s_common.c b/drivers/i2s/i2s_common.c new file mode 100644 index 00000000000..2c2fe884b02 --- /dev/null +++ b/drivers/i2s/i2s_common.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +int _impl_i2s_buf_read(struct device *dev, void *buf, size_t *size) +{ + void *mem_block; + int ret; + + ret = i2s_read((struct device *)dev, &mem_block, size); + + if (!ret) { + struct i2s_config *rx_cfg = + i2s_config_get((struct device *)dev, I2S_DIR_RX); + + memcpy(buf, mem_block, *size); + k_mem_slab_free(rx_cfg->mem_slab, mem_block); + } + + return ret; +} + +int _impl_i2s_buf_write(struct device *dev, void *buf, size_t size) +{ + int ret; + struct i2s_config *tx_cfg; + void *mem_block; + + tx_cfg = i2s_config_get((struct device *)dev, I2S_DIR_TX); + if (!tx_cfg) { + return -EIO; + } + + if (size > tx_cfg->block_size) { + return -EINVAL; + } + + ret = k_mem_slab_alloc(tx_cfg->mem_slab, &mem_block, K_FOREVER); + if (ret < 0) { + return -ENOMEM; + } + + memcpy(mem_block, (void *)buf, size); + + return i2s_write((struct device *)dev, mem_block, size); +} diff --git a/drivers/i2s/i2s_handlers.c b/drivers/i2s/i2s_handlers.c new file mode 100644 index 00000000000..53fabdaf942 --- /dev/null +++ b/drivers/i2s/i2s_handlers.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +Z_SYSCALL_HANDLER(i2s_buf_read, dev, buf, size) +{ + void *mem_block; + size_t data_size; + int ret; + + Z_OOPS(Z_SYSCALL_DRIVER_I2S(dev, read)); + + ret = i2s_read((struct device *)dev, &mem_block, &data_size); + + if (!ret) { + struct i2s_config *rx_cfg; + int copy_success; + + /* Presumed to be configured otherwise the i2s_read() call + * would have failed. + */ + rx_cfg = i2s_config_get((struct device *)dev, I2S_DIR_RX); + + copy_success = z_user_to_copy((void *)buf, mem_block, + data_size); + + k_mem_slab_free(rx_cfg->mem_slab, mem_block); + Z_OOPS(copy_success); + Z_OOPS(z_user_to_copy((void *)size, &data_size, + sizeof(data_size))); + } + + return ret; +} + +Z_SYSCALL_HANDLER(i2s_buf_write, dev, buf, size) +{ + int ret; + struct i2s_config *tx_cfg; + void *mem_block; + + Z_OOPS(Z_SYSCALL_DRIVER_I2S(dev, write)); + tx_cfg = i2s_config_get((struct device *)dev, I2S_DIR_TX); + if (!tx_cfg) { + return -EIO; + } + + if (size > tx_cfg->block_size) { + return -EINVAL; + } + + ret = k_mem_slab_alloc(tx_cfg->mem_slab, &mem_block, K_FOREVER); + if (ret < 0) { + return -ENOMEM; + } + + ret = z_user_from_copy(mem_block, (void *)buf, size); + if (ret) { + k_mem_slab_free(tx_cfg->mem_slab, mem_block); + Z_OOPS(ret); + } + + return i2s_write((struct device *)dev, mem_block, size); +} + +Z_SYSCALL_HANDLER(i2s_trigger, dev, dir, cmd) +{ + Z_OOPS(Z_SYSCALL_DRIVER_I2S(dev, trigger)); + + return _impl_i2s_trigger((struct device *)dev, dir, cmd); +} diff --git a/drivers/i2s/i2s_sam_ssc.c b/drivers/i2s/i2s_sam_ssc.c index 0407765cea9..58910f380d6 100644 --- a/drivers/i2s/i2s_sam_ssc.c +++ b/drivers/i2s/i2s_sam_ssc.c @@ -508,6 +508,25 @@ static int bit_clock_set(Ssc *const ssc, u32_t bit_clk_freq) return 0; } +static struct i2s_config *i2s_sam_config_get(struct device *dev, + enum i2s_dir dir) +{ + struct i2s_sam_dev_data *const dev_data = DEV_DATA(dev); + struct stream *stream; + + if (dir == I2S_DIR_RX) { + stream = &dev_data->rx; + } else { + stream = &dev_data->tx; + } + + if (stream->state == I2S_STATE_NOT_READY) { + return NULL; + } + + return &stream->cfg; +} + static int i2s_sam_configure(struct device *dev, enum i2s_dir dir, struct i2s_config *i2s_cfg) { @@ -925,6 +944,7 @@ static int i2s_sam_initialize(struct device *dev) static const struct i2s_driver_api i2s_sam_driver_api = { .configure = i2s_sam_configure, + .config_get = i2s_sam_config_get, .read = i2s_sam_read, .write = i2s_sam_write, .trigger = i2s_sam_trigger, diff --git a/include/i2s.h b/include/i2s.h index cdc2d40e7ff..bb587a2422f 100644 --- a/include/i2s.h +++ b/include/i2s.h @@ -316,6 +316,8 @@ struct i2s_config { struct i2s_driver_api { int (*configure)(struct device *dev, enum i2s_dir dir, struct i2s_config *cfg); + struct i2s_config *(*config_get)(struct device *dev, + enum i2s_dir dir); int (*read)(struct device *dev, void **mem_block, size_t *size); int (*write)(struct device *dev, void *mem_block, size_t size); int (*trigger)(struct device *dev, enum i2s_dir dir, @@ -352,6 +354,22 @@ static inline int i2s_configure(struct device *dev, enum i2s_dir dir, return api->configure(dev, dir, cfg); } +/** + * @brief Fetch configuration information of a host I2S controller + * + * @param dev Pointer to the device structure for the driver instance + * @param dir Stream direction: RX or TX as defined by I2S_DIR_* + * @retval Pointer to the structure containing configuration parameters, + * or NULL if un-configured + */ +static inline struct i2s_config *i2s_config_get(struct device *dev, + enum i2s_dir dir) +{ + const struct i2s_driver_api *api = dev->driver_api; + + return api->config_get(dev, dir); +} + /** * @brief Read data from the RX queue. * @@ -383,13 +401,40 @@ static inline int i2s_configure(struct device *dev, enum i2s_dir dir, * @retval -EBUSY Returned without waiting. * @retval -EAGAIN Waiting period timed out. */ -static inline int i2s_read(struct device *dev, void **mem_block, size_t *size) +static inline int i2s_read(struct device *dev, void **mem_block, + size_t *size) { const struct i2s_driver_api *api = dev->driver_api; return api->read(dev, mem_block, size); } +/** + * @brief Read data from the RX queue into a provided buffer + * + * Data received by the I2S interface is stored in the RX queue consisting of + * memory blocks preallocated by this function from rx_mem_slab (as defined by + * i2s_configure). Calling this function removes one block from the queue + * which is copied into the provided buffer and then freed. + * + * The provided buffer must be large enough to contain a full memory block + * of data, which is parameterized for the channel via i2s_configure(). + * + * This function is otherwise equivalent to i2s_read(). + * + * @param dev Pointer to the device structure for the driver instance. + * @param buf Destination buffer for read data, which must be at least the + * as large as the configured memory block size for the RX channel. + * @param size Pointer to the variable storing the number of bytes read. + * + * @retval 0 If successful. + * @retval -EIO The interface is in NOT_READY or ERROR state and there are no + * more data blocks in the RX queue. + * @retval -EBUSY Returned without waiting. + * @retval -EAGAIN Waiting period timed out. + */ +__syscall int i2s_buf_read(struct device *dev, void *buf, size_t *size); + /** * @brief Write data to the TX queue. * @@ -423,6 +468,27 @@ static inline int i2s_write(struct device *dev, void *mem_block, size_t size) return api->write(dev, mem_block, size); } +/** + * @brief Write data to the TX queue from a provided buffer + * + * This function acquires a memory block from the I2S channel TX queue + * and copies the provided data buffer into it. It is otherwise equivalent + * to i2s_write(). + * + * @param dev Pointer to the device structure for the driver instance. + * @param buf Pointer to a buffer containing the data to transmit. + * @param size Number of bytes to write. This value has to be equal or smaller + * than the size of the channel's TX memory block configuration. + * + * @retval 0 If successful. + * @retval -EIO The interface is not in READY or RUNNING state. + * @retval -EBUSY Returned without waiting. + * @retval -EAGAIN Waiting period timed out. + * @retval -ENOMEM No memory in TX slab queue. + * @retval -EINVAL Size parameter larger than TX queue memory block. + */ +__syscall int i2s_buf_write(struct device *dev, void *buf, size_t size); + /** * @brief Send a trigger command. * @@ -436,14 +502,19 @@ static inline int i2s_write(struct device *dev, void *mem_block, size_t size) * channel cannot be allocated. * @retval -ENOMEM RX/TX memory block not available. */ -static inline int i2s_trigger(struct device *dev, enum i2s_dir dir, - enum i2s_trigger_cmd cmd) +__syscall int i2s_trigger(struct device *dev, enum i2s_dir dir, + enum i2s_trigger_cmd cmd); + +static inline int _impl_i2s_trigger(struct device *dev, enum i2s_dir dir, + enum i2s_trigger_cmd cmd) { const struct i2s_driver_api *api = dev->driver_api; return api->trigger(dev, dir, cmd); } +#include + #ifdef __cplusplus } #endif