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