1#!/usr/bin/env python3
2# Copyright(c) 2022 Intel Corporation. All rights reserved.
3# SPDX-License-Identifier: Apache-2.0
4import os
5import sys
6import struct
7import logging
8import asyncio
9import time
10import subprocess
11import ctypes
12import mmap
13import argparse
14
15start_output = True
16
17logging.basicConfig(level=logging.INFO)
18log = logging.getLogger("cavs-fw")
19
20PAGESZ = 4096
21HUGEPAGESZ = 2 * 1024 * 1024
22HUGEPAGE_FILE = "/dev/hugepages/cavs-fw-dma.tmp."
23
24# SRAM windows.  Each appears in a 128k region starting at 512k.
25#
26# Window 0 is the FW_STATUS area, and 4k after that the IPC "outbox"
27# Window 1 is the IPC "inbox" (host-writable memory, just 384 bytes currently)
28# Window 2 is unused by this script
29# Window 3 is winstream-formatted log output
30OUTBOX_OFFSET    = (512 + (0 * 128)) * 1024 + 4096
31INBOX_OFFSET     = (512 + (1 * 128)) * 1024
32WINSTREAM_OFFSET = (512 + (3 * 128)) * 1024
33
34# ADSPCS bits
35CRST   = 0
36CSTALL = 8
37SPA    = 16
38CPA    = 24
39
40class HDAStream:
41    # creates an hda stream with at 2 buffers of buf_len
42    def __init__(self, stream_id: int):
43        self.stream_id = stream_id
44        self.base = hdamem + 0x0080 + (stream_id * 0x20)
45        log.info(f"Mapping registers for hda stream {self.stream_id} at base {self.base:x}")
46
47        self.hda = Regs(hdamem)
48        self.hda.GCAP    = 0x0000
49        self.hda.GCTL    = 0x0008
50        self.hda.DPLBASE = 0x0070
51        self.hda.DPUBASE = 0x0074
52        self.hda.SPBFCH  = 0x0700
53        self.hda.SPBFCTL = 0x0704
54        self.hda.PPCH    = 0x0800
55        self.hda.PPCTL   = 0x0804
56        self.hda.PPSTS   = 0x0808
57        self.hda.SPIB = 0x0708 + stream_id*0x08
58        self.hda.freeze()
59
60        self.regs = Regs(self.base)
61        self.regs.CTL  = 0x00
62        self.regs.STS  = 0x03
63        self.regs.LPIB = 0x04
64        self.regs.CBL  = 0x08
65        self.regs.LVI  = 0x0c
66        self.regs.FIFOW = 0x0e
67        self.regs.FIFOS = 0x10
68        self.regs.FMT = 0x12
69        self.regs.FIFOL= 0x14
70        self.regs.BDPL = 0x18
71        self.regs.BDPU = 0x1c
72        self.regs.freeze()
73
74        self.dbg0 = Regs(hdamem + 0x0084 + (0x20*stream_id))
75        self.dbg0.DPIB = 0x00
76        self.dbg0.EFIFOS = 0x10
77        self.dbg0.freeze()
78
79        self.reset()
80
81    def __del__(self):
82        self.reset()
83
84    def config(self, buf_len: int):
85        log.info(f"Configuring stream {self.stream_id}")
86        self.buf_len = buf_len
87        log.info("Allocating huge page and setting up buffers")
88        self.mem, self.hugef, self.buf_list_addr, self.pos_buf_addr, self.n_bufs = self.setup_buf(buf_len)
89
90        log.info("Setting buffer list, length, and stream id and traffic priority bit")
91        self.regs.CTL = ((self.stream_id & 0xFF) << 20) | (1 << 18) # must be set to something other than 0?
92        self.regs.BDPU = (self.buf_list_addr >> 32) & 0xffffffff
93        self.regs.BDPL = self.buf_list_addr & 0xffffffff
94        self.regs.CBL = buf_len
95        self.regs.LVI = self.n_bufs - 1
96        self.mem.seek(0)
97        self.debug()
98        log.info(f"Configured stream {self.stream_id}")
99
100    def write(self, data):
101
102        bufl = min(len(data), self.buf_len)
103        log.info(f"Writing data to stream {self.stream_id}, len {bufl}, SPBFCTL {self.hda.SPBFCTL:x}, SPIB {self.hda.SPIB}")
104        self.mem[0:bufl] = data[0:bufl]
105        self.mem[bufl:bufl+bufl] = data[0:bufl]
106        self.hda.SPBFCTL |= (1 << self.stream_id)
107        self.hda.SPIB += bufl
108        log.info(f"Wrote data to stream {self.stream_id}, SPBFCTL {self.hda.SPBFCTL:x}, SPIB {self.hda.SPIB}")
109
110    def start(self):
111        log.info(f"Starting stream {self.stream_id}, CTL {self.regs.CTL:x}")
112        self.regs.CTL |= 2
113        log.info(f"Started stream {self.stream_id}, CTL {self.regs.CTL:x}")
114
115    def stop(self):
116        log.info(f"Stopping stream {self.stream_id}, CTL {self.regs.CTL:x}")
117        self.regs.CTL &= 2
118        time.sleep(0.1)
119        self.regs.CTL |= 1
120        log.info(f"Stopped stream {self.stream_id}, CTL {self.regs.CTL:x}")
121
122    def setup_buf(self, buf_len: int):
123        (mem, phys_addr, hugef) = map_phys_mem(self.stream_id)
124
125        log.info(f"Mapped 2M huge page at 0x{phys_addr:x} for buf size ({buf_len})")
126
127        # create two buffers in the page of buf_len and mark them
128        # in a buffer descriptor list for the hardware to use
129        buf0_len = buf_len
130        buf1_len = buf_len
131        bdl_off = buf0_len + buf1_len
132        # bdl is 2 (64bits, 16 bytes) per entry, we have two
133        mem[bdl_off:bdl_off + 32] = struct.pack("<QQQQ",
134                                                phys_addr,
135                                                buf0_len,
136                                                phys_addr + buf0_len,
137                                                buf1_len)
138        dpib_off = bdl_off+32
139
140        # ensure buffer is initialized, sanity
141        for i in range(0, buf_len*2):
142            mem[i] = 0
143
144        log.info("Filled the buffer descriptor list (BDL) for DMA.")
145        return (mem, hugef, phys_addr + bdl_off, phys_addr+dpib_off, 2)
146
147    def debug(self):
148        log.debug("HDA %d: PPROC %d, CTL 0x%x, LPIB 0x%x, BDPU 0x%x, BDPL 0x%x, CBL 0x%x, LVI 0x%x",
149                 self.stream_id, (hda.PPCTL >> self.stream_id) & 1, self.regs.CTL, self.regs.LPIB, self.regs.BDPU,
150                 self.regs.BDPL, self.regs.CBL, self.regs.LVI)
151        log.debug("    FIFOW %d, FIFOS %d, FMT %x, FIFOL %d, DPIB %d, EFIFOS %d",
152                 self.regs.FIFOW & 0x7, self.regs.FIFOS, self.regs.FMT, self.regs.FIFOL, self.dbg0.DPIB, self.dbg0.EFIFOS)
153        log.debug("    status: FIFORDY %d, DESE %d, FIFOE %d, BCIS %d",
154                 (self.regs.STS >> 5) & 1, (self.regs.STS >> 4) & 1, (self.regs.STS >> 3) & 1, (self.regs.STS >> 2) & 1)
155
156    def reset(self):
157        # Turn DMA off and reset the stream.  Clearing START first is a
158        # noop per the spec, but absolutely required for stability.
159        # Apparently the reset doesn't stop the stream, and the next load
160        # starts before it's ready and kills the load (and often the DSP).
161        # The sleep too is required, on at least one board (a fast
162        # chromebook) putting the two writes next each other also hangs
163        # the DSP!
164        log.info(f"Resetting stream {self.stream_id}")
165        self.debug()
166        self.regs.CTL &= ~2 # clear START
167        time.sleep(0.1)
168        # set enter reset bit
169        self.regs.CTL = 1
170        while (self.regs.CTL & 1) == 0: pass
171        # clear enter reset bit to exit reset
172        self.regs.CTL = 0
173        while (self.regs.CTL & 1) == 1: pass
174
175        log.info(f"Disable SPIB and set position 0 of stream {self.stream_id}")
176        self.hda.SPBFCTL = 0
177        self.hda.SPIB = 0
178
179        #log.info("Setting dma position buffer and enable it")
180        #self.hda.DPUBASE = self.pos_buf_addr >> 32 & 0xffffffff
181        #self.hda.DPLBASE = self.pos_buf_addr & 0xfffffff0 | 1
182
183        log.info(f"Enabling dsp capture (PROCEN) of stream {self.stream_id}")
184        self.hda.PPCTL |= (1 << self.stream_id)
185
186        self.debug()
187        log.info(f"Reset stream {self.stream_id}")
188
189
190def map_regs():
191    p = runx(f"grep -iEl 'PCI_CLASS=40(10|38)0' /sys/bus/pci/devices/*/uevent")
192    pcidir = os.path.dirname(p)
193
194    # Platform/quirk detection.  ID lists cribbed from the SOF kernel driver
195    global cavs15, cavs18, cavs25
196    did = int(open(f"{pcidir}/device").read().rstrip(), 16)
197    cavs15 = did in [ 0x5a98, 0x1a98, 0x3198 ]
198    cavs18 = did in [ 0x9dc8, 0xa348, 0x02c8, 0x06c8, 0xa3f0 ]
199    cavs25 = did in [ 0xa0c8, 0x43c8, 0x4b55, 0x4b58, 0x7ad0, 0x51c8 ]
200
201    # Check sysfs for a loaded driver and remove it
202    if os.path.exists(f"{pcidir}/driver"):
203        mod = os.path.basename(os.readlink(f"{pcidir}/driver/module"))
204        found_msg = f"Existing driver \"{mod}\" found"
205        if args.log_only:
206            log.info(found_msg)
207        else:
208            log.warning(found_msg + ", unloading module")
209            runx(f"rmmod -f {mod}")
210            # Disengage runtime power management so the kernel doesn't put it to sleep
211            log.info(f"Forcing {pcidir}/power/control to always 'on'")
212            with open(f"{pcidir}/power/control", "w") as ctrl:
213                ctrl.write("on")
214
215    # Make sure PCI memory space access and busmastering are enabled.
216    # Also disable interrupts so as not to confuse the kernel.
217    with open(f"{pcidir}/config", "wb+") as cfg:
218        cfg.seek(4)
219        cfg.write(b'\x06\x04')
220
221    # Standard HD Audio Registers
222    global hdamem
223    (hdamem, _) = bar_map(pcidir, 0)
224    hda = Regs(hdamem)
225    hda.GCAP    = 0x0000
226    hda.GCTL    = 0x0008
227    hda.SPBFCTL = 0x0704
228    hda.PPCTL   = 0x0804
229
230    # Find the ID of the first output stream
231    hda_ostream_id = (hda.GCAP >> 8) & 0x0f # number of input streams
232    log.info(f"Selected output stream {hda_ostream_id} (GCAP = 0x{hda.GCAP:x})")
233    hda.SD_SPIB = 0x0708 + (8 * hda_ostream_id)
234    hda.freeze()
235
236
237    # Standard HD Audio Stream Descriptor
238    sd = Regs(hdamem + 0x0080 + (hda_ostream_id * 0x20))
239    sd.CTL  = 0x00
240    sd.CBL  = 0x08
241    sd.LVI  = 0x0c
242    sd.BDPL = 0x18
243    sd.BDPU = 0x1c
244    sd.freeze()
245
246    # Intel Audio DSP Registers
247    global bar4_mmap
248    (bar4_mem, bar4_mmap) = bar_map(pcidir, 4)
249    dsp = Regs(bar4_mem)
250    dsp.ADSPCS         = 0x00004
251    dsp.HIPCTDR        = 0x00040 if cavs15 else 0x000c0
252    dsp.HIPCTDA        =                        0x000c4 # 1.8+ only
253    dsp.HIPCTDD        = 0x00044 if cavs15 else 0x000c8
254    dsp.HIPCIDR        = 0x00048 if cavs15 else 0x000d0
255    dsp.HIPCIDA        =                        0x000d4 # 1.8+ only
256    dsp.HIPCIDD        = 0x0004c if cavs15 else 0x000d8
257    dsp.SRAM_FW_STATUS = 0x80000 # Start of first SRAM window
258    dsp.freeze()
259
260    return (hda, sd, dsp, hda_ostream_id)
261
262def setup_dma_mem(fw_bytes):
263    (mem, phys_addr, _) = map_phys_mem(hda_ostream_id)
264    mem[0:len(fw_bytes)] = fw_bytes
265
266    log.info("Mapped 2M huge page at 0x%x to contain %d bytes of firmware"
267          % (phys_addr, len(fw_bytes)))
268
269    # HDA requires at least two buffers be defined, but we don't care about
270    # boundaries because it's all a contiguous region. Place a vestigial
271    # 128-byte (minimum size and alignment) buffer after the main one, and put
272    # the 4-entry BDL list into the final 128 bytes of the page.
273    buf0_len = HUGEPAGESZ - 2 * 128
274    buf1_len = 128
275    bdl_off = buf0_len + buf1_len
276    mem[bdl_off:bdl_off + 32] = struct.pack("<QQQQ",
277                                            phys_addr, buf0_len,
278                                            phys_addr + buf0_len, buf1_len)
279    log.info("Filled the buffer descriptor list (BDL) for DMA.")
280    return (phys_addr + bdl_off, 2)
281
282global_mmaps = [] # protect mmap mappings from garbage collection!
283
284# Maps 2M of contiguous memory using a single page from hugetlbfs,
285# then locates its physical address for use as a DMA buffer.
286def map_phys_mem(stream_id):
287    # Make sure hugetlbfs is mounted (not there on chromeos)
288    os.system("mount | grep -q hugetlbfs ||"
289              + " (mkdir -p /dev/hugepages; "
290              + "  mount -t hugetlbfs hugetlbfs /dev/hugepages)")
291
292    # Ensure the kernel has enough budget for one new page
293    free = int(runx("awk '/HugePages_Free/ {print $2}' /proc/meminfo"))
294    if free == 0:
295        tot = 1 + int(runx("awk '/HugePages_Total/ {print $2}' /proc/meminfo"))
296        os.system(f"echo {tot} > /proc/sys/vm/nr_hugepages")
297
298    hugef_name = HUGEPAGE_FILE + str(stream_id)
299    hugef = open(hugef_name, "w+")
300    hugef.truncate(HUGEPAGESZ)
301    mem = mmap.mmap(hugef.fileno(), HUGEPAGESZ)
302    log.info("type of mem is %s", str(type(mem)))
303    global_mmaps.append(mem)
304    os.unlink(hugef_name)
305
306    # Find the local process address of the mapping, then use that to extract
307    # the physical address from the kernel's pagemap interface.  The physical
308    # page frame number occupies the bottom bits of the entry.
309    mem[0] = 0 # Fault the page in so it has an address!
310    vaddr = ctypes.addressof(ctypes.c_int.from_buffer(mem))
311    vpagenum = vaddr >> 12
312    pagemap = open("/proc/self/pagemap", "rb")
313    pagemap.seek(vpagenum * 8)
314    pent = pagemap.read(8)
315    paddr = (struct.unpack("Q", pent)[0] & ((1 << 55) - 1)) * PAGESZ
316    pagemap.close()
317    return (mem, paddr, hugef)
318
319# Maps a PCI BAR and returns the in-process address
320def bar_map(pcidir, barnum):
321    f = open(pcidir + "/resource" + str(barnum), "r+")
322    mm = mmap.mmap(f.fileno(), os.fstat(f.fileno()).st_size)
323    global_mmaps.append(mm)
324    log.info("Mapped PCI bar %d of length %d bytes."
325             % (barnum, os.fstat(f.fileno()).st_size))
326    return (ctypes.addressof(ctypes.c_int.from_buffer(mm)), mm)
327
328# Syntactic sugar to make register block definition & use look nice.
329# Instantiate from a base address, assign offsets to (uint32) named registers as
330# fields, call freeze(), then the field acts as a direct alias for the register!
331class Regs:
332    def __init__(self, base_addr):
333        vars(self)["base_addr"] = base_addr
334        vars(self)["ptrs"] = {}
335        vars(self)["frozen"] = False
336    def freeze(self):
337        vars(self)["frozen"] = True
338    def __setattr__(self, name, val):
339        if not self.frozen and name not in self.ptrs:
340            addr = self.base_addr + val
341            self.ptrs[name] = ctypes.c_uint32.from_address(addr)
342        else:
343            self.ptrs[name].value = val
344    def __getattr__(self, name):
345        return self.ptrs[name].value
346
347def runx(cmd):
348    return subprocess.check_output(cmd, shell=True).decode().rstrip()
349
350def mask(bit):
351    if cavs25:
352        return 0b1 << bit
353    if cavs18:
354        return 0b1111 << bit
355    if cavs15:
356        return 0b11 << bit
357
358def load_firmware(fw_file):
359    try:
360        fw_bytes = open(fw_file, "rb").read()
361    except Exception as e:
362        log.error(f"Could not read firmware file: `{fw_file}'")
363        log.error(e)
364        sys.exit(1)
365
366    (magic, sz) = struct.unpack("4sI", fw_bytes[0:8])
367    if magic == b'XMan':
368        log.info(f"Trimming {sz} bytes of extended manifest")
369        fw_bytes = fw_bytes[sz:len(fw_bytes)]
370
371    # This actually means "enable access to BAR4 registers"!
372    hda.PPCTL |= (1 << 30) # GPROCEN, "global processing enable"
373
374    log.info("Resetting HDA device")
375    hda.GCTL = 0
376    while hda.GCTL & 1: pass
377    hda.GCTL = 1
378    while not hda.GCTL & 1: pass
379
380    log.info(f"Stalling and Resetting DSP cores, ADSPCS = 0x{dsp.ADSPCS:x}")
381    dsp.ADSPCS |= mask(CSTALL)
382    dsp.ADSPCS |= mask(CRST)
383    while (dsp.ADSPCS & mask(CRST)) == 0: pass
384
385    log.info(f"Powering down DSP cores, ADSPCS = 0x{dsp.ADSPCS:x}")
386    dsp.ADSPCS &= ~mask(SPA)
387    while dsp.ADSPCS & mask(CPA): pass
388
389    log.info(f"Configuring HDA stream {hda_ostream_id} to transfer firmware image")
390    (buf_list_addr, num_bufs) = setup_dma_mem(fw_bytes)
391    sd.CTL = 1
392    while (sd.CTL & 1) == 0: pass
393    sd.CTL = 0
394    while (sd.CTL & 1) == 1: pass
395    sd.CTL = 1 << 20 # Set stream ID to anything non-zero
396    sd.BDPU = (buf_list_addr >> 32) & 0xffffffff
397    sd.BDPL = buf_list_addr & 0xffffffff
398    sd.CBL = len(fw_bytes)
399    sd.LVI = num_bufs - 1
400    hda.PPCTL |= (1 << hda_ostream_id)
401
402    # SPIB ("Software Position In Buffer") is an Intel HDA extension
403    # that puts a transfer boundary into the stream beyond which the
404    # other side will not read.  The ROM wants to poll on a "buffer
405    # full" bit on the other side that only works with this enabled.
406    hda.SPBFCTL |= (1 << hda_ostream_id)
407    hda.SD_SPIB = len(fw_bytes)
408
409    # Start DSP.  Host needs to provide power to all cores on 1.5
410    # (which also starts them) and 1.8 (merely gates power, DSP also
411    # has to set PWRCTL). On 2.5 where the DSP has full control,
412    # and only core 0 is set.
413    log.info(f"Starting DSP, ADSPCS = 0x{dsp.ADSPCS:x}")
414    dsp.ADSPCS = mask(SPA)
415    while (dsp.ADSPCS & mask(CPA)) == 0: pass
416
417    log.info(f"Unresetting DSP cores, ADSPCS = 0x{dsp.ADSPCS:x}")
418    dsp.ADSPCS &= ~mask(CRST)
419    while (dsp.ADSPCS & 1) != 0: pass
420
421    log.info(f"Running DSP cores, ADSPCS = 0x{dsp.ADSPCS:x}")
422    dsp.ADSPCS &= ~mask(CSTALL)
423
424    # Wait for the ROM to boot and signal it's ready.  This not so short
425    # sleep seems to be needed; if we're banging on the memory window
426    # during initial boot (before/while the window control registers
427    # are configured?) the DSP hardware will hang fairly reliably.
428    log.info(f"Wait for ROM startup, ADSPCS = 0x{dsp.ADSPCS:x}")
429    time.sleep(1)
430    while (dsp.SRAM_FW_STATUS >> 24) != 5: pass
431
432    # Send the DSP an IPC message to tell the device how to boot.
433    # Note: with cAVS 1.8+ the ROM receives the stream argument as an
434    # index within the array of output streams (and we always use the
435    # first one by construction).  But with 1.5 it's the HDA index,
436    # and depends on the number of input streams on the device.
437    stream_idx = hda_ostream_id if cavs15 else 0
438    ipcval = (  (1 << 31)            # BUSY bit
439                | (0x01 << 24)       # type = PURGE_FW
440                | (1 << 14)          # purge_fw = 1
441                | (stream_idx << 9)) # dma_id
442    log.info(f"Sending IPC command, HIPIDR = 0x{ipcval:x}")
443    dsp.HIPCIDR = ipcval
444
445    log.info(f"Starting DMA, FW_STATUS = 0x{dsp.SRAM_FW_STATUS:x}")
446    sd.CTL |= 2 # START flag
447
448    wait_fw_entered()
449
450    # Turn DMA off and reset the stream.  Clearing START first is a
451    # noop per the spec, but absolutely required for stability.
452    # Apparently the reset doesn't stop the stream, and the next load
453    # starts before it's ready and kills the load (and often the DSP).
454    # The sleep too is required, on at least one board (a fast
455    # chromebook) putting the two writes next each other also hangs
456    # the DSP!
457    sd.CTL &= ~2 # clear START
458    time.sleep(0.1)
459    sd.CTL |= 1
460    log.info(f"cAVS firmware load complete")
461
462def fw_is_alive():
463    return dsp.SRAM_FW_STATUS & ((1 << 28) - 1) == 5 # "FW_ENTERED"
464
465def wait_fw_entered(timeout_s=2):
466    log.info("Waiting %s for firmware handoff, FW_STATUS = 0x%x",
467             "forever" if timeout_s is None else f"{timeout_s} seconds",
468             dsp.SRAM_FW_STATUS)
469    hertz = 100
470    attempts = None if timeout_s is None else timeout_s * hertz
471    while True:
472        alive = fw_is_alive()
473        if alive:
474            break
475        if attempts is not None:
476            attempts -= 1
477            if attempts < 0:
478                break
479        time.sleep(1 / hertz)
480
481    if not alive:
482        log.warning("Load failed?  FW_STATUS = 0x%x", dsp.SRAM_FW_STATUS)
483    else:
484        log.info("FW alive, FW_STATUS = 0x%x", dsp.SRAM_FW_STATUS)
485
486
487# This SHOULD be just "mem[start:start+length]", but slicing an mmap
488# array seems to be unreliable on one of my machines (python 3.6.9 on
489# Ubuntu 18.04).  Read out bytes individually.
490def win_read(start, length):
491    try:
492        return b''.join(bar4_mmap[WINSTREAM_OFFSET + x].to_bytes(1, 'little')
493                        for x in range(start, start + length))
494    except IndexError as ie:
495        # A FW in a bad state may cause winstream garbage
496        log.error("IndexError in bar4_mmap[%d + %d]", WINSTREAM_OFFSET, start)
497        log.error("bar4_mmap.size()=%d", bar4_mmap.size())
498        raise ie
499
500def win_hdr():
501    return struct.unpack("<IIII", win_read(0, 16))
502
503# Python implementation of the same algorithm in sys_winstream_read(),
504# see there for details.
505def winstream_read(last_seq):
506    while True:
507        (wlen, start, end, seq) = win_hdr()
508        if last_seq == 0:
509            last_seq = seq if args.no_history else (seq - ((end - start) % wlen))
510        if seq == last_seq or start == end:
511            return (seq, "")
512        behind = seq - last_seq
513        if behind > ((end - start) % wlen):
514            return (seq, "")
515        copy = (end - behind) % wlen
516        suffix = min(behind, wlen - copy)
517        result = win_read(16 + copy, suffix)
518        if suffix < behind:
519            result += win_read(16, behind - suffix)
520        (wlen, start1, end, seq1) = win_hdr()
521        if start1 == start and seq1 == seq:
522            # Best effort attempt at decoding, replacing unusable characters
523            # Found to be useful when it really goes wrong
524            return (seq, result.decode("utf-8", "replace"))
525
526
527async def ipc_delay_done():
528    await asyncio.sleep(0.1)
529    dsp.HIPCTDA = 1<<31
530
531ipc_timestamp = 0
532
533# Super-simple command language, driven by the test code on the DSP
534def ipc_command(data, ext_data):
535    send_msg = False
536    done = True
537    log.debug ("ipc data %d, ext_data %x", data, ext_data)
538    if data == 0: # noop, with synchronous DONE
539        pass
540    elif data == 1: # async command: signal DONE after a delay (on 1.8+)
541        if not cavs15:
542            done = False
543            asyncio.ensure_future(ipc_delay_done())
544    elif data == 2: # echo back ext_data as a message command
545        send_msg = True
546    elif data == 3: # set ADSPCS
547        dsp.ADSPCS = ext_data
548    elif data == 4: # echo back microseconds since last timestamp command
549        global ipc_timestamp
550        t = round(time.time() * 1e6)
551        ext_data = t - ipc_timestamp
552        ipc_timestamp = t
553        send_msg = True
554    elif data == 5: # copy word at outbox[ext_data >> 16] to inbox[ext_data & 0xffff]
555        src = OUTBOX_OFFSET + 4 * (ext_data >> 16)
556        dst =  INBOX_OFFSET + 4 * (ext_data & 0xffff)
557        for i in range(4):
558            bar4_mmap[dst + i] = bar4_mmap[src + i]
559    elif data == 6: # HDA RESET (init if not exists)
560        stream_id = ext_data & 0xff
561        if stream_id in hda_streams:
562            hda_streams[stream_id].reset()
563        else:
564            hda_str = HDAStream(stream_id)
565            hda_streams[stream_id] = hda_str
566    elif data == 7: # HDA CONFIG
567        stream_id = ext_data & 0xFF
568        buf_len = ext_data >> 8 & 0xFFFF
569        hda_str = hda_streams[stream_id]
570        hda_str.config(buf_len)
571    elif data == 8: # HDA START
572        stream_id = ext_data & 0xFF
573        hda_streams[stream_id].start()
574        hda_streams[stream_id].mem.seek(0)
575
576    elif data == 9: # HDA STOP
577        stream_id = ext_data & 0xFF
578        hda_streams[stream_id].stop()
579    elif data == 10: # HDA VALIDATE
580        stream_id = ext_data & 0xFF
581        hda_str = hda_streams[stream_id]
582        hda_str.debug()
583        is_ramp_data = True
584        hda_str.mem.seek(0)
585        for (i, val) in enumerate(hda_str.mem.read(256)):
586            if i != val:
587                is_ramp_data = False
588            # log.info("stream[%d][%d]: %d", stream_id, i, val) # debug helper
589        log.info("Is ramp data? " + str(is_ramp_data))
590        ext_data = int(is_ramp_data)
591        log.info(f"Ext data to send back on ramp status {ext_data}")
592        send_msg = True
593    elif data == 11: # HDA HOST OUT SEND
594        stream_id = ext_data & 0xff
595        buf = bytearray(256)
596        for i in range(0, 256):
597            buf[i] = i
598        hda_streams[stream_id].write(buf)
599    elif data == 12: # HDA PRINT
600        stream_id = ext_data & 0xFF
601        buf_len = ext_data >> 8 & 0xFFFF
602        hda_str = hda_streams[stream_id]
603        # check for wrap here
604        pos = hda_str.mem.tell()
605        read_lens = [buf_len, 0]
606        if pos + buf_len >= hda_str.buf_len*2:
607            read_lens[0] = hda_str.buf_len*2 - pos
608            read_lens[1] = buf_len - read_lens[0]
609        # validate the read lens
610        assert (read_lens[0] + pos) <= (hda_str.buf_len*2)
611        assert read_lens[0] % 128 == 0
612        assert read_lens[1] % 128 == 0
613        buf_data0 = hda_str.mem.read(read_lens[0])
614        hda_msg0 = buf_data0.decode("utf-8", "replace")
615        sys.stdout.write(hda_msg0)
616        if read_lens[1] != 0:
617            hda_str.mem.seek(0)
618            buf_data1 = hda_str.mem.read(read_lens[1])
619            hda_msg1 = buf_data1.decode("utf-8", "replace")
620            sys.stdout.write(hda_msg1)
621        pos = hda_str.mem.tell()
622        sys.stdout.flush()
623    else:
624        log.warning(f"cavstool: Unrecognized IPC command 0x{data:x} ext 0x{ext_data:x}")
625        if not fw_is_alive():
626            if args.log_only:
627                log.info("DSP power seems off")
628                wait_fw_entered(timeout_s=None)
629            else:
630                log.warning("DSP power seems off?!")
631                time.sleep(2) # potential spam reduction
632
633            return
634
635    dsp.HIPCTDR = 1<<31 # Ack local interrupt, also signals DONE on v1.5
636    if cavs18:
637        time.sleep(0.01) # Needed on 1.8, or the command below won't send!
638
639    if done and not cavs15:
640        dsp.HIPCTDA = 1<<31 # Signal done
641    if send_msg:
642        dsp.HIPCIDD = ext_data
643        dsp.HIPCIDR = (1<<31) | ext_data
644
645async def main():
646    #TODO this bit me, remove the globals, write a little FirmwareLoader class or something to contain.
647    global hda, sd, dsp, hda_ostream_id, hda_streams
648
649    try:
650        (hda, sd, dsp, hda_ostream_id) = map_regs()
651    except Exception as e:
652        log.error("Could not map device in sysfs; run as root?")
653        log.error(e)
654        sys.exit(1)
655
656    log.info(f"Detected cAVS {'1.5' if cavs15 else '1.8+'} hardware")
657
658    if args.log_only:
659        wait_fw_entered(timeout_s=None)
660    else:
661        if not args.fw_file:
662            log.error("Firmware file argument missing")
663            sys.exit(1)
664
665        load_firmware(args.fw_file)
666        time.sleep(0.1)
667        if not args.quiet:
668            sys.stdout.write("--\n")
669
670    hda_streams = dict()
671
672    last_seq = 0
673    while start_output is True:
674        await asyncio.sleep(0.03)
675        (last_seq, output) = winstream_read(last_seq)
676        if output:
677            sys.stdout.write(output)
678            sys.stdout.flush()
679        if not args.log_only:
680            if dsp.HIPCIDA & 0x80000000:
681                dsp.HIPCIDA = 1<<31 # must ACK any DONE interrupts that arrive!
682            if dsp.HIPCTDR & 0x80000000:
683                ipc_command(dsp.HIPCTDR & ~0x80000000, dsp.HIPCTDD)
684
685
686ap = argparse.ArgumentParser(description="DSP loader/logger tool", allow_abbrev=False)
687ap.add_argument("-q", "--quiet", action="store_true",
688                help="No loader output, just DSP logging")
689ap.add_argument("-v", "--verbose", action="store_true",
690                help="More loader output, DEBUG logging level")
691ap.add_argument("-l", "--log-only", action="store_true",
692                help="Don't load firmware, just show log output")
693ap.add_argument("-n", "--no-history", action="store_true",
694                help="No current log buffer at start, just new output")
695ap.add_argument("fw_file", nargs="?", help="Firmware file")
696
697args = ap.parse_args()
698
699if args.quiet:
700    log.setLevel(logging.WARN)
701elif args.verbose:
702    log.setLevel(logging.DEBUG)
703
704if __name__ == "__main__":
705    try:
706        asyncio.run(main())
707    except KeyboardInterrupt:
708        start_output = False
709