drivers/ipm: Add ipm_cavs_host: host/DSP communication on adsp_intel

Intel Audio DSPs have "IPC" interrupt delivery and shared memory
window hardware.  The Sound Open Firmware project has historically
used the combination of these to implement a bidirectional
message-passing interface.  As it happens, this protocol is an
excellent fit for Zephyr's somewhat geriatric but still useful IPM
interface.

This implements a SOF-protocol-compatible transport that will
hopefully prove a bit more futureproof for non-Intel SOF
architectures.  It is a software-only device, built on top of the
underlying SOC APIs for the SRAM windows (in cavs-shim) and IPC
(cavs_ipc).

Note that SOF actually has two protocol variants (ipc3 and ipc4): in
both, the command header (passed as the "id" parameter in IPM) is sent
via the hardware doorbell register.  But in ipc4, the second hardware
scratch register is used to transmit the first four bytes of the
command before involving the SRAM window (in ipc3, it's ignored).
Both modes are supported by this driver, set IPM_CAVS_HOST_REGWORD to
choose the "ipc4" variant.

Finally: note that the memory layout for the windows in question is
inherited from SOF, and for compatibility (with both SOF and with the
offsets used by the host!) these can't be changed without major
surgery.  They're defined in kconfig, but should be treated as
read-only until we get a chance to rework the way Zephyr does its SRAM
window management (and probably in concert with the host drivers).

Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This commit is contained in:
Andy Ross 2022-02-15 08:44:37 -08:00 committed by Anas Nashif
commit 27a59ec9d5
3 changed files with 229 additions and 0 deletions

View file

@ -10,5 +10,6 @@ zephyr_library_sources_ifdef(CONFIG_IPM_STM32_IPCC ipm_stm32_ipcc.c)
zephyr_library_sources_ifdef(CONFIG_IPM_NRFX ipm_nrfx_ipc.c)
zephyr_library_sources_ifdef(CONFIG_IPM_CAVS_IDC ipm_cavs_idc.c)
zephyr_library_sources_ifdef(CONFIG_IPM_STM32_HSEM ipm_stm32_hsem.c)
zephyr_library_sources_ifdef(CONFIG_IPM_CAVS_HOST ipm_cavs_host.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE ipm_handlers.c)

View file

@ -131,12 +131,54 @@ config IPM_STM32_HSEM_CPU
config IPM_CALLBACK_ASYNC
bool "Deliver callbacks asynchronously"
default y if IPM_CAVS_HOST
help
When selected, the driver supports "asynchronous" command
delivery. Commands will stay active after the ISR returns,
until the application expressly "completes" the command
later.
config IPM_CAVS_HOST
bool "cAVS DSP/host communication"
select CAVS_IPC
help
Driver for host/DSP communication on intel_adsp devices
if IPM_CAVS_HOST
config IPM_CAVS_HOST_INBOX_OFFSET
hex "Byte offset of cAVS inbox window"
depends on CAVS_IPC
default 0x6000
help
Location of the host-writable inbox window within the
HP_SRAM_RESERVE region. This location must be synchronized
with host driver and SOF source code (must match
SRAM_INBOX_BASE). Be careful.
config IPM_CAVS_HOST_OUTBOX_OFFSET
hex "Byte offset of cAVS outbox memory"
depends on CAVS_IPC
default 0x1000
help
Location of the "outbox" region for SOF IPC3/4 message
within the pre-existing window 0 (this is not the same as
the HP_SRAM_RESERVE region used for INBOX_OFFSET). This
location must be synchronized with host driver and SOF
source code (where it must equal SRAM_SW_REG_SIZE). Be
careful.
config IPM_CAVS_HOST_REGWORD
bool "Store first 4 bytes in IPC register"
depends on CAVS_IPC
depends on !SOC_SERIES_INTEL_CAVS_V15
help
Protocol variant. When true, the first four bytes of a
message are passed in the cAVS IDR/TDR register pair instead
of in the SRAM window. Only available on cAVS 1.8+.
endif # IPM_CAVS_HOST
module = IPM
module-str = ipm
source "subsys/logging/Kconfig.template.log_config"

186
drivers/ipm/ipm_cavs_host.c Normal file
View file

@ -0,0 +1,186 @@
/* Copyright (c) 2022, Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#include <kernel.h>
#include <drivers/ipm.h>
#include <cavs-mem.h>
#include <cavs-shim.h>
#include <cavs_ipc.h>
/* Matches SOF_IPC_MSG_MAX_SIZE, though in practice nothing anywhere
* near that big is ever sent. Should maybe consider making this a
* kconfig to avoid waste.
*/
#define MAX_MSG 384
/* Note: these addresses aren't flexible! We require that they match
* current SOF ipc3/4 layout, which means that:
*
* + Buffer addresses are 4k-aligned (this is a hardware requirement)
* + Inbuf must be 4k after outbuf, with no use of the intervening memory
* + Oubuf must be 4k after the start of win0 (this is where the host driver looks)
*
* One side effect is that the word "before" MSG_INBUF is owned by our
* code too, and can be used for a nice trick below.
*/
#define BUFPTR(ptr, off) ((uint32_t *) \
arch_xtensa_uncached_ptr((void *)((uint32_t)ptr + off)))
#define MSG_INBUF BUFPTR(L2_SRAM_BASE, CONFIG_IPM_CAVS_HOST_INBOX_OFFSET)
#define MSG_OUTBUF BUFPTR(HP_SRAM_WIN0_BASE, CONFIG_IPM_CAVS_HOST_OUTBOX_OFFSET)
struct ipm_cavs_host_data {
ipm_callback_t callback;
void *user_data;
bool enabled;
};
/* Note: this call is unsynchronized. The IPM docs are silent as to
* whether this is required, and the SOF code that will be using this
* is externally synchronized already.
*/
static int send(const struct device *ipmdev, int wait, uint32_t id,
const void *data, int size)
{
if (!cavs_ipc_is_complete(CAVS_HOST_DEV)) {
return -EBUSY;
}
if (size > MAX_MSG) {
return -EMSGSIZE;
}
if ((id & 0xc0000000) != 0) {
/* cAVS IDR register has only 30 usable bits */
return -EINVAL;
}
uint32_t ext_data = 0;
/* Protocol variant (used by SOF "ipc4"): store the first word
* of the message in the IPC scratch registers
*/
if (IS_ENABLED(CONFIG_IPM_CAVS_HOST_REGWORD) && size >= 4) {
ext_data = ((uint32_t *)data)[0];
data = &((const uint32_t *)data)[1];
size -= 4;
}
memcpy(MSG_OUTBUF, data, size);
bool ok = cavs_ipc_send_message(CAVS_HOST_DEV, id, ext_data);
/* The IPM docs call for "busy waiting" here, but in fact
* there's a blocking synchrnous call available that might be
* better. But then we'd have to check whether we're in
* interrupt context, and it's not clear to me that SOF would
* benefit anyway as all its usage is async. This is OK for
* now.
*/
if (ok && wait) {
while (!cavs_ipc_is_complete(CAVS_HOST_DEV)) {
k_busy_wait(1);
}
}
return ok ? 0 : -EBUSY;
}
static bool ipc_handler(const struct device *dev, void *arg,
uint32_t data, uint32_t ext_data)
{
ARG_UNUSED(arg);
struct device *ipmdev = arg;
struct ipm_cavs_host_data *devdata = ipmdev->data;
uint32_t *msg = MSG_INBUF;
/* We play tricks to leave one word available before the
* beginning of the SRAM window, this way the host can see the
* same offsets it does with the original ipc4 protocol
* implementation, but here in the firmware we see a single
* contiguous buffer. See above.
*/
if (IS_ENABLED(CONFIG_IPM_CAVS_HOST_REGWORD)) {
msg = &msg[-1];
msg[0] = ext_data;
}
if (devdata->enabled && (devdata->callback != NULL)) {
devdata->callback(ipmdev, devdata->user_data,
data & 0x3fffffff, msg);
}
/* Return false for async handling */
return !IS_ENABLED(IPM_CALLBACK_ASYNC);
}
static int max_data_size_get(const struct device *ipmdev)
{
return MAX_MSG;
}
static uint32_t max_id_val_get(const struct device *ipmdev)
{
/* 30 user-writable bits in cAVS IDR register */
return 0x3fffffff;
}
static void register_callback(const struct device *port,
ipm_callback_t cb,
void *user_data)
{
struct ipm_cavs_host_data *data = port->data;
data->callback = cb;
data->user_data = user_data;
}
static int set_enabled(const struct device *ipmdev, int enable)
{
/* This protocol doesn't support any kind of queuing, and in
* fact will stall if a message goes unacknowledged. Support
* it as best we can by gating the callbacks only. That will
* allow the DONE notifications to proceed as normal, at the
* cost of dropping any messages received while not "enabled"
* of course.
*/
struct ipm_cavs_host_data *data = ipmdev->data;
data->enabled = enable;
return 0;
}
static void complete(const struct device *ipmdev)
{
cavs_ipc_complete(CAVS_HOST_DEV);
}
static int init(const struct device *dev)
{
struct ipm_cavs_host_data *data = dev->data;
/* Initialize hardware SRAM window. SOF will give the host 8k
* here, let's limit it to just the memory we're using for
* futureproofing.
*/
CAVS_WIN[1].dmwlo = ROUND_UP(MAX_MSG, 8);
CAVS_WIN[1].dmwba = ((uint32_t) MSG_INBUF) | CAVS_DMWBA_ENABLE;
cavs_ipc_set_message_handler(CAVS_HOST_DEV, ipc_handler, (void *)dev);
data->enabled = true;
return 0;
}
static const struct ipm_driver_api api = {
.send = send,
.max_data_size_get = max_data_size_get,
.max_id_val_get = max_id_val_get,
.register_callback = register_callback,
.set_enabled = set_enabled,
.complete = complete,
};
static struct ipm_cavs_host_data data;
DEVICE_DEFINE(ipm_cavs_host, "ipm_cavs_host", init, NULL, &data, NULL,
PRE_KERNEL_2, 1, &api);