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