1#!/usr/bin/env python3
2#
3# Copyright (c) 2019 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
6import os
7import fcntl
8import struct
9import mmap
10import logging
11
12from ctypes import cast, POINTER, c_uint8, c_uint32, c_uint16, c_uint64,\
13                   addressof, byref
14
15# diag_driver file
16DIAG_DRV_PATH = "/dev/hda"
17
18# Diag Driver Definition - sof-diagnostic-driver/ioctl.h
19
20# IOCTL definition
21CMD_OPEN_DEVICE  = 0x47
22CMD_ALLOC_MEMORY = 0x3A
23CMD_FREE_MEMORY  = 0x3B
24
25CMD_OPEN_DEVICE_LEN = 40
26
27
28class HdaBar:
29    """ Data structure for HDA BAR information """
30
31    def __init__(self, raw):
32        self.base_p = 0
33        self.base_v = 0
34        self.size = 0
35        (self.base_p, self.base_v, self.size) = struct.unpack('=QQL', raw)
36
37    def __str__(self):
38        return "   Base Physical Address: 0x%08X\n" \
39               "   Base Virtual Address:  0x%08X\n" \
40               "   Base Size:             0x%08X" \
41                   % (self.base_p, self.base_v, self.size)
42
43
44class HdaMemory:
45    """ Data structure for HDA memory allocation """
46
47    def __init__(self, size=0):
48        self.dma_addr_p = 0
49        self.dma_addr_v = 0
50        self.size = size
51        self.memmap = None
52
53    def set_value(self, raw):
54        (self.dma_addr_p,
55         self.dma_addr_v,
56         self.size) = struct.unpack('=QQL', raw)
57
58    def get_value(self):
59        data = bytearray(struct.pack('=QQL', self.dma_addr_p,
60                                             self.dma_addr_v,
61                                             self.size))
62        return data
63
64    def __str__(self):
65        return "   DMA Physical Address: 0x%08X\n" \
66               "   DMA Virtual  Address: 0x%08X\n" \
67               "   DMA Size:             0x%08X"   \
68               % (self.dma_addr_p, self.dma_addr_v, self.size)
69
70
71class HdaHandle:
72    """ Data structure for HDA handles """
73
74    def __init__(self, raw):
75
76        data = struct.unpack('20s20s', raw)
77        self.hda_bar = HdaBar(data[0])
78        self.dsp_bar = HdaBar(data[1])
79
80    def __str__(self):
81        output = (
82            "HDA BAR:\n"
83            "{hda}\n"
84            "DSP BAR:\n"
85            "{dsp}"
86        ).format(
87            hda = self.hda_bar, dsp = self.dsp_bar
88        )
89        return output
90
91
92class DiagDriver:
93    """ Interface for diag_driver """
94
95    def __init__(self):
96        self._handle = None
97        self._mem_map_list = []
98        self._buff_list = []
99
100    def open_device(self):
101        """
102        Send CMD_OPEN_DEVICE and get HDA BAR and DSP BAR
103
104        Returns:
105            (handle)(obj:HdaHandle): HDA and DSP Bars objs
106        """
107
108        logging.debug(">>> DiagDriver.open_device()")
109
110        # Allocate bytearry for HDABusTest_OpenDevice
111        buf = bytearray(CMD_OPEN_DEVICE_LEN)
112
113        logging.info("Open HDA device: %s" % DIAG_DRV_PATH)
114        # Send CMD_OPEN_DEVICE
115        with open(DIAG_DRV_PATH) as fd:
116            fcntl.ioctl(fd, CMD_OPEN_DEVICE, buf)
117
118        self._handle = HdaHandle(buf)
119
120        logging.debug("<<< DiagDriver.open_device()")
121
122        return self._handle
123
124    def alloc_mem(self, size):
125        """
126        Send CMD_ALLOC_MEMORY to allocate DMA buffer
127
128        Returns:
129            hda_mem (obj:HDAMemory): Allocated DMA buffer information
130        """
131
132        logging.debug(">>> Diag_Driver.alloc_mem(size=0x%08X)" % size)
133
134        hda_buf = HdaMemory(size)
135
136        # Allocate bytearray for HDABusTestMem
137        buf = hda_buf.get_value()
138
139        # Send CMD_ALLOC_MEMORY
140        with open(DIAG_DRV_PATH) as fd:
141            fcntl.ioctl(fd, CMD_ALLOC_MEMORY, buf)
142
143        hda_buf.set_value(buf)
144
145        mem = self.mmap(hda_buf.dma_addr_p, hda_buf.size)
146        hda_buf.memmap = mem
147        hda_buf.dma_addr_v = addressof(mem)
148
149        logging.debug("DMA Memory:\n%s" % hda_buf)
150
151        # Append to buffer list for later clean up.
152        self._buff_list.append(hda_buf)
153
154        logging.debug("<<< Diag_Driver.alloc_mem()")
155
156        return hda_buf
157
158    def free_mem(self, hda_buf):
159        """
160        Send CMD_FREE_MEMORY to free the DMA buffer
161
162        Params:
163            had_mem (obj:HDAMemory): DMA buffer information to be freed
164
165        Returns:
166            0 for success, otherwise fail.
167        """
168        logging.debug(">>> Diag_Driver.free_mem()")
169
170        if hda_buf not in self._buff_list:
171            logging.error("Cannot find buffer from the list")
172            raise ValueError("Cannot find buffer to free")
173
174        logging.debug("Buffer to Free:\n%s" % hda_buf)
175
176        buf = hda_buf.get_value()
177
178        # Send CMD_FREE_MEMORY
179        with open(DIAG_DRV_PATH) as fd:
180            ret = fcntl.ioctl(fd, CMD_FREE_MEMORY, buf)
181
182        self._buff_list.remove(hda_buf)
183
184        logging.debug("<<< Diag_Driver.free_mem()")
185        return ret
186
187    def mmap(self, addr, length):
188        """
189        Setup mmap for HDA and DSP from /dev/mem
190
191        Returns:
192            (mem map,..)(uint32_t..): Array of uint32_t in mapped memory
193        """
194
195        logging.debug(">>> Diag_Driver.mmap(addr=0x%08X, length=0x%08X)"
196                       % (addr, length))
197
198        try:
199            fd = os.open(DIAG_DRV_PATH, os.O_RDWR)
200            mem_map = mmap.mmap(fd, length, offset=addr,
201                                prot=mmap.PROT_READ | mmap.PROT_WRITE,
202                                flags=mmap.MAP_SHARED)
203
204            self._mem_map_list.append(mem_map)
205
206            # Array of uint8
207            mem = (c_uint8 * length).from_buffer(mem_map)
208        finally:
209            os.close(fd)
210
211        logging.debug("<<< Diag_Driver.mmap()")
212
213        return mem
214
215
216class Register:
217    def __init__(self, base_addr, offset, type=c_uint32):
218        self._type = type
219        self._obj = cast(byref(base_addr, offset), POINTER(type))
220
221    def __str__(self):
222        if self._type == c_uint8:
223            return "0x%02X" % self.value
224        elif self._type == c_uint16:
225            return "0x%04X" % self.value
226        elif self._type == c_uint32:
227            return "0x%08X" % self.value
228        elif self._type == c_uint64:
229            return "0x%08X %08X" % (
230                self.value >> 32,
231                self.value & 0xFFFFFFFF
232            )
233        else:
234            return "0x%08X (unknown type)" % self.value
235
236    @property
237    def value(self):
238        return self._obj.contents.value
239    @value.setter
240    def value(self, value):
241        self._obj[0] = value
242