├── COPYING.txt ├── Makefile ├── README.md ├── data ├── README.md ├── brom_info.csv ├── regs-asm1042a.yaml ├── regs-asm1142.yaml └── regs-asm2142.yaml ├── doc ├── Notes.md ├── Promontory-Notes.md ├── firmware-urls.txt ├── out │ ├── .gitignore │ └── README.md └── src │ └── index.adoc ├── monitor ├── .gitignore ├── Makefile ├── README.md ├── build_info.inc.c ├── gen_sfr_c.py ├── main.c ├── make_image.py ├── sfr.h ├── sfr.inc.c └── vectors.S └── tools ├── .gitignore ├── README.md ├── asm_fw.ksy ├── asm_tool.py ├── asmedia-xhc-trace ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── bug_demo.py ├── emulator └── README.md ├── extract_promontory_fw.py ├── generate_docs.py ├── generate_labels.py ├── ghidra-scripts └── AsmediaXhcFirmwareHelper.java ├── load_fw.py ├── prom_fw.ksy ├── validate_brom.py └── validate_fw.py /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | 3 | # Copyright (C) 2020-2021 Forest Crossman 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | KAITAI_STRUCT_COMPILER ?= kaitai-struct-compiler 20 | 21 | DOC_SOURCES := $(wildcard data/regs-*.yaml) 22 | DOC_TARGETS := $(DOC_SOURCES:data/%.yaml=doc/out/%.xhtml) 23 | DOC_TARGETS += doc/out/pm/index.html 24 | 25 | TOOL_TARGETS := tools/asm_fw.py tools/prom_fw.py 26 | 27 | 28 | all: $(TOOL_TARGETS) 29 | 30 | tools/%.py: tools/%.ksy 31 | $(KAITAI_STRUCT_COMPILER) --target=python --outdir=$(@D) $< 32 | 33 | doc/out/%.xhtml: data/%.yaml tools/generate_docs.py 34 | python3 tools/generate_docs.py -o $@ $< 35 | 36 | doc/out/pm/index.html: doc/src/index.adoc $(wildcard doc/src/*.adoc) 37 | asciidoctor --out-file $@ $< 38 | 39 | doc: $(DOC_TARGETS) 40 | 41 | clean: 42 | rm -f $(TOOL_TARGETS) $(DOC_TARGETS) 43 | 44 | 45 | .PHONY: clean doc 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASMedia xHC RE 2 | 3 | 4 | ## Quick start 5 | 6 | 7 | ### Software dependencies 8 | 9 | * Python 3 10 | * Firmware image parser: 11 | * [Kaitai Struct Compiler][ksc] 12 | * [Kaitai Struct Python Runtime][kspr] 13 | * Documentation generator: 14 | * [Asciidoctor][asciidoctor] 15 | * [lxml][lxml] 16 | * [Python-Markdown][python-markdown] 17 | * [PyYAML][pyyaml] 18 | 19 | 20 | ### Procedure 21 | 22 | 1. Install dependencies. 23 | 2. Run `make` to generate the parser code used by 24 | `tools/validate_fw.py`. 25 | 3. Obtain the firmware binaries you're interested in. You can download 26 | some installer executables from the links in [this file][urls]. 27 | They're self-extracting archives, so you can use the `7z` utility to 28 | extract the executable inside. Use `wine` to run the extracted 29 | executable (no need to finish the install), then grab the files it 30 | extracts from the Wine user's TEMP folder. 31 | 4. Explore the firmware with [the Kaitai Web IDE][ide] and the 32 | [Kaitai Struct definition file][ksy], or run `./tools/validate_fw.py` 33 | to check the integrity and version of the firmware. 34 | 5. Run `make doc` to generate XHTML documentation in [doc/out][doc]. 35 | 36 | 37 | ## Reverse engineering notes 38 | 39 | See [doc/Notes.md](doc/Notes.md). 40 | 41 | 42 | ## Project status 43 | 44 | Overall, maybe 30% of the chip's functionality is understood. Much more 45 | reverse engineering work is needed before work on a FOSS replacement firmware 46 | can begin. 47 | 48 | 49 | ### Detailed overview 50 | 51 | * It's possible to build and load arbitrary code for all ASMedia USB host 52 | controllers released before March 2023. 53 | * Almost all the parts of the firmware image that are read by the boot ROMs 54 | are understood completely. 55 | * The first 4 bytes appear to be the offset of the next image, but this 56 | hasn't been confirmed. 57 | * The bytes between the 16-byte header and the configuration words are 58 | never read by the boot ROM and have an unknown purpose. 59 | * Firmware images can be booted from flash (written using an external SPI 60 | flash programmer). 61 | * Some chips can have their code loaded directly by the host over PCIe, 62 | without having to write it to flash. 63 | * Only the ASM1042A, ASM1142, and ASM2142/ASM3142 are currently supported. 64 | * MMIO peripherals (ASM1142 only): 65 | * Timer is 100% understood (including errata). 66 | * UART is 99% understood. 67 | * There are some registers and bits in registers whose functionality is 68 | not entirely understood, but enough of the UART is understood well 69 | enough for most practical purposes. 70 | * For instance, bits 1-7 in `UART_LSR` have meanings that have been 71 | difficult to determine experimentally, but they aren't needed for simply 72 | sending and receiving data. 73 | * PCIe functionality is maybe only 40% understood. 74 | * Software-managed PCIe functionality is probably 80% understood. 75 | * There appear to be eight distinct operations, numbered 1 through 8. 76 | * Of those eight, only operations 1 (write Device Context entry), 3 77 | (write Event TRB), and 6 (DMA read) are understood completely. 78 | * Operations 7 and 8 appear to be Scratchpad memory write and read 79 | operations, respectively, but this hasn't been confirmed. 80 | * Operations 2, 4, and 5 have unknown functions. 81 | * Hardware-managed PCIe functionality is not understood at all. 82 | * It's unknown whether USB transfers all pass through buffers in SRAM or 83 | whether the data gets send straight to the host, bypassing any 84 | buffering. 85 | * There appear to be MMIO registers where buffer addresses are getting 86 | set, but it's difficult to know what each MMIO buffer pointer register 87 | is for exactly (e.g., what the buffer is storing, where the data comes 88 | from, etc.). 89 | * USB functionality is maybe only 5% understood. 90 | * Most XHCI Capability Registers and Operational Registers have been 91 | identified. 92 | * Registers used by the USB Debug Class have been identified. 93 | * It's unknown how to get the Debug Class functionality to actually 94 | work. Reverse engineering this functionality is further complicated by 95 | the fact that it doesn't appear to work with the stock firmware (at 96 | least with Linux as both the debug host and device--it's possible it 97 | may work correctly with Windows, but this has not been tested). 98 | * It's not yet known how device enumeration and large data transfers are 99 | performed. 100 | * SFR peripherals (ASM1142 only): 101 | * Most functionality is understood. 102 | * Some registers still need to be experimented with to finish reverse 103 | engineering their complete functionality, but most of them appear to be 104 | used simply for hardware-accelerated data movement and vector operations, 105 | so they're not strictly necessary. 106 | * Bits 0-2 of `HARD_COPY_CTRL`. 107 | * Bits 1-3 of `MISC_CTRL`. 108 | * Bits 0-2 of `0x8E` are R/W, but their purpose is not known. 109 | 110 | 111 | ## License 112 | 113 | Except where otherwise noted: 114 | 115 | * All software in this repository (e.g., the serial monitor and the scripts 116 | and tools to build it, tools for unpacking and generating firmware, tools 117 | for building documentation, etc.) is made available under the 118 | [GNU General Public License, version 3 or later][gpl]. 119 | * All copyrightable content that is not software (e.g., chip register and 120 | programming manuals, reverse engineering notes, this README file, etc.) is 121 | licensed under the 122 | [Creative Commons Attribution-ShareAlike 4.0 International License][cc-by-sa]. 123 | 124 | 125 | [ksc]: https://github.com/kaitai-io/kaitai_struct_compiler 126 | [kspr]: https://github.com/kaitai-io/kaitai_struct_python_runtime 127 | [asciidoctor]: https://asciidoctor.org/ 128 | [lxml]: https://lxml.de/ 129 | [python-markdown]: https://python-markdown.github.io/ 130 | [pyyaml]: https://pyyaml.org/ 131 | [urls]: doc/firmware-urls.txt 132 | [ide]: https://ide.kaitai.io/ 133 | [ksy]: tools/asm_fw.ksy 134 | [doc]: doc/out 135 | [gpl]: COPYING.txt 136 | [cc-by-sa]: https://creativecommons.org/licenses/by-sa/4.0/ 137 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Data 2 | 3 | `brom_info.csv` lists the known versions, sizes, and checksums of the boot ROMs 4 | (BROMs) of each chip. The "CRC-32 (Embedded)" values listed in the file are the 5 | CRC-32 values included in the BROMs themselves, while the cryptographic hashes 6 | are calculated over all the bytes in the BROMs--including the CRC-32 values. In 7 | other words, the CRC-32 is calculated over the first `Size - 4` bytes of each 8 | BROM, while the hashes are calculated over the first `Size` bytes. 9 | 10 | The `regs-asmXXXX.yaml` files contain the memory maps and register definitions 11 | for the chips. They are used as inputs into the documentation generator. 12 | 13 | To generate documentation, run `make doc` in the parent directory. 14 | -------------------------------------------------------------------------------- /data/brom_info.csv: -------------------------------------------------------------------------------- 1 | Chip,Version,Size (Bytes),CRC-32 (Embedded),MD5,SHA-1,SHA-256,SHA-512,BLAKE2b 2 | ASM1042,100803_00_00_0D,65536,0xB81A5BB3,5ed5334b6ce62c491b43492f0efbf367,48b0710c9d3bbddad2ba7723e389f4ca17f6155e,539e89bd5a31495d4fc5057cfd3b9fb4f62c90c27c7f7689abffc61b35f31cb9,746ac788f3d4514ea5c9e56ef34542965c273f7c579d762adcd5d6f3626887f89c3d1f5bab1c796d9a1dcce464db01d9bb081921fc7e4cbfceb30b78c38eeb3a,e748f025c3a976559b07982cc8b01853f88897fd57964da2d5350b95aa9594ca423fd02fbab060da6f0d37058577f12aca97dcd7328d683a4c8111b7915cad8b 3 | ASM1042A,120507_00_10_1B,65536,0x53D4C29D,3be9b74506b10e48ecaeac2022aa7051,e152f0e01706b88d17fc857a452e85a6d21b8f98,d5452369de00b82ae2e900a12840b2006ab3f6cb128ebd7528c4fbb282801ea2,ccd4760c71bb7aee9b59e06d54a697150c76797c994819c6339346a9898380f19fbc2ffb01fd0b3b3dc6f43a12063c70da86c56f01fc15e857ca6d638b544ec4,6f8ce9d9a56e939ba2246266bda53a6c251b608197304b6692eeb7734821fbd0d050085476d59f55d88d616e4ef733fd2fad98518a4c788069a4556e27bd71b8 4 | ASM1142,141029_30_02_00,65536,0x2283C056,5cda2cc75e5ce66e0dd07baecd41d896,a55cc116a1716d71c32f48e7ae4530ca369c4c14,25f82ecae891e4baf873dcbfcc5d21f1bca9734fe761af5d611e309a3ee088ff,9ac261069e7155a177d651a763a734ea165178ffcc824c83180ec79ca21bc5b4650868ead3aac003cd3df0193c4f607263e4b8ea0c3de63261fdb5a69e8c8f56,e960df754d5b2d953a042b92e874c38eabfae4a0bf246165333bdb00e695199da7464afb38007e04387a14bafb7cc99d8776163514b8fe6b27d40fb1678c1315 5 | ASM2142/ASM3142,160809_50_02_01,32768,0x648BA942,85b4b346e102809d6db18f3df2356a85,e84bc7fb3a27cd08bec3148fc3e86b40abf0edb6,ae9795fe2c9c41f047a1cd5058741987368cfd7e3c868849a203c70ad783680b,de3bd568c1608e4d672a2bdd219a145a33e6a83c25aa55dd6fe4988b09b6fa684f4fca6b83b0ad76485394b5cca0dd5631a126a853141f82db8177bfd5d00688,8b9ff8c4d934d3d0b91da5005c09995b80399e8d17a4f737fe7814f794f9454a886289ec6f324acc02f00430e28836b541bb24fb32f542a918acfd3e3b56efb3 6 | ASM3242,190829_83_02_00,32768,0x0DA16A08,78c3c1f757231b9177f498f97505f505,5bdb97c0da4c0a312acbf299b6fcd89c923fe8d0,9122d6b11415dc83eb3ab8a898c7554ca7f6252c646fc3dd12379dbd8fd254b7,0de90db5baeddc50285806a8511e6faf3edb00555d722d82cf767726c82dcd8be0bc1c866c487258add8fd3757c6c4658b4b4a62f4b3c302deb6f0f39b552367,0999be1365910c99e60683df762dc21c586e35a939957373f3dc6d1f1a323bd44c2e0e0384871e385025eaef0920bb45bf7b94a1429332f29e7e9633d11c018e 7 | ASM3283/Prom21,211126_A0_02_01,32768,0x9358A8E4,2690c42f19ab3356be9143ed77f5807c,aae4e1ee33844b4ce62cdb775344084436e36360,5c2adb8639d2d43129713291b5a4a8a0e5a126d3256886646bc95b0c89720f42,e73c3f3d1f90f1de1c6565ec206afc430a93f8f14bcb69540e2f2b669aced899514f9d4cb289c0357b5c9f33f299b9a412b347669e7757da85adf784f2b32888,04d914028c252e08da734fcbe9b5ada478159ce9a8d5a99e955f1fdf8c4d805c128f4f364487f17d6c66d705d7350a78e19b441cc81b94009b5744f363b1f4f3 8 | -------------------------------------------------------------------------------- /data/regs-asm1042a.yaml: -------------------------------------------------------------------------------- 1 | meta: 2 | chip: ASM1042A 3 | license: CC-BY-SA-4.0 4 | 5 | registers: 6 | pci: 7 | - name: MBOX_DOORBELL 8 | start: 0xE0 9 | end: 0xE0 10 | bits: 11 | - name: READ_ACK 12 | start: 0 13 | end: 0 14 | permissions: RW1C 15 | notes: | 16 | A flag that can be read, set, or cleared by the 8051, but can only 17 | be read or cleared by the host. Corresponds to 18 | `PCI_CONFIG_READ_ACK` in XDATA. 19 | - name: WRITE_START 20 | start: 1 21 | end: 1 22 | permissions: RW1S 23 | notes: | 24 | A flag that can be read, set, or cleared by the 8051, but can only 25 | be read or set by the host. Corresponds to `PCI_CONFIG_WRITE_START` 26 | in XDATA. 27 | - name: CODE_RAM_ADDR 28 | start: 0xE2 29 | end: 0xE3 30 | permissions: RW 31 | notes: | 32 | The next address in CODE RAM to be written to. Aligned on 2-byte 33 | boudaries (bit zero is permanently set to zero). Highest bit is always 34 | zero--the 32 kB bank to write to is selected by writing the data to 35 | either `CODE_RAM_DATA.LOWER_BANK_DATA` or 36 | `CODE_RAM_DATA.UPPER_BANK_DATA`. Increments by two on every write to 37 | `CODE_RAM_DATA.LOWER_BANK_DATA` or `CODE_RAM_DATA.UPPER_BANK_DATA`. 38 | - name: CODE_RAM_DATA 39 | start: 0xE4 40 | end: 0xE7 41 | bits: 42 | - name: LOWER_BANK_DATA 43 | start: 0 44 | end: 15 45 | permissions: WO 46 | notes: | 47 | The 16-bit little-endian data to write to `CODE_RAM_ADDR + 0x0000`. 48 | - name: UPPER_BANK_DATA 49 | start: 16 50 | end: 31 51 | permissions: WO 52 | notes: | 53 | The 16-bit little-endian data to write to `CODE_RAM_ADDR + 0x8000`. 54 | notes: | 55 | Bit 15 in the CODE RAM write address is cleared when writing to 56 | `LOWER_BANK_DATA` and set when writing to `UPPER_BANK_DATA`. After 57 | writing to either `LOWER_BANK_DATA` or `UPPER_BANK_DATA`, 58 | `CODE_RAM_ADDR` is increased by two. 59 | - name: CPU_STATUS 60 | start: 0xE4 61 | end: 0xE7 62 | bits: 63 | - name: PC 64 | start: 0 65 | end: 15 66 | permissions: RO 67 | notes: | 68 | The current 8051 program counter. 69 | - name: MODE 70 | start: 16 71 | end: 23 72 | permissions: RO 73 | notes: | 74 | The current value of `CPU_MODE_CURRENT`. See `CPU_MODE_CURRENT` for 75 | more information. 76 | - name: MMIO_ACCESS 77 | start: 0xE8 78 | end: 0xEB 79 | bits: 80 | - name: ADDRESS 81 | start: 0 82 | end: 15 83 | permissions: RW 84 | notes: | 85 | The address of the MMIO byte that you want to access. 86 | - name: WRITE_DATA 87 | start: 16 88 | end: 23 89 | permissions: RW 90 | notes: | 91 | The data you want to write to `ADDRESS`. 92 | - name: READ_DATA 93 | start: 24 94 | end: 31 95 | permissions: RO 96 | notes: | 97 | The byte read from `ADDRESS`. 98 | notes: | 99 | This register grants access to MMIO registers entirely in hardware, 100 | without involving the 8051 core. To read or write a register, first 101 | write the address to `ADDRESS`, then either write your data to 102 | `WRITE_DATA` or read it from `READ_DATA`. Please note that data is 103 | latched into `READ_DATA` only when writing to `ADDRESS`, so if you want 104 | to read a register you have just written to, you need to write the 105 | address to `ADDRESS` again before reading `READ_DATA`. Also note that 106 | this register can not be used to access XRAM--only MMIO access is 107 | possible. 108 | - name: CPU_CONTROL 109 | start: 0xEC 110 | end: 0xEF 111 | bits: 112 | - name: RESET 113 | start: 31 114 | end: 31 115 | permissions: RW 116 | notes: | 117 | Set to hold the 8051 and xHC in reset, clear to release from reset. 118 | - name: MBOX_D2H 119 | start: 0xF0 120 | end: 0xF7 121 | permissions: RO 122 | notes: | 123 | A mailbox register, from which data sent by the device can be read. 124 | - name: MBOX_H2D 125 | start: 0xF8 126 | end: 0xFF 127 | permissions: RW 128 | notes: | 129 | A mailbox register, to which data from the host can be written. 130 | 131 | xdata: 132 | - name: PCI_CONFIG_READ_ACK 133 | start: 0xF0D0 134 | end: 0xF0D0 135 | bits: 136 | - name: READ_ACK 137 | start: 0 138 | end: 0 139 | permissions: RW 140 | notes: | 141 | Corresponds to PCI configuration space register 0xE0 142 | (`MBOX_DOORBELL`), bit 0. 143 | - name: PCI_CONFIG_WRITE_START 144 | start: 0xF0D1 145 | end: 0xF0D1 146 | bits: 147 | - name: WRITE_START 148 | start: 0 149 | end: 0 150 | permissions: RW 151 | notes: | 152 | Corresponds to PCI configuration space register 0xE0 153 | (`MBOX_DOORBELL`), bit 1. 154 | - name: PCI_CONFIG_D2H0 155 | start: 0xF0D8 156 | end: 0xF0DB 157 | permissions: RW 158 | notes: | 159 | Corresponds to PCI configuration space register 0xF0 (`MBOX_D2H`). 160 | Writable by the device, RO for the host. 161 | - name: PCI_CONFIG_D2H1 162 | start: 0xF0DC 163 | end: 0xF0DF 164 | permissions: RW 165 | notes: | 166 | Corresponds to PCI configuration space register 0xF4 (`MBOX_D2H`). 167 | Writable by the device, RO for the host. 168 | - name: PCI_CONFIG_H2D0 169 | start: 0xF0E0 170 | end: 0xF0E3 171 | permissions: RO 172 | notes: | 173 | Corresponds to PCI configuration space register 0xF8 (`MBOX_H2D`). 174 | Writable by the host, RO for the device. 175 | - name: PCI_CONFIG_H2D1 176 | start: 0xF0E4 177 | end: 0xF0E7 178 | permissions: RO 179 | notes: | 180 | Corresponds to PCI configuration space register 0xFC (`MBOX_H2D`). 181 | Writable by the host, RO for the device. 182 | - name: UART_RBR 183 | start: 0xF100 184 | end: 0xF100 185 | permissions: RO 186 | notes: | 187 | UART Receive Buffer Register. 188 | - name: UART_THR 189 | start: 0xF101 190 | end: 0xF101 191 | permissions: RW 192 | notes: | 193 | UART Transmit Hold Register. 194 | - name: UART_RFBR 195 | start: 0xF105 196 | end: 0xF105 197 | permissions: RO 198 | notes: | 199 | UART RX FIFO Bytes Received. Indicates the number of bytes in the RX 200 | FIFO available for reading, and decreases by 1 each time `UART_RBR` is 201 | read. 202 | - name: UART_TFBF 203 | start: 0xF106 204 | end: 0xF106 205 | permissions: RO 206 | notes: | 207 | UART TX FIFO Bytes Free. Indicates the number of bytes available in 208 | the TX FIFO. Decreases by 1 each time `UART_THR` is written to, and 209 | increases by 1 for each byte that gets transmitted. 210 | - name: UART_LCR 211 | start: 0xF107 212 | end: 0xF107 213 | bits: 214 | - name: DATA_BITS 215 | start: 0 216 | end: 1 217 | permissions: RW 218 | notes: | 219 | The number of data bits per character. 220 | 221 | * 0: 5 bits 222 | * 1: 6 bits 223 | * 2: 7 bits 224 | * 3: 8 bits 225 | - name: STOP_BITS 226 | start: 2 227 | end: 2 228 | permissions: RW 229 | notes: | 230 | The number of stop bits per character. 231 | 232 | * 0: 1 stop bit 233 | * 1: 2 stop bits 234 | 235 | Unlike 8250-compatible UARTs, when this bit is set, 5-bit 236 | characters will have two stop bits, not 1.5. 237 | - name: PARITY 238 | start: 3 239 | end: 5 240 | permissions: RW 241 | notes: | 242 | The parity bit setting. 243 | 244 | * 0bXX0: None 245 | * 0b001: Odd 246 | * 0b011: Even 247 | * 0b101: Mark 248 | * 0b111: Space 249 | notes: | 250 | UART Line Control Register. The reset value is 0x0B (mode 8O1), so if 251 | you want to use mode 8N1, you need to set this to 0x03. 252 | - name: UART_DIV 253 | start: 0xF109 254 | end: 0xF10A 255 | permissions: RW 256 | notes: | 257 | UART clock divisor. The baudrate of the UART is equal to 258 | `125 MHz / UART_DIV`. The UART clock is not affected by the CPU clock 259 | divider--the base frequency is always 125 MHz. 260 | - name: CPU_MODE_NEXT 261 | start: 0xF340 262 | end: 0xF340 263 | bits: 264 | - name: CODE_LOCATION 265 | start: 0 266 | end: 0 267 | permissions: RW 268 | notes: | 269 | Set this bit to boot from CODE RAM, clear to boot from CODE ROM. 270 | - name: CLOCK_DIV 271 | start: 1 272 | end: 1 273 | permissions: RW 274 | notes: | 275 | Set this bit to divide the CPU clock frequency by two. This affects 276 | both the CPU and the timer peripheral, as the timer clock is derived 277 | from the CPU clock. Set this bit to set the CPU clock to 62.5 MHz 278 | and the timer clock to approximately ??? kHz. Clear this bit to 279 | use the undivided CPU clock speed of 125 MHz and timer clock 280 | speed of approximately ??? kHz. 281 | notes: | 282 | The settings in this register will take effect and be latched into 283 | `CPU_MODE_CURRENT` at the next reset. 284 | - name: CPU_MODE_CURRENT 285 | start: 0xF341 286 | end: 0xF341 287 | bits: 288 | - name: CODE_LOCATION 289 | start: 0 290 | end: 0 291 | permissions: RO 292 | notes: | 293 | This bit is set if the current code is executing from CODE RAM, 294 | and it's clear if executing from CODE ROM. This bit will always be 295 | set when our code is running, since our code can only ever be 296 | written to CODE RAM as CODE ROM is read-only. 297 | - name: CLOCK_DIV 298 | start: 1 299 | end: 1 300 | permissions: RO 301 | notes: | 302 | If set, this bit indicates that the CPU clock is being divided by 303 | two. If it's clear, the CPU clock is not being divided by two. See 304 | `CPU_MODE_NEXT.CLOCK_DIV` for more information. 305 | notes: | 306 | The value of this register is latched from `CPU_MODE_NEXT` on reset, 307 | and indicates the settings for the currently-executing code. 308 | - name: CPU_EXEC_CTRL 309 | start: 0xF342 310 | end: 0xF342 311 | bits: 312 | - name: RESET 313 | start: 0 314 | end: 0 315 | permissions: RW 316 | notes: | 317 | Set this bit to reset the CPU. 318 | - name: HALT 319 | start: 1 320 | end: 1 321 | permissions: RW 322 | notes: | 323 | Set this bit to halt the CPU and hold it in reset. Since the CPU 324 | can't un-halt itself, the only way to recover from this state is to 325 | use the `MMIO_ACCESS` register in PCI config space to clear this 326 | bit. 327 | - name: CPU_MISC 328 | start: 0xF343 329 | end: 0xF343 330 | bits: 331 | - name: CODE_RAM_WRITE_ENABLE 332 | start: 1 333 | end: 1 334 | permissions: RW 335 | notes: | 336 | Set this bit to enable the host to write to CODE RAM using the 337 | `CODE_RAM_ADDR` and `CODE_RAM_DATA` PCI config space registers. 338 | - name: CHIP_VERSION 339 | start: 0xF38C 340 | end: 0xF38C 341 | permissions: RO 342 | notes: | 343 | Some hardware version number that is set during the manufacturing 344 | process. Presumably, this represents the silicon die revision. 345 | - name: PCI_CONFIG_SVID_SSID 346 | start: 0xF390 347 | end: 0xF393 348 | bits: 349 | - name: SUBSYSTEM_VID 350 | start: 0 351 | end: 15 352 | permissions: RW 353 | notes: | 354 | PCI Subsystem Vendor ID. 355 | - name: SUBSYSTEM_ID 356 | start: 16 357 | end: 31 358 | permissions: RW 359 | notes: | 360 | PCI Subsystem ID. 361 | notes: | 362 | The Subsystem Vendor ID and Subsystem ID register in PCI configuration 363 | space (offset 0x2C). 364 | - name: PCI_CONFIG_VID_DID 365 | start: 0xF3E0 366 | end: 0xF3E3 367 | bits: 368 | - name: VENDOR_ID 369 | start: 0 370 | end: 15 371 | permissions: RW 372 | notes: | 373 | PCI Vendor ID. 374 | - name: DEVICE_ID 375 | start: 16 376 | end: 31 377 | permissions: RW 378 | notes: | 379 | PCI Device ID. 380 | notes: | 381 | The Vendor ID and Device ID register in PCI configuration space 382 | (offset 0x00). 383 | - name: PCI_CONFIG_CLASS_REV 384 | start: 0xF3E4 385 | end: 0xF3E7 386 | bits: 387 | - name: REVISION_ID 388 | start: 0 389 | end: 7 390 | permissions: RW 391 | notes: | 392 | PCI Revision ID. 393 | - name: CLASS_CODE 394 | start: 8 395 | end: 31 396 | permissions: RW 397 | notes: | 398 | PCI Class Code. 399 | notes: | 400 | The Class Code and Revision ID register in PCI configuration space 401 | (offset 0x08). 402 | -------------------------------------------------------------------------------- /data/regs-asm2142.yaml: -------------------------------------------------------------------------------- 1 | meta: 2 | chip: ASM2142/ASM3142 3 | license: CC-BY-SA-4.0 4 | 5 | xdata: 6 | - name: XRAM 7 | start: 0x00000 8 | end: 0x0BFFF 9 | permissions: RW 10 | notes: | 11 | 48 kB of data memory, used primarily for DMA buffers between the USB and 12 | PCIe controllers, but also for the firmware's temporary data storage. 13 | - name: N/A 14 | start: 0x0C000 15 | end: 0x0FFFF 16 | permissions: RO 17 | notes: | 18 | 16 kB of unused address space. 19 | - name: MMIO 20 | start: 0x10000 21 | end: 0x1FFFF 22 | permissions: RW 23 | notes: | 24 | 64 kB of memory-mapped peripheral address space. 25 | 26 | registers: 27 | pci: 28 | - name: MBOX_DOORBELL 29 | start: 0xE0 30 | end: 0xE0 31 | bits: 32 | - name: READ_ACK 33 | start: 0 34 | end: 0 35 | permissions: RW1C 36 | notes: | 37 | A flag that can be read, set, or cleared by the 8051, but can only 38 | be read or cleared by the host. Corresponds to 39 | `PCI_CONFIG_READ_ACK` in XDATA. 40 | - name: WRITE_START 41 | start: 1 42 | end: 1 43 | permissions: RW1S 44 | notes: | 45 | A flag that can be read, set, or cleared by the 8051, but can only 46 | be read or set by the host. Corresponds to `PCI_CONFIG_WRITE_START` 47 | in XDATA. 48 | - name: CODE_RAM_ADDR 49 | start: 0xE2 50 | end: 0xE3 51 | permissions: RW 52 | notes: | 53 | The next address in CODE RAM to be written to or read from. Aligned on 54 | 2-byte boudaries (bit zero is permanently set to zero). 55 | 56 | When writing, the 32 kB bank to write to is selected by writing the data 57 | to either `CODE_RAM_WRITE_DATA.LOWER_BANK_DATA` or 58 | `CODE_RAM_WRITE_DATA.UPPER_BANK_DATA`, so bit 15 in this register 59 | actually maps to bit 16 in the actual address. i.e., the real address is 60 | `((CODE_RAM_ADDR & 0x8000) << 1) | (0x8000 if CODE_RAM_WRITE_DATA.UPPER_BANK_DATA else 0x0000) | (CODE_RAM_ADDR & 0x7FFE)`. 61 | The address increments by two on every write to `CODE_RAM_WRITE_DATA`. 62 | 63 | When reading, the 16 kB bank to read from is selected by reading the 64 | data from one (or all) of the `BANK0_DATA..BANK3_DATA` registers in 65 | `CODE_RAM_READ_DATA`. Bit 15 in this register maps to bit 16 in the 66 | actual address, so the final address is 67 | `(((CODE_RAM_ADDR & 0x8000) << 1) | (CODE_RAM_ADDR & 0x7FFE)) + ((the bank index) << 14)`. 68 | - name: CPU_MODE 69 | start: 0xE6 70 | end: 0xE6 71 | permissions: RO 72 | notes: | 73 | The current value of `CPU_MODE_CURRENT`. See `CPU_MODE_CURRENT` for more 74 | information. 75 | - name: CODE_ACCESS 76 | start: 0xEF 77 | end: 0xEF 78 | bits: 79 | - name: READ_N_WRITE 80 | start: 6 81 | end: 6 82 | permissions: RW 83 | notes: | 84 | When the `ENABLE_CODE_ACCESS` bit is set: 85 | 86 | - Clear this bit to enable writing to CODE RAM through the 87 | `CODE_RAM_WRITE_DATA` register and disable reading from CODE RAM 88 | through the `CODE_RAM_READ_DATA` register. 89 | - Set this bit to disable writing to CODE RAM through the 90 | `CODE_RAM_WRITE_DATA` register and enable reading from CODE RAM 91 | through the `CODE_RAM_READ_DATA` register. 92 | - name: ENABLE_CODE_ACCESS 93 | start: 7 94 | end: 7 95 | permissions: RW 96 | notes: | 97 | Set to enable access to CODE RAM and hold the 8051 and xHC in reset. 98 | Clear to disable access to CODE RAM and release the 8051 and xHC 99 | from reset. The type of access granted depends on the state of the 100 | `READ_N_WRITE` bit. 101 | - name: MBOX_D2H 102 | start: 0xF0 103 | end: 0xF7 104 | permissions: RO 105 | notes: | 106 | A mailbox register, from which data sent by the device can be read. 107 | - name: MBOX_H2D 108 | start: 0xF8 109 | end: 0xFF 110 | permissions: RW 111 | notes: | 112 | A mailbox register, to which data from the host can be written. 113 | 114 | bar0: 115 | - name: MMIO_ACCESS_ADDRESS 116 | start: 0x3000 117 | end: 0x3001 118 | permissions: RW 119 | notes: | 120 | The lowest 16 bits of the address of the MMIO byte that you want to 121 | access. 122 | - name: MMIO_ACCESS_WRITE_DATA 123 | start: 0x3004 124 | end: 0x3004 125 | permissions: RW 126 | notes: | 127 | The data you want to write to `MMIO_ACCESS_ADDRESS`. 128 | - name: MMIO_ACCESS_READ_DATA 129 | start: 0x3008 130 | end: 0x3008 131 | permissions: RO 132 | notes: | 133 | The byte read from `MMIO_ACCESS_ADDRESS`. 134 | - name: CPU_PC 135 | start: 0x300A 136 | end: 0x300B 137 | permissions: RO 138 | notes: | 139 | The current 8051 program counter. 140 | - name: CODE_RAM_WRITE_DATA 141 | start: 0x3010 142 | end: 0x3013 143 | bits: 144 | - name: LOWER_BANK_DATA 145 | start: 0 146 | end: 15 147 | permissions: WO 148 | notes: | 149 | The 16-bit little-endian data to write to `CODE_RAM_ADDR + 0x0000`. 150 | - name: UPPER_BANK_DATA 151 | start: 16 152 | end: 31 153 | permissions: WO 154 | notes: | 155 | The 16-bit little-endian data to write to `CODE_RAM_ADDR + 0x8000`. 156 | notes: | 157 | Bit 15 in the CODE RAM write address is cleared when writing to 158 | `LOWER_BANK_DATA` and set when writing to `UPPER_BANK_DATA`. After 159 | writing to either `LOWER_BANK_DATA` or `UPPER_BANK_DATA`, 160 | `CODE_RAM_ADDR` is increased by two. 161 | - name: CODE_RAM_READ_DATA 162 | start: 0x3018 163 | end: 0x301F 164 | bits: 165 | - name: BANK0_DATA 166 | start: 0 167 | end: 15 168 | permissions: RO 169 | notes: | 170 | The 16-bit little-endian data to read from `CODE_RAM_ADDR + 0x0000`. 171 | - name: BANK2_DATA 172 | start: 16 173 | end: 31 174 | permissions: RO 175 | notes: | 176 | The 16-bit little-endian data to read from `CODE_RAM_ADDR + 0x8000`. 177 | - name: BANK1_DATA 178 | start: 32 179 | end: 47 180 | permissions: RO 181 | notes: | 182 | The 16-bit little-endian data to read from `CODE_RAM_ADDR + 0x4000`. 183 | - name: BANK3_DATA 184 | start: 48 185 | end: 63 186 | permissions: RO 187 | notes: | 188 | The 16-bit little-endian data to read from `CODE_RAM_ADDR + 0xC000`. 189 | notes: | 190 | Bits 14 and 15 in the CODE RAM read address are set or cleared depending 191 | which bank data register is being accessed. Reading from `BANK0_DATA` 192 | sets both of those bits to 0, reading from `BANK1_DATA` sets bit 14 to 1 193 | and 15 to 0, reading from `BANK2_DATA` sets bit 14 to 0 and 15 to 1, and 194 | reading from `BANK3_DATA` sets both bits 14 and 15 to 1. 195 | 196 | xdata: 197 | - name: PCI_CONFIG_C4 198 | start: 0x10030 199 | end: 0x10033 200 | permissions: RW 201 | notes: | 202 | Corresponds to PCI configuration space register 0xC4. 203 | - name: PCI_CONFIG_SVID_SSID 204 | start: 0x10050 205 | end: 0x10053 206 | bits: 207 | - name: SUBSYSTEM_VID 208 | start: 0 209 | end: 15 210 | permissions: RW 211 | notes: | 212 | PCI Subsystem Vendor ID. 213 | - name: SUBSYSTEM_ID 214 | start: 16 215 | end: 31 216 | permissions: RW 217 | notes: | 218 | PCI Subsystem ID. 219 | notes: | 220 | The Subsystem Vendor ID and Subsystem ID register in PCI configuration 221 | space (offset 0x2C). 222 | - name: CPU_MISC 223 | start: 0x1500E 224 | end: 0x1500E 225 | bits: 226 | - name: CODE_RAM_WRITE_ENABLE 227 | start: 0 228 | end: 0 229 | permissions: RW 230 | notes: | 231 | Set this bit to enable the host to write to CODE RAM using the 232 | `CODE_RAM_ADDR` PCI config space register and the 233 | `CODE_RAM_WRITE_DATA` PCI BAR0 register. 234 | - name: CPU_MODE_NEXT 235 | start: 0x15040 236 | end: 0x15040 237 | bits: 238 | - name: CODE_LOCATION 239 | start: 0 240 | end: 0 241 | permissions: RW 242 | notes: | 243 | Set this bit to boot from CODE RAM, clear to boot from CODE ROM. 244 | - name: CLOCK_DIV 245 | start: 1 246 | end: 1 247 | permissions: RW 248 | notes: | 249 | Set this bit to divide the CPU clock frequency by two. This affects 250 | both the CPU and the timer peripheral, as the timer clock is derived 251 | from the CPU clock. Set this bit to set the CPU clock to 78.125 MHz 252 | and the timer clock to approximately 9.537 kHz. Clear this bit to 253 | use the undivided CPU clock speed of 156.25 MHz and timer clock 254 | speed of approximately 19.073 kHz. 255 | notes: | 256 | The settings in this register will take effect and be latched into 257 | `CPU_MODE_CURRENT` at the next reset. 258 | - name: CPU_MODE_CURRENT 259 | start: 0x15041 260 | end: 0x15041 261 | bits: 262 | - name: CODE_LOCATION 263 | start: 0 264 | end: 0 265 | permissions: RO 266 | notes: | 267 | This bit is set if the current code is executing from CODE RAM, 268 | and it's clear if executing from CODE ROM. This bit will always be 269 | set when our code is running, since our code can only ever be 270 | written to CODE RAM as CODE ROM is read-only. 271 | - name: CLOCK_DIV 272 | start: 1 273 | end: 1 274 | permissions: RO 275 | notes: | 276 | If set, this bit indicates that the CPU clock is being divided by 277 | two. If it's clear, the CPU clock is not being divided by two. See 278 | `CPU_MODE_NEXT.CLOCK_DIV` for more information. 279 | notes: | 280 | The value of this register is latched from `CPU_MODE_NEXT` on reset, 281 | and indicates the settings for the currently-executing code. 282 | - name: CPU_EXEC_CTRL 283 | start: 0x15042 284 | end: 0x15042 285 | bits: 286 | - name: RESET 287 | start: 0 288 | end: 0 289 | permissions: RW 290 | notes: | 291 | Set this bit to reset the CPU. 292 | - name: HALT 293 | start: 1 294 | end: 1 295 | permissions: RW 296 | notes: | 297 | Set this bit to halt the CPU and hold it in reset. Since the CPU 298 | can't un-halt itself, the only way to recover from this state is to 299 | use the `MMIO_ACCESS_ADDRESS`/`MMIO_ACCESS_WRITE_DATA` registers in 300 | PCI BAR0 space to clear this bit. 301 | - name: HARDWARE_ACCESS 302 | start: 0x15087 303 | end: 0x15087 304 | bits: 305 | - name: MMIO_ACCESS_ENABLE 306 | start: 4 307 | end: 4 308 | permissions: RW 309 | notes: | 310 | Set this bit to enable access to MMIO in XDATA via the 311 | `MMIO_ACCESS_ADDRESS` / `MMIO_ACCESS_WRITE_DATA` / 312 | `MMIO_ACCESS_READ_DATA` registers in BAR0. Clear this bit to disable 313 | this access. 314 | - name: CHIP_VERSION 315 | start: 0x150B2 316 | end: 0x150B2 317 | permissions: RO 318 | notes: | 319 | Some hardware version number that is set during the manufacturing 320 | process. Presumably, this represents the silicon die revision. 321 | - name: UART_RBR 322 | start: 0x15100 323 | end: 0x15100 324 | permissions: RO 325 | notes: | 326 | UART Receive Buffer Register. 327 | - name: UART_THR 328 | start: 0x15101 329 | end: 0x15101 330 | permissions: RW 331 | notes: | 332 | UART Transmit Hold Register. 333 | - name: UART_RFBR 334 | start: 0x15105 335 | end: 0x15105 336 | permissions: RO 337 | notes: | 338 | UART RX FIFO Bytes Received. Indicates the number of bytes in the RX 339 | FIFO available for reading, and decreases by 1 each time `UART_RBR` is 340 | read. 341 | - name: UART_TFBF 342 | start: 0x15106 343 | end: 0x15106 344 | permissions: RO 345 | notes: | 346 | UART TX FIFO Bytes Free. Indicates the number of bytes available in 347 | the TX FIFO. Decreases by 1 each time `UART_THR` is written to, and 348 | increases by 1 for each byte that gets transmitted. 349 | - name: UART_LCR 350 | start: 0x15107 351 | end: 0x15107 352 | bits: 353 | - name: DATA_BITS 354 | start: 0 355 | end: 1 356 | permissions: RW 357 | notes: | 358 | The number of data bits per character. 359 | 360 | * 0: 5 bits 361 | * 1: 6 bits 362 | * 2: 7 bits 363 | * 3: 8 bits 364 | - name: STOP_BITS 365 | start: 2 366 | end: 2 367 | permissions: RW 368 | notes: | 369 | The number of stop bits per character. 370 | 371 | * 0: 1 stop bit 372 | * 1: 2 stop bits 373 | 374 | Unlike 8250-compatible UARTs, when this bit is set, 5-bit 375 | characters will have two stop bits, not 1.5. 376 | - name: PARITY 377 | start: 3 378 | end: 5 379 | permissions: RW 380 | notes: | 381 | The parity bit setting. 382 | 383 | * 0bXX0: None 384 | * 0b001: Odd 385 | * 0b011: Even 386 | * 0b101: Mark 387 | * 0b111: Space 388 | notes: | 389 | UART Line Control Register. The reset value is 0x0B (mode 8O1), so if 390 | you want to use mode 8N1, you need to set this to 0x03. 391 | - name: UART_DIV 392 | start: 0x15109 393 | end: 0x1510A 394 | permissions: RW 395 | notes: | 396 | UART clock divisor. The baudrate of the UART is equal to 397 | `156.25 MHz / UART_DIV`. The UART clock is not affected by the CPU clock 398 | divider--the base frequency is always 156.25 MHz. 399 | - name: PCI_CONFIG_READ_ACK 400 | start: 0x18140 401 | end: 0x18140 402 | bits: 403 | - name: READ_ACK 404 | start: 0 405 | end: 0 406 | permissions: RW 407 | notes: | 408 | Corresponds to PCI configuration space register 0xE0 409 | (`MBOX_DOORBELL`), bit 0. 410 | - name: PCI_CONFIG_WRITE_START 411 | start: 0x18141 412 | end: 0x18141 413 | bits: 414 | - name: WRITE_START 415 | start: 0 416 | end: 0 417 | permissions: RW 418 | notes: | 419 | Corresponds to PCI configuration space register 0xE0 420 | (`MBOX_DOORBELL`), bit 1. 421 | - name: PCI_CONFIG_D2H0 422 | start: 0x18148 423 | end: 0x1814B 424 | permissions: RW 425 | notes: | 426 | Corresponds to PCI configuration space register 0xF0 (`MBOX_D2H`). 427 | Writable by the device, RO for the host. 428 | - name: PCI_CONFIG_D2H1 429 | start: 0x1814C 430 | end: 0x1814F 431 | permissions: RW 432 | notes: | 433 | Corresponds to PCI configuration space register 0xF4 (`MBOX_D2H`). 434 | Writable by the device, RO for the host. 435 | - name: PCI_CONFIG_H2D0 436 | start: 0x18150 437 | end: 0x18153 438 | permissions: RO 439 | notes: | 440 | Corresponds to PCI configuration space register 0xF8 (`MBOX_H2D`). 441 | Writable by the host, RO for the device. 442 | - name: PCI_CONFIG_H2D1 443 | start: 0x18154 444 | end: 0x18157 445 | permissions: RO 446 | notes: | 447 | Corresponds to PCI configuration space register 0xFC (`MBOX_H2D`). 448 | Writable by the host, RO for the device. 449 | - name: PCI_CONFIG_VID_DID 450 | start: 0x18550 451 | end: 0x18553 452 | bits: 453 | - name: VENDOR_ID 454 | start: 0 455 | end: 15 456 | permissions: RW 457 | notes: | 458 | PCI Vendor ID. 459 | - name: DEVICE_ID 460 | start: 16 461 | end: 31 462 | permissions: RW 463 | notes: | 464 | PCI Device ID. 465 | notes: | 466 | The Vendor ID and Device ID register in PCI configuration space 467 | (offset 0x00). 468 | - name: PCI_CONFIG_CLASS_REV 469 | start: 0x18554 470 | end: 0x18557 471 | bits: 472 | - name: REVISION_ID 473 | start: 0 474 | end: 7 475 | permissions: RW 476 | notes: | 477 | PCI Revision ID. 478 | - name: CLASS_CODE 479 | start: 8 480 | end: 31 481 | permissions: RW 482 | notes: | 483 | PCI Class Code. 484 | notes: | 485 | The Class Code and Revision ID register in PCI configuration space 486 | (offset 0x08). 487 | - name: XHCI_HCCPARAMS1 488 | start: 0x18810 489 | end: 0x18813 490 | permissions: RW 491 | notes: | 492 | Host Controller Capability Parameters 1. See the xHCI specification 493 | for details. 494 | - name: XHCI_CRCR 495 | start: 0x18838 496 | end: 0x1883F 497 | permissions: RO 498 | notes: | 499 | Command Ring Control Register. See the xHCI specification for details. 500 | - name: XHCI_DCBAAP 501 | start: 0x18850 502 | end: 0x18857 503 | permissions: RO 504 | notes: | 505 | Device Context Base Address Array Pointer Register. See the xHCI 506 | specification for details. 507 | -------------------------------------------------------------------------------- /doc/Notes.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | 4 | ## Miscellaneous 5 | 6 | - CPU 7 | - Compatible with the MCS-51 (8051) instruction set. 8 | - One clock cycle per machine cycle ("1T"). 9 | - Instruction cycle counts match the STCmicro STC15 series with the STC-Y5 10 | 8051 core, with the exception of the MOVX instructions, which each seem 11 | to take between 2 and 5 clock cycles. See the instruction set summary 12 | starting on page 340 of [this PDF][stc] for a list of instructions and 13 | their cycle counts. 14 | - Operating frequency (high/low): 15 | - ASM1042, ASM1042A: 125 MHz/62.5 MHz 16 | - ASM1142, ASM2142/ASM3142, ASM3242: 156.25 MHz/78.125 MHz 17 | - IRAM size: 256 bytes 18 | - PMEM/CODE size: 19 | - ASM1042, ASM1042A, ASM1142: 64 kB 20 | - ASM2142/ASM3142: 96 kB (48 kB common bank + 3 × 16 kB banks) 21 | - ASM3242: 112 kB (48 kB common bank + 4 × 16 kB banks) 22 | - XDATA (XRAM + MMIO) size: 23 | - ASM1042, ASM1042A, ASM1142: 64 kB 24 | - ASM2142/ASM3142, ASM3242: 128 kB (2 × 64 kB banks) 25 | - Bank-switching (ASM2142/ASM3142 and ASM3242 only): 26 | - `DPX` (SFR 0x93) is used as an extra data pointer byte for the `MOVX` 27 | instruction. Practically, however, because XDATA addresses are only 17 28 | bits wide only the lowest bit of `DPX` is used. 29 | - The lowest two bits of `PSBANK`/`FMAP` (SFR 0x96) are used to switch 30 | between code banks. The common bank is 48 kB in size and is accessible 31 | from 0x0000 to 0xBFFF regardless of the current value of 32 | `PSBANK`/`FMAP`. Banks 0-3 are each 16 kB in size and are located in 33 | physical code RAM at `0xC000 + 0x4000 * BANK`, where `BANK` is the index 34 | of the bank. All four banks are mapped at 0xC000 in PMEM/CODE space. 35 | - UART 36 | - 3V3 37 | - 921600 8N1 38 | - Pins: 39 | - ASM1042, ASM1042A (QFN-64) 40 | - RX: IC pin 14 41 | - TX: IC pin 15 42 | - ASM1042AE, ASM1142, ASM2142/ASM3142, ASM3042 43 | - RX: IC pin 10 44 | - TX: IC pin 11 45 | - ASM3242 46 | - RX: IC pin 15 47 | - TX: IC pin 16 48 | - Not much gets printed here, and the text that does isn't 49 | particularly useful. 50 | - Debugging 51 | - ASM1042A, ASM1142, ASM2142/ASM3142, ASM3242 52 | - The 8051 program counter can be read by the host over PCIe. 53 | - The 8051 MMIO registers (in XDATA) can be read from or written to by the 54 | host over PCIe. 55 | - The 8051 code RAM can be written to by the host over PCIe. 56 | - ASM2142/ASM3142, ASM3242 57 | - The 8051 code RAM can be read from by the host over PCIe. 58 | 59 | 60 | ## Feature comparison 61 | 62 | | IC | PCI VID:DID | USB 3 Ports × Generation × Lanes | PCIe Version × Lanes | IC Package | 63 | | --- | --- | --- | --- | --- | 64 | | [ASM1042][ASM1042] | 1b21:1042 | 2× Gen 1×1 | PCIe 2.x ×1 | QFN-64 | 65 | | [ASM1042A][ASM1042A] | 1b21:1142 | 2× Gen 1×1 | PCIe 2.x ×1 | QFN-64 / QFN-48 | 66 | | [ASM1042AE][ASM1042AE] | 1b21:1142 | 2× Gen 1×1 | PCIe 2.x ×1 | QFN-64 | 67 | | [ASM1142][ASM1142] | 1b21:1242 | 2× Gen 2×1 | PCIe 2.x ×2 / PCIe 3.x ×1 | QFN-64 | 68 | | [ASM2142][ASM2142] | 1b21:2142 | 2× Gen 2×1 | PCIe 3.x ×2 | QFN-64 | 69 | | [ASM3142][ASM3142] | 1b21:2142 | 2× Gen 2×1 | PCIe 3.x ×2 | QFN-64 | 70 | | [ASM3242][ASM3242] | 1b21:3242 | 1× Gen 2×2 | PCIe 3.x ×4 | QFN-88 | 71 | 72 | 73 | ## Hardware 74 | 75 | 76 | ### ORICO PE20-1C (ASM3242) 77 | 78 | - Connectors 79 | - J7 80 | - 1: NC 81 | - Can be pulled up to 3.3V by populating restistors R54 and R351. 82 | - 2: GND 83 | - 3: 4.7k pull-up to 3.3V 84 | - Connected to ASM3242 pin 38 and unpopulated ASM1543 (U17) pin 27 85 | (`STATUS_IND2`) by unpopulated resistor R353. 86 | - 4: 4.7k pull-up to 3.3V 87 | - Connected to ASM3242 pin 37 and unpopulated ASM1543 (U17) pin 26 88 | (`STATUS_IND1`) by unpopulated resistor R352. 89 | - J4 90 | - 1: NC 91 | - Can be pulled up to 3.3V by populating restistors R54 and R85. 92 | - 2: GND 93 | - 3: TX 94 | - Connected to ASM3242 pin 16. 95 | - 4: RX - 1k pull-up to 3.3V 96 | - Connected to ASM3242 pin 15. 97 | - J1 98 | - 4: NC 99 | - 3: NC 100 | - 2: GND 101 | - 1: GND 102 | - LEDs 103 | - LED1 104 | - Power. 105 | - LED2 106 | - Cable connected. 107 | - LED4 (unpopulated) 108 | - Connected to ASM3242 pin 17. 109 | - Signal is also available on one of the three testpoints in a cluster 110 | near the ASM3242. 111 | - LED5 (unpopulated) 112 | - Connected to ASM3242 pin 18. 113 | - Signal is also available on one of the three testpoints in a cluster 114 | near the ASM3242. 115 | - LED6 (unpopulated) 116 | - Connected to ASM3242 pin 19. 117 | - Signal is also available on one of the three testpoints in a cluster 118 | near the ASM3242. 119 | - LED7 (unpopulated) 120 | - Connected to ASM3242 pin 39 by unpopulated resistor R357. 121 | - LED8 (unpopulated) 122 | - Connected to ASM3242 pin 43 by resistor R359. 123 | - LED9 (unpopulated) 124 | - Connected to ASM3242 pin 44 by resistor R360. 125 | - Oscillators: 126 | - X1: 20 MHz 127 | 128 | 129 | ### IOCrest IO-PCE3242-1C (ASM3242) 130 | 131 | - Voltage rails 132 | - 3V3 (I/O) 133 | - 2V5 (Analog?) 134 | - 1V05 suspend (SRAM?) 135 | - 1V05 (Core?) 136 | - Components 137 | - ICs: 138 | - U6: [TD6817][td6817] 1.5MHz 2A Synchronous Step-Down Regulator Dropout 139 | - Capacitors: 140 | - C69: 10 pF, 10% (measured value: 11 pF) 141 | - C72: 100 nF, 20% (measured value: 123 nF) 142 | - Inductors: 143 | - L2: ??? H, 0.2 Ω 144 | - Resistors: 145 | - R44: 100 kΩ 146 | - R47: 75 kΩ (measured value: 74.4 kΩ) 147 | - Oscillators: 148 | - X1: 20 MHz 149 | - Connectors 150 | - J1 (pin 1 is the one closest to the ASM3242 IC) 151 | - 1: 3V3 152 | - 2: GND 153 | - 3: TX 154 | - Connected to ASM3242 pin 16. 155 | - 4: RX 156 | - Connected to ASM3242 pin 15. 157 | 158 | 159 | [stc]: https://web.archive.org/web/20200305112930/http://stcmicro.com/datasheet/STC15F2K60S2-en.pdf 160 | [ASM1042]: https://web.archive.org/web/20100830082618/http://www.asmedia.com.tw/eng/e_show_products.php?item=100&cate_index=98 161 | [ASM1042A]: https://web.archive.org/web/20131216093704/http://www.asmedia.com.tw/eng/e_show_products.php?cate_index=98&item=143 162 | [ASM1042AE]: https://web.archive.org/web/20131216094546/http://www.asmedia.com.tw/eng/e_show_products.php?cate_index=98&item=144 163 | [ASM1142]: https://web.archive.org/web/20150210162040/http://www.asmedia.com.tw/eng/e_show_products.php?item=155&cate_index=154 164 | [ASM2142]: https://web.archive.org/web/20180131031242/http://www.asmedia.com.tw/eng/e_show_products.php?cate_index=175&item=178 165 | [ASM3142]: https://web.archive.org/web/20180131031247/http://www.asmedia.com.tw/eng/e_show_products.php?cate_index=175&item=179 166 | [ASM3242]: https://web.archive.org/web/20210227053040/https://www.asmedia.com.tw/product/E0CYQ4fSpaQxdjzA/f32YQ14SmApn1wNA 167 | [td6817]: https://web.archive.org/web/20220401041252if_/http://techcodesemi.com/datasheet/TD6817.pdf 168 | -------------------------------------------------------------------------------- /doc/Promontory-Notes.md: -------------------------------------------------------------------------------- 1 | # Notes on Promontory Chips 2 | 3 | 4 | ## Design 5 | 6 | Despite being branded by AMD, Promontory chips use an architecture that is very similar to that of ASMedia's USB host controllers. 7 | Like ASMedia's USB host controllers, Promontory chips are controlled by an 8051 CPU core. 8 | Unlike ASMedia's USB host controllers, Promontory chips also include a PCIe switch core and a SATA host controller core, with their configuration controlled by MMIO registers in the 8051's XDATA address space. 9 | 10 | Note: Promontory chips should not be confused with AMD's "Bixby" chipset (also called ["AMD 2019 Premium Chipset"][bixby]). 11 | Bixby is the code name for what is marketed as the 500-series X570 chipset. 12 | Bixby is [based on the AMD Matisse IO die on a 14nm process][x570], so it has a completely different architecture from the Promontory chips. 13 | 14 | 15 | ## Naming 16 | 17 | "Promontory" is both the name of the chip family as well as the name of the first chip in the series. 18 | 19 | - Promontory 20 | - No other qualifiers--the original chip in the series. 21 | - Also called: 22 | - "PT" 23 | - Marketing names: 24 | - A320 (["PROM1"][prom1]) 25 | - B350 (["PROM2"][prom2]) 26 | - X370 (["PROM4"][prom4]) 27 | - ASMedia identifiers: 28 | - Chip / Silicon Version: `0x40` 29 | - Firmware ID: `3306A` 30 | - Promontory-LP 31 | - Also called: 32 | - "Low-Power Promontory" 33 | - "LP Promontory" 34 | - "LPPT" 35 | - Marketing names: 36 | - B450 ("PROM26.A" according to HWiNFO) 37 | - X470 ("PROM28.A" according to HWiNFO) 38 | - ASMedia identifiers: 39 | - Chip / Silicon Version: `0x60` 40 | - Firmware ID: `3306B` 41 | - Promontory-19: 42 | - Also called: 43 | - "Promontory Plus" 44 | - "Promontory 2019" 45 | - "PT19" 46 | - "PROM19" 47 | - Marketing names: 48 | - A520 49 | - B550 50 | - ASMedia identifiers: 51 | - Chip / Silicon Version: `0x90` 52 | - Firmware ID: `3308A` 53 | - Promontory-21: 54 | - Also called: 55 | - "Promontory 2021" 56 | - "PT21" 57 | - "PROM21" 58 | - Marketing names: 59 | - A620 60 | - B650(E) 61 | - X670(E) 62 | - B840 63 | - B850 64 | - X870(E) 65 | - ASMedia identifiers: 66 | - Chip / Silicon Version: `0xA0` 67 | - Firmware ID: `3328A` 68 | 69 | 70 | ## Miscellaneous Info 71 | 72 | - Promontory 73 | - [55nm process][55nm] 74 | - Promontory-LP 75 | - 40nm process (allegedly--it gets repeated a lot online but I haven't found a good source for this claim) 76 | 77 | 78 | [bixby]: https://web.archive.org/web/20200715182721/https://thinkstation-specs.com/thinkstation/p620/#:~:text=AMD%202019%20Premium%20Chipset 79 | [x570]: https://web.archive.org/web/20211005022310/https://twitter.com/IanCutress/status/1138443875154944000 80 | [prom1]: https://web.archive.org/web/20250128060224/https://community.amd.com/sdtpp67534/attachments/sdtpp67534/processors-discussions/29559/1/A320AM4-M3_M3D_manual.pdf 81 | [prom2]: https://web.archive.org/web/20250128061134/https://www.computerbase.de/forum/threads/aldi-pc-mit-amd-b350-prom2-lf-und-rgb-moeglich.1983544/#:~:text=Mainboard%3A%20AMD-,B350%20PROM2,-LF 82 | [prom4]: https://web.archive.org/web/20250128054923/https://www.dell.com/community/en/conversations/monitors/se2717h-amd-onboard-gpu-input-timing-is-not-supported/647f79cdf4ccf8a8de7f41e1#:~:text=Promontory%20X370%20PROM4%20chipset 83 | [55nm]: https://web.archive.org/web/20170807124916/http://www.anandtech.com/print/10705/amd-7th-gen-bristol-ridge-and-am4-analysis-a12-9800-b350-a320-chipset#:~:text=We%20were%20informed%20that%20the%20chipsets%20are%20manufactured%20at%20TSMC%20using%20a%2055nm%20process 84 | -------------------------------------------------------------------------------- /doc/firmware-urls.txt: -------------------------------------------------------------------------------- 1 | https://dl.dell.com/FOLDER03837411M/2/FWUPG_ASM1142_WT_A00_Setup-W2PTG_ZPE.exe 2 | https://dl.dell.com/FOLDER03837339M/3/FWUPG_ASM1142_WT_A00_Setup-Y6M87_ZPE.exe 3 | -------------------------------------------------------------------------------- /doc/out/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | regs-*.xhtml 3 | -------------------------------------------------------------------------------- /doc/out/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | Generated XHTML documentation goes here. 4 | 5 | To generate documentation, run `make doc` in the root directory of this git 6 | repo. 7 | -------------------------------------------------------------------------------- /doc/src/index.adoc: -------------------------------------------------------------------------------- 1 | = ASM1142 USB Host Controller Programming Manual 2 | :doctype: book 3 | :reproducible: 4 | :sectnums: 5 | :sectlinks: 6 | :stem: 7 | :icons: font 8 | :toc: left 9 | :toclevels: 4 10 | 11 | 12 | == System Overview 13 | 14 | The ASM1142 is a USB host controller with an MCS-51-compatible CPU running at up to 156.25 MHz, 64 kB boot ROM, 64 kB of code RAM, 256 B internal data RAM, 48 kB of external data RAM, PCIe and USB controller peripherals, and supporting peripherals. 15 | 16 | Interesting features include: 17 | 18 | * One machine cycle per clock cycle (1T). 19 | * Code RAM writable from the device CPU. 20 | * Hardware-accelerated memcpy and memset. 21 | * Mailbox registers in PCI configuration space. 22 | * DMA over PCIe. 23 | 24 | 25 | == Memory Architecture and Access 26 | 27 | The ASM1142's MCS-51-compatible CPU can address several memory regions: program/code memory (CODE), external data (XDATA), direct/indirect internal data (IRAM), and special function registers (SFRs). 28 | XRAM is located at address zero in XDATA, and MMIO begins at XDATA address 0xE000. 29 | 30 | 31 | === CPU Access 32 | 33 | Architecturally, CODE is read-only, and can be accessed using MOVC instructions. 34 | XDATA is read/write, and can be accessed using MOVX instructions. 35 | The lower 128 bytes of IRAM can be accessed either directly or indirectly, but the upper 128 bytes can only be accessed indirectly. 36 | SFRs are accessed using direct reads/writes to the upper 128 bytes of IRAM. 37 | 38 | While CODE is architecturally read-only, in the ASM1142 the CODE memory space can be mapped to either boot ROM or code RAM, and code RAM can be written to in several ways. 39 | One method, which is used by the boot ROM, is to direct the SPI flash controller to write the data it reads directly into code RAM. 40 | Unfortunately, it is not possible to do this while executing out of code RAM, so this capability is not particularly useful for custom firmware. 41 | 42 | The other method of writing to code RAM, which is usable by code running from code RAM, is to set bit 4 of the `PCON` SFR (`PCON.MEMSEL`), then use a MOVX instruction to read or write data in code RAM the same way MOVX is normally used to access XDATA. 43 | After accessing code RAM, `PCON.MEMSEL` must be cleared to make XDATA access work again. 44 | 45 | 46 | === Host Access 47 | 48 | The host can access certain memory regions through the PCI configuration space. 49 | This access can be organized into three major categories: firmware-assisted access, direct MMIO access, and direct code RAM write. 50 | 51 | 52 | ==== Firmware-assisted Access 53 | 54 | Firmware-assisted access, as its name implies, is memory accessed by communicating with the currently-running firmware. 55 | As this is firmware-specific, and not a function of the hardware, it will not be discussed further, other than to say that any memory that can be accessed by the firmware can be accessed by the host by communicating with the running firmware. 56 | 57 | 58 | ==== Direct MMIO Access 59 | 60 | Direct MMIO access can be performed as follows: 61 | 62 | . Write the address of the MMIO register to the `MMIO_ACCESS.ADDRESS` register in PCI configuration space. 63 | . Repeatedly read the `MMIO_ACCESS.ADDRESS` register until the address in the register matches the address of the MMIO register you want to access. 64 | . If you want to write data to the MMIO register, do so by writing the data to `MMIO_ACCESS.WRITE_DATA`. 65 | If you want to read data, you can do so by reading from `MMIO_ACCESS.READ_DATA`. 66 | 67 | Note that while writes to `MMIO_ACCESS.WRITE_DATA` take effect immediately, and can be performed repeatedly without writing the MMIO address another time, if you want to read updated data from the selected MMIO register, a second write to `MMIO_ACCESS.ADDRESS` is required to latch the new data into `MMIO_ACCESS.READ_DATA`. 68 | 69 | 70 | ==== Code RAM Write Access 71 | 72 | Code RAM can be written to (but not read from) directly by the host, which enables the host to load firmware directly into the device without first writing it to flash. 73 | Data is written in 2-byte chunks and must be aligned on 2-byte boundaries, and the address auto-increments by two on every write. 74 | The address register can only be used to address 32 kB of memory (the high bit, bit 15, is permanently zero), so the highest bit of the address is set or cleared depending on which data register is written to (enabling access to the full 64 kB). 75 | 76 | The process to write data to code RAM is as follows: 77 | 78 | . Enable writing to code RAM by using <> to set bit 1 in `CPU_MISC`. 79 | . Write the code RAM destination address to `CODE_RAM_ADDR` in PCI configuration space. 80 | . For addresses less than 0x8000, write two bytes of the code RAM data in a single writew operation to `CODE_RAM_DATA.LOWER_BANK_DATA` in PCI configuration space. 81 | For addresses greater than or equal to 0x8000, write the code RAM data to `CODE_RAM_DATA.UPPER_BANK_DATA` instead. 82 | . For each 2-byte chunk of code RAM data remaining, repeat step 3 (the address auto-increments so repeating step 2 is not necessary). 83 | 84 | 85 | == Clocking 86 | 87 | TODO 88 | 89 | 90 | == Boot Process 91 | 92 | After reset, the CPU begins executing from the boot ROM. 93 | The boot ROM's primary responsibility is to do some minor hardware initialization before loading and executing code from SPI flash. 94 | If the SPI flash is not present, or if the firmware image in flash is not valid, the boot ROM will continue to initialize hardware and wait in a loop until it is commanded to do something by the host. 95 | 96 | 97 | === Flash Firmware Loading 98 | 99 | Normally, code is loaded from flash by the boot ROM. 100 | 101 | TODO: Explain how this process works in more detail. 102 | 103 | 104 | === Direct Firmware Loading 105 | 106 | It is possible to load firmware directly into the code RAM of the ASM1142, without the host communicating with the boot ROM. 107 | This means that writing to the attached SPI flash is not necessary in order to run custom code on the ASM1142. 108 | It also means that firmware can be repeatedly loaded, as during development. 109 | 110 | To boot by directly loading firmware, perform the following steps: 111 | 112 | . Halt the device CPU and hold it in reset by using <> to write 0x02 to `CPU_EXEC_CTRL`. 113 | . Write the firmware to code RAM using <>. 114 | . Configure the device CPU to boot from code RAM by using <> to set bit 0 in `CPU_MODE_NEXT`. 115 | . Release the device CPU from reset by using <> to write 0x00 to `CPU_EXEC_CTRL`. 116 | 117 | 118 | == Peripherals 119 | 120 | TODO 121 | 122 | 123 | === Hardware Copy Controller 124 | 125 | The Hardware Copy Controller has two main modes of operation: 126 | 127 | . XRAM-to-XRAM copy ("memcpy" mode). 128 | . SFR region scratch registers-to-XRAM copy ("memset" mode). 129 | 130 | In the first mode, the controller is used to copy data between locations in XRAM. 131 | In other words, it's essentially an accelerated memcpy. 132 | 133 | In the second mode, data in SFRs 0xC0-0xCF can be copied in a loop into XRAM. 134 | This can be used to repeatedly write an arbitrary 16-byte pattern of data to a much larger region of XRAM (which can be used to accelerate memset operations). 135 | 136 | 137 | ==== Initialization 138 | 139 | TODO 140 | 141 | 142 | ==== Usage 143 | 144 | TODO 145 | 146 | 147 | === Mailbox 148 | 149 | TODO 150 | 151 | 152 | ==== Initialization 153 | 154 | TODO 155 | 156 | 157 | ==== Usage 158 | 159 | TODO 160 | 161 | 162 | === UART 163 | 164 | TODO 165 | 166 | 167 | ==== Initialization 168 | 169 | The UART comes out of reset with parity enabled (mode 8O1), so if you want the mode to be 8N1 you need to explicitly configure that. 170 | 171 | TODO: Explain full initialization process. 172 | 173 | 174 | ==== Usage 175 | 176 | TODO 177 | 178 | 179 | === Timer 180 | 181 | The timer peripheral is used for setting precise delays and timeouts, and can optionally be used to trigger <>. 182 | It is comprised of a counter, a clock divider/prescaler, a threshold value register, and an interrupt output. 183 | The counter is the core of the timer, and increments by one on every tick of the timer clock. 184 | The clock divider/prescaler can be used to control the speed of the timer clock. 185 | The threshold value register contains the threshold value, which is the value at which the counter will stop and the `TIMER_CSR.THRESHOLD_MET` bit will be set. 186 | The interrupt output is used to trigger <> when the the `TIMER_CSR.THRESHOLD_MET` bit is set. 187 | 188 | The timer is only capable of being used in a one-shot mode. 189 | Once it's started, on every tick of the timer clock the value in the counter is increased by one. 190 | When the value in the counter meets (TODO: determine if it's "meets" or "exceeds") the value in the threshold register `TIMER_THRESHOLD`, the counter will stop, the `TIMER_CSR.THRESHOLD_MET` bit will be set, and the `TIMER_CSR.RUN` bit will be cleared. 191 | If the `TIMER_IE.EX1` bit is set, <> will be triggered when the `TIMER_CSR.THRESHOLD_MET` bit is set. 192 | 193 | The timer can be stopped at any time before the threshold is met. 194 | To do so, simply clear the `TIMER_CSR.RUN` bit. 195 | Doing so will/will not (TODO: determine if the timer be "paused" and "resumed") reset the counter back to zero. 196 | 197 | WARNING: The timer has an error in its implementation that can cause the counter to stop and the `TIMER_CSR.THRESHOLD_MET` bit to be set immediately as soon as the timer is started. 198 | To avoid triggering the bug, please follow the timer programming instructions in the <> section exactly. 199 | For more details on this issue, please see the timer's <>. 200 | 201 | 202 | [#timer-usage] 203 | ==== Usage 204 | 205 | CAUTION: Please follow these directions exactly. 206 | Failing to do so may cause the timer to function improperly. 207 | 208 | To start the timer: 209 | 210 | . Calculate the values for `TIMER_DIV` and `TIMER_THRESHOLD` as specified in <>. 211 | . Reset the timer by first writing 0x00 to `TIMER_CSR`, and then writing 0x02. 212 | . Set `TIMER_IE.EX1` to zero to avoid triggering an interrupt. 213 | . Set `TIMER_DIV` to one. 214 | . Set `TIMER_THRESHOLD` to zero. 215 | . Trigger the hardware bug (see <>) by writing 0x01 to `TIMER_CSR`. 216 | . Wait for the timer to stop by waiting for the `TIMER_CSR.RUN` bit to be cleared by hardware. 217 | . Reset the timer by writing 0x02 to `TIMER_CSR`. 218 | . Set the desired `TIMER_DIV` and `TIMER_THRESHOLD` values. 219 | . Optionally, set `TIMER_IE.EX1` to enable triggering an interrupt. 220 | . Start the timer by writing 0x01 to `TIMER_CSR`. 221 | 222 | To check the status of the timer: 223 | 224 | . Read the `TIMER_CSR` register and check the state of the `TIMER_CSR.THRESHOLD_MET` bit. 225 | 226 | To stop the timer early: 227 | 228 | . Write 0x00 to the `TIMER_CSR` register. 229 | 230 | 231 | [#timer-config] 232 | ==== Configuration 233 | 234 | The timer's clock frequency is derived from the current CPU clock frequency, and can be calculated with the following formula: 235 | 236 | [latexmath] 237 | ++++ 238 | f_{timer} = \frac {f_{cpu}} {8192 \times TIMER\_DIV} 239 | ++++ 240 | 241 | Where latexmath:[f_{timer}] is the timer clock frequency in Hz, latexmath:[f_{cpu}] is the current CPU clock frequency in Hz (see <>), and latexmath:[TIMER\_DIV] is the current value of the `TIMER_DIV` register. 242 | 243 | The time it will take for the `TIMER_CSR.THRESHOLD_MET` to be set after the timer is started can be calculated with the following formula: 244 | 245 | [latexmath] 246 | ++++ 247 | t = \frac {TIMER\_THRESHOLD} {f_{timer}} 248 | ++++ 249 | 250 | Where latexmath:[t] is the time it will take, in seconds, for the counter to reach the threshold value, latexmath:[TIMER\_THRESHOLD] is the current value of the `TIMER_THRESHOLD` register, and latexmath:[f_{timer}] is the timer clock frequency in Hz. 251 | 252 | Substituting in the formula for latexmath:[f_{timer}], we get: 253 | 254 | [latexmath] 255 | ++++ 256 | t = \frac {8192 \times TIMER\_DIV \times TIMER\_THRESHOLD} {f_{cpu}} 257 | ++++ 258 | 259 | Rearranging to make latexmath:[t] an independent variable, we get: 260 | 261 | [latexmath] 262 | ++++ 263 | TIMER\_DIV \times TIMER\_THRESHOLD = \frac {t \times f_{cpu}} {8192} 264 | ++++ 265 | 266 | Since `TIMER_DIV` is only 8 bits and `TIMER_THRESHOLD` is 16 bits, the best way to determine the optimal values of those registers for an arbitrary latexmath:[t] is to do the following: 267 | 268 | . Assume `TIMER_DIV` is 1. 269 | . Solve for `TIMER_THRESHOLD`. 270 | . If the solved-for `TIMER_THRESHOLD` is greater than 65535, increase `TIMER_DIV` until the value of `TIMER_THRESHOLD` is less than or equal to 65535. 271 | . If the value of `TIMER_DIV` is greater than 255, then the timer can not be programmed for that timeout value. 272 | 273 | 274 | [#timer-errata] 275 | ==== Errata 276 | 277 | The timer has an error in its implementation that can cause the counter to stop and the `TIMER_CSR.THRESHOLD_MET` bit to be set immediately as soon as the timer is started. 278 | This bug will be triggered if any of the following conditions are met: 279 | 280 | * The previous run of the timer was not stopped before the counter met the threshold value. 281 | * The timer was run with the threshold value set to zero. 282 | 283 | After the bug is triggered, the timer will behave normally on the immediately following run. 284 | 285 | The erroneous behavior can be detected by reading the `TIMER_CSR` immediately after setting the `TIMER_CSR.RUN` bit. 286 | If the value of the register is 0x03, then the bug was definitely triggered. 287 | If the value of the register is 0x02, then the bug was most likely triggered, but the read from `TIMER_CSR` took more than a few instructions. 288 | 289 | To work around this issue, the bug must be deliberately triggered before each use of the timer. 290 | To do so, simply set the threshold value `TIMER_THRESHOLD` to zero and then run the timer. 291 | This will trigger the bug, meaning the next time the timer runs it will work properly. 292 | 293 | 294 | === SPI Flash Controller 295 | 296 | Packet format: 297 | 298 | * 1 command byte 299 | * 0-3 address bytes (transmitted) 300 | * 0-65535 data bytes (transmitted or received, depending on the value of `FLASH_CON_MODE.WRITE_N_READ`) 301 | 302 | Can read data into either XRAM or code RAM. 303 | 304 | Supports CRC calculation on data read from flash, standard CRC algo (Ethernet/zlib). 305 | 306 | TODO: Elaborate on capabilities. 307 | 308 | 309 | ==== Initialization 310 | 311 | TODO 312 | 313 | 314 | ==== Usage 315 | 316 | TODO: Explain SPI transactions and CRC calculation. 317 | 318 | 319 | == Interrupts 320 | 321 | The ASM1142 appears to only support two primary interrupt sources, the external interrupts EINT0 and EINT1. 322 | However, both primary interrupts can be triggered by multiple sources, and the interrupt service routines are responsible for determining which source triggered an interrupt. 323 | 324 | TODO: Explain interrupt masking. 325 | 326 | 327 | === External Interrupt 0 (EINT0) 328 | 329 | EINT0 has several sources: 330 | 331 | * UART 332 | * Others TBD 333 | 334 | TODO: Include interrupt routing diagram. 335 | 336 | 337 | === External Interrupt 1 (EINT1) 338 | 339 | EINT1 has several sources: 340 | 341 | * Timer 342 | * Mailbox read ACK/write start 343 | * Others TBD 344 | 345 | TODO: Include interrupt routing diagram. 346 | -------------------------------------------------------------------------------- /monitor/.gitignore: -------------------------------------------------------------------------------- 1 | *.asm 2 | *.bin 3 | *.ihx 4 | *.img 5 | *.lk 6 | *.lst 7 | *.map 8 | *.mem 9 | *.rel 10 | *.rst 11 | *.sym 12 | build_info.c 13 | sfr.c 14 | -------------------------------------------------------------------------------- /monitor/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | 3 | # Copyright (C) 2020-2021 Forest Crossman 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | AS := sdas8051 20 | ASFLAGS := -l -o -s -p 21 | CC := sdcc 22 | CFLAGS := -mmcs51 --std-sdcc11 --model-small --stack-auto 23 | OBJCOPY := sdobjcopy 24 | 25 | CHIPS := ASM1042 ASM1042A ASM1142 ASM2142 ASM3242 26 | CHIPS_LOWER := $(shell echo $(CHIPS) | tr A-Z a-z) 27 | BINARIES := $(CHIPS_LOWER:%=monitor-%.bin) 28 | IMAGES := $(BINARIES:%.bin=%.img) 29 | 30 | TARGET ?= ASM1142 31 | TARGET_LOWER = $(shell echo $(TARGET) | tr A-Z a-z) 32 | FLASH_SIZE ?= 64K 33 | 34 | all: $(IMAGES) $(BINARIES) 35 | 36 | %.rel: %.c 37 | $(CC) $(CFLAGS) -c -o $@ $< 38 | 39 | %.rel: %.S 40 | $(AS) $(ASFLAGS) $< 41 | 42 | build_info.c: build_info.inc.c 43 | sed s/BUILD_VERSION/$(shell printf "r%s.g%s" "$(shell git rev-list --count HEAD)" "$(shell git rev-parse --short HEAD)")/g $< | \ 44 | sed s/BUILD_TIME/$(shell date -u '+%FT%H:%M:%SZ')/g > $@ 45 | 46 | sfr.c: sfr.inc.c gen_sfr_c.py 47 | ./gen_sfr_c.py -o $@ $< 48 | 49 | monitor.ihx: main.rel sfr.rel vectors.rel build_info.rel 50 | $(CC) $(CFLAGS) -o $@ $^ 51 | 52 | %.bin: %.ihx 53 | $(OBJCOPY) -I ihex -O binary $< $@ 54 | 55 | $(BINARIES): monitor.bin 56 | ./make_image.py -t bin -c $$(echo $@ | sed -e 's/monitor-\([0-9a-z]\+\)\.bin/\1/g' | tr a-z A-Z) -o $@ $< 57 | 58 | %.img: %.bin 59 | ./make_image.py -t image -c $$(echo $@ | sed -e 's/monitor-\([0-9a-z]\+\)\.img/\1/g' | tr a-z A-Z) -o $@ $< 60 | 61 | %.flash: %.img 62 | cp $< $@ 63 | truncate -c -s $(FLASH_SIZE) $@ 64 | 65 | flash: monitor-$(TARGET_LOWER).flash 66 | flashrom -p ch341a_spi -w $< 67 | 68 | clean: 69 | rm -f *.asm *.bin *.flash *.ihx *.img *.lk *.lst *.map *.mem *.rel *.rst *.sym build_info.c sfr.c 70 | 71 | .PHONY: all clean flash build_info.c 72 | -------------------------------------------------------------------------------- /monitor/README.md: -------------------------------------------------------------------------------- 1 | # monitor 2 | 3 | This is a simple firmware that can be flashed to an ASMedia USB 3 host 4 | controller card and interacted with over a serial interface. 5 | 6 | 7 | #### Table of contents 8 | 9 | 1. [User interface](#user-interface) 10 | * [help](#help) 11 | * [version](#version) 12 | * [mrb](#mrb) 13 | * [mrw](#mrw) 14 | * [mwb](#mwb) 15 | * [mww](#mww) 16 | * [reset](#reset) 17 | * [bmo](#bmo) 18 | 2. [Build instructions](#build-instructions) 19 | 3. [Hardware modifications](#hardware-modifications) 20 | * [SPI flash socket](#spi-flash-socket) 21 | - [Necessary parts and equipment](#necessary-parts-and-equipment) 22 | - [Procedure](#procedure) 23 | * [UART header](#uart-header) 24 | - [Necessary parts and equipment](#necessary-parts-and-equipment-2) 25 | - [Procedure](#procedure-2) 26 | 27 | 28 | ## User interface 29 | 30 | It's a simple command line with some useful commands. 31 | 32 | ``` 33 | Hello from monitor! 34 | monitor version r117.g628dc28 (built on 20XX-01-01T00:00:00Z) 35 | > help 36 | Commands available: 37 | - help 38 | - version 39 | - mrb 40 | - mrh 41 | - mrw 42 | - mwb 43 | - mwh 44 | - mww 45 | - reset 46 | - bmo 47 | > 48 | ``` 49 | 50 | 51 | ### help 52 | 53 | Display the list of commands. 54 | 55 | 56 | ### version 57 | 58 | Print the monitor version, build date/time, and chip name. 59 | 60 | 61 | ### mrb 62 | 63 | "Memory Read Byte": Read a single byte of memory. 64 | 65 | 66 | ### mrh 67 | 68 | "Memory Read Half-word": Read a 2-byte little-endian half-word of 69 | memory. 70 | 71 | On an 8051, "words" are typically considered 16 bits (2 bytes), but I 72 | originally wrote this code for ARM and didn't feel like changing the 73 | names of the commands after porting it to the 8051. 74 | 75 | 76 | ### mrw 77 | 78 | "Memory Read Word": Read a 4-byte little-endian word of memory. 79 | 80 | On an 8051, "words" are typically considered 16 bits (2 bytes), but I 81 | originally wrote this code for ARM and didn't feel like changing the 82 | names of the commands after porting it to the 8051. 83 | 84 | 85 | ### mwb 86 | 87 | "Memory Write Byte": Write a single byte of memory. 88 | 89 | 90 | ### mwh 91 | 92 | "Memory Write Half-word": Write a 2-byte little-endian half-word of 93 | memory. 94 | 95 | On an 8051, "words" are typically considered 16 bits (2 bytes), but I 96 | originally wrote this code for ARM and didn't feel like changing the 97 | names of the commands after porting it to the 8051. 98 | 99 | 100 | ### mww 101 | 102 | "Memory Write Word": Write a 4-byte little-endian word of memory. 103 | 104 | On an 8051, "words" are typically considered 16 bits (2 bytes), but I 105 | originally wrote this code for ARM and didn't feel like changing the 106 | names of the commands after porting it to the 8051. 107 | 108 | 109 | ### reset 110 | 111 | Perform a CPU soft reset. 112 | 113 | 114 | ### bmo 115 | 116 | Switch the monitor into Binary MOde, for use with `bmo.py`. If you 117 | accidentally run this command and enter binary mode, press "Enter" to 118 | exit it and return to a prompt. 119 | 120 | 121 | ## Build instructions 122 | 123 | 1. Install [sdcc][sdcc]. 124 | 2. Run `make`. 125 | 3. Connect your SPI flash programmer to the flash chip on the board, 126 | then run `make flash` to write `monitor.img` to the flash. If you 127 | aren't using a CH341A-based programmer, run the flashrom command 128 | appropriate for your device. 129 | 130 | 131 | ## Hardware modifications 132 | 133 | If you don't already have a pin header soldered to the UART pins on the 134 | chip (the specific pins and other UART information are listed in 135 | [Notes.md][notes] in the [../doc][doc] directory), you'll need to do that 136 | before you can use this. Please note that if you want to replace the SPI 137 | flash chip with a SOIC socket, you'll probably want to do that _before_ 138 | adding the UART header, since it's easiest to remove the IC with a hot 139 | air gun or a hot air rework tool, and using those kinds of tools will 140 | put a lot of heat into the board, which would likely melt the hot glue 141 | used to hold the header in place. 142 | 143 | 144 | ### SPI flash socket 145 | 146 | 147 | #### Necessary parts and equipment 148 | 149 | - Safety glasses (to protect your eyes from splattering solder, sharp 150 | objects, and hot objects. 151 | - A decent microscope (all the better to see things with). 152 | - A soldering iron. 153 | - Soldering iron tips of various sizes. 154 | - A thin/sharp one for soldering to the signal pads. 155 | - A large one (preferably with a chisel tip) for soldering the 156 | ground pad. 157 | - A fume extractor (optional, but good so you don't breathe in any 158 | fumes). 159 | - Solder. 160 | - Flux. 161 | - A hot air rework tool or a hot air gun. 162 | - An SOIC (SOP8) socket matching the width of the flash chip. 163 | - Typically, for the boards these chips are used on, the width is 164 | 150mil. 165 | - Precision tweezers. 166 | - Kapton (polyimide) tape. 167 | - PCB vise. 168 | - Paper towels (to apply and clean up the flux). 169 | 170 | 171 | #### Procedure 172 | 173 | TODO 174 | 175 | Basically: Put on your safety glasses, fire up your hot air tool, and 176 | heat the flash chip until you can remove it with tweezers. Then remove 177 | the flash chip and wait for the board to cool down. Then put some flux 178 | on the now-empty PCB pads, stick the socket on the pads, and hold it 179 | down with polyimide tape. Then use the soldering iron with the 180 | sharp/thin tip to solder all the signal pins first, then switch to the 181 | large tip to solder power, then finally ground. Check conductivity with 182 | a multimeter to ensure there are no shorts or broken connections, then 183 | clean up the flux residue. 184 | 185 | 186 | ### UART header 187 | 188 | 189 | #### Necessary parts and equipment 190 | 191 | - Safety glasses (to protect your eyes from splattering solder, sharp 192 | objects, and hot objects. 193 | - A decent microscope (all the better to see things with). 194 | - A soldering iron. 195 | - Soldering iron tips of various sizes. 196 | - A thin/sharp one for soldering to the TX/RX pads. 197 | - A large one (preferably with a chisel tip) for soldering to 198 | ground. 199 | - A fume extractor (optional, but good so you don't breathe in any 200 | fumes). 201 | - Solder. 202 | - 31 AWG armature wire/magnet wire/enamel-coated wire. 203 | - You don't need this exact gauge (I actually don't remember what 204 | gauge I used), but you want it to be very thin, very flexible, and 205 | coated in enamel. 206 | - Pin or socket header. 207 | - Doesn't matter which you use, but personally I find pins to be the 208 | most convenient and least expensive. 209 | - A hot glue gun. 210 | - Hot glue. 211 | - A hobby knife/precision knife. 212 | - Precision tweezers. 213 | - Kapton (polyimide) tape. 214 | - PCB vise. 215 | 216 | 217 | #### Procedure 218 | 219 | Find the correct pins on the IC, then trace the pins to the pull up/pull 220 | down resistors they connect to on the PCB. Then, using a multimeter, 221 | confirm the connection between the pins on the IC and the pads you just 222 | found. These will be the TX and RX pads you'll solder to. 223 | 224 | Using a multimeter, find a nice ground pad to solder to. Try to find one 225 | somewhat close to the TX/RX pads, to help maintain the signal integrity. 226 | If you can't find one, you can make one by scraping off the soldermask 227 | on the copper ground fill. If the ground pad you're using is large, or 228 | completely surrounded by copper (as is the case when you use the ground 229 | fill as your connection to ground), you'll want to make sure to use the 230 | large soldering iron tip when soldering to it. 231 | 232 | With your TX, RX, and ground pads identified, it's time to prepare the 233 | magnet wire. With the wire still on the spool, use your hobby knife to 234 | scrape off a few millimeters of enamel from the tip. Make sure to scrape 235 | it off the entire surface--not just on one side--and try to avoid 236 | cutting through the wire while you do it. Then cut the wire to the 237 | appropriate length--try to keep the it under 4 in. (10 cm) long. Repeat 238 | this process two times, for the other two wires. Don't scrape the enamel 239 | off the other end just yet--it's alright if you've already done so, but 240 | it'll be easier if you wait to do that until after soldering the exposed 241 | end of the wire to the header. 242 | 243 | Now you should have three, short, enamel-coated wires, each with the 244 | enamel entirely scraped off of one of the tips. Heat up your soldering 245 | iron (thin/sharp tip), then use the solder to tin the enamel-free tips 246 | of each of the wires. Then use a pair of sharp tweezers to twist one of 247 | the tinned tips of the wires around one of the pins. Ideally, the wire 248 | should now be able to stay there by friction. Now use the soldering iron 249 | and solder to solder the wire to the pin. Repeat this process for the 250 | other two wires and pins. 251 | 252 | With the wires soldered to the pin header, repeat the enamel-removal 253 | process on the free tips of each of the wires, and then tin them like 254 | the other ends. 255 | 256 | Now, place the pin header on the board in approximately the location you 257 | would like it to stay in, and use the polyimide tape to keep it there. 258 | 259 | With the soldering iron (sharp/thin tip), solder the TX and RX wires to 260 | the pads you found earlier. Use polyimide tape to keep the solder from 261 | bridging to any other pads, components, or wires, if necessary. 262 | 263 | Wait for the iron to cool down, then change to the large tip. Then 264 | re-heat the iron and use it to solder the final wire to the ground pad 265 | you identified earlier, once again using polyimide tape as needed. 266 | 267 | With the wires soldered in place, use the multimeter to check the 268 | conductivity from each pin on the header to the corresponding TX and RX 269 | pins on the IC, as well as ground. Make sure not only that each pin 270 | connects to the appropriate signals, but also that there are no 271 | short-circuits between any of the pins. 272 | 273 | Now that you know that the circuits are electrically correct, remove any 274 | unncessary polyimide tape that remains on the board. Use the hot glue 275 | gun to bind the pin/socket header to the circuit board, as well as to 276 | insulate the pins and provide strain relief for the wires. If you like, 277 | you can also use the hot glue to protect the places where you soldered 278 | the wires to the PCB pads, so that bending the wires won't accidentally 279 | break the connections, as well as to provide strain relief and 280 | electrical insulation. 281 | 282 | The board modification is now complete, and you can use this header to 283 | connect to the UART of the USB host controller. 284 | 285 | 286 | [sdcc]: http://sdcc.sourceforge.net/ 287 | [notes]: ../doc/Notes.md 288 | [doc]: ../doc 289 | -------------------------------------------------------------------------------- /monitor/build_info.inc.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-3.0-or-later */ 2 | 3 | /* 4 | * Copyright (C) 2020 Forest Crossman 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | char const * const build_version = "BUILD_VERSION"; 21 | char const * const build_time = "BUILD_TIME"; 22 | -------------------------------------------------------------------------------- /monitor/gen_sfr_c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | # gen_sfr_c.py - Helper script to generate SFR read/write code. 5 | # Copyright (C) 2020-2021 Forest Crossman 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | import argparse 22 | import string 23 | 24 | 25 | def main(): 26 | parser = argparse.ArgumentParser() 27 | parser.add_argument("template", type=str, help="Template file.") 28 | parser.add_argument("-o", "--output", type=str, default="sfr.c", help="Output C file.") 29 | args = parser.parse_args() 30 | 31 | template = string.Template(open(args.template, 'r').read()) 32 | 33 | sfr_range = range(0x80, 0x100) 34 | 35 | sfr_defs = [] 36 | for i in sfr_range: 37 | sfr_defs.append("static SFR(SFR_{0:02X}, 0x{0:02X});".format(i)) 38 | 39 | get_cases = [] 40 | for i in sfr_range: 41 | get_cases.append("\tcase 0x{0:02X}:\n\t\treturn SFR_{0:02X};".format(i)) 42 | 43 | set_cases = [] 44 | for i in sfr_range: 45 | if i in (0x81, 0x82, 0x83, 0xD0, 0xE0, 0xF0): 46 | # Disable writes to critical SFRs. 47 | continue 48 | set_cases.append("\tcase 0x{0:02X}:\n\t\tSFR_{0:02X} = value;\n\t\tbreak;".format(i)) 49 | 50 | mapping = { 51 | 'SFR_DEFS': '\n'.join(sfr_defs), 52 | 'GET_CASES': '\n'.join(get_cases), 53 | 'SET_CASES': '\n'.join(set_cases), 54 | } 55 | 56 | generated = template.substitute(mapping) 57 | 58 | output = open(args.output, 'w') 59 | output.write(generated) 60 | output.close() 61 | 62 | 63 | if __name__ == "__main__": 64 | main() 65 | -------------------------------------------------------------------------------- /monitor/make_image.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | # make_image.py - Script to generate a firmware image from a raw binary. 5 | # Copyright (C) 2020-2023, 2025 Forest Crossman 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | import argparse 22 | import struct 23 | import sys 24 | from datetime import datetime 25 | from zlib import crc32 26 | 27 | 28 | CHIP_INFO = { 29 | "ASM1042": ("U2104_RCFG", "U2104_FW", "H"), 30 | "ASM1042A": ("2104B_RCFG", "2104B_FW", "H"), 31 | "ASM1142": ("2114A_RCFG", "2114A_FW", "H"), 32 | "ASM2142": ("2214A_RCFG", "2214A_FW", "I"), 33 | "ASM3242": ("2324A_RCFG", "2324A_FW", "I"), 34 | } 35 | 36 | 37 | def checksum(data: bytes) -> int: 38 | return sum(data) & 0xff 39 | 40 | def gen_header(chip: str, sig_bypass: bool = False) -> bytes: 41 | header_magic = CHIP_INFO[chip][0] 42 | 43 | data = bytes() 44 | if CHIP_INFO[chip][2] == "I": 45 | # Config words start at 0x20 on ASM2142 and later. 46 | data += bytes([0] * 16) 47 | 48 | if chip == "ASM3242" and sig_bypass: 49 | # Bypass the signature check via ROM config MMIO writes. 50 | xram_addr = 0x000000 # Always zero. 51 | data_len = 0x008000 # Limited by the flash read speed. 52 | 53 | cc_words = 14 54 | delay_writes = (0x400 - (16 + len(data) + 8 * cc_words)) // 8 55 | firmware_flash_addr = 16 + len(data) + 8 * (cc_words + delay_writes) + 9 56 | 57 | writes_pre_delay = ( 58 | (0x505c, 2, data_len & 0xffff), # DATA_LEN 59 | (0x505e, 1, (data_len >> 16) & 0xff), # DATA_LEN_HI 60 | (0x5060, 1, 0x01), # DIV 61 | (0x5061, 1, 0x03), # ADDR_LEN 62 | (0x5062, 1, 0x03), # CMD 63 | (0x5063, 1, 0x00), # MODE 64 | (0x5064, 1, 0x01), # MEMSEL 65 | (0x5066, 2, xram_addr & 0xffff), # XRAM_ADDR 66 | (0x5068, 1, (xram_addr >> 16) & 0xff), # XRAM_ADDR_HI 67 | (0x506b, 2, firmware_flash_addr & 0xffff), # FLASH_ADDR 68 | (0x506d, 1, (firmware_flash_addr >> 16) & 0xff), # FLASH_ADDR_HI 69 | (0x506e, 1, 0x04 | 0x08 | 0x01), # CSR 70 | ) 71 | 72 | writes_post_delay = ( 73 | (0x5040, 1, 0x03), # CPU_MODE_NEXT 74 | (0x5042, 1, 0x01), # CPU_EXEC_CTRL 75 | ) 76 | 77 | loading_string = b"\r\nLoading..." 78 | loading_string += b"." * (delay_writes - len(loading_string)) 79 | 80 | for addr, count, value in writes_pre_delay: 81 | data += struct.pack(' bytes: 97 | chip_info = CHIP_INFO[chip] 98 | body_magic = chip_info[1].encode('ASCII') 99 | body_len_type = chip_info[2] 100 | 101 | body = struct.pack('<' + body_len_type, len(data)) 102 | body += data 103 | body += struct.pack('8s', body_magic) 104 | 105 | csum = checksum(data) 106 | crc = crc32(data) 107 | body += struct.pack('> 2) 114 | body += bytes([0] * 0x20) 115 | 116 | return body 117 | 118 | def add_fw_meta(chip: str, data: bytes) -> bytes: 119 | chip_info = CHIP_INFO[chip] 120 | body_magic = chip_info[1].encode('ASCII') 121 | 122 | data = bytearray(data) 123 | bcd_timestamp = bytes.fromhex(datetime.utcnow().strftime('%y%m%d%H%M%S')) 124 | struct.pack_into('6s', data, 0x80, bcd_timestamp) 125 | struct.pack_into('8s', data, 0x87, body_magic) 126 | 127 | return bytes(data) 128 | 129 | def main() -> int: 130 | parser = argparse.ArgumentParser() 131 | parser.add_argument("input", type=str, help="Input binary.") 132 | parser.add_argument("-t", "--type", type=str, choices=["bin", "image"], default="image", help="Image type.") 133 | parser.add_argument("-o", "--output", type=str, default="monitor.img", help="Output image.") 134 | parser.add_argument("-c", "--chip", type=str, choices=CHIP_INFO.keys(), default="ASM1142", help="Chip to target.") 135 | parser.add_argument("-s", "--sig-bypass", default=False, action="store_true", help="Add MMIO writes in the image header to bypass the signature check, when applicable.") 136 | args = parser.parse_args() 137 | 138 | binary = open(args.input, 'rb').read() 139 | 140 | if args.type == "bin": 141 | image = add_fw_meta(args.chip, binary) 142 | elif args.type == "image": 143 | header = gen_header(args.chip, args.sig_bypass) 144 | body = gen_body(args.chip, binary) 145 | image = header + body 146 | else: 147 | print("Error: Unrecognized image type: {}".format(args.type)) 148 | return 1 149 | 150 | output = open(args.output, 'wb') 151 | output.write(image) 152 | output.close() 153 | 154 | return 0 155 | 156 | 157 | if __name__ == "__main__": 158 | sys.exit(main()) 159 | -------------------------------------------------------------------------------- /monitor/sfr.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-3.0-or-later */ 2 | 3 | /* 4 | * Copyright (C) 2020 Forest Crossman 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef SFR_H 21 | #define SFR_H 22 | 23 | #include 24 | 25 | uint8_t get_sfr(uint8_t addr); 26 | void set_sfr(uint8_t addr, uint8_t value); 27 | 28 | #endif /* SFR_H */ 29 | -------------------------------------------------------------------------------- /monitor/sfr.inc.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-3.0-or-later */ 2 | 3 | /* 4 | * Copyright (C) 2020 Forest Crossman 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | 22 | #include 23 | 24 | #include "sfr.h" 25 | 26 | ${SFR_DEFS} 27 | 28 | uint8_t get_sfr(uint8_t addr) { 29 | switch (addr) { 30 | ${GET_CASES} 31 | default: 32 | return 0; 33 | } 34 | } 35 | 36 | void set_sfr(uint8_t addr, uint8_t value) { 37 | switch (addr) { 38 | ${SET_CASES} 39 | default: 40 | break; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /monitor/vectors.S: -------------------------------------------------------------------------------- 1 | ; SPDX-License-Identifier: GPL-3.0-or-later 2 | 3 | ; Copyright (C) 2020 Forest Crossman 4 | ; 5 | ; This program is free software: you can redistribute it and/or modify 6 | ; it under the terms of the GNU General Public License as published by 7 | ; the Free Software Foundation, either version 3 of the License, or 8 | ; (at your option) any later version. 9 | ; 10 | ; This program is distributed in the hope that it will be useful, 11 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ; GNU General Public License for more details. 14 | ; 15 | ; You should have received a copy of the GNU General Public License 16 | ; along with this program. If not, see . 17 | 18 | 19 | .globl __sdcc_gsinit_startup 20 | .globl _isr_eint0 21 | .globl _isr_eint1 22 | 23 | .area VECTOR (ABS,CODE) 24 | .org 0x0000 25 | __interrupt_vect: 26 | ljmp __sdcc_gsinit_startup 27 | ljmp _isr_eint0 28 | .ds 0x13 - (0x3 + 3) 29 | ljmp _isr_eint1 30 | .ds 0x8F - (0x13 + 3) 31 | loop_forever: 32 | nop 33 | nop 34 | nop 35 | sjmp loop_forever 36 | -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | asm_fw.py 2 | prom_fw.py 3 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # Tools for working with ASMedia USB host controllers 2 | 3 | 4 | ## [asmedia-xhc-trace](asmedia-xhc-trace) 5 | 6 | A Rust utility to record a trace of the program counter of the 8051 in a host 7 | controller. Currently only works with the ASM1042A and ASM1142. 8 | 9 | 10 | ## [emulator](emulator) 11 | 12 | This is where I'd put my custom host controller emulator (if I had one). For now 13 | it just contains some of my thoughts on how I'd write an 8051 emulator. 14 | 15 | 16 | ## [ghidra-scripts](ghidra-scripts) 17 | 18 | Scripts for [Ghidra][ghidra] that can help with reverse engineering host 19 | controller firmware. 20 | 21 | 22 | ## [asm\_fw.ksy](asm_fw.ksy) 23 | 24 | A [Kaitai Struct][kaitai] definition for the ASMedia USB host controller 25 | firmware image format. 26 | 27 | 28 | ## [asm\_tool.py](asm_tool.py) 29 | 30 | A Python library for interacting with ASMedia USB host controllers over PCIe. 31 | Can be used to read and write internal MMIO registers and load/execute arbitrary 32 | code on certain host controllers. Currently only the ASM1042A, ASM1142, and 33 | ASM2142/ASM3142 are supported. 34 | 35 | 36 | ## [bug\_demo.py](bug_demo.py) 37 | 38 | This tool uses the [asm\_tool](asm_tool.py) library to demonstrate the hardware 39 | bug present on all 64-bit ASMedia USB host controllers that prevents them from 40 | accessing the xHCI Command Ring if the Command Ring is located at an address 41 | greater than or equal to 0x0001000000000000. The program works by writing the 42 | CRCR and DCBAAP registers in PCI BAR0, then reading them back through the 43 | indirect internal MMIO register access mechanism to see how the 8051 core sees 44 | the values in those registers. 45 | 46 | Currently only the ASM1042A, ASM1142, and ASM2142/ASM3142 are supported. 47 | 48 | Example output: 49 | 50 | ``` 51 | Chip: ASM1042A 52 | Unbinding the kernel driver if it's attached... 53 | CRCR: Expected 0x0000000000000000, got 0x0000000000000000: OK 54 | CRCR: Expected 0xffffffffffffffc0, got 0xff00ffffffffffc0: ERROR: Internal value does not match what was written! 55 | CRCR: Expected 0x12345678abcdefc0, got 0x34005678abcdefc0: ERROR: Internal value does not match what was written! 56 | DCBAAP: Expected 0x0000000000000000, got 0x0000000000000000: OK 57 | DCBAAP: Expected 0xffffffffffffffc0, got 0xffffffffffffffc0: OK 58 | DCBAAP: Expected 0x12345678abcdefc0, got 0x12345678abcdefc0: OK 59 | ``` 60 | 61 | 62 | ## [extract\_promontory\_fw.py](extract_promontory_fw.py) 63 | 64 | This is a Python script for extracting Promontory chipset firmware images from 65 | other files. Useful for extracting Promontory firmware images from UEFI 66 | firmware images. 67 | 68 | 69 | ## [generate\_docs.py](generate_docs.py) 70 | 71 | This is a Python script that generates XHTML documentation pages from the YAML 72 | register definitions in the [data][data] directory. 73 | 74 | 75 | ## [generate\_labels.py](generate_labels.py) 76 | 77 | This Python script can use the YAML register definitions in the [data][data] 78 | directory to generate a list of memory address labels that can be imported into 79 | Ghidra. 80 | 81 | 82 | ## [load\_fw.py](load_fw.py) 83 | 84 | This tool provides a convenient way to directly load code into a host controller 85 | and execute it without having to first write the code to flash. 86 | 87 | Currently only the ASM1042A, ASM1142, and ASM2142/ASM3142 are supported. 88 | 89 | 90 | ## [prom\_fw.ksy](prom_fw.ksy) 91 | 92 | A [Kaitai Struct][kaitai] definition for the Promontory chipset firmware image 93 | format. 94 | 95 | 96 | ## [validate\_brom.py](validate_brom.py) 97 | 98 | Validates a BROM (boot ROM/mask ROM) dump by verifying the CRC-32 checksum 99 | embedded in the image. Also prints BROM version information. 100 | 101 | 102 | ## [validate\_fw.py](validate_fw.py) 103 | 104 | Validates a flash firmware image by verifying the various checksums in the 105 | image. Also prints the firmware version and lists the registers/values set by 106 | the sequence of config words in the header. 107 | 108 | 109 | [ghidra]: https://ghidra-sre.org/ 110 | [kaitai]: https://kaitai.io/ 111 | [data]: ../data 112 | -------------------------------------------------------------------------------- /tools/asm_fw.ksy: -------------------------------------------------------------------------------- 1 | meta: 2 | id: asm_fw 3 | endian: le 4 | title: ASMedia xHC Firmware Image 5 | license: CC0-1.0 6 | seq: 7 | - id: header 8 | type: header 9 | - id: body 10 | type: body 11 | types: 12 | header: 13 | seq: 14 | - id: unk0 15 | type: u2 16 | - id: unk1 17 | type: u2 18 | - id: len 19 | type: u2 20 | - id: magic 21 | size: 10 22 | type: str 23 | encoding: ascii 24 | - id: data 25 | size: len - 16 26 | type: config_words 27 | - id: checksum 28 | type: u1 29 | doc: "A uint8 sum of all the bytes in the header, excluding the checksum and crc32." 30 | - id: crc32 31 | type: u4 32 | doc: "A CRC32 of all the bytes in the header, excluding the checksum and crc32." 33 | types: 34 | config_words: 35 | seq: 36 | - id: config_words 37 | size: 8 38 | type: config_word 39 | repeat: eos 40 | config_word: 41 | seq: 42 | - id: type 43 | type: u1 44 | - id: info 45 | size-eos: true 46 | type: 47 | switch-on: type 48 | cases: 49 | 0xcc: write_data 50 | types: 51 | write_data: 52 | seq: 53 | - id: size 54 | type: u1 55 | - id: addr 56 | type: u2 57 | - id: value 58 | type: 59 | switch-on: size 60 | cases: 61 | 1: u1 62 | 2: u2 63 | 4: u4 64 | body: 65 | seq: 66 | - id: len 67 | type: 68 | switch-on: _parent.header.magic 69 | cases: 70 | '"U2104_RCFG"': u2 71 | '"2104B_RCFG"': u2 72 | '"2114A_RCFG"': u2 73 | '"2214A_RCFG"': u4 74 | '"2324A_RCFG"': u4 75 | - id: firmware 76 | size: len 77 | type: firmware 78 | - id: magic 79 | size: 8 80 | type: str 81 | encoding: ascii 82 | - id: checksum 83 | type: u1 84 | doc: "A uint8 sum of all the bytes in body.firmware." 85 | - id: crc32 86 | type: u4 87 | doc: "A CRC32 of all the bytes in body.firmware." 88 | - id: signature 89 | type: signature 90 | if: _parent.header.magic == "2324A_RCFG" 91 | doc: "An encrypted SHA-256 hash of body.firmware." 92 | types: 93 | firmware: 94 | seq: 95 | - id: code 96 | size-eos: true 97 | instances: 98 | version: 99 | pos: 0x80 100 | size: 6 101 | magic: 102 | pos: 0x87 103 | size: 8 104 | type: str 105 | encoding: ascii 106 | signature: 107 | instances: 108 | encoded_start: 109 | pos: _root.header.len + 5 + _root.body.len + 17 + 1 110 | type: u1 111 | encoded_15180_high_nybble: 112 | pos: _root.header.len + 5 + _root.body.len + 17 + 2 113 | type: u1 114 | actual_start: 115 | value: '(encoded_start >> 1) & 0x3f' 116 | actual_15180_high_nybble: 117 | value: '(encoded_15180_high_nybble << 2) & 0xf0' 118 | data: 119 | pos: _root.header.len + 5 + _root.body.len + 17 + actual_start 120 | size: 0x20 121 | -------------------------------------------------------------------------------- /tools/asm_tool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | # asm_tool.py - A library for interacting with ASMedia USB host controllers. 5 | # Copyright (C) 2021-2022, 2024-2025 Forest Crossman 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | import argparse 22 | import mmap 23 | import os 24 | import struct 25 | import time 26 | 27 | 28 | class BusError(Exception): 29 | pass 30 | 31 | class MmapError(Exception): 32 | pass 33 | 34 | class PciDev: 35 | '''Lightweight abstraction over the PCI userspace API''' 36 | 37 | width_map = { 38 | 1: 'b', 39 | 2: 'w', 40 | 4: 'l', 41 | } 42 | 43 | struct_map = { 44 | 1: 'B', 45 | 2: ' None: 50 | self.dbsf = dbsf 51 | self.debug = debug 52 | self.verbose = debug or verbose 53 | self.auto_unbind = auto_unbind 54 | self._config = open("/sys/bus/pci/devices/{}/config".format(self.dbsf), "r+b", buffering=0) 55 | 56 | # Check bus status. 57 | if self.config_reg_read(0, 4) == 0xffffffff: 58 | raise BusError("Can't access device.") 59 | 60 | self.vid = int(open("/sys/bus/pci/devices/{}/vendor".format(self.dbsf), "r").read().rstrip('\n'), 16) 61 | self.did = int(open("/sys/bus/pci/devices/{}/device".format(self.dbsf), "r").read().rstrip('\n'), 16) 62 | 63 | self._mmap: mmap.mmap | None = None 64 | 65 | def _mmap_init(self) -> None: 66 | if self.auto_unbind: 67 | # Try to unbind the kernel driver if it's attached. 68 | self.driver_unbind() 69 | 70 | fd = os.open('/sys/bus/pci/devices/{}/resource0'.format(self.dbsf), os.O_RDWR) 71 | 72 | try: 73 | self._mmap = mmap.mmap(fd, 0) 74 | except OSError as error: 75 | if error.errno != 22: 76 | raise error 77 | 78 | raise MmapError("Failed to mmap BAR0--you may need to unbind the kernel driver for this device.") 79 | 80 | def driver_unbind(self) -> None: 81 | try: 82 | open("/sys/bus/pci/devices/{}/driver/unbind".format(self.dbsf), "wb").write(self.dbsf.encode('utf-8')) 83 | except FileNotFoundError: 84 | # If the file doesn't exist, then the driver isn't attached. 85 | pass 86 | 87 | def config_reg_read(self, reg: int, width: int) -> int: 88 | if width not in self.struct_map.keys(): 89 | raise ValueError("Invalid width: {}".format(width)) 90 | 91 | if self.debug: 92 | print("PciDev.config_reg_read: Reading {} bytes from {:#x}...".format(width, reg)) 93 | 94 | self._config.seek(reg) 95 | raw = self._config.read(width) 96 | assert len(raw) == width 97 | value = struct.unpack(self.struct_map[width], raw)[0] 98 | 99 | if self.debug: 100 | print("PciDev.config_reg_read: Read: {:#x}".format(value)) 101 | 102 | return value 103 | 104 | def config_reg_write(self, reg: int, width: int, value: int, confirm: bool = False) -> None: 105 | if width not in self.struct_map.keys(): 106 | raise ValueError("Invalid width: {}".format(width)) 107 | 108 | if self.debug: 109 | print("PciDev.config_reg_write: Writing {} bytes of {:#x} to {:#x}...".format(width, value, reg)) 110 | 111 | self._config.seek(reg) 112 | self._config.write(struct.pack(self.struct_map[width], value)) 113 | self._config.flush() 114 | 115 | # If "confirm" is set, repeatedly read the register until its contents 116 | # match the value written. 117 | if confirm: 118 | while self.config_reg_read(reg, width) != value: 119 | continue 120 | 121 | def bar0_reg_read(self, reg: int, width: int) -> int: 122 | if width not in self.struct_map.keys(): 123 | raise ValueError("Invalid width: {}".format(width)) 124 | 125 | if self.debug: 126 | print("PciDev.bar0_reg_read: Reading {} bytes from {:#x}...".format(width, reg)) 127 | 128 | if self._mmap is None: 129 | self._mmap_init() 130 | 131 | # Reads need to be performed in one transaction, but 132 | # struct.unpack_from performs one read for every byte. Work around 133 | # this limitation by performing the read and unpacking the value in 134 | # two separate steps. 135 | value = struct.unpack(self.struct_map[width], self._mmap[reg:reg+width])[0] # type: ignore[index] 136 | 137 | if self.debug: 138 | print("PciDev.bar0_reg_read: Read: {:#x}".format(value)) 139 | 140 | return value 141 | 142 | def bar0_reg_write(self, reg: int, width: int, value: int, confirm: bool = False) -> None: 143 | if width not in self.struct_map.keys(): 144 | raise ValueError("Invalid width: {}".format(width)) 145 | 146 | if self.debug: 147 | print("PciDev.bar0_reg_write: Writing {} bytes of {:#x} to {:#x}...".format(width, value, reg)) 148 | 149 | if self._mmap is None: 150 | self._mmap_init() 151 | 152 | # Writes need to be performed in one transaction, but struct.pack_into 153 | # performs one write for every byte. Work around this limitation by 154 | # packing the value and performing the write in two separate steps. 155 | data = struct.pack(self.struct_map[width], value) 156 | self._mmap[reg:reg+width] = data # type: ignore[index] 157 | 158 | # If "confirm" is set, repeatedly read the register until its contents 159 | # match the value written. 160 | if confirm: 161 | while self.bar0_reg_read(reg, width) != value: 162 | continue 163 | 164 | class AsmDev: 165 | width_map = { 166 | 1: 'b', 167 | 2: 'w', 168 | 4: 'l', 169 | } 170 | 171 | struct_map = { 172 | 1: 'B', 173 | 2: ' None: 230 | self.debug = debug 231 | self.verbose = debug or verbose 232 | self.pci = PciDev(dbsf, debug, verbose) 233 | 234 | vid = self.pci.vid 235 | did = self.pci.did 236 | if (vid, did) not in self.ids_map.keys(): 237 | raise KeyError("Unrecognized PCI VID:DID pair: {:04x}:{:04x}".format(vid, did)) 238 | 239 | self.chip = self.ids_map[(vid, did)] 240 | self.name = self.chip['name'] # type: ignore[index] 241 | self.hw_code_and_mmio = self.chip.get('hw_code_and_mmio', None) # type: ignore[attr-defined] 242 | 243 | def hw_code_write(self, addr: int, code: bytes) -> None: 244 | if self.hw_code_and_mmio not in (1, 2): 245 | raise ValueError("{} is not capable of hardware CODE access.".format(self.name)) 246 | 247 | if not (addr >= 0 and addr <= 0xfffe): 248 | raise ValueError("Invalid address, must be >= 0x0000 and <= 0xFFFE: {:#x}".format(addr)) 249 | 250 | if addr % 2 != 0: 251 | raise ValueError("Invalid address, must be 2-byte aligned: 0x{:04x}".format(addr)) 252 | 253 | code_size_limit = 0x10000 254 | if self.hw_code_and_mmio == 2: 255 | code_size_limit = 0x18000 256 | 257 | if len(code) > code_size_limit: 258 | raise ValueError("Invalid code length, must be less than {:#x}: {:#x}".format(code_size_limit, len(code))) 259 | 260 | if len(code) % 2 != 0: 261 | raise ValueError("Invalid code length, must be a multiple of 2: 0x{:04x}".format(len(code))) 262 | 263 | # Enable hardware CODE write access. 264 | if self.hw_code_and_mmio == 1: 265 | reg_F343 = self.hw_mmio_reg_read(0xF343, 1) 266 | self.hw_mmio_reg_write(0xF343, 1, reg_F343 | (1 << 1), confirm=True) 267 | elif self.hw_code_and_mmio == 2: 268 | reg_1500E = self.hw_mmio_reg_read(0x1500E, 1) 269 | self.hw_mmio_reg_write(0x1500E, 1, reg_1500E | (1 << 0), confirm=True) 270 | self.pci.config_reg_write(0xef, 1, 1 << 7, confirm=True) 271 | 272 | # Write to CODE memory. 273 | if self.hw_code_and_mmio == 1: 274 | for i, (word,) in enumerate(struct.iter_unpack('> 1) | (offset_addr & 0x7ffe) 300 | self.pci.config_reg_write(self.CODE_RAM_ADDR, 2, masked_addr, confirm=True) 301 | self.pci.bar0_reg_write(self.CODE_RAM_WRITE_DATA_BAR0, 4, data) 302 | while self.pci.config_reg_read(self.CODE_RAM_ADDR, 2) == masked_addr: 303 | pass 304 | 305 | i += 2 306 | 307 | if i & 0x8000: 308 | i -= 0x8000 309 | i += 0x10000 310 | 311 | # Enable hardware CODE read access. 312 | self.pci.config_reg_write(0xef, 1, self.pci.config_reg_read(0xef, 1) | (1 << 6), confirm=True) 313 | 314 | # Read back firmware 315 | readback = bytearray(len(code)) 316 | i = 0 317 | while i < len(code): 318 | offset_addr = addr + i 319 | masked_addr = ((offset_addr & 0x10000) >> 1) | (offset_addr & 0x7ffe) 320 | self.pci.config_reg_write(self.CODE_RAM_ADDR, 2, masked_addr, confirm=True) 321 | self.pci.bar0_reg_write(self.CODE_RAM_WRITE_DATA_BAR0, 4, 0) 322 | while self.pci.config_reg_read(self.CODE_RAM_ADDR, 2) == masked_addr: 323 | pass 324 | 325 | word_ev, word_od = struct.unpack(' None: 359 | if self.hw_code_and_mmio not in (1, 2): 360 | raise ValueError("{} is not capable of hardware CODE access.".format(self.name)) 361 | 362 | code_size_limit = 0x10000 363 | if self.hw_code_and_mmio == 2: 364 | code_size_limit = 0x18000 365 | 366 | if len(code) > code_size_limit: 367 | raise ValueError("Invalid code length, must be less than {:#x}: {:#x}".format(code_size_limit, len(code))) 368 | 369 | if len(code) % 2 != 0: 370 | raise ValueError("Invalid code length, must be a multiple of 2: 0x{:04x}".format(len(code))) 371 | 372 | if self.hw_code_and_mmio == 1: 373 | cpu_mode_next = self.CPU_MODE_NEXT_64K 374 | cpu_exec_ctrl = self.CPU_EXEC_CTRL_64K 375 | elif self.hw_code_and_mmio == 2: 376 | cpu_mode_next = self.CPU_MODE_NEXT_128K 377 | cpu_exec_ctrl = self.CPU_EXEC_CTRL_128K 378 | 379 | # Halt the CPU. 380 | self.hw_mmio_reg_write(cpu_exec_ctrl, 1, 1 << 1) 381 | 382 | # Write the program to CODE RAM. 383 | self.hw_code_write(0x0000, code) 384 | 385 | # Configure CPU to boot from CODE RAM. 386 | self.hw_mmio_reg_write(cpu_mode_next, 1, ((1 if half_speed else 0) << 1) | 1) 387 | 388 | # Release the CPU from reset. 389 | self.hw_mmio_reg_write(cpu_exec_ctrl, 1, 0) 390 | 391 | def hw_mmio_reg_read(self, addr: int, width: int) -> int: 392 | if self.hw_code_and_mmio not in (1, 2): 393 | raise ValueError("{} is not capable of hardware MMIO access.".format(self.name)) 394 | 395 | if width not in self.struct_map.keys(): 396 | raise ValueError("Invalid width: {}".format(width)) 397 | 398 | if self.debug: 399 | print("AsmDev.hw_mmio_reg_read: Reading {} bytes from {:#x}...".format(width, addr)) 400 | 401 | value = 0 402 | for i in range(width): 403 | byte_addr = (addr + i) & 0xffff 404 | if self.hw_code_and_mmio == 1: 405 | self.pci.config_reg_write(self.MMIO_ACCESS_ADDR, 2, byte_addr, confirm=True) 406 | time.sleep(0.0001) 407 | byte_value = self.pci.config_reg_read(self.MMIO_ACCESS_READ_DATA, 1) 408 | time.sleep(0.0001) 409 | elif self.hw_code_and_mmio == 2: 410 | while self.pci.bar0_reg_read(self.MMIO_ACCESS_STATUS_BAR0, 1) & (1 << 7): 411 | pass 412 | self.pci.bar0_reg_write(self.MMIO_ACCESS_ADDR_BAR0, 2, byte_addr) 413 | while self.pci.bar0_reg_read(self.MMIO_ACCESS_STATUS_BAR0, 1) & (1 << 7): 414 | pass 415 | byte_value = self.pci.bar0_reg_read(self.MMIO_ACCESS_READ_DATA_BAR0, 1) 416 | 417 | value |= byte_value << (8 * i) 418 | 419 | if self.debug: 420 | print("AsmDev.hw_mmio_reg_read: Read: {:#x}".format(value)) 421 | 422 | return value 423 | 424 | def hw_mmio_reg_write(self, addr: int, width: int, value: int, confirm: bool = False) -> None: 425 | if self.hw_code_and_mmio not in (1, 2): 426 | raise ValueError("{} is not capable of hardware MMIO access.".format(self.name)) 427 | 428 | if width not in self.struct_map.keys(): 429 | raise ValueError("Invalid width: {}".format(width)) 430 | 431 | if self.debug: 432 | print("AsmDev.hw_mmio_reg_write: Writing {} bytes of {:#x} to {:#x}...".format(width, value, addr)) 433 | 434 | for i in range(width): 435 | byte_addr = (addr + i) & 0xffff 436 | byte_value = (value >> (8 * i)) & 0xff 437 | if self.hw_code_and_mmio == 1: 438 | self.pci.config_reg_write(self.MMIO_ACCESS_ADDR, 2, byte_addr, confirm=True) 439 | time.sleep(0.0001) 440 | self.pci.config_reg_write(self.MMIO_ACCESS_WRITE_DATA, 1, byte_value) 441 | time.sleep(0.0001) 442 | elif self.hw_code_and_mmio == 2: 443 | self.pci.bar0_reg_write(self.MMIO_ACCESS_ADDR_BAR0, 2, byte_addr) 444 | while self.pci.bar0_reg_read(self.MMIO_ACCESS_STATUS_BAR0, 1) & (1 << 7): 445 | pass 446 | self.pci.bar0_reg_write(self.MMIO_ACCESS_WRITE_DATA_BAR0, 1, byte_value) 447 | while self.pci.bar0_reg_read(self.MMIO_ACCESS_STATUS_BAR0, 1) & (1 << 7): 448 | pass 449 | 450 | # If "confirm" is set, repeatedly read the register until its contents 451 | # match the value written. 452 | if confirm: 453 | while self.hw_mmio_reg_read(addr, width) != value: 454 | continue 455 | 456 | 457 | def main() -> None: 458 | parser = argparse.ArgumentParser() 459 | parser.add_argument("dbsf", type=str, help="The \"::.\" for the ASMedia USB 3 host controller.") 460 | args = parser.parse_args() 461 | 462 | dev = AsmDev(args.dbsf) 463 | dev.pci.auto_unbind = True 464 | print("Chip: {}".format(dev.name)) 465 | 466 | 467 | if __name__ == "__main__": 468 | main() 469 | -------------------------------------------------------------------------------- /tools/asmedia-xhc-trace/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /tools/asmedia-xhc-trace/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.3.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is-terminal", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.0.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "1.0.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" 49 | dependencies = [ 50 | "anstyle", 51 | "windows-sys", 52 | ] 53 | 54 | [[package]] 55 | name = "asmedia-xhc-trace" 56 | version = "0.1.0" 57 | dependencies = [ 58 | "clap", 59 | "memmap", 60 | ] 61 | 62 | [[package]] 63 | name = "bitflags" 64 | version = "1.3.2" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 67 | 68 | [[package]] 69 | name = "cc" 70 | version = "1.0.79" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 73 | 74 | [[package]] 75 | name = "clap" 76 | version = "4.2.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" 79 | dependencies = [ 80 | "clap_builder", 81 | "clap_derive", 82 | "once_cell", 83 | ] 84 | 85 | [[package]] 86 | name = "clap_builder" 87 | version = "4.2.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" 90 | dependencies = [ 91 | "anstream", 92 | "anstyle", 93 | "bitflags", 94 | "clap_lex", 95 | "strsim", 96 | ] 97 | 98 | [[package]] 99 | name = "clap_derive" 100 | version = "4.2.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" 103 | dependencies = [ 104 | "heck", 105 | "proc-macro2", 106 | "quote", 107 | "syn", 108 | ] 109 | 110 | [[package]] 111 | name = "clap_lex" 112 | version = "0.4.1" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" 115 | 116 | [[package]] 117 | name = "colorchoice" 118 | version = "1.0.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 121 | 122 | [[package]] 123 | name = "errno" 124 | version = "0.3.1" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" 127 | dependencies = [ 128 | "errno-dragonfly", 129 | "libc", 130 | "windows-sys", 131 | ] 132 | 133 | [[package]] 134 | name = "errno-dragonfly" 135 | version = "0.1.2" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 138 | dependencies = [ 139 | "cc", 140 | "libc", 141 | ] 142 | 143 | [[package]] 144 | name = "heck" 145 | version = "0.4.1" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 148 | 149 | [[package]] 150 | name = "hermit-abi" 151 | version = "0.3.1" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 154 | 155 | [[package]] 156 | name = "io-lifetimes" 157 | version = "1.0.10" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" 160 | dependencies = [ 161 | "hermit-abi", 162 | "libc", 163 | "windows-sys", 164 | ] 165 | 166 | [[package]] 167 | name = "is-terminal" 168 | version = "0.4.7" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" 171 | dependencies = [ 172 | "hermit-abi", 173 | "io-lifetimes", 174 | "rustix", 175 | "windows-sys", 176 | ] 177 | 178 | [[package]] 179 | name = "libc" 180 | version = "0.2.141" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" 183 | 184 | [[package]] 185 | name = "linux-raw-sys" 186 | version = "0.3.1" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" 189 | 190 | [[package]] 191 | name = "memmap" 192 | version = "0.7.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" 195 | dependencies = [ 196 | "libc", 197 | "winapi", 198 | ] 199 | 200 | [[package]] 201 | name = "once_cell" 202 | version = "1.17.1" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 205 | 206 | [[package]] 207 | name = "proc-macro2" 208 | version = "1.0.56" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 211 | dependencies = [ 212 | "unicode-ident", 213 | ] 214 | 215 | [[package]] 216 | name = "quote" 217 | version = "1.0.26" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 220 | dependencies = [ 221 | "proc-macro2", 222 | ] 223 | 224 | [[package]] 225 | name = "rustix" 226 | version = "0.37.11" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" 229 | dependencies = [ 230 | "bitflags", 231 | "errno", 232 | "io-lifetimes", 233 | "libc", 234 | "linux-raw-sys", 235 | "windows-sys", 236 | ] 237 | 238 | [[package]] 239 | name = "strsim" 240 | version = "0.10.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 243 | 244 | [[package]] 245 | name = "syn" 246 | version = "2.0.15" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" 249 | dependencies = [ 250 | "proc-macro2", 251 | "quote", 252 | "unicode-ident", 253 | ] 254 | 255 | [[package]] 256 | name = "unicode-ident" 257 | version = "1.0.8" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 260 | 261 | [[package]] 262 | name = "utf8parse" 263 | version = "0.2.1" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 266 | 267 | [[package]] 268 | name = "winapi" 269 | version = "0.3.9" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 272 | dependencies = [ 273 | "winapi-i686-pc-windows-gnu", 274 | "winapi-x86_64-pc-windows-gnu", 275 | ] 276 | 277 | [[package]] 278 | name = "winapi-i686-pc-windows-gnu" 279 | version = "0.4.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 282 | 283 | [[package]] 284 | name = "winapi-x86_64-pc-windows-gnu" 285 | version = "0.4.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 288 | 289 | [[package]] 290 | name = "windows-sys" 291 | version = "0.48.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 294 | dependencies = [ 295 | "windows-targets", 296 | ] 297 | 298 | [[package]] 299 | name = "windows-targets" 300 | version = "0.48.0" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 303 | dependencies = [ 304 | "windows_aarch64_gnullvm", 305 | "windows_aarch64_msvc", 306 | "windows_i686_gnu", 307 | "windows_i686_msvc", 308 | "windows_x86_64_gnu", 309 | "windows_x86_64_gnullvm", 310 | "windows_x86_64_msvc", 311 | ] 312 | 313 | [[package]] 314 | name = "windows_aarch64_gnullvm" 315 | version = "0.48.0" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 318 | 319 | [[package]] 320 | name = "windows_aarch64_msvc" 321 | version = "0.48.0" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 324 | 325 | [[package]] 326 | name = "windows_i686_gnu" 327 | version = "0.48.0" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 330 | 331 | [[package]] 332 | name = "windows_i686_msvc" 333 | version = "0.48.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 336 | 337 | [[package]] 338 | name = "windows_x86_64_gnu" 339 | version = "0.48.0" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 342 | 343 | [[package]] 344 | name = "windows_x86_64_gnullvm" 345 | version = "0.48.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 348 | 349 | [[package]] 350 | name = "windows_x86_64_msvc" 351 | version = "0.48.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 354 | -------------------------------------------------------------------------------- /tools/asmedia-xhc-trace/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asmedia-xhc-trace" 3 | authors = ["cyrozap"] 4 | license = "GPL-3.0-or-later" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [dependencies] 9 | clap = { version = "4.2.2", features = ["derive"] } 10 | memmap = "0.7.0" 11 | -------------------------------------------------------------------------------- /tools/asmedia-xhc-trace/src/main.rs: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-3.0-or-later */ 2 | 3 | /* 4 | * Copyright (C) 2020, 2023 Forest Crossman 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | use std::collections::HashMap; 21 | use std::fs::File; 22 | use std::fs::OpenOptions; 23 | use std::io::prelude::*; 24 | use std::io::SeekFrom; 25 | use std::process::exit; 26 | use std::time::Instant; 27 | 28 | use clap::Parser; 29 | use memmap::{MmapMut, MmapOptions}; 30 | 31 | #[derive(Clone)] 32 | struct DeviceInfo { 33 | name: String, 34 | has_config: bool, 35 | has_mmio: bool, 36 | } 37 | 38 | impl DeviceInfo { 39 | fn new(name: &str, has_config: bool, has_mmio: bool) -> Self { 40 | Self { 41 | name: name.to_string(), 42 | has_config, 43 | has_mmio, 44 | } 45 | } 46 | } 47 | 48 | struct PciConfig { 49 | regs: File, 50 | } 51 | 52 | impl PciConfig { 53 | fn new(device_address: &str) -> Result { 54 | let regs = OpenOptions::new() 55 | .read(true) 56 | .write(true) 57 | .open(format!("/sys/bus/pci/devices/{}/config", &device_address))?; 58 | Ok(Self { regs }) 59 | } 60 | 61 | fn readl(&mut self, reg: u16) -> Result { 62 | self.regs.seek(SeekFrom::Start(reg.into()))?; 63 | let mut buf: [u8; 4] = [0; 4]; 64 | self.regs.read_exact(&mut buf)?; 65 | Ok(u32::from_le_bytes(buf)) 66 | } 67 | 68 | fn writel(&mut self, reg: u16, value: u32) -> Result<(), std::io::Error> { 69 | self.regs.seek(SeekFrom::Start(reg.into()))?; 70 | self.regs.write_all(&value.to_le_bytes()) 71 | } 72 | } 73 | 74 | struct PciBar0 { 75 | regs: MmapMut, 76 | } 77 | 78 | impl PciBar0 { 79 | fn new(dbsf: &str) -> Result { 80 | let file = OpenOptions::new() 81 | .read(true) 82 | .write(true) 83 | .open(format!("/sys/bus/pci/devices/{}/resource0", &dbsf))?; 84 | let regs = unsafe { MmapOptions::new().map_mut(&file)? }; 85 | Ok(Self { regs }) 86 | } 87 | 88 | fn readw(&mut self, reg: usize) -> u16 { 89 | u16::from_le_bytes(self.regs[reg..reg + 2].try_into().unwrap()) 90 | } 91 | } 92 | 93 | #[derive(Parser, Debug)] 94 | #[command(author, version, about, long_about = None)] 95 | struct Args { 96 | /// Trigger a device reset before tracing 97 | #[arg(short, long, default_value_t = false)] 98 | reset: bool, 99 | 100 | /// The number of samples to take 101 | #[arg(short = 'c', long, default_value_t = 1_000_000)] 102 | samples: usize, 103 | 104 | /// Unbind the device driver, if bound 105 | #[arg(short, long, default_value_t = false)] 106 | unbind: bool, 107 | 108 | /// The "::." for the ASMedia USB 3 host controller 109 | dbsf: String, 110 | } 111 | 112 | fn get_u16_from_file(path: &str) -> Result { 113 | let mut value: u16 = 0; 114 | let mut file = File::open(path)?; 115 | file.seek(SeekFrom::Start(2))?; 116 | let mut buf: [u8; 4] = [0; 4]; 117 | file.read_exact(&mut buf)?; 118 | for b in buf.iter() { 119 | let v: u8 = if (b'0'..=b'9').contains(b) { 120 | b - b'0' 121 | } else if (b'A'..=b'F').contains(b) { 122 | b - b'A' + 0xa 123 | } else if (b'a'..=b'f').contains(b) { 124 | b - b'a' + 0xa 125 | } else { 126 | panic!("Invalid hex char: {:#04x}", b); 127 | }; 128 | value <<= 4; 129 | value |= >::into(v); 130 | } 131 | Ok(value) 132 | } 133 | 134 | fn main() { 135 | let args = Args::parse(); 136 | 137 | let device_info_map = HashMap::from([ 138 | ((0x1b21, 0x1042), DeviceInfo::new("ASM1042", false, false)), 139 | ((0x1b21, 0x1142), DeviceInfo::new("ASM1042A", true, true)), 140 | ((0x1b21, 0x1242), DeviceInfo::new("ASM1142", true, true)), 141 | ( 142 | (0x1b21, 0x2142), 143 | DeviceInfo::new("ASM2142/ASM3142", false, true), 144 | ), 145 | ((0x1b21, 0x3242), DeviceInfo::new("ASM3242", false, true)), 146 | ]); 147 | 148 | let vid = match get_u16_from_file(&format!("/sys/bus/pci/devices/{}/vendor", args.dbsf)) { 149 | Ok(id) => id, 150 | Err(err) => { 151 | eprintln!("Error: Failed to read PCI VID: {:?}", err); 152 | exit(1); 153 | } 154 | }; 155 | let did = match get_u16_from_file(&format!("/sys/bus/pci/devices/{}/device", args.dbsf)) { 156 | Ok(id) => id, 157 | Err(err) => { 158 | eprintln!("Error: Failed to read PCI DID: {:?}", err); 159 | exit(1); 160 | } 161 | }; 162 | 163 | let device_info = match device_info_map.get(&(vid, did)) { 164 | Some(info) => info.clone(), 165 | None => DeviceInfo::new("Unknown", false, false), 166 | }; 167 | 168 | println!("Device: {} ({:04x}:{:04x})", device_info.name, vid, did); 169 | 170 | if !(device_info.has_config || device_info.has_mmio) { 171 | eprintln!("Error: Device is not supported."); 172 | exit(1); 173 | } 174 | 175 | let driver_is_bound: bool = match OpenOptions::new() 176 | .write(true) 177 | .open(format!("/sys/bus/pci/devices/{}/driver/unbind", args.dbsf)) 178 | { 179 | Ok(mut file) => { 180 | if args.unbind { 181 | match file.write_all(args.dbsf.as_bytes()) { 182 | Ok(_) => false, 183 | Err(err) => { 184 | eprintln!("Error: Failed to unbind driver: {}", err); 185 | exit(1); 186 | } 187 | } 188 | } else { 189 | true 190 | } 191 | } 192 | Err(err) => { 193 | if err.kind() == std::io::ErrorKind::NotFound { 194 | false 195 | } else { 196 | eprintln!("Error: Failed to unbind driver: {}", err); 197 | exit(1); 198 | } 199 | } 200 | }; 201 | 202 | if driver_is_bound && !device_info.has_config { 203 | eprintln!("Error: Can't read PC from device: A driver is bound to the device and the device doesn't support falling back to PCI config access."); 204 | exit(1); 205 | } 206 | 207 | let mut statuses: Vec = Vec::with_capacity(args.samples); 208 | let elapsed = if driver_is_bound || !device_info.has_mmio { 209 | let mut config = match PciConfig::new(&args.dbsf) { 210 | Ok(c) => c, 211 | Err(err) => { 212 | eprintln!("Error: Failed to initialize PciConfig: {:?}", err); 213 | exit(1); 214 | } 215 | }; 216 | if args.reset { 217 | println!("Resetting device..."); 218 | match config.writel(0xec, 1 << 31) { 219 | Ok(_) => (), 220 | Err(err) => { 221 | eprintln!("Error: Failed to set reset flag: {:?}", err); 222 | exit(1); 223 | } 224 | } 225 | match config.writel(0xec, 0) { 226 | Ok(_) => (), 227 | Err(err) => { 228 | eprintln!("Error: Failed to clear reset flag: {:?}", err); 229 | exit(1); 230 | } 231 | } 232 | println!("Reset complete!"); 233 | } 234 | let now = Instant::now(); 235 | for _ in 0..statuses.capacity() { 236 | match config.readl(0xe4) { 237 | Ok(val) => statuses.push(val), 238 | Err(err) => { 239 | eprintln!("Error: Failed to read status: {:?}", err); 240 | exit(1); 241 | } 242 | } 243 | } 244 | now.elapsed().as_micros() 245 | } else { 246 | let mut bar0 = match PciBar0::new(&args.dbsf) { 247 | Ok(c) => c, 248 | Err(err) => { 249 | eprintln!("Error: Failed to initialize PciBar0: {:?}", err); 250 | exit(1); 251 | } 252 | }; 253 | let now = Instant::now(); 254 | for _ in 0..statuses.capacity() { 255 | statuses.push(bar0.readw(0x300a).into()); 256 | } 257 | now.elapsed().as_micros() 258 | }; 259 | 260 | println!( 261 | "Logged {} statuses in {}.{:06} seconds ({} statuses per second)", 262 | statuses.len(), 263 | elapsed / 1_000_000, 264 | elapsed % 1_000_000, 265 | ((statuses.len() as u128) * 1_000_000) / elapsed 266 | ); 267 | for val in statuses.iter() { 268 | println!("{:#06x}", val & 0xffff); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /tools/bug_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | # bug_demo.py - A tool to demonstrate hardware bugs in ASMedia USB host 5 | # controllers. 6 | # Copyright (C) 2022, 2025 Forest Crossman 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | 22 | import argparse 23 | import sys 24 | 25 | from asm_tool import AsmDev 26 | 27 | 28 | def main() -> int: 29 | parser = argparse.ArgumentParser() 30 | parser.add_argument("dbsf", type=str, help="The \"::.\" for the ASMedia USB 3 host controller.") 31 | args = parser.parse_args() 32 | 33 | dev = AsmDev(args.dbsf) 34 | print("Chip: {}".format(dev.name)) 35 | 36 | if dev.hw_code_and_mmio == 1: 37 | cpu_mode_next = dev.CPU_MODE_NEXT_64K 38 | cpu_exec_ctrl = dev.CPU_EXEC_CTRL_64K 39 | crcr_addr_internal = 0xF638 40 | dcbaap_addr_internal = 0xF650 41 | elif dev.hw_code_and_mmio == 2: 42 | cpu_mode_next = dev.CPU_MODE_NEXT_128K 43 | cpu_exec_ctrl = dev.CPU_EXEC_CTRL_128K 44 | crcr_addr_internal = 0x18838 45 | dcbaap_addr_internal = 0x18850 46 | else: 47 | print("This chip does not support hardware-based MMIO.") 48 | return -1 49 | 50 | print("Unbinding the kernel driver if it's attached...") 51 | dev.pci.driver_unbind() 52 | 53 | # Put the 8051 in an infinite loop to prevent it from interfering. 54 | dev.hw_code_load_exec(b'\x80\xfe' * 100) 55 | 56 | # Calculate base addresses. 57 | operational_base = dev.pci.bar0_reg_read(0x0000, 1) 58 | _runtime_base = dev.pci.bar0_reg_read(0x0018, 1) 59 | 60 | def read_qword_internal(addr_internal) -> int: 61 | return (dev.hw_mmio_reg_read(addr_internal + 4, 4) << 32) | dev.hw_mmio_reg_read(addr_internal, 4) 62 | 63 | def write_qword_bar0(bar0_addr, value) -> None: 64 | dev.pci.bar0_reg_write(bar0_addr, 4, value & 0xffffffff) 65 | dev.pci.bar0_reg_write(bar0_addr + 4, 4, value >> 32) 66 | 67 | for name, opb_offset, addr_internal in (("CRCR", 0x18, crcr_addr_internal), ("DCBAAP", 0x30, dcbaap_addr_internal)): 68 | for value in (0, 0xffffffffffffffc0, 0x12345678abcdefc0): 69 | # Write the expected value to the proper address in BAR0. 70 | write_qword_bar0(operational_base + opb_offset, value) 71 | 72 | # Read the internal representation of the register. 73 | value_internal = read_qword_internal(addr_internal) 74 | 75 | print("{}: Expected 0x{:016x}, got 0x{:016x}: {}".format( 76 | name, value, value_internal, "OK" if value == value_internal else "ERROR: Internal value does not match what was written!")) 77 | 78 | # Reload firmware from flash. 79 | 80 | # Configure CPU to boot from CODE ROM. 81 | dev.hw_mmio_reg_write(cpu_mode_next, 1, 2) 82 | 83 | # Reset the CPU. 84 | dev.hw_mmio_reg_write(cpu_exec_ctrl, 1, 2) 85 | 86 | return 0 87 | 88 | 89 | if __name__ == "__main__": 90 | sys.exit(main()) 91 | -------------------------------------------------------------------------------- /tools/emulator/README.md: -------------------------------------------------------------------------------- 1 | # Thoughts on emulator implementation 2 | 3 | 4 | ## Why write an emulator? 5 | 6 | - Simplifies tracing 7 | - An emulator can show me every single instruction that gets executed 8 | - Breakpoints and watchpoints 9 | - Potential for reversible execution 10 | - "Reverse" semihosting 11 | - Emulate a binary on the host PC, but forward SFR and MMIO accesses to a monitor program running on real hardware 12 | - Helps with mapping SFR/MMIO accesses 13 | - Virtual peripherals can be built based on the behavior of their real counterparts 14 | - Enable rapid iteration in new firmware development 15 | 16 | 17 | ## Miscellaneous thoughts 18 | 19 | - Memories: 20 | - ASM1042/ASM1042A/ASM1142 21 | - 64 kB CODE ROM 22 | - 64 kB CODE RAM 23 | - 64 kB XDATA 24 | - 48 kB XRAM 25 | - 8 kB unused 26 | - 8 kB MMIO 27 | - ASM2142/ASM3142 28 | - 32 kB CODE ROM 29 | - 96 kB CODE RAM 30 | - 48 kB common bank 31 | - 3 × 16 kB switchable banks 32 | - 128 kB XDATA 33 | - 48 kB XRAM 34 | - 16 kB unused 35 | - 64 kB MMIO 36 | - ASM3242 37 | - 32 kB CODE ROM 38 | - 112 kB CODE RAM 39 | - 48 kB common bank 40 | - 4 × 16 kB switchable banks 41 | - 128 kB XDATA 42 | - 48 kB XRAM 43 | - 16 kB unused 44 | - 64 kB MMIO 45 | - Memory switching 46 | - All 47 | - CODE memory switching (ROM/RAM) on reset 48 | - XDATA points to CODE RAM when `PCON.MEMSEL` is set. 49 | - ASM2142/ASM3142 and ASM3242 50 | - XDATA bank switching with `DPX` (SFR 0x93) 51 | - CODE bank switching with `PSBANK`/`FMAP` (SFR 0x96) and jump instructions 52 | - MMIO memory map 53 | - Literally each model has a different one 54 | - ASM1042 55 | - ASM1042A 56 | - ASM1142 57 | - ASM2142/ASM3142 58 | - ASM3242 59 | - Should be able to pass asynchronous interrupts from external sources (e.g., 60 | emulated peripherals, a real microcontroller running a monitor that forwards 61 | interrupts over serial to the emulator host, etc.) to the emulated core. 62 | - Needs to be able to handle async inputs from several different sources. 63 | - Virtual Timer Interrupts + MMIO 64 | - Virtual UART Interrupts + MMIO + FIFOs 65 | - Virtual SPI Interrupts + MMIO + DMA 66 | - Virtual PCIe Interrupts + MMIO + DMA 67 | - (Future) Virtual USB Interrupts + MMIO + DMA 68 | - Separate cores for pure 8051, ASM1042/ASM1042A/ASM1142, and ASM2142/ASM3142/ASM3242. 69 | - Pure 8051 core 70 | - Handles the ISA 71 | - All instructions can be hooked 72 | - Pre/post-execution 73 | - Each instruction execution function returns `Result`. 74 | - The unused 0xA5 instruction returns `ExitReason::Unimpl` if it isn't handled by a hook. 75 | - Receives interrupts 76 | - Use message interface? 77 | - No SFR handling beyond the ones that are absolutely necessary 78 | - Core SFRs: 79 | - SP 80 | - DPL 81 | - DPH 82 | - IE 83 | - IP 84 | - PSW 85 | - ACC 86 | - B 87 | - Performance monitoring 88 | - Log stats of each instruction retired 89 | - Instruction X was executed Y times 90 | - Stats for conditional jumps should indicate whether they were taken or not. 91 | - Start/stop counting 92 | - Reset stats 93 | - Debugging 94 | - Breakpoints 95 | - Break before execution of the instruction at the specified address. 96 | - Implement with CODE bus access filters? 97 | - CODE bus can also be accessed with MOVC, so this won't work. 98 | - Bus watchpoints 99 | - Buses that should be able to be watched: 100 | - CODE 101 | - IRAM 102 | - BANK[0-3] 103 | - Bit-addressable registers 104 | - SFR 105 | - XDATA 106 | - Implement with filters? 107 | - Reversible execution? 108 | - Save/restore state? 109 | - Generate a new state, derived from the previous state, on every instruction execution? 110 | - State "FIFO"? 111 | - FIFO with capacity for a certain number of states 112 | - Configurable at runtime? 113 | - Allowed to be infinite? 114 | - Disable by setting capacity to zero? 115 | - Once the capacity limit is reached, old states start being discarded on each new execution. 116 | - Before every instruction "step", a clone of the current state is pushed into the FIFO. 117 | - Core is initialized by setting the current state. 118 | - All bus accesses can be filtered 119 | - Pre-read 120 | - Arguments: `addr: MemorySpaceAddress` 121 | - Returns `Result, ExitReason>` 122 | - Post-read 123 | - Arguments: `addr: MemorySpaceAddress, value_read: u8` 124 | - Returns `Result, ExitReason>` 125 | - Pre-write 126 | - Arguments: `addr: MemorySpaceAddress, value_to_write: u8` 127 | - Returns `Result, ExitReason>` 128 | - Post-write 129 | - Arguments: `addr: MemorySpaceAddress, value_written: u8` 130 | - Returns `Result<_, ExitReason>` 131 | - "Standard" 8051 core 132 | - Wraps pure 8051 core. 133 | - Implements UART, timers 134 | - Should timers use wall clock time or instruction-based timing? 135 | - Wall clock time based on specified frequency. 136 | - ASM1042/ASM1042A/ASM1142 core 137 | - Wraps pure 8051 core. 138 | - Handles CODE ROM/RAM switching at reset 139 | - Implemented as part of the `CPU_MODE_NEXT`/`CPU_MODE_CURRENT`/`CPU_EXEC_CTRL` MMIO registers? 140 | - Handles all "non-core" peripherals (in SFR and MMIO space) 141 | - Loading memory 142 | - Load into any memory space (CODE, IRAM, SFRs, XDATA) 143 | - Writes to all memory spaces are treated as writes to the reset values of those memory cells 144 | - Each memory space has a shadow space 145 | - The main space contains the current values of the cells in that space 146 | - The shadow space contains the reset values of the cells in that space 147 | - The shadow space is copied to the main space when the core is reset 148 | - For SFRs and XDATA MMIOs, a "reset with value" method for the SFR/MMIO is called with 149 | the shadow space data at the corresponding address passed as an argument 150 | - This works for SFRs and XDATA MMIOs where the SFR/MMIO has a reset value 151 | - Works for SP, DPTR, SBUF, etc. 152 | - Doesn't work for values that are updated dynamically, like I/O port SFRs 153 | - Load from multiple files into arbitrary memory regions 154 | - Loaded in the order they were passed on the command line 155 | - Later writes overwrite previous writes to the same addresses 156 | - Both raw binary files and .ihx images should be supported 157 | - Both raw binary images and .ihx images support being loaded at an offset 158 | - The offset is zero if left unspecified 159 | - An error is thrown if the load ends up writing out of bounds 160 | - This can happen when a 64kB raw binary is loaded at a non-zero offset, 161 | or when the memory address in a .ihx file plus the offset is out of bounds 162 | - Options to fill memory 163 | - Fill with byte 164 | - Fill with zeros (default, shortcut for "fill with byte: 0") 165 | - Fill with 0xFF (shortcut for "fill with byte: 0xFF") 166 | - Fill with seeded random data 167 | - Random seed if not specified 168 | - Seed is printed when the emulator is run with this option 169 | - More advanced memory loading can be implemented with memory-like peripherals 170 | - Maybe all memory should be implemented with bus-based memory peripherals? 171 | - How should "bus conflicts" be handled? 172 | - Memory aliasing 173 | - Some 8051 implementations have parts of XDATA mapped into CODE space to enable executing from XRAM. 174 | - Many 8051 implementations have parts of their memory maps "mirrored" due to simplified address decoding. 175 | - Maybe the emulator should be structured like real hardware, with buses connecting the memories to the CPU? 176 | - Weird features seen in real 8051 implementations 177 | - Dual DPTRs 178 | - Auto-switching between DPTRs for, e.g., memcpy implementations. 179 | - DPTR auto-increment 180 | - Using I/O Port 2 and R0/R1 for paged access to XDATA. 181 | - Custom instructions 182 | - `0xA5` is undefined 183 | - Some implementations use `0xA5` as just one instruction, others use it as a prefix for multiple custom instructions. 184 | - SFR bank switching 185 | - Written in Rust, with Python bindings 186 | - Rust for correctness and speed 187 | - Python for ease of scripting 188 | - Should be able to do all setup/initialization of the emulator (e.g., loading binaries into memory, setting register values and PC, etc.) 189 | - Should be able to define new core variants in Python, as well as emulated peripherals and new instructions 190 | 191 | 192 | ## Serial monitor 193 | 194 | - Protocol 195 | - Binary only 196 | - Interrupts are disabled while transmitting/receiving data and executing commands 197 | - Only whole packets get received or transmitted. 198 | - Host doesn't send the next command until it receives the response from the command in flight. 199 | - Commands are at most 16 bytes long. 200 | - Host to device: 201 | - Commands: 202 | - Read/Write XDATA, non-critical SFRs 203 | - Client is responsible for not resetting the chip except through the reset command. 204 | - Get info 205 | - Chip ID 206 | - Chip version 207 | - Build date 208 | - Build hash 209 | - Reset 210 | - Enable/disable interrupts 211 | - Ping 212 | - Device to host: 213 | - Interrupt packets 214 | - Type: Interrupt 215 | - Data: Interrupt number (byte) 216 | - Response packets 217 | - Type: Response 218 | - Data: Response data (variable length, depends on what command it's in response to) 219 | - Interrupt process 220 | - An interrupt gets triggered 221 | - 8051 ISR for that interrupt executes 222 | - Interrupts are disabled 223 | - Interrupt packet is transmitted to the host 224 | - Interrupt flag for the triggered interrupt is cleared (if it isn't cleared by hardware) 225 | - Interrupts are enabled (masked with interrupt enable status) 226 | - Return from ISR 227 | -------------------------------------------------------------------------------- /tools/extract_promontory_fw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | # extract_promontory_fw.py - A tool to extract Promontory firmware images from other files. 5 | # Copyright (C) 2025 Forest Crossman 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | import argparse 22 | import struct 23 | import sys 24 | 25 | 26 | def checksum32(data: bytes) -> int: 27 | return sum(data) & 0xffffffff 28 | 29 | def find_and_extract_embedded_files(data: bytes, input_file: str, ignore_checksum: bool) -> int: 30 | count: int = 0 31 | offset: int = 0 32 | while offset < len(data): 33 | pos: int = data.find(b"_PT_", offset) 34 | if pos == -1: 35 | break 36 | 37 | # Check if the full header (4B magic, 4B len, 4B checksum) can be read 38 | if pos + 12 > len(data): 39 | offset = pos + 1 40 | continue 41 | 42 | # Skip if the length is less than the minimum 43 | length_value: int = struct.unpack_from(" len(data): 50 | offset = pos + 1 51 | continue 52 | 53 | fw_image: bytes = data[pos : pos + length_value] 54 | 55 | # Only extract if the checksum matches or we're ignoring the checksum 56 | valid: bool = ignore_checksum 57 | if not valid: 58 | stored_checksum: int = struct.unpack_from(" int: 73 | parser: argparse.ArgumentParser = argparse.ArgumentParser() 74 | parser.add_argument("-i", "--ignore-checksum", action="store_true", 75 | help="Ignore the Promontory firmware image checksum, and extract it even if it doesn't match.") 76 | parser.add_argument("input_file", help="Input binary file to search for embedded files") 77 | args: argparse.Namespace = parser.parse_args() 78 | 79 | try: 80 | with open(args.input_file, "rb") as f: 81 | data: bytes = f.read() 82 | except FileNotFoundError: 83 | print(f"Error: Could not open input file '{args.input_file}'.", file=sys.stderr) 84 | return 1 85 | 86 | files_extracted: int = find_and_extract_embedded_files(data, args.input_file, args.ignore_checksum) 87 | 88 | if files_extracted > 0: 89 | images: str = "image" if files_extracted == 1 else "images" 90 | print(f"Successfully extracted {files_extracted} Promontory firmware {images} from '{args.input_file}'.") 91 | return 0 92 | else: 93 | print(f"No Promontory firmware images were found in '{args.input_file}'.") 94 | return 1 95 | 96 | 97 | if __name__ == "__main__": 98 | sys.exit(main()) 99 | -------------------------------------------------------------------------------- /tools/generate_docs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | # generate_docs.py - A tool to generate XHTML documentation from a YAML file 5 | # containing register definitions. 6 | # Copyright (C) 2020-2023, 2025 Forest Crossman 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | 22 | import argparse 23 | import re 24 | import subprocess 25 | import sys 26 | from datetime import datetime 27 | 28 | import markdown # type: ignore[import-untyped] 29 | import yaml # type: ignore[import-untyped] 30 | from lxml import etree as ET # type: ignore[import-untyped] 31 | 32 | 33 | REGION_NAMES = { 34 | "pci": "PCI Configuration", 35 | "bar0": "PCI BAR0", 36 | "sfr": "SFR", 37 | "xdata": "XDATA", 38 | } 39 | 40 | PERMISSIONS = { 41 | "RO": "Read-Only", 42 | "RW": "Read and Write", 43 | "RW1C": "Read and Write 1 to Clear", 44 | "RW1S": "Read and Write 1 to Set", 45 | "RWNC": "Read and Write Non-zero to Clear", 46 | "WO": "Write-Only", 47 | } 48 | 49 | def validate(doc) -> bool: 50 | unknown_keys = set(doc.keys()).difference(set(['meta', 'xdata', 'registers'])) 51 | if unknown_keys: 52 | print("Error: Invalid keys in document: {}".format(unknown_keys)) 53 | return False 54 | 55 | xdata = doc.get('xdata', list()) 56 | if type(xdata) is not list: 57 | print("Error: \"xdata\" is not a list.") 58 | return False 59 | 60 | for i, region in enumerate(xdata): 61 | unknown_keys = set(region.keys()).difference(set(['name', 'start', 'end', 'permissions', 'notes'])) 62 | if unknown_keys: 63 | print("Error: Invalid keys in xdata[{}]: {}".format(i, unknown_keys)) 64 | return False 65 | 66 | registers = doc.get('registers', dict()) 67 | if type(registers) is not dict: 68 | print("Error: \"registers\" is not a dict.") 69 | return False 70 | 71 | for region in REGION_NAMES.keys(): 72 | region_registers = registers.get(region, list()) 73 | if type(region_registers) is not list: 74 | print("Error: Register region \"{}\" is not a list.".format(region)) 75 | return False 76 | 77 | for i, register in enumerate(region_registers): 78 | unknown_keys = set(register.keys()).difference(set(['name', 'start', 'end', 'permissions', 'bits', 'notes'])) 79 | if unknown_keys: 80 | print("Error: Invalid keys in registers.{}[{}]: {}".format(region, i, unknown_keys)) 81 | return False 82 | 83 | bits = register.get('bits', list()) 84 | if type(bits) is not list: 85 | print("Error: Register {}.{}.bits is not a list.".format(region, i)) 86 | return False 87 | 88 | for b, bit_range in enumerate(bits): 89 | unknown_keys = set(bit_range.keys()).difference(set(['name', 'start', 'end', 'permissions', 'notes'])) 90 | if unknown_keys: 91 | print("Error: Invalid keys in registers.{}[{}].bits[{}]: {}".format(region, i, b, unknown_keys)) 92 | return False 93 | 94 | return True 95 | 96 | def gen_css() -> str: 97 | style = ''' 98 | code { 99 | background-color: #dedede; 100 | padding-left: 5px; 101 | padding-right: 5px; 102 | padding-top: 2px; 103 | padding-bottom: 2px; 104 | border-radius: 3px; 105 | } 106 | table, th, td { 107 | border: 1px solid black; 108 | } 109 | td.bitfield { 110 | text-align: center; 111 | } 112 | td.bitfield-unused { 113 | background-color: #c0c0c0; 114 | } 115 | ''' 116 | return style 117 | 118 | def markdown_subelement(parent, tag, md) -> None: 119 | xhtml = markdown.markdown(md, output_format='xhtml') 120 | element = ET.fromstring("<{}>{}".format(tag, xhtml, tag)) 121 | parent.append(element) 122 | 123 | def gen_xhtml(filename, doc) -> bytes: 124 | html = ET.Element('html', { 125 | ET.QName("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation"): "http://www.w3.org/MarkUp/SCHEMA/xhtml11.xsd", 126 | ET.QName("http://www.w3.org/XML/1998/namespace", "lang"): "en", 127 | }, nsmap={None: "http://www.w3.org/1999/xhtml"}) 128 | 129 | head = ET.SubElement(html, 'head') 130 | body = ET.SubElement(html, 'body') 131 | 132 | meta = doc.get('meta', dict()) 133 | chip = meta.get('chip', "UNKNOWN") 134 | 135 | title = ET.SubElement(head, 'title') 136 | title.text = "{} Memory Map and Register Manual".format(chip) 137 | 138 | style = ET.SubElement(head, 'style') 139 | style.text = gen_css() 140 | 141 | heading = ET.SubElement(body, 'h1') 142 | heading.text = title.text 143 | 144 | ET.SubElement(body, 'h2').text = "XDATA Memory Map" 145 | xdata_memory_map = ET.SubElement(body, 'table') 146 | xdata_memory_map_header = ET.SubElement(xdata_memory_map, 'tr') 147 | for header in ["Start", "End", "Size", "Name", "Permissions", "Notes"]: 148 | ET.SubElement(xdata_memory_map_header, 'th').text = header 149 | 150 | xdata = doc.get('xdata', list()) 151 | for region in xdata: 152 | tr = ET.SubElement(xdata_memory_map, 'tr') 153 | start = region.get('start') 154 | start_text = "" 155 | if start is not None: 156 | start_text = "0x{:04X}".format(start) 157 | ET.SubElement(tr, 'td').text = start_text 158 | end = region.get('end') 159 | end_text = "" 160 | if end is not None: 161 | end_text = "0x{:04X}".format(end) 162 | ET.SubElement(tr, 'td').text = end_text 163 | size_text = "" 164 | if start is not None and end is not None: 165 | size_text = "0x{:04X}".format(end + 1 - start) 166 | ET.SubElement(tr, 'td').text = size_text 167 | ET.SubElement(tr, 'td').text = region.get('name', "") 168 | ET.SubElement(tr, 'td').text = region.get('permissions', "") 169 | markdown_subelement(tr, 'td', region.get('notes', "")) 170 | 171 | ET.SubElement(body, 'h2').text = "Register Permissions" 172 | permissions = ET.SubElement(body, 'table') 173 | permissions_header = ET.SubElement(permissions, 'tr') 174 | for header in ("Abbreviation", "Description"): 175 | ET.SubElement(permissions_header, 'th').text = header 176 | 177 | for abbr, desc in PERMISSIONS.items(): 178 | tr = ET.SubElement(permissions, 'tr') 179 | ET.SubElement(tr, 'td').text = abbr 180 | ET.SubElement(tr, 'td').text = desc 181 | 182 | ET.SubElement(body, 'h2').text = "Register Map" 183 | ET.SubElement(body, 'p').text = " ".join([ 184 | "NOTE: Except where specified otherwise, all register addresses are byte offsets, and all multi-byte register values are little-endian.", 185 | "In other words, for a 32-bit register at address 0x0000, bit 0 is the least-significant bit of the byte located at address 0x0000, and bit 31 is the most-significant bit of the byte located at address 0x0003.", 186 | "This applies to all memory spaces ({}).".format(", ".join(map(lambda x: x[1], REGION_NAMES.items()))), 187 | ]) 188 | register_regions = doc.get('registers', dict()) 189 | for region_name, region_registers in register_regions.items(): 190 | ET.SubElement(body, 'hr') 191 | ET.SubElement(body, 'h3').text = "{} Region Registers".format(REGION_NAMES[region_name]) 192 | for register in region_registers: 193 | ET.SubElement(body, 'hr') 194 | reg_name = register.get('name', "") 195 | addr_format = "0x{:04X}" 196 | if region_name in ("pci", "sfr"): 197 | addr_format = "0x{:02X}" 198 | start = register.get('start') 199 | addr_string = "" 200 | if start is not None: 201 | addr_string = addr_format.format(start) 202 | reg_heading = ET.SubElement(body, 'h4') 203 | reg_heading_text = "{}: {}".format(addr_string, reg_name) 204 | reg_heading.attrib['id'] = "_" + re.sub(r'[^a-z0-9]', "_", reg_heading_text.lower()) 205 | reg_heading_link = ET.SubElement(reg_heading, 'a') 206 | reg_heading_link.attrib['href'] = "#" + reg_heading.attrib['id'] 207 | reg_heading_link.text = "#" 208 | reg_heading_link.tail = "\u00A0" + reg_heading_text 209 | end = register.get('end') 210 | size = 0 211 | if start is not None and end is not None: 212 | size = end + 1 - start 213 | max_bits = size * 8 214 | ET.SubElement(body, 'p').text = "Size: {} byte{}".format(size, "s" if size > 1 else "") 215 | markdown_subelement(body, 'div', register.get('notes', "")) 216 | if max_bits > 64: 217 | continue 218 | bit_table = ET.SubElement(body, 'table') 219 | bits = register.get('bits') 220 | if bits is None: 221 | bits = [{ 222 | 'name': reg_name, 223 | 'start': 0, 224 | 'end': max_bits - 1, 225 | 'permissions': register.get('permissions', ""), 226 | }] 227 | unused_bits = set(range(max_bits)) 228 | bit_spans = dict() 229 | for bit_range in bits: 230 | bit_start = bit_range.get('start') 231 | bit_end = bit_range.get('end') 232 | if bit_start is None or bit_end is None: 233 | continue 234 | unused_bits = unused_bits.difference(set(range(bit_start, bit_end + 1))) 235 | bit_len = bit_end + 1 - bit_start 236 | start_bit_block = bit_start // 16 237 | end_bit_block = bit_end // 16 238 | if end_bit_block > start_bit_block: 239 | for block in range(start_bit_block, end_bit_block + 1): 240 | if block == end_bit_block: 241 | upper_len = bit_end + 1 - block*16 242 | bit_spans[bit_end] = { 243 | 'name': bit_range.get('name', ""), 244 | 'permissions': bit_range.get('permissions', ""), 245 | 'span': upper_len, 246 | } 247 | elif block == start_bit_block: 248 | lower_len = block*16+16 - bit_start 249 | bit_spans[block*16+16-1] = { 250 | 'name': bit_range.get('name', ""), 251 | 'permissions': bit_range.get('permissions', ""), 252 | 'span': lower_len, 253 | } 254 | else: 255 | bit_spans[block*16+16-1] = { 256 | 'name': bit_range.get('name', ""), 257 | 'permissions': bit_range.get('permissions', ""), 258 | 'span': 16, 259 | } 260 | else: 261 | bit_spans[bit_end] = { 262 | 'name': bit_range.get('name', ""), 263 | 'permissions': bit_range.get('permissions', ""), 264 | 'span': bit_len, 265 | } 266 | if max_bits > 32: 267 | bit_table_upper_header = ET.SubElement(bit_table, 'tr') 268 | ET.SubElement(bit_table_upper_header, 'th').text = "Bit" 269 | for bit in range(48, 64)[::-1]: 270 | ET.SubElement(bit_table_upper_header, 'th').text = str(bit) 271 | bit_table_upper_perms = ET.SubElement(bit_table, 'tr') 272 | ET.SubElement(bit_table_upper_perms, 'th').text = "Type" 273 | for bit in range(48, 64)[::-1]: 274 | if bit in bit_spans.keys(): 275 | span = bit_spans[bit] 276 | bit_element = ET.SubElement(bit_table_upper_perms, 'td', {'class': 'bitfield'}) 277 | bit_element.text = span['permissions'] 278 | colspan = span['span'] 279 | if colspan > 1: 280 | bit_element.set('colspan', str(colspan)) 281 | elif bit in unused_bits: 282 | ET.SubElement(bit_table_upper_perms, 'td', {'class': 'bitfield bitfield-unused'}) 283 | bit_table_upper_data = ET.SubElement(bit_table, 'tr') 284 | ET.SubElement(bit_table_upper_data, 'th').text = "Name" 285 | for bit in range(48, 64)[::-1]: 286 | if bit in bit_spans.keys(): 287 | span = bit_spans[bit] 288 | bit_element = ET.SubElement(bit_table_upper_data, 'td', {'class': 'bitfield'}) 289 | bit_element.text = span['name'] 290 | colspan = span['span'] 291 | if colspan > 1: 292 | bit_element.set('colspan', str(colspan)) 293 | elif bit in unused_bits: 294 | ET.SubElement(bit_table_upper_data, 'td', {'class': 'bitfield bitfield-unused'}) 295 | bit_table_lower_header = ET.SubElement(bit_table, 'tr') 296 | ET.SubElement(bit_table_lower_header, 'th').text = "Bit" 297 | for bit in range(32, 48)[::-1]: 298 | ET.SubElement(bit_table_lower_header, 'th').text = str(bit) 299 | bit_table_lower_perms = ET.SubElement(bit_table, 'tr') 300 | ET.SubElement(bit_table_lower_perms, 'th').text = "Type" 301 | for bit in range(32, 48)[::-1]: 302 | if bit in bit_spans.keys(): 303 | span = bit_spans[bit] 304 | bit_element = ET.SubElement(bit_table_lower_perms, 'td', {'class': 'bitfield'}) 305 | bit_element.text = span['permissions'] 306 | colspan = span['span'] 307 | if colspan > 1: 308 | bit_element.set('colspan', str(colspan)) 309 | elif bit in unused_bits: 310 | ET.SubElement(bit_table_lower_perms, 'td', {'class': 'bitfield bitfield-unused'}) 311 | bit_table_lower_data = ET.SubElement(bit_table, 'tr') 312 | ET.SubElement(bit_table_lower_data, 'th').text = "Name" 313 | for bit in range(32, 48)[::-1]: 314 | if bit in bit_spans.keys(): 315 | span = bit_spans[bit] 316 | bit_element = ET.SubElement(bit_table_lower_data, 'td', {'class': 'bitfield'}) 317 | bit_element.text = span['name'] 318 | colspan = span['span'] 319 | if colspan > 1: 320 | bit_element.set('colspan', str(colspan)) 321 | elif bit in unused_bits: 322 | ET.SubElement(bit_table_lower_data, 'td', {'class': 'bitfield bitfield-unused'}) 323 | if max_bits > 16: 324 | bit_table_upper_header = ET.SubElement(bit_table, 'tr') 325 | ET.SubElement(bit_table_upper_header, 'th').text = "Bit" 326 | for bit in range(16, 32)[::-1]: 327 | ET.SubElement(bit_table_upper_header, 'th').text = str(bit) 328 | bit_table_upper_perms = ET.SubElement(bit_table, 'tr') 329 | ET.SubElement(bit_table_upper_perms, 'th').text = "Type" 330 | for bit in range(16, 32)[::-1]: 331 | if bit in bit_spans.keys(): 332 | span = bit_spans[bit] 333 | bit_element = ET.SubElement(bit_table_upper_perms, 'td', {'class': 'bitfield'}) 334 | bit_element.text = span['permissions'] 335 | colspan = span['span'] 336 | if colspan > 1: 337 | bit_element.set('colspan', str(colspan)) 338 | elif bit in unused_bits: 339 | ET.SubElement(bit_table_upper_perms, 'td', {'class': 'bitfield bitfield-unused'}) 340 | bit_table_upper_data = ET.SubElement(bit_table, 'tr') 341 | ET.SubElement(bit_table_upper_data, 'th').text = "Name" 342 | for bit in range(16, 32)[::-1]: 343 | if bit in bit_spans.keys(): 344 | span = bit_spans[bit] 345 | bit_element = ET.SubElement(bit_table_upper_data, 'td', {'class': 'bitfield'}) 346 | bit_element.text = span['name'] 347 | colspan = span['span'] 348 | if colspan > 1: 349 | bit_element.set('colspan', str(colspan)) 350 | elif bit in unused_bits: 351 | ET.SubElement(bit_table_upper_data, 'td', {'class': 'bitfield bitfield-unused'}) 352 | bit_table_lower_header = ET.SubElement(bit_table, 'tr') 353 | ET.SubElement(bit_table_lower_header, 'th').text = "Bit" 354 | for bit in range(0, min(16, max_bits))[::-1]: 355 | ET.SubElement(bit_table_lower_header, 'th').text = str(bit) 356 | bit_table_lower_perms = ET.SubElement(bit_table, 'tr') 357 | ET.SubElement(bit_table_lower_perms, 'th').text = "Type" 358 | for bit in range(0, min(16, max_bits))[::-1]: 359 | if bit in bit_spans.keys(): 360 | span = bit_spans[bit] 361 | bit_element = ET.SubElement(bit_table_lower_perms, 'td', {'class': 'bitfield'}) 362 | bit_element.text = span['permissions'] 363 | colspan = span['span'] 364 | if colspan > 1: 365 | bit_element.set('colspan', str(colspan)) 366 | elif bit in unused_bits: 367 | ET.SubElement(bit_table_lower_perms, 'td', {'class': 'bitfield bitfield-unused'}) 368 | bit_table_lower_data = ET.SubElement(bit_table, 'tr') 369 | ET.SubElement(bit_table_lower_data, 'th').text = "Name" 370 | for bit in range(0, min(16, max_bits))[::-1]: 371 | if bit in bit_spans.keys(): 372 | span = bit_spans[bit] 373 | bit_element = ET.SubElement(bit_table_lower_data, 'td', {'class': 'bitfield'}) 374 | bit_element.text = span['name'] 375 | colspan = span['span'] 376 | if colspan > 1: 377 | bit_element.set('colspan', str(colspan)) 378 | elif bit in unused_bits: 379 | ET.SubElement(bit_table_lower_data, 'td', {'class': 'bitfield bitfield-unused'}) 380 | 381 | actual_bits = register.get('bits', list()) 382 | if actual_bits: 383 | ET.SubElement(body, 'p') 384 | bit_info_table = ET.SubElement(body, 'table') 385 | 386 | bit_info_table_header = ET.SubElement(bit_info_table, 'tr') 387 | ET.SubElement(bit_info_table_header, 'th').text = "Bits" 388 | ET.SubElement(bit_info_table_header, 'th').text = "Name" 389 | ET.SubElement(bit_info_table_header, 'th').text = "Description" 390 | 391 | for bit_range in actual_bits[::-1]: 392 | start_bit = bit_range.get('start') 393 | end_bit = bit_range.get('end') 394 | if start_bit is None or end_bit is None: 395 | continue 396 | 397 | bits_str: str = "[{}]".format(start_bit) 398 | if end_bit != start_bit: 399 | bits_str = "[{}:{}]".format(end_bit, start_bit) 400 | 401 | bit_info_table_row = ET.SubElement(bit_info_table, 'tr') 402 | ET.SubElement(bit_info_table_row, 'td').text = bits_str 403 | ET.SubElement(bit_info_table_row, 'td').text = bit_range.get('name', "") 404 | markdown_subelement(bit_info_table_row, 'td', bit_range.get('notes', "")) 405 | 406 | for code_element in body.findall(".//code"): 407 | reg_name = code_element.text.split(".")[0] 408 | for h4 in body.xpath(".//h4[contains(text(), ': {}')]".format(reg_name)): 409 | # Do a precise match to make sure this is the right heading. 410 | if not h4[0].tail.endswith(": {}".format(reg_name)): 411 | continue 412 | 413 | # Create link element 414 | link = ET.Element('a') 415 | link.attrib['href'] = "#" + h4.attrib['id'] 416 | link.text = "\u21A9" 417 | 418 | # Move the tail to the new element. 419 | link.tail = code_element.tail 420 | code_element.tail = "" 421 | 422 | # Insert the link element. 423 | parent = code_element.getparent() 424 | parent.insert(parent.index(code_element) + 1, link) 425 | 426 | break 427 | 428 | ET.SubElement(body, 'hr') 429 | try: 430 | git_rev = "r{}.g{}".format( 431 | subprocess.check_output(["git", "rev-list", "--count", "HEAD"]).rstrip().decode('utf-8'), 432 | subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).rstrip().decode('utf-8'), 433 | ) 434 | except subprocess.CalledProcessError: 435 | git_rev = "UNKNOWN" 436 | date_string = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") 437 | footer = ET.SubElement(body, 'p') 438 | ET.SubElement(footer, 'i').text = "Generated from {} version {} on {}.".format(filename, git_rev, date_string) 439 | 440 | return ET.tostring( 441 | html, 442 | encoding='utf-8', 443 | xml_declaration=True, 444 | doctype="" 445 | ) 446 | 447 | def main() -> int: 448 | parser = argparse.ArgumentParser() 449 | parser.add_argument("-o", "--output", type=str, default="regs.xhtml", help="The output file.") 450 | parser.add_argument("input", type=str, help="The input YAML register definition file.") 451 | args = parser.parse_args() 452 | 453 | doc = yaml.safe_load(open(args.input, 'r')) 454 | 455 | doc_valid = validate(doc) 456 | if not doc_valid: 457 | print("Error: Document \"{}\" invalid.".format(args.input)) 458 | return 1 459 | 460 | xhtml = gen_xhtml(args.input, doc) 461 | output = open(args.output, 'wb') 462 | output.write(xhtml) 463 | 464 | return 0 465 | 466 | 467 | if __name__ == "__main__": 468 | sys.exit(main()) 469 | -------------------------------------------------------------------------------- /tools/generate_labels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | # generate_labels.py - A tool to generate a list of labels for Ghidra from a 5 | # YAML file containing register definitions. 6 | # Copyright (C) 2022, 2025 Forest Crossman 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | 22 | import argparse 23 | import sys 24 | 25 | import yaml # type: ignore[import-untyped] 26 | 27 | 28 | ADDR_FORMATS = { 29 | "sfr": "SFR:0x{:02X}", 30 | "xdata": "EXTMEM:0x{:04X}", 31 | } 32 | 33 | 34 | def main() -> int: 35 | parser = argparse.ArgumentParser() 36 | parser.add_argument("-o", "--output", type=str, help="The output file. Defaults to stdout if not specified.") 37 | parser.add_argument("input", type=str, help="The input YAML register definition file.") 38 | args = parser.parse_args() 39 | 40 | doc = yaml.safe_load(open(args.input, 'r')) 41 | 42 | output = sys.stdout 43 | if args.output: 44 | output = open(args.output, 'w') 45 | 46 | register_regions = doc.get('registers', dict()) 47 | for region_name, region_registers in register_regions.items(): 48 | if region_name not in ADDR_FORMATS.keys(): 49 | continue 50 | 51 | addr_format = ADDR_FORMATS[region_name] 52 | for register in region_registers: 53 | reg_name = register.get('name', "") 54 | if not reg_name: 55 | continue 56 | 57 | start = register.get('start') 58 | if start is None: 59 | continue 60 | 61 | addr_string = addr_format.format(start) 62 | 63 | label = "{} {} l\n".format(reg_name, addr_string) 64 | output.write(label) 65 | 66 | output.close() 67 | 68 | return 0 69 | 70 | 71 | if __name__ == "__main__": 72 | sys.exit(main()) 73 | -------------------------------------------------------------------------------- /tools/ghidra-scripts/AsmediaXhcFirmwareHelper.java: -------------------------------------------------------------------------------- 1 | // Script that analyzes ASMedia xHC firmware. 2 | // @author cyrozap 3 | // @category ASMedia.xHC 4 | 5 | // SPDX-License-Identifier: GPL-3.0-or-later 6 | 7 | // Copyright (C) 2022 Forest Crossman 8 | // 9 | // This program is free software: you can redistribute it and/or modify 10 | // it under the terms of the GNU General Public License as published by 11 | // the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | // GNU General Public License for more details. 18 | // 19 | // You should have received a copy of the GNU General Public License 20 | // along with this program. If not, see . 21 | 22 | import java.util.*; 23 | 24 | import ghidra.app.script.GhidraScript; 25 | import ghidra.program.model.address.*; 26 | import ghidra.program.model.lang.*; 27 | import ghidra.program.model.listing.*; 28 | import ghidra.program.model.scalar.*; 29 | import ghidra.program.model.symbol.*; 30 | import ghidra.util.bytesearch.*; 31 | import ghidra.util.exception.*; 32 | import ghidra.util.task.*; 33 | 34 | public class AsmediaXhcFirmwareHelper extends GhidraScript { 35 | private Register DPTR; 36 | private Register DPL; 37 | private Register DPH; 38 | private Register R1; 39 | private Register R2; 40 | private Register R3; 41 | private Register R4; 42 | private Register R5; 43 | private Register R6; 44 | private Register R7; 45 | private Address DPL_addr; 46 | private Address DPH_addr; 47 | private Address DPX_addr; 48 | 49 | private boolean isCfReferenceToInstruction(Reference instRef, Instruction inst) { 50 | return instRef.isPrimary() && 51 | instRef.getToAddress() == inst.getAddress() && 52 | (instRef.getReferenceType() == FlowType.UNCONDITIONAL_CALL || 53 | instRef.getReferenceType() == FlowType.UNCONDITIONAL_JUMP || 54 | instRef.getReferenceType() == FlowType.CONDITIONAL_JUMP); 55 | } 56 | 57 | private void createStringXrefsForPrintFunctionInner(Instruction startInstruction) { 58 | // Working backwards, find explicit assignments to the pointer variables. 59 | Instruction inst = startInstruction; 60 | Instruction prevInst = inst.getPrevious(); 61 | boolean r2Found = false; 62 | boolean r3Found = false; 63 | Address r3Address = null; 64 | int newRefAddrInt = 0; 65 | 66 | for (int i = 0; i < 10 && !(r2Found && r3Found); i++) { 67 | // Check if this instruction is the target of a branch, then perform the analysis following the branch. 68 | Reference[] instRefs = getReferencesTo(inst.getAddress()); 69 | for (Reference instRef : instRefs) { 70 | if (!isCfReferenceToInstruction(instRef, inst)) { 71 | continue; 72 | } 73 | 74 | Address refAddr = instRef.getFromAddress(); 75 | Instruction branchInst = getInstructionAt(refAddr); 76 | 77 | //printf("Found branch to %s at %s, following...\n", inst.getAddress(), refAddr); 78 | createStringXrefsForPrintFunctionInner(branchInst); 79 | } 80 | 81 | inst = prevInst; 82 | prevInst = inst.getPrevious(); 83 | 84 | String mnemonic = inst.getMnemonicString(); 85 | if (mnemonic.equals("LCALL") || 86 | mnemonic.startsWith("AJ") || 87 | mnemonic.startsWith("CJ") || 88 | mnemonic.startsWith("DJ") || 89 | mnemonic.startsWith("J") || 90 | mnemonic.startsWith("LJ") || 91 | mnemonic.startsWith("SJ") || 92 | mnemonic.startsWith("RET")) { 93 | break; 94 | } 95 | 96 | if (!mnemonic.equals("MOV")) { 97 | continue; 98 | } 99 | 100 | Object[] resultObjects = inst.getResultObjects(); 101 | if (resultObjects.length < 1) { 102 | continue; 103 | } 104 | 105 | Object[] inputObjects = inst.getInputObjects(); 106 | if (inputObjects.length < 1) { 107 | continue; 108 | } 109 | 110 | Object dst = resultObjects[0]; 111 | Object src = inputObjects[0]; 112 | if (!(dst instanceof Register && src instanceof Scalar)) { 113 | continue; 114 | } 115 | 116 | Register reg = (Register)dst; 117 | Scalar value = (Scalar)src; 118 | 119 | //printf("Register %s: 0x%02x\n", reg, value.getUnsignedValue()); 120 | 121 | if (reg == R2) { 122 | newRefAddrInt |= value.getUnsignedValue() & 0xff; 123 | r2Found = true; 124 | } 125 | if (reg == R3) { 126 | newRefAddrInt |= (value.getUnsignedValue() & 0xff) << 8; 127 | r3Found = true; 128 | r3Address = inst.getAddress(); 129 | } 130 | } 131 | 132 | if (!(r2Found && r3Found)) { 133 | return; 134 | } 135 | 136 | Address newRefAddr = toAddr(String.format("CODE:%04x", newRefAddrInt)); 137 | 138 | currentProgram.getReferenceManager().addMemoryReference(r3Address, newRefAddr, RefType.DATA, SourceType.USER_DEFINED, 1); 139 | 140 | printf(getScriptName() + "> Added reference from %s to %s.\n", r3Address, newRefAddr); 141 | } 142 | 143 | private Address findAddressByBytesMaskAndOffset(byte[] bytes, byte[] mask, int offset) throws CancelledException { 144 | List
results = new ArrayList
(); 145 | GenericMatchAction
action = new GenericMatchAction
(null) { 146 | @Override 147 | public void apply(Program prog, Address addr, Match match) { 148 | results.add(addr); 149 | } 150 | }; 151 | GenericByteSequencePattern pattern = new GenericByteSequencePattern(bytes, mask, action); 152 | MemoryBytePatternSearcher searcher = new MemoryBytePatternSearcher("findAddressByBytesMaskAndOffset", 153 | new ArrayList(Arrays.asList(pattern))); 154 | searcher.setSearchExecutableOnly(true); 155 | searcher.search(currentProgram, currentProgram.getMemory(), new TaskDialog("findAddressByBytesMaskAndOffset", true, false, true)); 156 | 157 | if (results.size() < 1) { 158 | return null; 159 | } 160 | 161 | return results.get(0).add(offset); 162 | } 163 | 164 | private void createStringXrefsForPrintFunction() throws CancelledException { 165 | // Get the function address. 166 | Address functionAddr = null; 167 | while (true) { 168 | Function function = getFunction("asm_print_log"); 169 | if (function != null) { 170 | functionAddr = function.getEntryPoint(); 171 | if (functionAddr != null) { 172 | printf(getScriptName() + "> Found print function by name: %s\n", functionAddr); 173 | break; 174 | } 175 | } 176 | 177 | functionAddr = findAddressByBytesMaskAndOffset( 178 | new byte[] { (byte)0xe5, (byte)0x18, (byte)0x54, (byte)0x04, (byte)0x70, (byte)0x21 }, 179 | new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff }, 180 | -13); 181 | if (functionAddr != null) { 182 | printf(getScriptName() + "> Found print function by bytes (64K): %s\n", functionAddr); 183 | break; 184 | } 185 | 186 | functionAddr = findAddressByBytesMaskAndOffset( 187 | new byte[] { (byte)0xea, (byte)0xfe, (byte)0xeb, (byte)0xff, (byte)0x80, (byte)0x0f }, 188 | new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff }, 189 | -5); 190 | if (functionAddr != null) { 191 | printf(getScriptName() + "> Found print function by bytes (128K): %s\n", functionAddr); 192 | break; 193 | } 194 | 195 | break; 196 | } 197 | 198 | if (functionAddr == null) { 199 | printf(getScriptName() + "> Failed to find \"asm_print_log\" function! Try defining it manually.\n"); 200 | return; 201 | } 202 | 203 | // Loop over all the locations where the function is called. 204 | Reference[] calls = getReferencesTo(functionAddr); 205 | for (Reference call : calls) { 206 | Address callSite = call.getFromAddress(); 207 | Instruction inst = getInstructionAt(callSite); 208 | 209 | createStringXrefsForPrintFunctionInner(inst); 210 | } 211 | } 212 | 213 | private void createDataXrefsForFunctionInner(String region, Instruction startInstruction) { 214 | // Working backwards, find explicit assignments to the pointer variables. 215 | Instruction inst = startInstruction; 216 | Instruction prevInst = inst.getPrevious(); 217 | boolean dptrFound = false; 218 | Address dptrAddress = null; 219 | long newRefAddrInt = 0; 220 | 221 | for (int i = 0; i < 10 && !dptrFound; i++) { 222 | // Check if this instruction is the target of a branch, then perform the analysis following the branch. 223 | Reference[] instRefs = getReferencesTo(inst.getAddress()); 224 | for (Reference instRef : instRefs) { 225 | if (!isCfReferenceToInstruction(instRef, inst)) { 226 | continue; 227 | } 228 | 229 | Address refAddr = instRef.getFromAddress(); 230 | Instruction branchInst = getInstructionAt(refAddr); 231 | 232 | //printf("Found branch to %s at %s, following...\n", inst.getAddress(), refAddr); 233 | createDataXrefsForFunctionInner(region, branchInst); 234 | } 235 | 236 | inst = prevInst; 237 | prevInst = inst.getPrevious(); 238 | 239 | String mnemonic = inst.getMnemonicString(); 240 | if (mnemonic.equals("LCALL") || 241 | mnemonic.startsWith("AJ") || 242 | mnemonic.startsWith("CJ") || 243 | mnemonic.startsWith("DJ") || 244 | mnemonic.startsWith("J") || 245 | mnemonic.startsWith("LJ") || 246 | mnemonic.startsWith("SJ") || 247 | mnemonic.startsWith("RET")) { 248 | break; 249 | } 250 | 251 | if (!mnemonic.equals("MOV")) { 252 | continue; 253 | } 254 | 255 | Object[] resultObjects = inst.getResultObjects(); 256 | if (resultObjects.length < 1) { 257 | continue; 258 | } 259 | 260 | Object[] inputObjects = inst.getInputObjects(); 261 | if (inputObjects.length < 1) { 262 | continue; 263 | } 264 | 265 | Object dst = resultObjects[0]; 266 | Object src = inputObjects[0]; 267 | 268 | if ((dst instanceof Address && 269 | ((Address)dst == DPL_addr || (Address)dst == DPH_addr)) || 270 | dst instanceof Register && 271 | ((Register)dst == DPL || (Register)dst == DPH)) { 272 | break; 273 | } 274 | 275 | if (!(dst instanceof Register && src instanceof Scalar)) { 276 | continue; 277 | } 278 | 279 | Register reg = (Register)dst; 280 | Scalar value = (Scalar)src; 281 | 282 | //printf("Register %s: 0x%04x\n", reg, value.getUnsignedValue()); 283 | 284 | if (reg == DPTR) { 285 | newRefAddrInt = value.getUnsignedValue() & 0xffff; 286 | dptrAddress = inst.getAddress(); 287 | dptrFound = true; 288 | } 289 | } 290 | 291 | if (!dptrFound) { 292 | return; 293 | } 294 | 295 | Address newRefAddr = toAddr(String.format("%s:%04x", region, newRefAddrInt)); 296 | 297 | currentProgram.getReferenceManager().addMemoryReference(dptrAddress, newRefAddr, RefType.DATA, SourceType.USER_DEFINED, 1); 298 | 299 | printf(getScriptName() + "> Added reference from %s to %s.\n", dptrAddress, newRefAddr); 300 | } 301 | 302 | private void createDataXrefsForFunction(String substring, String region) { 303 | // Search through the symbol table for functions containing the substring. 304 | for (Symbol sym : currentProgram.getSymbolTable().getSymbolIterator(substring, true)) { 305 | Address functionAddr = sym.getAddress(); 306 | 307 | // Loop over all the locations where the function is called. 308 | Reference[] calls = getReferencesTo(functionAddr); 309 | for (Reference call : calls) { 310 | Address callSite = call.getFromAddress(); 311 | Instruction inst = getInstructionAt(callSite); 312 | 313 | createDataXrefsForFunctionInner(region, inst); 314 | } 315 | } 316 | } 317 | 318 | public void run() throws Exception { 319 | // Get the registers we care about. 320 | DPTR = currentProgram.getRegister("DPTR"); 321 | DPL = currentProgram.getRegister("DPL"); 322 | DPH = currentProgram.getRegister("DPH"); 323 | R1 = currentProgram.getRegister("R1"); 324 | R2 = currentProgram.getRegister("R2"); 325 | R3 = currentProgram.getRegister("R3"); 326 | R4 = currentProgram.getRegister("R4"); 327 | R5 = currentProgram.getRegister("R5"); 328 | R6 = currentProgram.getRegister("R6"); 329 | R7 = currentProgram.getRegister("R7"); 330 | 331 | // Get the addresses of the SFRs. 332 | DPL_addr = toAddr("SFR:82"); 333 | DPH_addr = toAddr("SFR:83"); 334 | DPX_addr = toAddr("SFR:93"); 335 | 336 | createStringXrefsForPrintFunction(); 337 | 338 | createDataXrefsForFunction("*from_pmem*", "CODE"); 339 | createDataXrefsForFunction("*with_pmem*", "CODE"); 340 | createDataXrefsForFunction("*check_equal_u32_iram_pmem", "CODE"); 341 | createDataXrefsForFunction("*from_xram*", "EXTMEM"); 342 | createDataXrefsForFunction("*to_xram_from_iram*", "EXTMEM"); 343 | createDataXrefsForFunction("*to_xram_with_iram*", "EXTMEM"); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /tools/load_fw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | # load_fw.py - A tool to directly load firmware into an ASMedia USB host 5 | # controller. 6 | # Copyright (C) 2021-2022, 2025 Forest Crossman 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | 22 | import argparse 23 | import time 24 | 25 | from asm_tool import AsmDev 26 | 27 | 28 | def main() -> None: 29 | parser = argparse.ArgumentParser() 30 | parser.add_argument("dbsf", type=str, help="The \"::.\" for the ASMedia USB 3 host controller.") 31 | parser.add_argument("firmware", type=str, help="The raw firmware binary to load.") 32 | args = parser.parse_args() 33 | 34 | dev = AsmDev(args.dbsf) 35 | print("Chip: {}".format(dev.name)) 36 | 37 | print("Unbinding the kernel driver if it's attached...") 38 | dev.pci.driver_unbind() 39 | 40 | binary = open(args.firmware,'rb').read() 41 | if len(binary) % 2: 42 | binary += b'\0' 43 | 44 | print("Loading \"{}\"...".format(args.firmware)) 45 | start = time.perf_counter_ns() 46 | dev.hw_code_load_exec(binary) 47 | stop = time.perf_counter_ns() 48 | print("Loaded {} bytes in {:.06f} seconds ({} bytes/second)".format(len(binary), (stop-start)/1e9, int(len(binary)*1000000000/(stop-start)))) 49 | 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /tools/prom_fw.ksy: -------------------------------------------------------------------------------- 1 | meta: 2 | id: prom_fw 3 | endian: le 4 | title: Promontory Firmware Image 5 | license: CC0-1.0 6 | seq: 7 | - id: header 8 | type: header 9 | - id: body 10 | type: body 11 | size: header.len - 12 12 | types: 13 | header: 14 | seq: 15 | - id: magic 16 | contents: "_PT_" 17 | - id: len 18 | type: u4 19 | - id: checksum 20 | type: u4 21 | doc: "32-bit sum of all the bytes in body.firmware." 22 | body: 23 | seq: 24 | - id: firmware 25 | type: firmware 26 | size: _io.size - (_io.size & 0xff) # Exclude the signature, if present 27 | - id: signature 28 | type: signature 29 | if: (_io.size & 0xff) != 0 30 | doc: "An HMAC-SHA256 digest of body.firmware." 31 | types: 32 | firmware: 33 | seq: 34 | - id: code 35 | size-eos: true 36 | instances: 37 | version: 38 | pos: 0x80 39 | size: 6 40 | magic: 41 | pos: 0x87 42 | size: 8 43 | type: str 44 | encoding: ascii 45 | signature: 46 | instances: 47 | encoded_start: 48 | pos: _root.body.firmware._io.size + 1 49 | type: u1 50 | encoded_15180_high_nybble: 51 | pos: _root.body.firmware._io.size + 2 52 | type: u1 53 | actual_start: 54 | value: '(encoded_start >> 1) & 0x3f' 55 | actual_15180_high_nybble: 56 | value: '(encoded_15180_high_nybble << 2) & 0xf0' 57 | data: 58 | pos: _root.body.firmware._io.size + actual_start 59 | size: 0x20 60 | -------------------------------------------------------------------------------- /tools/validate_brom.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | # validate_brom.py - A tool to validate that a boot ROM was dumped properly. 5 | # Copyright (C) 2021, 2024-2025 Forest Crossman 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | import argparse 22 | import csv 23 | import io 24 | import struct 25 | import sys 26 | from hashlib import md5, sha1, sha256, sha512, blake2b 27 | from zlib import crc32 28 | 29 | 30 | CHIPS: dict[bytes, str] = { 31 | bytes(8): "ASM1042", 32 | b"2104B_FW": "ASM1042A", 33 | b"2114A_FW": "ASM1142", 34 | b"2214A_FW": "ASM2142/ASM3142", 35 | b"2324A_FW": "ASM3242", 36 | b"3306A_FW": "ASM3063/Prom", 37 | b"3306B_FW": "ASM3063A/PromLP", 38 | b"3308A_FW": "ASM3083/Prom19", 39 | b"3328A_FW": "ASM3283/Prom21", 40 | } 41 | 42 | 43 | def validate_crc32(data: bytes, expected: int) -> None: 44 | calc_crc32: int = crc32(data) 45 | if calc_crc32 != expected: 46 | raise ValueError("Invalid CRC-32: expected {:#010x}, got: {:#010x}".format(expected, calc_crc32)) 47 | 48 | def main() -> int: 49 | parser: argparse.ArgumentParser = argparse.ArgumentParser() 50 | parser.add_argument("-c", "--csv", default=False, action="store_true", help="Output in CSV format.") 51 | parser.add_argument("firmware", type=str, help="The ASMedia USB 3 host controller boot ROM binary.") 52 | args: argparse.Namespace = parser.parse_args() 53 | 54 | fw_bytes: bytes 55 | with open(args.firmware, "rb") as f: 56 | fw_bytes = f.read() 57 | 58 | chip_magic: bytes = fw_bytes[0x87:0x87+8] 59 | chip_name: str = CHIPS.get(chip_magic, "Unknown magic: {!r}".format(chip_magic)) 60 | 61 | for size in (0x8000, 0x10000): 62 | expected: int = struct.unpack_from(' 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | import argparse 22 | import pathlib 23 | import sys 24 | from typing import Callable, NamedTuple 25 | from zlib import crc32 26 | 27 | try: 28 | import asm_fw 29 | except ModuleNotFoundError: 30 | print("Error: Failed to import \"asm_fw.py\". Please run \"make\" in the root directory of this repository to generate that file, then try running this script again.", file=sys.stderr) 31 | sys.exit(1) 32 | 33 | try: 34 | import prom_fw 35 | except ModuleNotFoundError: 36 | print("Error: Failed to import \"prom_fw.py\". Please run \"make\" in the root directory of this repository to generate that file, then try running this script again.", file=sys.stderr) 37 | sys.exit(1) 38 | 39 | 40 | class ChipInfo(NamedTuple): 41 | name: str 42 | data_filename: str | None 43 | mmio_offset: int 44 | 45 | 46 | CHIP_INFO: dict[str, ChipInfo] = { 47 | "U2104": ChipInfo("ASM1042", "regs-asm1042.yaml", 0), 48 | "2104B": ChipInfo("ASM1042A", "regs-asm1042a.yaml", 0), 49 | "2114A": ChipInfo("ASM1142", "regs-asm1142.yaml", 0), 50 | "2214A": ChipInfo("ASM2142/ASM3142", "regs-asm2142.yaml", 0x10000), 51 | "2324A": ChipInfo("ASM3242", "regs-asm3242.yaml", 0x10000), 52 | "3306A": ChipInfo("ASM3063/Prom", None, 0x10000), 53 | "3306B": ChipInfo("ASM3063A/PromLP", None, 0x10000), 54 | "3308A": ChipInfo("ASM3083/Prom19", None, 0x10000), 55 | "3328A": ChipInfo("ASM3283/Prom21", None, 0x10000), 56 | } 57 | 58 | 59 | def checksum8(data: bytes) -> int: 60 | return sum(data) & 0xff 61 | 62 | def checksum32(data: bytes) -> int: 63 | return sum(data) & 0xffffffff 64 | 65 | def validate_checksum(name: str, data: bytes, expected: int, function: Callable[[bytes], int]) -> None: 66 | calc_csum: int = function(data) 67 | if calc_csum != expected: 68 | print("Error: Invalid {} checksum: expected {:#04x}, got: {:#04x}".format(name, expected, calc_csum), file=sys.stderr) 69 | sys.exit(1) 70 | print("{} checksum OK! 0x{:02x}".format(name.capitalize(), expected)) 71 | 72 | def validate_crc32(name: str, data: bytes, expected: int) -> None: 73 | calc_crc32: int = crc32(data) 74 | if calc_crc32 != expected: 75 | print("Error: Invalid {} crc32: expected {:#010x}, got: {:#010x}".format(name, expected, calc_crc32), file=sys.stderr) 76 | sys.exit(1) 77 | print("{} CRC32 OK! 0x{:08x}".format(name.capitalize(), expected)) 78 | 79 | def format_version(version: bytes) -> str: 80 | return "{:02X}{:02X}{:02X}_{:02X}_{:02X}_{:02X}".format(*version) 81 | 82 | def get_chip_info(magic: str) -> ChipInfo: 83 | chip_id: str = magic[:5] 84 | return CHIP_INFO.get(chip_id, ChipInfo("UNKNOWN (\"{}\")".format(chip_id), None, 0x10000)) 85 | 86 | def promontory(args: argparse.Namespace, fw_bytes: bytes) -> prom_fw.PromFw: 87 | fw: prom_fw.PromFw = prom_fw.PromFw.from_bytes(fw_bytes) 88 | 89 | chip_info: ChipInfo = get_chip_info(fw.body.firmware.magic) 90 | 91 | print("Chip: {}".format(chip_info.name)) 92 | print("Firmware version: {}".format(format_version(fw.body.firmware.version))) 93 | 94 | validate_checksum("code", fw.body.firmware.code, fw.header.checksum, checksum32) 95 | 96 | try: 97 | print("Code signature: {}".format(fw.body.signature.data.hex())) 98 | except AttributeError: 99 | pass 100 | 101 | return fw 102 | 103 | def xhc(args: argparse.Namespace, fw_bytes: bytes) -> asm_fw.AsmFw: 104 | fw: asm_fw.AsmFw = asm_fw.AsmFw.from_bytes(fw_bytes) 105 | 106 | chip_info: ChipInfo = get_chip_info(fw.header.magic) 107 | 108 | print("Chip: {}".format(chip_info.name)) 109 | print("Firmware version: {}".format(format_version(fw.body.firmware.version))) 110 | 111 | header_bytes: bytes = fw_bytes[:fw.header.len] 112 | validate_checksum("header", header_bytes, fw.header.checksum, checksum8) 113 | validate_crc32("header", header_bytes, fw.header.crc32) 114 | 115 | validate_checksum("code", fw.body.firmware.code, fw.body.checksum, checksum8) 116 | validate_crc32("code", fw.body.firmware.code, fw.body.crc32) 117 | 118 | try: 119 | print("Code signature: {}".format(fw.body.signature.data.hex())) 120 | except AttributeError: 121 | pass 122 | 123 | reg_names: list[tuple[int, int, str]] = [] 124 | chip_data_yaml: str | None = chip_info.data_filename 125 | if chip_data_yaml: 126 | try: 127 | yaml_path: pathlib.Path = pathlib.Path(args.data_dir) / chip_data_yaml 128 | 129 | import yaml # type: ignore[import-untyped] 130 | doc: dict[str, dict[str, list[dict]]] = yaml.safe_load(open(yaml_path, 'r')) 131 | xdata: list[dict] = doc.get('registers', dict()).get('xdata', []) 132 | for reg in xdata: 133 | start: int = reg['start'] 134 | end: int = reg['end'] 135 | name: str = reg['name'] 136 | reg_names.append((start, end, name)) 137 | except FileNotFoundError: 138 | pass 139 | except ModuleNotFoundError: 140 | pass 141 | 142 | mmio_offset: int = chip_info.mmio_offset 143 | header_printed: bool = False 144 | for config_word in fw.header.data.config_words: 145 | if isinstance(config_word.info, asm_fw.AsmFw.Header.ConfigWord.WriteData): 146 | if not header_printed: 147 | print("Header MMIO writes:") 148 | header_printed = True 149 | 150 | info: asm_fw.AsmFw.Header.ConfigWord.WriteData = config_word.info 151 | addr: int = info.addr + mmio_offset 152 | addr_format: str = "0x{:04x}" 153 | if mmio_offset >= 0x10000: 154 | addr_format = "0x{:05x}" 155 | formatted_addr: str = addr_format.format(addr) 156 | value_format: str | None = { 157 | 1: '0x{:02x}', 158 | 2: '0x{:04x}', 159 | 4: '0x{:08x}', 160 | }.get(info.size) 161 | if value_format is None: 162 | raise ValueError("Invalid config word value size: {}".format(info.size)) 163 | formatted_value: str = value_format.format(info.value) 164 | 165 | for start, end, name in reg_names: 166 | if addr in range(start, end + 1): 167 | formatted_addr = "{}[{}] ({})".format(name, addr-start, formatted_addr) 168 | print(" {} <= {}".format(formatted_addr, formatted_value)) 169 | 170 | return fw 171 | 172 | def main() -> None: 173 | project_dir: pathlib.Path = pathlib.Path(__file__).resolve().parents[1] 174 | default_data_dir: str = str(project_dir/"data") 175 | 176 | parser: argparse.ArgumentParser = argparse.ArgumentParser() 177 | parser.add_argument("-d", "--data-dir", type=str, default=default_data_dir, help="The YAML data directory. Default is \"{}\"".format(default_data_dir)) 178 | parser.add_argument("-e", "--extract", action="store_true", default=False, help="Extract the code from the firmware image.") 179 | parser.add_argument("firmware", type=str, help="The ASMedia USB 3 host controller firmware image.") 180 | args: argparse.Namespace = parser.parse_args() 181 | 182 | fw: asm_fw.AsmFw | prom_fw.PromFw 183 | 184 | fw_bytes: bytes = open(args.firmware, 'rb').read() 185 | if fw_bytes.startswith(b"_PT_"): 186 | fw = promontory(args, fw_bytes) 187 | else: 188 | fw = xhc(args, fw_bytes) 189 | 190 | if args.extract: 191 | open('.'.join(args.firmware.split('.')[:-1]) + ".code.bin", 'wb').write(fw.body.firmware.code) 192 | 193 | 194 | if __name__ == "__main__": 195 | main() 196 | --------------------------------------------------------------------------------