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