soc/intel_adsp: Low level HDA driver and tests

Adds a header only low level driver for HDA streams along with smoke
tests to ensure basic host in and out stream functionality.

The tests require host side interaction. In cavstool a new HDAStream
class encapsulates somewhat a single stream and its registers. This
is manipulated in the tests using IPC with the Host ensuring that a
specific order of operations is done.

This low level driver allows testing certain hardware configurations
and flows with easy to use register dump debugging. It is not
intended to be the end API an application might use. That would be
a DMA driver using this.

Signed-off-by: Tom Burdick <thomas.burdick@intel.com>
This commit is contained in:
Tom Burdick 2022-03-02 11:43:51 -06:00 committed by Anas Nashif
commit cc6e9c094a
9 changed files with 764 additions and 7 deletions

View file

@ -0,0 +1,289 @@
/* Copyright (c) 2022 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_CAVS_HDA_H
#define ZEPHYR_INCLUDE_CAVS_HDA_H
#include <arch/xtensa/cache.h>
#include <kernel.h>
#include <device.h>
#include <cavs-shim.h>
#include <cavs-mem.h>
/**
* @brief HDA stream functionality for cAVS
*
* Provides low level calls to support cAVS HDA streams with
* minimal abstraction that allows testing the hardware
* and its demands separately from the intended DMA API
* usage.
*/
#define HDA_HOST_OUT_BASE 0x72800
#define HDA_HOST_IN_BASE 0x72c00
#define HDA_STREAM_COUNT 7
#define HDA_REGBLOCK_SIZE 0x40
/* The read/write positions are masked to 24 bits */
#define HDA_RWP_MASK 0x00FFFFFF
/* Buffers must be 128 byte aligned, this mask enforces that */
#define HDA_ALIGN_MASK 0xFFFFFF80
#define HDA_ADDR(base, stream) ((base) + (stream)*HDA_REGBLOCK_SIZE)
/* Gateway Control and Status Register */
#define DGCS(base, stream) ((volatile uint32_t *)HDA_ADDR(base, stream))
#define DGCS_SCS BIT(31) /* Sample container size */
#define DGCS_GEN BIT(26) /* Gateway Enable */
#define DGCS_L1ETP BIT(25) /* L1 Enter Prevent */
#define DGCS_L1EXP BIT(25) /* L1 Exit Prevent */
#define DGCS_FWCB BIT(23) /* Firmware Control Buffer */
#define DGCS_GBUSY BIT(15) /* Gateway Busy */
#define DGCS_TE BIT(14) /* Transfer Error */
#define DGCS_BSC BIT(11) /* Buffer Segment Completion */
#define DGCS_BOR BIT(10) /* Buffer Overrun */
#define DGCS_BUR BIT(10) /* Buffer Underrun */
#define DGCS_BF BIT(9) /* Buffer Full */
#define DGCS_BNE BIT(8) /* Buffer Not Empty */
#define DGCS_FIFORDY BIT(5) /* Enable FIFO */
#define DGCS_BSCIE BIT(3) /* Buffer Segment Completion Interrupt Enable */
/* Gateway Buffer Base Address */
#define DGBBA(base, stream) ((volatile uint32_t *)(HDA_ADDR(base, stream) + 0x04))
/* Gateway Buffer Size */
#define DGBS(base, stream) ((volatile uint32_t *)(HDA_ADDR(base, stream) + 0x08))
/* Gateway Buffer Position Increment */
#define DGBFPI(base, stream) ((volatile uint32_t *)(HDA_ADDR(base, stream) + 0x0c))
/* Gateway Buffer Read Position */
#define DGBRP(base, stream) ((volatile uint32_t *)(HDA_ADDR(base, stream) + 0x10))
/* Gateway Buffer Write Position */
#define DGBWP(base, stream) ((volatile uint32_t *)(HDA_ADDR(base, stream) + 0x14))
/* Gateway Buffer Segment Position */
#define DGBSP(base, stream) ((volatile uint32_t *)(HDA_ADDR(base, stream) + 0x18))
/* Gateway Minimum Buffer Size */
#define DGMBS(base, stream) ((volatile uint32_t *)(HDA_ADDR(base, stream) + 0x1c))
/* Gateway Linear Link Position Increment */
#define DGLLPI(base, stream) ((volatile uint32_t *)(HDA_ADDR(base, stream) + 0x24))
/* Gateway Linear Position In Buffer Increment */
#define DGLPIBI(base, stream) ((volatile uint32_t *)(HDA_ADDR(base, stream) + 0x28))
/**
* @brief Dump all the useful registers of an HDA stream to printk
*
* This can be invaluable when finding out why HDA isn't doing (or maybe is)
* doing what you want it to do. Macro so you get the file and line
* of the call site included.
*
* @param name String that contains a name of the hda stream (or anything really)
* @param base Base address of the IP register block
* @param sid Stream ID
*/
#define cavs_hda_dbg(name, base, sid) \
printk("%s:%u %s(%u:0x%p), dgcs: 0x%x, dgbba 0x%x, " \
"dgbs %u, dgbrp %u, dgbwp %u, dgbsp %u, " \
"dgmbs %u, dgbllpi 0x%x, dglpibi 0x%x\n", \
__FILE__, __LINE__, name, \
sid, DGCS(base, sid), \
*DGCS(base, sid), \
*DGBBA(base, sid), \
*DGBS(base, sid), \
*DGBRP(base, sid), \
*DGBWP(base, sid), \
*DGBSP(base, sid), \
*DGMBS(base, sid), \
*DGLLPI(base, sid), \
*DGLPIBI(base, sid))
/**
* @brief Initialize an HDA stream for use with the firmware
*
* @param hda Stream set to work with
* @param sid Stream ID
*/
static inline void cavs_hda_init(uint32_t base, uint32_t sid)
{
*DGCS(base, sid) |= DGCS_FWCB;
}
/**
* @brief Set the buffer, size, and element size for an HDA stream
*
* Sanity checks that the buffer address and size are valid and that the
* stream isn't enabled or busy.
*
* Prior to enabling an HDA stream to/from the host this is the minimum configuration
* that is required. It must be set *after* the host has configured its own buffers.
*
*
* @param hda Stream set to work with
* @param sid Stream ID
* @param buf Buffer address to use for the shared FIFO. Must be in L2 and 128 byte aligned.
* @param buf_size Buffer size in bytes Must be 128 byte aligned
*
* @retval -EBUSY if the HDA stream is already enabled
* @retval -EINVAL if the buf is not in L2, buf isn't aligned on 128 byte boundaries
* @retval 0 on Success
*/
static inline int cavs_hda_set_buffer(uint32_t base, uint32_t sid,
uint8_t *buf, uint32_t buf_size)
{
/* While we don't actually care if the pointer is in the cached
* region or not, we do need a consistent address space to check
* against for our assertion. This is cheap.
*/
uint32_t addr = (uint32_t)arch_xtensa_cached_ptr(buf);
uint32_t aligned_addr = addr & HDA_ALIGN_MASK;
uint32_t aligned_size = buf_size & HDA_ALIGN_MASK;
__ASSERT(aligned_addr == addr, "Buffer must be 128 byte aligned");
__ASSERT(aligned_addr >= L2_SRAM_BASE
&& aligned_addr < L2_SRAM_BASE + L2_SRAM_SIZE,
"Buffer must be in L2 address space");
__ASSERT(aligned_size == buf_size,
"Buffer must be 128 byte aligned in size");
__ASSERT(aligned_addr + aligned_size < L2_SRAM_BASE + L2_SRAM_SIZE,
"Buffer must end in L2 address space");
if (*DGCS(base, sid) & DGCS_GEN) {
return -EBUSY;
}
if (*DGCS(base, sid) & DGCS_GBUSY) {
return -EBUSY;
}
*DGBBA(base, sid) = aligned_addr;
*DGBS(base, sid) = aligned_size;
return 0;
}
/**
* @brief Enable the stream
*
* @param hda HDA stream set
* @param sid Stream ID
*/
static inline void cavs_hda_enable(uint32_t base, uint32_t sid)
{
*DGCS(base, sid) |= DGCS_GEN | DGCS_FIFORDY;
}
/**
* @brief Disable stream
*
* @param hda HDA stream set
* @param sid Stream ID
*/
static inline void cavs_hda_disable(uint32_t base, uint32_t sid)
{
*DGCS(base, sid) &= ~(DGCS_GEN | DGCS_FIFORDY);
}
/**
* @brief Determine the number of unused bytes in the buffer
*
* This is useful primarily for a host in (dsp -> host) stream.
*
* @param base Base address of the IP register block
* @param sid Stream ID within the register block
*
* @retval n Number of unused bytes
*/
static inline uint32_t cavs_hda_unused(uint32_t base, uint32_t sid)
{
uint32_t dgcs = *DGCS(base, sid);
uint32_t dgbs = *DGBS(base, sid);
/* Check if buffer is empty */
if ((dgcs & DGCS_BNE) == 0) {
return dgbs;
}
int32_t rp = *DGBRP(base, sid);
int32_t wp = *DGBWP(base, sid);
int32_t size = rp - wp;
if (size <= 0) {
size += dgbs;
}
return size;
}
/**
* @brief Commit a number of bytes that have been transferred
*
* Writes the length to BFPI. For host transfers LLPI and LPIB are
* also written to with the given length.
*
* This then updates the read or write position depending on the direction.
*
* LPIBI writes here can be seen on the host side of the transfer in the
* matching LPIB register.
*
* LLPI seems to, from behavior, inform the hardware to actually read/write
* from the buffer. Without updating BFPI AND LLPI, the transfer doesn't
* happen in testing for host transfers.
*
* @param base Base address of the IP register block
* @param sid Stream ID within the register block
* @param len Len to increment postion by
*/
static inline void cavs_hda_commit(uint32_t base, uint32_t sid, uint32_t len)
{
*DGBFPI(base, sid) = len;
if (base == HDA_HOST_IN_BASE || base == HDA_HOST_OUT_BASE) {
*DGLLPI(base, sid) = len;
*DGLPIBI(base, sid) = len;
}
}
/**
* @brief Read the buffer full bit of the given stream.
*
* @param base Base address of the IP register block
* @param sid Stream ID within the register block
*
* @retval true If the buffer full flag is set
*/
static inline bool cavs_hda_buf_full(uint32_t base, uint32_t sid)
{
return *DGCS(base, sid) & DGCS_BF;
}
/**
* @brief Check if the write and read position are equal
*
* For HDA this does not mean that the buffer is full or empty
* there are bit flags for those cases.
*
* This can let you wait on the hardware to catch up to your
* reads or writes (ex after a cavs_hda_commit)
*
* @param dev HDA Stream device
* @param sid Stream ID
*
* @retval true If the read and write positions are equal
*/
static inline bool cavs_hda_wp_rp_eq(uint32_t base, uint32_t sid)
{
return *DGBWP(base, sid) == *DGBRP(base, sid);
}
#endif /* ZEPHYR_INCLUDE_CAVS_HDA_H */

View file

@ -40,6 +40,26 @@ enum cavstool_cmd {
/* The host copies OUTBOX[ext_data >> 16] to INBOX[ext_data & 0xffff] */
IPCCMD_WINCOPY,
/* The host clears the run bit and resets the HDA stream */
IPCCMD_HDA_RESET,
/* The host configures an HDA stream (with provided buffer size and stream id) */
IPCCMD_HDA_CONFIG,
/* The host runs (sets the SDxCTL.RUN bit) for a given HDA stream */
IPCCMD_HDA_START,
/* The host stops (sets the SDxCTL.RUN bit) for a given HDA stream */
IPCCMD_HDA_STOP,
/* The host validates an HDA byte stream contains an 8bit counter and received a given
* number of bytes
*/
IPCCMD_HDA_VALIDATE,
/* Host sends some data */
IPCCMD_HDA_SEND
};
#endif /* ZEPHYR_INCLUDE_CAVS_TEST_H */

View file

@ -18,7 +18,7 @@ log.setLevel(logging.INFO)
PAGESZ = 4096
HUGEPAGESZ = 2 * 1024 * 1024
HUGEPAGE_FILE = "/dev/hugepages/cavs-fw-dma.tmp"
HUGEPAGE_FILE = "/dev/hugepages/cavs-fw-dma.tmp."
# SRAM windows. Each appears in a 128k region starting at 512k.
#
@ -30,6 +30,155 @@ OUTBOX_OFFSET = (512 + (0 * 128)) * 1024 + 4096
INBOX_OFFSET = (512 + (1 * 128)) * 1024
WINSTREAM_OFFSET = (512 + (3 * 128)) * 1024
class HDAStream:
# creates an hda stream with at 2 buffers of buf_len
def __init__(self, stream_id: int):
self.stream_id = stream_id
self.base = hdamem + 0x0080 + (stream_id * 0x20)
log.info(f"Mapping registers for hda stream {self.stream_id} at base {self.base:x}")
self.hda = Regs(hdamem)
self.hda.GCAP = 0x0000
self.hda.GCTL = 0x0008
self.hda.DPLBASE = 0x0070
self.hda.DPUBASE = 0x0074
self.hda.SPBFCH = 0x0700
self.hda.SPBFCTL = 0x0704
self.hda.PPCH = 0x0800
self.hda.PPCTL = 0x0804
self.hda.PPSTS = 0x0808
self.hda.SPIB = 0x0708 + stream_id*0x08
self.hda.freeze()
self.regs = Regs(self.base)
self.regs.CTL = 0x00
self.regs.STS = 0x03
self.regs.LPIB = 0x04
self.regs.CBL = 0x08
self.regs.LVI = 0x0c
self.regs.FIFOW = 0x0e
self.regs.FIFOS = 0x10
self.regs.FMT = 0x12
self.regs.FIFOL= 0x14
self.regs.BDPL = 0x18
self.regs.BDPU = 0x1c
self.regs.freeze()
self.dbg0 = Regs(hdamem + 0x0084 + (0x20*stream_id))
self.dbg0.DPIB = 0x00
self.dbg0.EFIFOS = 0x10
self.dbg0.freeze()
self.reset()
def __del__(self):
self.reset()
def config(self, buf_len: int):
log.info(f"Configuring stream {self.stream_id}")
self.buf_len = buf_len
log.info("Allocating huge page and setting up buffers")
self.mem, self.hugef, self.buf_list_addr, self.pos_buf_addr, self.n_bufs = self.setup_buf(buf_len)
log.info("Setting buffer list, length, and stream id and traffic priority bit")
self.regs.CTL = ((self.stream_id & 0xFF) << 20) | (1 << 18) # must be set to something other than 0?
self.regs.BDPU = (self.buf_list_addr >> 32) & 0xffffffff
self.regs.BDPL = self.buf_list_addr & 0xffffffff
self.regs.CBL = buf_len
self.regs.LVI = self.n_bufs - 1
self.debug()
log.info(f"Configured stream {self.stream_id}")
def write(self, data):
bufl = min(len(data), self.buf_len)
log.info(f"Writing data to stream {self.stream_id}, len {bufl}, SPBFCTL {self.hda.SPBFCTL:x}, SPIB {self.hda.SPIB}")
self.mem[0:bufl] = data[0:bufl]
self.mem[bufl:bufl+bufl] = data[0:bufl]
self.hda.SPBFCTL |= (1 << self.stream_id)
self.hda.SPIB += bufl
log.info(f"Wrote data to stream {self.stream_id}, SPBFCTL {self.hda.SPBFCTL:x}, SPIB {self.hda.SPIB}")
def start(self):
log.info(f"Starting stream {self.stream_id}, CTL {self.regs.CTL:x}")
self.regs.CTL |= 2
log.info(f"Started stream {self.stream_id}, CTL {self.regs.CTL:x}")
def stop(self):
log.info(f"Stopping stream {self.stream_id}, CTL {self.regs.CTL:x}")
self.regs.CTL &= 2
time.sleep(0.1)
self.regs.CTL |= 1
log.info(f"Stopped stream {self.stream_id}, CTL {self.regs.CTL:x}")
def setup_buf(self, buf_len: int):
(mem, phys_addr, hugef) = map_phys_mem(self.stream_id)
log.info(f"Mapped 2M huge page at 0x{phys_addr:x} for buf size ({buf_len})")
# create two buffers in the page of buf_len and mark them
# in a buffer descriptor list for the hardware to use
buf0_len = buf_len
buf1_len = buf_len
bdl_off = buf0_len + buf1_len
# bdl is 2 (64bits, 16 bytes) per entry, we have two
mem[bdl_off:bdl_off + 32] = struct.pack("<QQQQ",
phys_addr,
buf0_len,
phys_addr + buf0_len,
buf1_len)
dpib_off = bdl_off+32
# ensure buffer is initialized, sanity
for i in range(0, buf_len*2):
mem[i] = 0
log.info("Filled the buffer descriptor list (BDL) for DMA.")
return (mem, hugef, phys_addr + bdl_off, phys_addr+dpib_off, 2)
def debug(self):
log.info("HDA %d: PPROC %d, CTL 0x%x, LPIB 0x%x, BDPU 0x%x, BDPL 0x%x, CBL 0x%x, LVI 0x%x",
self.stream_id, (hda.PPCTL >> self.stream_id) & 1, self.regs.CTL, self.regs.LPIB, self.regs.BDPU,
self.regs.BDPL, self.regs.CBL, self.regs.LVI)
log.info(" FIFOW %d, FIFOS %d, FMT %x, FIFOL %d, DPIB %d, EFIFOS %d",
self.regs.FIFOW & 0x7, self.regs.FIFOS, self.regs.FMT, self.regs.FIFOL, self.dbg0.DPIB, self.dbg0.EFIFOS)
log.info(" status: FIFORDY %d, DESE %d, FIFOE %d, BCIS %d",
(self.regs.STS >> 5) & 1, (self.regs.STS >> 4) & 1, (self.regs.STS >> 3) & 1, (self.regs.STS >> 2) & 1)
def reset(self):
# Turn DMA off and reset the stream. Clearing START first is a
# noop per the spec, but absolutely required for stability.
# Apparently the reset doesn't stop the stream, and the next load
# starts before it's ready and kills the load (and often the DSP).
# The sleep too is required, on at least one board (a fast
# chromebook) putting the two writes next each other also hangs
# the DSP!
log.info(f"Resetting stream {self.stream_id}")
self.debug()
self.regs.CTL &= ~2 # clear START
time.sleep(0.1)
# set enter reset bit
self.regs.CTL = 1
while (self.regs.CTL & 1) == 0: pass
# clear enter reset bit to exit reset
self.regs.CTL = 0
while (self.regs.CTL & 1) == 1: pass
log.info(f"Disable SPIB and set position 0 of stream {self.stream_id}")
self.hda.SPBFCTL = 0
self.hda.SPIB = 0
#log.info("Setting dma position buffer and enable it")
#self.hda.DPUBASE = self.pos_buf_addr >> 32 & 0xffffffff
#self.hda.DPLBASE = self.pos_buf_addr & 0xfffffff0 | 1
log.info(f"Enabling dsp capture (PROCEN) of stream {self.stream_id}")
self.hda.PPCTL |= (1 << self.stream_id)
self.debug()
log.info(f"Reset stream {self.stream_id}")
def map_regs():
p = runx(f"grep -iPl 'PCI_CLASS=40(10|38)0' /sys/bus/pci/devices/*/uevent")
pcidir = os.path.dirname(p)
@ -62,6 +211,7 @@ def map_regs():
cfg.write(b'\x06\x04')
# Standard HD Audio Registers
global hdamem
(hdamem, _) = bar_map(pcidir, 0)
hda = Regs(hdamem)
hda.GCAP = 0x0000
@ -75,6 +225,7 @@ def map_regs():
hda.SD_SPIB = 0x0708 + (8 * hda_ostream_id)
hda.freeze()
# Standard HD Audio Stream Descriptor
sd = Regs(hdamem + 0x0080 + (hda_ostream_id * 0x20))
sd.CTL = 0x00
@ -101,7 +252,7 @@ def map_regs():
return (hda, sd, dsp, hda_ostream_id)
def setup_dma_mem(fw_bytes):
(mem, phys_addr) = map_phys_mem()
(mem, phys_addr, _) = map_phys_mem(hda_ostream_id)
mem[0:len(fw_bytes)] = fw_bytes
log.info("Mapped 2M huge page at 0x%x to contain %d bytes of firmware"
@ -124,7 +275,7 @@ global_mmaps = [] # protect mmap mappings from garbage collection!
# Maps 2M of contiguous memory using a single page from hugetlbfs,
# then locates its physical address for use as a DMA buffer.
def map_phys_mem():
def map_phys_mem(stream_id):
# Make sure hugetlbfs is mounted (not there on chromeos)
os.system("mount | grep -q hugetlbfs ||"
+ " (mkdir -p /dev/hugepages; "
@ -136,11 +287,13 @@ def map_phys_mem():
tot = 1 + int(runx("awk '/HugePages_Total/ {print $2}' /proc/meminfo"))
os.system(f"echo {tot} > /proc/sys/vm/nr_hugepages")
hugef = open(HUGEPAGE_FILE, "w+")
hugef_name = HUGEPAGE_FILE + str(stream_id)
hugef = open(hugef_name, "w+")
hugef.truncate(HUGEPAGESZ)
mem = mmap.mmap(hugef.fileno(), HUGEPAGESZ)
log.info("type of mem is %s", str(type(mem)))
global_mmaps.append(mem)
os.unlink(HUGEPAGE_FILE)
os.unlink(hugef_name)
# Find the local process address of the mapping, then use that to extract
# the physical address from the kernel's pagemap interface. The physical
@ -153,7 +306,7 @@ def map_phys_mem():
pent = pagemap.read(8)
paddr = (struct.unpack("Q", pent)[0] & ((1 << 55) - 1)) * PAGESZ
pagemap.close()
return (mem, paddr)
return (mem, paddr, hugef)
# Maps a PCI BAR and returns the in-process address
def bar_map(pcidir, barnum):
@ -337,6 +490,7 @@ ipc_timestamp = 0
def ipc_command(data, ext_data):
send_msg = False
done = True
log.debug ("ipc data %d, ext_data %x", data, ext_data)
if data == 0: # noop, with synchronous DONE
pass
elif data == 1: # async command: signal DONE after a delay (on 1.8+)
@ -358,6 +512,45 @@ def ipc_command(data, ext_data):
dst = INBOX_OFFSET + 4 * (ext_data & 0xffff)
for i in range(4):
bar4_mmap[dst + i] = bar4_mmap[src + i]
elif data == 6: # HDA RESET (init if not exists)
stream_id = ext_data & 0xff
if stream_id in hda_streams:
hda_streams[stream_id].reset()
else:
hda_str = HDAStream(stream_id)
hda_streams[stream_id] = hda_str
elif data == 7: # HDA CONFIG
stream_id = ext_data & 0xFF
buf_len = ext_data >> 8 & 0xFFFF
hda_str = hda_streams[stream_id]
hda_str.config(buf_len)
elif data == 8: # HDA START
stream_id = ext_data & 0xFF
hda_streams[stream_id].start()
elif data == 9: # HDA STOP
stream_id = ext_data & 0xFF
hda_streams[stream_id].stop()
elif data == 10: # HDA VALIDATE
stream_id = ext_data & 0xFF
hda_str = hda_streams[stream_id]
hda_str.debug()
is_ramp_data = True
hda_str.mem.seek(0)
for (i, val) in enumerate(hda_str.mem.read(256)):
if i != val:
is_ramp_data = False
# log.info("stream[%d][%d]: %d", stream_id, i, val) # debug helper
log.info("Is ramp data? " + str(is_ramp_data))
ext_data = int(is_ramp_data)
log.info(f"Ext data to send back on ramp status {ext_data}")
send_msg = True
elif data == 11: # HDA HOST OUT SEND
stream_id = ext_data & 0xff
buf = bytearray(256)
for i in range(0, 256):
buf[i] = i
hda_streams[stream_id].write(buf)
else:
log.warning(f"cavstool: Unrecognized IPC command 0x{data:x} ext 0x{ext_data:x}")
@ -372,7 +565,8 @@ def ipc_command(data, ext_data):
dsp.HIPCIDR = (1<<31) | ext_data
async def main():
global hda, sd, dsp, hda_ostream_id
#TODO this bit me, remove the globals, write a little FirmwareLoader class or something to contain.
global hda, sd, dsp, hda_ostream_id, hda_streams
try:
(hda, sd, dsp, hda_ostream_id) = map_regs()
except Exception as e:
@ -394,6 +588,7 @@ async def main():
if not args.quiet:
sys.stdout.write("--\n")
hda_streams = dict()
last_seq = 0
while True:
await asyncio.sleep(0.03)

View file

@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(intel_adsp)
target_sources(app PRIVATE src/main.c src/smoke.c)

View file

@ -0,0 +1,2 @@
CONFIG_ZTEST=y
CONFIG_DMA=y

View file

@ -0,0 +1,17 @@
/* Copyright (c) 2022 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <ztest.h>
#include <stdlib.h>
#include "tests.h"
void test_main(void)
{
ztest_test_suite(intel_adsp_hda,
ztest_unit_test(test_hda_host_in_smoke),
ztest_unit_test(test_hda_host_out_smoke)
);
ztest_run_test_suite(intel_adsp_hda);
}

View file

@ -0,0 +1,196 @@
/* Copyright (c) 2022 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#include "arch/xtensa/cache.h"
#include <kernel.h>
#include <ztest.h>
#include <cavs_ipc.h>
#include <cavs_hda.h>
#include "tests.h"
#define IPC_TIMEOUT K_MSEC(500)
#define STREAM_ID 3U
#define HDA_BUF_SIZE 256
#define TRANSFER_COUNT 8
static __aligned(128) uint8_t hda_buf[HDA_BUF_SIZE];
static volatile int msg_cnt;
static volatile int msg_res;
static bool ipc_message(const struct device *dev, void *arg,
uint32_t data, uint32_t ext_data)
{
printk("HDA message received, data %u, ext_data %u\n", data, ext_data);
msg_res = data;
msg_cnt++;
return true;
}
/*
* Tests host input streams
*
* Note that the order of operations in this test are important and things potentially will not
* work in horrible and unexpected ways if not done as they are here.
*/
void test_hda_host_in_smoke(void)
{
int res;
uint32_t last_msg_cnt;
printk("smoke testing hda with fifo buffer at address %p, size %d\n",
hda_buf, HDA_BUF_SIZE);
cavs_ipc_set_message_handler(CAVS_HOST_DEV, ipc_message, NULL);
printk("Using buffer of size %d at addr %p\n", HDA_BUF_SIZE, hda_buf);
/* setup a ramp in the buffer */
for (uint32_t i = 0; i < HDA_BUF_SIZE; i++) {
hda_buf[i] = i & 0xff;
}
#if (IS_ENABLED(CONFIG_KERNEL_COHERENCE))
zassert_true(arch_mem_coherent(hda_buf), "Buffer is unexpectedly incoherent!");
#else
/* The buffer is in the cached address range and must be flushed */
zassert_false(arch_mem_coherent(hda_buf), "Buffer is unexpectedly coherent!");
z_xtensa_cache_flush(hda_buf, HDA_BUF_SIZE);
#endif
cavs_hda_init(HDA_HOST_IN_BASE, STREAM_ID);
printk("dsp init: "); cavs_hda_dbg("host_in", HDA_HOST_IN_BASE, STREAM_ID);
hda_ipc_msg(CAVS_HOST_DEV, IPCCMD_HDA_RESET, STREAM_ID, IPC_TIMEOUT);
printk("host reset: ");
cavs_hda_dbg("host_in", HDA_HOST_IN_BASE, STREAM_ID);
hda_ipc_msg(CAVS_HOST_DEV, IPCCMD_HDA_CONFIG,
STREAM_ID | (HDA_BUF_SIZE << 8), IPC_TIMEOUT);
printk("host config: ");
cavs_hda_dbg("host_in", HDA_HOST_IN_BASE, STREAM_ID);
res = cavs_hda_set_buffer(HDA_HOST_IN_BASE, STREAM_ID, hda_buf, HDA_BUF_SIZE);
printk("dsp set_buffer: "); cavs_hda_dbg("host_in", HDA_HOST_IN_BASE, STREAM_ID);
zassert_ok(res, "Expected set buffer to succeed");
cavs_hda_enable(HDA_HOST_IN_BASE, STREAM_ID);
printk("dsp enable: "); cavs_hda_dbg("host_in", HDA_HOST_IN_BASE, STREAM_ID);
hda_ipc_msg(CAVS_HOST_DEV, IPCCMD_HDA_START, STREAM_ID, IPC_TIMEOUT);
printk("host start: ");
cavs_hda_dbg("host_in", HDA_HOST_IN_BASE, STREAM_ID);
for (uint32_t i = 0; i < TRANSFER_COUNT; i++) {
cavs_hda_commit(HDA_HOST_IN_BASE, STREAM_ID, HDA_BUF_SIZE);
printk("dsp inc_pos: "); cavs_hda_dbg("host_in", HDA_HOST_IN_BASE, STREAM_ID);
WAIT_FOR(cavs_hda_wp_rp_eq(HDA_HOST_IN_BASE, STREAM_ID));
printk("dsp wp_rp_eq: "); cavs_hda_dbg("host_in", HDA_HOST_IN_BASE, STREAM_ID);
last_msg_cnt = msg_cnt;
hda_ipc_msg(CAVS_HOST_DEV, IPCCMD_HDA_VALIDATE, STREAM_ID,
IPC_TIMEOUT);
WAIT_FOR(msg_cnt > last_msg_cnt);
zassert_true(msg_res == 1,
"Expected data validation to be true from Host");
}
hda_ipc_msg(CAVS_HOST_DEV, IPCCMD_HDA_RESET, STREAM_ID, IPC_TIMEOUT);
cavs_hda_disable(HDA_HOST_IN_BASE, STREAM_ID);
}
/*
* Tests host output streams
*
* Note that the order of operations in this test are important and things potentially will not
* work in horrible and unexpected ways if not done as they are here.
*/
void test_hda_host_out_smoke(void)
{
int res;
bool is_ramp;
printk("smoke testing hda with fifo buffer at address %p, size %d\n",
hda_buf, HDA_BUF_SIZE);
cavs_ipc_set_message_handler(CAVS_HOST_DEV, ipc_message, NULL);
printk("Using buffer of size %d at addr %p\n", HDA_BUF_SIZE, hda_buf);
cavs_hda_init(HDA_HOST_OUT_BASE, STREAM_ID);
printk("dsp init: "); cavs_hda_dbg("host_out", HDA_HOST_OUT_BASE, STREAM_ID);
hda_ipc_msg(CAVS_HOST_DEV, IPCCMD_HDA_RESET, (STREAM_ID + 7), IPC_TIMEOUT);
printk("host reset: ");
cavs_hda_dbg("host_out", HDA_HOST_OUT_BASE, STREAM_ID);
hda_ipc_msg(CAVS_HOST_DEV, IPCCMD_HDA_CONFIG,
(STREAM_ID + 7) | (HDA_BUF_SIZE << 8), IPC_TIMEOUT);
printk("host config: ");
cavs_hda_dbg("host_out", HDA_HOST_OUT_BASE, STREAM_ID);
res = cavs_hda_set_buffer(HDA_HOST_OUT_BASE, STREAM_ID, hda_buf, HDA_BUF_SIZE);
printk("dsp set buffer: "); cavs_hda_dbg("host_out", HDA_HOST_OUT_BASE, STREAM_ID);
zassert_ok(res, "Expected set buffer to succeed");
hda_ipc_msg(CAVS_HOST_DEV, IPCCMD_HDA_START, (STREAM_ID + 7), IPC_TIMEOUT);
printk("host start: ");
cavs_hda_dbg("host_out", HDA_HOST_OUT_BASE, STREAM_ID);
cavs_hda_enable(HDA_HOST_OUT_BASE, STREAM_ID);
printk("dsp enable: ");
cavs_hda_dbg("host_out", HDA_HOST_OUT_BASE, STREAM_ID);
for (uint32_t i = 0; i < TRANSFER_COUNT; i++) {
for (int j = 0; j < HDA_BUF_SIZE; j++) {
hda_buf[j] = 0;
}
hda_ipc_msg(CAVS_HOST_DEV, IPCCMD_HDA_SEND,
(STREAM_ID + 7) | (HDA_BUF_SIZE << 8), IPC_TIMEOUT);
printk("host send: ");
cavs_hda_dbg("host_out", HDA_HOST_OUT_BASE, STREAM_ID);
WAIT_FOR(cavs_hda_buf_full(HDA_HOST_OUT_BASE, STREAM_ID));
printk("dsp wait for full: ");
cavs_hda_dbg("host_out", HDA_HOST_OUT_BASE, STREAM_ID);
#if (IS_ENABLED(CONFIG_KERNEL_COHERENCE))
zassert_true(arch_mem_coherent(hda_buf), "Buffer is unexpectedly incoherent!");
#else
/* The buffer is in the cached address range and must be invalidated
* prior to reading.
*/
zassert_false(arch_mem_coherent(hda_buf), "Buffer is unexpectedly coherent!");
z_xtensa_cache_inv(hda_buf, HDA_BUF_SIZE);
#endif
is_ramp = true;
for (int j = 0; j < HDA_BUF_SIZE; j++) {
/* printk("hda_buf[%d] = %d\n", j, hda_buf[j]); */ /* DEBUG HELPER */
if (hda_buf[j] != j) {
is_ramp = false;
}
}
zassert_true(is_ramp, "Expected data to be a ramp");
cavs_hda_commit(HDA_HOST_OUT_BASE, STREAM_ID, HDA_BUF_SIZE);
printk("dsp inc pos: "); cavs_hda_dbg("host_out", HDA_HOST_OUT_BASE, STREAM_ID);
}
hda_ipc_msg(CAVS_HOST_DEV, IPCCMD_HDA_RESET, (STREAM_ID + 7),
IPC_TIMEOUT);
printk("host reset: ");
cavs_hda_dbg("host_out", HDA_HOST_OUT_BASE, STREAM_ID);
cavs_hda_disable(HDA_HOST_OUT_BASE, STREAM_ID);
printk("dsp disable: "); cavs_hda_dbg("host_out", HDA_HOST_OUT_BASE, STREAM_ID);
}

View file

@ -0,0 +1,24 @@
/* Copyright (c) 2022 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_TESTS_INTEL_ADSP_TESTS_H
#define ZEPHYR_TESTS_INTEL_ADSP_TESTS_H
#include "sys_clock.h"
#include <cavs_ipc.h>
#include <cavstool.h>
#include <stdint.h>
#include <device.h>
#include <ztest.h>
void test_hda_host_in_smoke(void);
void test_hda_host_out_smoke(void);
static inline void hda_ipc_msg(const struct device *dev, uint32_t data,
uint32_t ext, k_timeout_t timeout)
{
zassert_true(cavs_ipc_send_message_sync(dev, data, ext, timeout),
"Unexpected ipc send message failure, try increasing IPC_TIMEOUT");
}
#endif /* ZEPHYR_TESTS_INTEL_ADSP_TESTS_H */

View file

@ -0,0 +1,7 @@
tests:
boards.intel_adsp:
platform_allow: intel_adsp_cavs15 intel_adsp_cavs18 intel_adsp_cavs20 intel_adsp_cavs25
boards.intel_adsp.1cpu:
platform_allow: intel_adsp_cavs15 intel_adsp_cavs18 intel_adsp_cavs20 intel_adsp_cavs25
extra_configs:
- CONFIG_MP_NUM_CPUS=1