soc/intel_adsp: Low level HDA driver and tests
Adds a header only low level driver for HDA streams along with smoke tests to ensure basic host in and out stream functionality. The tests require host side interaction. In cavstool a new HDAStream class encapsulates somewhat a single stream and its registers. This is manipulated in the tests using IPC with the Host ensuring that a specific order of operations is done. This low level driver allows testing certain hardware configurations and flows with easy to use register dump debugging. It is not intended to be the end API an application might use. That would be a DMA driver using this. Signed-off-by: Tom Burdick <thomas.burdick@intel.com>
This commit is contained in:
parent
345a536794
commit
cc6e9c094a
9 changed files with 764 additions and 7 deletions
|
@ -18,7 +18,7 @@ log.setLevel(logging.INFO)
|
|||
|
||||
PAGESZ = 4096
|
||||
HUGEPAGESZ = 2 * 1024 * 1024
|
||||
HUGEPAGE_FILE = "/dev/hugepages/cavs-fw-dma.tmp"
|
||||
HUGEPAGE_FILE = "/dev/hugepages/cavs-fw-dma.tmp."
|
||||
|
||||
# SRAM windows. Each appears in a 128k region starting at 512k.
|
||||
#
|
||||
|
@ -30,6 +30,155 @@ OUTBOX_OFFSET = (512 + (0 * 128)) * 1024 + 4096
|
|||
INBOX_OFFSET = (512 + (1 * 128)) * 1024
|
||||
WINSTREAM_OFFSET = (512 + (3 * 128)) * 1024
|
||||
|
||||
class HDAStream:
|
||||
# creates an hda stream with at 2 buffers of buf_len
|
||||
def __init__(self, stream_id: int):
|
||||
self.stream_id = stream_id
|
||||
self.base = hdamem + 0x0080 + (stream_id * 0x20)
|
||||
log.info(f"Mapping registers for hda stream {self.stream_id} at base {self.base:x}")
|
||||
|
||||
self.hda = Regs(hdamem)
|
||||
self.hda.GCAP = 0x0000
|
||||
self.hda.GCTL = 0x0008
|
||||
self.hda.DPLBASE = 0x0070
|
||||
self.hda.DPUBASE = 0x0074
|
||||
self.hda.SPBFCH = 0x0700
|
||||
self.hda.SPBFCTL = 0x0704
|
||||
self.hda.PPCH = 0x0800
|
||||
self.hda.PPCTL = 0x0804
|
||||
self.hda.PPSTS = 0x0808
|
||||
self.hda.SPIB = 0x0708 + stream_id*0x08
|
||||
self.hda.freeze()
|
||||
|
||||
self.regs = Regs(self.base)
|
||||
self.regs.CTL = 0x00
|
||||
self.regs.STS = 0x03
|
||||
self.regs.LPIB = 0x04
|
||||
self.regs.CBL = 0x08
|
||||
self.regs.LVI = 0x0c
|
||||
self.regs.FIFOW = 0x0e
|
||||
self.regs.FIFOS = 0x10
|
||||
self.regs.FMT = 0x12
|
||||
self.regs.FIFOL= 0x14
|
||||
self.regs.BDPL = 0x18
|
||||
self.regs.BDPU = 0x1c
|
||||
self.regs.freeze()
|
||||
|
||||
self.dbg0 = Regs(hdamem + 0x0084 + (0x20*stream_id))
|
||||
self.dbg0.DPIB = 0x00
|
||||
self.dbg0.EFIFOS = 0x10
|
||||
self.dbg0.freeze()
|
||||
|
||||
self.reset()
|
||||
|
||||
def __del__(self):
|
||||
self.reset()
|
||||
|
||||
def config(self, buf_len: int):
|
||||
log.info(f"Configuring stream {self.stream_id}")
|
||||
self.buf_len = buf_len
|
||||
log.info("Allocating huge page and setting up buffers")
|
||||
self.mem, self.hugef, self.buf_list_addr, self.pos_buf_addr, self.n_bufs = self.setup_buf(buf_len)
|
||||
|
||||
log.info("Setting buffer list, length, and stream id and traffic priority bit")
|
||||
self.regs.CTL = ((self.stream_id & 0xFF) << 20) | (1 << 18) # must be set to something other than 0?
|
||||
self.regs.BDPU = (self.buf_list_addr >> 32) & 0xffffffff
|
||||
self.regs.BDPL = self.buf_list_addr & 0xffffffff
|
||||
self.regs.CBL = buf_len
|
||||
self.regs.LVI = self.n_bufs - 1
|
||||
self.debug()
|
||||
log.info(f"Configured stream {self.stream_id}")
|
||||
|
||||
def write(self, data):
|
||||
|
||||
bufl = min(len(data), self.buf_len)
|
||||
log.info(f"Writing data to stream {self.stream_id}, len {bufl}, SPBFCTL {self.hda.SPBFCTL:x}, SPIB {self.hda.SPIB}")
|
||||
self.mem[0:bufl] = data[0:bufl]
|
||||
self.mem[bufl:bufl+bufl] = data[0:bufl]
|
||||
self.hda.SPBFCTL |= (1 << self.stream_id)
|
||||
self.hda.SPIB += bufl
|
||||
log.info(f"Wrote data to stream {self.stream_id}, SPBFCTL {self.hda.SPBFCTL:x}, SPIB {self.hda.SPIB}")
|
||||
|
||||
def start(self):
|
||||
log.info(f"Starting stream {self.stream_id}, CTL {self.regs.CTL:x}")
|
||||
self.regs.CTL |= 2
|
||||
log.info(f"Started stream {self.stream_id}, CTL {self.regs.CTL:x}")
|
||||
|
||||
def stop(self):
|
||||
log.info(f"Stopping stream {self.stream_id}, CTL {self.regs.CTL:x}")
|
||||
self.regs.CTL &= 2
|
||||
time.sleep(0.1)
|
||||
self.regs.CTL |= 1
|
||||
log.info(f"Stopped stream {self.stream_id}, CTL {self.regs.CTL:x}")
|
||||
|
||||
def setup_buf(self, buf_len: int):
|
||||
(mem, phys_addr, hugef) = map_phys_mem(self.stream_id)
|
||||
|
||||
log.info(f"Mapped 2M huge page at 0x{phys_addr:x} for buf size ({buf_len})")
|
||||
|
||||
# create two buffers in the page of buf_len and mark them
|
||||
# in a buffer descriptor list for the hardware to use
|
||||
buf0_len = buf_len
|
||||
buf1_len = buf_len
|
||||
bdl_off = buf0_len + buf1_len
|
||||
# bdl is 2 (64bits, 16 bytes) per entry, we have two
|
||||
mem[bdl_off:bdl_off + 32] = struct.pack("<QQQQ",
|
||||
phys_addr,
|
||||
buf0_len,
|
||||
phys_addr + buf0_len,
|
||||
buf1_len)
|
||||
dpib_off = bdl_off+32
|
||||
|
||||
# ensure buffer is initialized, sanity
|
||||
for i in range(0, buf_len*2):
|
||||
mem[i] = 0
|
||||
|
||||
log.info("Filled the buffer descriptor list (BDL) for DMA.")
|
||||
return (mem, hugef, phys_addr + bdl_off, phys_addr+dpib_off, 2)
|
||||
|
||||
def debug(self):
|
||||
log.info("HDA %d: PPROC %d, CTL 0x%x, LPIB 0x%x, BDPU 0x%x, BDPL 0x%x, CBL 0x%x, LVI 0x%x",
|
||||
self.stream_id, (hda.PPCTL >> self.stream_id) & 1, self.regs.CTL, self.regs.LPIB, self.regs.BDPU,
|
||||
self.regs.BDPL, self.regs.CBL, self.regs.LVI)
|
||||
log.info(" FIFOW %d, FIFOS %d, FMT %x, FIFOL %d, DPIB %d, EFIFOS %d",
|
||||
self.regs.FIFOW & 0x7, self.regs.FIFOS, self.regs.FMT, self.regs.FIFOL, self.dbg0.DPIB, self.dbg0.EFIFOS)
|
||||
log.info(" status: FIFORDY %d, DESE %d, FIFOE %d, BCIS %d",
|
||||
(self.regs.STS >> 5) & 1, (self.regs.STS >> 4) & 1, (self.regs.STS >> 3) & 1, (self.regs.STS >> 2) & 1)
|
||||
|
||||
def reset(self):
|
||||
# Turn DMA off and reset the stream. Clearing START first is a
|
||||
# noop per the spec, but absolutely required for stability.
|
||||
# Apparently the reset doesn't stop the stream, and the next load
|
||||
# starts before it's ready and kills the load (and often the DSP).
|
||||
# The sleep too is required, on at least one board (a fast
|
||||
# chromebook) putting the two writes next each other also hangs
|
||||
# the DSP!
|
||||
log.info(f"Resetting stream {self.stream_id}")
|
||||
self.debug()
|
||||
self.regs.CTL &= ~2 # clear START
|
||||
time.sleep(0.1)
|
||||
# set enter reset bit
|
||||
self.regs.CTL = 1
|
||||
while (self.regs.CTL & 1) == 0: pass
|
||||
# clear enter reset bit to exit reset
|
||||
self.regs.CTL = 0
|
||||
while (self.regs.CTL & 1) == 1: pass
|
||||
|
||||
log.info(f"Disable SPIB and set position 0 of stream {self.stream_id}")
|
||||
self.hda.SPBFCTL = 0
|
||||
self.hda.SPIB = 0
|
||||
|
||||
#log.info("Setting dma position buffer and enable it")
|
||||
#self.hda.DPUBASE = self.pos_buf_addr >> 32 & 0xffffffff
|
||||
#self.hda.DPLBASE = self.pos_buf_addr & 0xfffffff0 | 1
|
||||
|
||||
log.info(f"Enabling dsp capture (PROCEN) of stream {self.stream_id}")
|
||||
self.hda.PPCTL |= (1 << self.stream_id)
|
||||
|
||||
self.debug()
|
||||
log.info(f"Reset stream {self.stream_id}")
|
||||
|
||||
|
||||
def map_regs():
|
||||
p = runx(f"grep -iPl 'PCI_CLASS=40(10|38)0' /sys/bus/pci/devices/*/uevent")
|
||||
pcidir = os.path.dirname(p)
|
||||
|
@ -62,6 +211,7 @@ def map_regs():
|
|||
cfg.write(b'\x06\x04')
|
||||
|
||||
# Standard HD Audio Registers
|
||||
global hdamem
|
||||
(hdamem, _) = bar_map(pcidir, 0)
|
||||
hda = Regs(hdamem)
|
||||
hda.GCAP = 0x0000
|
||||
|
@ -75,6 +225,7 @@ def map_regs():
|
|||
hda.SD_SPIB = 0x0708 + (8 * hda_ostream_id)
|
||||
hda.freeze()
|
||||
|
||||
|
||||
# Standard HD Audio Stream Descriptor
|
||||
sd = Regs(hdamem + 0x0080 + (hda_ostream_id * 0x20))
|
||||
sd.CTL = 0x00
|
||||
|
@ -101,7 +252,7 @@ def map_regs():
|
|||
return (hda, sd, dsp, hda_ostream_id)
|
||||
|
||||
def setup_dma_mem(fw_bytes):
|
||||
(mem, phys_addr) = map_phys_mem()
|
||||
(mem, phys_addr, _) = map_phys_mem(hda_ostream_id)
|
||||
mem[0:len(fw_bytes)] = fw_bytes
|
||||
|
||||
log.info("Mapped 2M huge page at 0x%x to contain %d bytes of firmware"
|
||||
|
@ -124,7 +275,7 @@ global_mmaps = [] # protect mmap mappings from garbage collection!
|
|||
|
||||
# Maps 2M of contiguous memory using a single page from hugetlbfs,
|
||||
# then locates its physical address for use as a DMA buffer.
|
||||
def map_phys_mem():
|
||||
def map_phys_mem(stream_id):
|
||||
# Make sure hugetlbfs is mounted (not there on chromeos)
|
||||
os.system("mount | grep -q hugetlbfs ||"
|
||||
+ " (mkdir -p /dev/hugepages; "
|
||||
|
@ -136,11 +287,13 @@ def map_phys_mem():
|
|||
tot = 1 + int(runx("awk '/HugePages_Total/ {print $2}' /proc/meminfo"))
|
||||
os.system(f"echo {tot} > /proc/sys/vm/nr_hugepages")
|
||||
|
||||
hugef = open(HUGEPAGE_FILE, "w+")
|
||||
hugef_name = HUGEPAGE_FILE + str(stream_id)
|
||||
hugef = open(hugef_name, "w+")
|
||||
hugef.truncate(HUGEPAGESZ)
|
||||
mem = mmap.mmap(hugef.fileno(), HUGEPAGESZ)
|
||||
log.info("type of mem is %s", str(type(mem)))
|
||||
global_mmaps.append(mem)
|
||||
os.unlink(HUGEPAGE_FILE)
|
||||
os.unlink(hugef_name)
|
||||
|
||||
# Find the local process address of the mapping, then use that to extract
|
||||
# the physical address from the kernel's pagemap interface. The physical
|
||||
|
@ -153,7 +306,7 @@ def map_phys_mem():
|
|||
pent = pagemap.read(8)
|
||||
paddr = (struct.unpack("Q", pent)[0] & ((1 << 55) - 1)) * PAGESZ
|
||||
pagemap.close()
|
||||
return (mem, paddr)
|
||||
return (mem, paddr, hugef)
|
||||
|
||||
# Maps a PCI BAR and returns the in-process address
|
||||
def bar_map(pcidir, barnum):
|
||||
|
@ -337,6 +490,7 @@ ipc_timestamp = 0
|
|||
def ipc_command(data, ext_data):
|
||||
send_msg = False
|
||||
done = True
|
||||
log.debug ("ipc data %d, ext_data %x", data, ext_data)
|
||||
if data == 0: # noop, with synchronous DONE
|
||||
pass
|
||||
elif data == 1: # async command: signal DONE after a delay (on 1.8+)
|
||||
|
@ -358,6 +512,45 @@ def ipc_command(data, ext_data):
|
|||
dst = INBOX_OFFSET + 4 * (ext_data & 0xffff)
|
||||
for i in range(4):
|
||||
bar4_mmap[dst + i] = bar4_mmap[src + i]
|
||||
elif data == 6: # HDA RESET (init if not exists)
|
||||
stream_id = ext_data & 0xff
|
||||
if stream_id in hda_streams:
|
||||
hda_streams[stream_id].reset()
|
||||
else:
|
||||
hda_str = HDAStream(stream_id)
|
||||
hda_streams[stream_id] = hda_str
|
||||
elif data == 7: # HDA CONFIG
|
||||
stream_id = ext_data & 0xFF
|
||||
buf_len = ext_data >> 8 & 0xFFFF
|
||||
hda_str = hda_streams[stream_id]
|
||||
hda_str.config(buf_len)
|
||||
elif data == 8: # HDA START
|
||||
stream_id = ext_data & 0xFF
|
||||
hda_streams[stream_id].start()
|
||||
|
||||
elif data == 9: # HDA STOP
|
||||
stream_id = ext_data & 0xFF
|
||||
hda_streams[stream_id].stop()
|
||||
elif data == 10: # HDA VALIDATE
|
||||
stream_id = ext_data & 0xFF
|
||||
hda_str = hda_streams[stream_id]
|
||||
hda_str.debug()
|
||||
is_ramp_data = True
|
||||
hda_str.mem.seek(0)
|
||||
for (i, val) in enumerate(hda_str.mem.read(256)):
|
||||
if i != val:
|
||||
is_ramp_data = False
|
||||
# log.info("stream[%d][%d]: %d", stream_id, i, val) # debug helper
|
||||
log.info("Is ramp data? " + str(is_ramp_data))
|
||||
ext_data = int(is_ramp_data)
|
||||
log.info(f"Ext data to send back on ramp status {ext_data}")
|
||||
send_msg = True
|
||||
elif data == 11: # HDA HOST OUT SEND
|
||||
stream_id = ext_data & 0xff
|
||||
buf = bytearray(256)
|
||||
for i in range(0, 256):
|
||||
buf[i] = i
|
||||
hda_streams[stream_id].write(buf)
|
||||
else:
|
||||
log.warning(f"cavstool: Unrecognized IPC command 0x{data:x} ext 0x{ext_data:x}")
|
||||
|
||||
|
@ -372,7 +565,8 @@ def ipc_command(data, ext_data):
|
|||
dsp.HIPCIDR = (1<<31) | ext_data
|
||||
|
||||
async def main():
|
||||
global hda, sd, dsp, hda_ostream_id
|
||||
#TODO this bit me, remove the globals, write a little FirmwareLoader class or something to contain.
|
||||
global hda, sd, dsp, hda_ostream_id, hda_streams
|
||||
try:
|
||||
(hda, sd, dsp, hda_ostream_id) = map_regs()
|
||||
except Exception as e:
|
||||
|
@ -394,6 +588,7 @@ async def main():
|
|||
if not args.quiet:
|
||||
sys.stdout.write("--\n")
|
||||
|
||||
hda_streams = dict()
|
||||
last_seq = 0
|
||||
while True:
|
||||
await asyncio.sleep(0.03)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue