1#!/usr/bin/env python 2# Copyright 2023 The ChromiumOS Authors 3# SPDX-License-Identifier: Apache-2.0 4import ctypes 5import mmap 6import os 7import re 8import struct 9import sys 10import time 11from glob import glob 12 13# MT8195 audio firmware load/debug gadget 14 15# Note that the hardware handling here is only partial: in practice 16# the audio DSP depends on clock and power well devices whose drivers 17# live elsewhere in the kernel. Those aren't duplicated here. Make 18# sure the DSP has been started by a working kernel driver first. 19# 20# See gen_img.py for docs on the image format itself. The way this 21# script works is to map the device memory regions and registers via 22# /dev/mem and copy the two segments while resetting the DSP. 23# 24# In the kernel driver, the address/size values come from devicetree. 25# But currently the MediaTek architecture is one kernel driver per SOC 26# (i.e. the devicetree values in the kenrel source are tied to the 27# specific SOC anyway), so it really doesn't matter and we hard-code 28# the addresses for simplicity. 29# 30# (For future reference: in /proc/device-tree on current ChromeOS 31# kernels, the host registers are a "cfg" platform resource on the 32# "adsp@10803000" node. The sram is likewise the "sram" resource on 33# that device node, and the two dram areas are "memory-region" 34# phandles pointing to "adsp_mem_region" and "adsp_dma_mem_region" 35# nodes under "/reserved-memory"). 36 37FILE_MAGIC = 0xE463BE95 38 39# Runtime mmap objects for each MAPPINGS entry 40maps = {} 41 42 43# Returns a string (e.g. "mt8195", "mt8186", "mt8188") if a supported 44# adsp is detected, or None if not 45def detect(): 46 compat = readfile(glob("/proc/device-tree/**/adsp@*/compatible", recursive=True)[0], "r") 47 m = re.match(r'.*(mt\d{4})-dsp', compat) 48 if m: 49 return m.group(1) 50 51 52# Parse devicetree to find the MMIO mappings: there is an "adsp" node 53# (in various locations) with an array of named "reg" mappings. It 54# also refers by reference to reserved-memory regions of system 55# DRAM. Those don't have names, call them dram0/1 (dram1 is the main 56# region to which code is linked, dram0 is presumably a dma pool but 57# unused by current firmware). Returns a dict mapping name to a 58# (addr, size) tuple. 59def mappings(): 60 path = glob("/proc/device-tree/**/adsp@*/", recursive=True)[0] 61 rnames = readfile(path + "reg-names", "r").split('\0')[:-1] 62 regs = struct.unpack(f">{2 * len(rnames)}Q", readfile(path + "reg")) 63 maps = {n: (regs[2 * i], regs[2 * i + 1]) for i, n in enumerate(rnames)} 64 for i, ph in enumerate(struct.unpack(">II", readfile(path + "memory-region"))): 65 for rmem in glob("/proc/device-tree/reserved-memory/*/"): 66 phf = rmem + "phandle" 67 if os.path.exists(phf) and struct.unpack(">I", readfile(phf))[0] == ph: 68 (addr, sz) = struct.unpack(">QQ", readfile(rmem + "reg")) 69 maps[f"dram{i}"] = (addr, sz) 70 break 71 return maps 72 73 74# Register API for 8195 75class MT8195: 76 def __init__(self, maps): 77 # Create a Regs object for the registers 78 r = Regs(ctypes.addressof(ctypes.c_int.from_buffer(maps["cfg"]))) 79 r.ALTRESETVEC = 0x0004 # Xtensa boot address 80 r.RESET_SW = 0x0024 # Xtensa halt/reset/boot control 81 r.PDEBUGBUS0 = 0x000C # Unclear, enabled by host, unused by SOF? 82 r.SRAM_POOL_CON = 0x0930 # SRAM power control: low 4 bits (banks?) enable 83 r.EMI_MAP_ADDR = 0x981C # == host SRAM mapping - 0x40000000 (controls MMIO map?) 84 r.freeze() 85 self.cfg = r 86 87 def logrange(self): 88 return range(0x700000, 0x800000) 89 90 def stop(self): 91 self.cfg.RESET_SW |= 8 # Set RUNSTALL: halt CPU 92 self.cfg.RESET_SW |= 3 # Set low two bits: "BRESET|DRESET" 93 94 def start(self, boot_vector): 95 self.stop() 96 self.cfg.RESET_SW |= 0x10 # Enable "alternate reset" boot vector 97 self.cfg.ALTRESETVEC = boot_vector 98 self.cfg.RESET_SW &= ~3 # Release reset bits 99 self.cfg.RESET_SW &= ~8 # Clear RUNSTALL: go! 100 101 102# Register API for 8186/8188 103class MT818x: 104 def __init__(self, maps): 105 # These have registers spread across two blocks 106 cfg_base = ctypes.addressof(ctypes.c_int.from_buffer(maps["cfg"])) 107 sec_base = ctypes.addressof(ctypes.c_int.from_buffer(maps["sec"])) 108 self.cfg = Regs(cfg_base) 109 self.cfg.SW_RSTN = 0x00 110 self.cfg.IO_CONFIG = 0x0C 111 self.cfg.freeze() 112 self.sec = Regs(sec_base) 113 self.sec.ALTVEC_C0 = 0x04 114 self.sec.ALTVECSEL = 0x0C 115 self.sec.freeze() 116 117 def logrange(self): 118 return range(0x700000, 0x800000) 119 120 def stop(self): 121 self.cfg.IO_CONFIG |= 1 << 31 # Set RUNSTALL to stop core 122 time.sleep(0.1) 123 self.cfg.SW_RSTN |= 0x11 # Assert reset: SW_RSTN_C0|SW_DBG_RSTN_C0 124 125 # Note: 8186 and 8188 use different bits in ALTVECSEC, but 126 # it's safe to write both to enable the alternate boot vector 127 def start(self, boot_vector): 128 self.cfg.IO_CONFIG |= 1 << 31 # Set RUNSTALL 129 self.sec.ALTVEC_C0 = boot_vector 130 self.sec.ALTVECSEL = 0x03 # Enable alternate vector 131 self.cfg.SW_RSTN |= 0x00000011 # Assert reset 132 self.cfg.SW_RSTN &= 0xFFFFFFEE # Release reset 133 self.cfg.IO_CONFIG &= 0x7FFFFFFF # Clear RUNSTALL 134 135 136class MT8196: 137 def __init__(self, maps): 138 cfg_base = ctypes.addressof(ctypes.c_int.from_buffer(maps["cfg"])) 139 sec_base = ctypes.addressof(ctypes.c_int.from_buffer(maps["sec"])) 140 self.cfg = Regs(cfg_base) 141 self.cfg.CFGREG_SW_RSTN = 0x0000 142 self.cfg.MBOX_IRQ_EN = 0x009C 143 self.cfg.HIFI_RUNSTALL = 0x0108 144 self.cfg.freeze() 145 self.sec = Regs(sec_base) 146 self.sec.ALTVEC_C0 = 0x04 147 self.sec.ALTVECSEL = 0x0C 148 self.sec.freeze() 149 150 def logrange(self): 151 return range(0x580000, 0x600000) 152 153 def stop(self): 154 self.cfg.HIFI_RUNSTALL |= 0x1000 155 self.cfg.CFGREG_SW_RSTN |= 0x11 156 157 def start(self, boot_vector): 158 self.sec.ALTVEC_C0 = 0 159 self.sec.ALTVECSEL = 0 160 self.sec.ALTVEC_C0 = boot_vector 161 self.sec.ALTVECSEL = 1 162 self.cfg.HIFI_RUNSTALL |= 0x1000 163 self.cfg.MBOX_IRQ_EN |= 3 164 self.cfg.CFGREG_SW_RSTN |= 0x11 165 time.sleep(0.1) 166 self.cfg.CFGREG_SW_RSTN &= ~0x11 167 self.cfg.HIFI_RUNSTALL &= ~0x1000 168 169 170# Temporary logging protocol: watch the 1M null-terminated log 171# stream at 0x60700000 -- the top of the linkable region of 172# existing SOF firmware, before the heap. Nothing uses this 173# currently. Will be replaced by winstream very soon. 174def log(dev): 175 msg = b'' 176 dram = maps["dram1"] 177 for i in dev.logrange(): 178 x = dram[i] 179 if x == 0: 180 sys.stdout.buffer.write(msg) 181 sys.stdout.buffer.flush() 182 msg = b'' 183 while x == 0: 184 time.sleep(0.1) 185 x = dram[i] 186 msg += x.to_bytes(1, "little") 187 sys.stdout.buffer.write(msg) 188 sys.stdout.buffer.flush() 189 190 191# (Cribbed from cavstool.py) 192class Regs: 193 def __init__(self, base_addr): 194 vars(self)["base_addr"] = base_addr 195 vars(self)["ptrs"] = {} 196 vars(self)["frozen"] = False 197 198 def freeze(self): 199 vars(self)["frozen"] = True 200 201 def __setattr__(self, name, val): 202 if not self.frozen and name not in self.ptrs: 203 addr = self.base_addr + val 204 self.ptrs[name] = ctypes.c_uint32.from_address(addr) 205 else: 206 self.ptrs[name].value = val 207 208 def __getattr__(self, name): 209 return self.ptrs[name].value 210 211 212def readfile(f, mode="rb"): 213 return open(f, mode).read() 214 215 216def le4(bstr): 217 assert len(bstr) == 4 218 return struct.unpack("<I", bstr)[0] 219 220 221def main(): 222 dsp = detect() 223 assert dsp 224 225 # Probe devicetree for mappable memory regions 226 mmio = mappings() 227 228 # Open device and establish mappings 229 with open("/dev/mem", "wb+") as devmem_fd: 230 for mp in mmio: 231 paddr = mmio[mp][0] 232 mapsz = mmio[mp][1] 233 mapsz = int((mapsz + 4095) / 4096) * 4096 234 maps[mp] = mmap.mmap( 235 devmem_fd.fileno(), 236 mapsz, 237 offset=paddr, 238 flags=mmap.MAP_SHARED, 239 prot=mmap.PROT_WRITE | mmap.PROT_READ, 240 ) 241 242 if dsp == "mt8195": 243 dev = MT8195(maps) 244 elif dsp in ("mt8186", "mt8188"): 245 dev = MT818x(maps) 246 elif dsp == "mt8196": 247 dev = MT8196(maps) 248 249 if sys.argv[1] == "load": 250 dat = None 251 with open(sys.argv[2], "rb") as f: 252 dat = f.read() 253 assert le4(dat[0:4]) == FILE_MAGIC 254 sram_len = le4(dat[4:8]) 255 boot_vector = le4(dat[8:12]) 256 sram = dat[12 : 12 + sram_len] 257 dram = dat[12 + sram_len :] 258 assert len(sram) <= mmio["sram"][1] 259 assert len(dram) <= mmio["dram1"][1] 260 261 # Stop the device and write the regions. Note that we don't 262 # zero-fill SRAM, as that's been observed to reboot the host 263 # (!!) on mt8186 when the writes near the end of the 512k 264 # region. 265 # pylint: disable=consider-using-enumerate 266 for i in range(sram_len): 267 maps["sram"][i] = sram[i] 268 # for i in range(sram_len, mmio["sram"][1]): 269 # maps["sram"][i] = 0 270 for i in range(len(dram)): 271 maps["dram1"][i] = dram[i] 272 for i in range(len(dram), mmio["dram1"][1]): 273 maps["dram1"][i] = 0 274 dev.start(boot_vector) 275 log(dev) 276 277 elif sys.argv[1] == "log": 278 log(dev) 279 280 elif sys.argv[1] == "dump": 281 sz = mmio[sys.argv[2]][1] 282 mm = maps[sys.argv[2]] 283 sys.stdout.buffer.write(mm[0:sz]) 284 285 else: 286 print(f"Usage: {sys.argv[0]} log | load <file>") 287 288 289if __name__ == "__main__": 290 main() 291