diff --git a/soc/xtensa/intel_adsp/common/include/cavs_hda.h b/soc/xtensa/intel_adsp/common/include/cavs_hda.h new file mode 100644 index 00000000000..a1a3d6d4b0a --- /dev/null +++ b/soc/xtensa/intel_adsp/common/include/cavs_hda.h @@ -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 +#include +#include +#include +#include + +/** + * @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 */ diff --git a/soc/xtensa/intel_adsp/common/include/cavstool.h b/soc/xtensa/intel_adsp/common/include/cavstool.h index 9427917d731..7d44d1823f8 100644 --- a/soc/xtensa/intel_adsp/common/include/cavstool.h +++ b/soc/xtensa/intel_adsp/common/include/cavstool.h @@ -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 */ diff --git a/soc/xtensa/intel_adsp/tools/cavstool.py b/soc/xtensa/intel_adsp/tools/cavstool.py index c4053048978..6cee1ed0565 100755 --- a/soc/xtensa/intel_adsp/tools/cavstool.py +++ b/soc/xtensa/intel_adsp/tools/cavstool.py @@ -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("> 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) diff --git a/tests/boards/intel_adsp/hda/CMakeLists.txt b/tests/boards/intel_adsp/hda/CMakeLists.txt new file mode 100644 index 00000000000..732ab1b9a40 --- /dev/null +++ b/tests/boards/intel_adsp/hda/CMakeLists.txt @@ -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) diff --git a/tests/boards/intel_adsp/hda/prj.conf b/tests/boards/intel_adsp/hda/prj.conf new file mode 100644 index 00000000000..0d43e90cdf3 --- /dev/null +++ b/tests/boards/intel_adsp/hda/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +CONFIG_DMA=y diff --git a/tests/boards/intel_adsp/hda/src/main.c b/tests/boards/intel_adsp/hda/src/main.c new file mode 100644 index 00000000000..cea6fd8c7d1 --- /dev/null +++ b/tests/boards/intel_adsp/hda/src/main.c @@ -0,0 +1,17 @@ +/* Copyright (c) 2022 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#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); +} diff --git a/tests/boards/intel_adsp/hda/src/smoke.c b/tests/boards/intel_adsp/hda/src/smoke.c new file mode 100644 index 00000000000..f07e3e562ad --- /dev/null +++ b/tests/boards/intel_adsp/hda/src/smoke.c @@ -0,0 +1,196 @@ +/* Copyright (c) 2022 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "arch/xtensa/cache.h" +#include +#include +#include +#include +#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); +} diff --git a/tests/boards/intel_adsp/hda/src/tests.h b/tests/boards/intel_adsp/hda/src/tests.h new file mode 100644 index 00000000000..0b091e97e50 --- /dev/null +++ b/tests/boards/intel_adsp/hda/src/tests.h @@ -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 +#include +#include +#include +#include + +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 */ diff --git a/tests/boards/intel_adsp/hda/testcase.yaml b/tests/boards/intel_adsp/hda/testcase.yaml new file mode 100644 index 00000000000..1f7e935f6ca --- /dev/null +++ b/tests/boards/intel_adsp/hda/testcase.yaml @@ -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