├── .github └── workflows │ └── main.yml ├── LICENSES ├── CC0-1.0.txt └── MIT.txt ├── Makefile ├── README.md ├── src ├── bare-metal │ ├── .gitignore │ ├── Makefile │ ├── bootscript.txt.default │ ├── getuartdiv.s │ ├── interact.py │ ├── monitor.c │ ├── monitor.ld │ ├── scream.s │ ├── start.s │ └── tlbtest.s ├── buildroot │ └── var │ │ └── www │ │ ├── index.html │ │ ├── main.js │ │ └── style.css ├── dellfw │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── lcd-gif.py │ ├── lcd.py │ ├── run-fullfw.sh │ └── trace.c └── linux │ ├── .gitignore │ ├── Makefile │ ├── gpiodump.c │ ├── memdump.c │ ├── memscan.c │ └── power.c └── tools ├── .gitignore ├── apply-patches.sh ├── gen-aten-symbol.py └── lolconv /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Copyright (C) J. Neuschäfer 3 | 4 | name: CI 5 | on: [push] 6 | 7 | jobs: 8 | 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - run: | 14 | sudo apt-get update 15 | sudo apt-get install -y gcc-arm-linux-gnueabi 16 | - run: make 17 | - uses: actions/upload-artifact@v3 18 | with: 19 | name: monitor 20 | path: | 21 | src/bare-metal/monitor.elf 22 | src/bare-metal/monitor*.bin 23 | 24 | lint: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - run: | 28 | sudo apt-get update 29 | sudo apt-get install -y reuse 30 | - uses: actions/checkout@v3 31 | - run: make lint 32 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2020-2024 J. Neuschäfer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Copyright (C) J. Neuschäfer 3 | 4 | src: 5 | +$(MAKE) -C src/bare-metal 6 | +$(MAKE) -C src/linux 7 | +$(MAKE) -C src/dellfw 8 | 9 | clean: 10 | +$(MAKE) -C src clean 11 | 12 | lint: 13 | reuse lint 14 | 15 | .PHONY: src clean 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Third-party tools and documentation for the Nuvoton WPCM450 BMC 5 | 6 | - [tools](./tools/): Tools that run on any workstation 7 | - [src](./src/): Code that can run on the BMC 8 | - [bare-metal/monitor](./src/bare-metal/monitor.c): A [machine monitor](https://en.wikipedia.org/wiki/Machine_code_monitor) 9 | program that lets you peek, poke and copy memory, as well as run code. 10 | It can also act as a primitive bootloader. 11 | - [linux](./src/linux): Tools that run under Linux 12 | - [dellfw](./src/dellfw): Tools that help with reverse-engineering Dell firmware 13 | - My Linux patches are in [neuschaefer/linux](https://github.com/neuschaefer/linux/tree/wpcm) 14 | 15 | Hardware documentation is [in the wiki](https://github.com/neuschaefer/wpcm450/wiki/) ([GitLab mirror](https://gitlab.com/neuschaefer/wpcm450/-/wikis/home), [Codeberg mirror](https://codeberg.org/neuschaefer/wpcm450/wiki/Home)). 16 | 17 | This reposity is available on [GitHub](https://github.com/neuschaefer/wpcm450/), [GitLab](https://gitlab.com/neuschaefer/wpcm450/), and [Codeberg](https://codeberg.org/neuschaefer/wpcm450). 18 | 19 | To discuss a specific mainboard, please find the corresponding 20 | [issue](https://github.com/neuschaefer/wpcm450/issues?q=is%3Aopen+is%3Aissue+label%3A%22New+board%22) 21 | or open a new one. 22 | 23 | If you want to talk on IRC, join [`##ehh`](https://web.libera.chat/##ehh) 24 | on [Libera.Chat](https://libera.chat). 25 | -------------------------------------------------------------------------------- /src/bare-metal/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Copyright (C) J. Neuschäfer 3 | 4 | *.bin 5 | *.elf 6 | *.img 7 | *.o 8 | *.lol 9 | bootscript.txt 10 | -------------------------------------------------------------------------------- /src/bare-metal/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Copyright (C) J. Neuschäfer 3 | 4 | CROSS_COMPILE := arm-linux-gnueabi- 5 | OBJCOPY := $(CROSS_COMPILE)objcopy 6 | AS := $(CROSS_COMPILE)as 7 | CC := $(CROSS_COMPILE)gcc 8 | LD := $(CROSS_COMPILE)ld 9 | ASFLAGS := 10 | LOLCONV := ../../tools/lolconv 11 | CFLAGS := -Os -fno-builtin -nostdlib -Wall -Wno-unused-function -Wno-main -ggdb 12 | LDFLAGS := -T monitor.ld 13 | 14 | all: tlbtest.lol getuartdiv.lol monitor-flash.bin \ 15 | monitor-flash-16m.bin monitor-flash-32m.bin 16 | 17 | 18 | %.o: %.s 19 | $(AS) $< -o $@ 20 | 21 | %.o: %.c 22 | $(CC) $(CFLAGS) -c $< -o $@ 23 | 24 | %.bin: %.elf 25 | $(OBJCOPY) -O binary $< $@ 26 | 27 | %.lol: %.bin 28 | $(LOLCONV) 0x10000 $< > $@ 29 | cat $@ 30 | 31 | %.elf: %.o 32 | $(LD) $(LDFLAGS) $< -o $@ 33 | 34 | bootscript.elf: bootscript.txt 35 | cp bootscript.txt bootscript.tmp 36 | printf '\0' >> bootscript.tmp 37 | $(OBJCOPY) -I binary -O elf32-littlearm -B armv5t --rename-section .data=.bootscript bootscript.tmp $@ 38 | rm bootscript.tmp 39 | 40 | bootscript.txt: bootscript.txt.default 41 | cp $+ $@ 42 | 43 | MONITOR_OBJS = start.o monitor.o bootscript.elf 44 | monitor.elf: $(MONITOR_OBJS) monitor.ld 45 | $(LD) $(LDFLAGS) $(MONITOR_OBJS) -o $@ 46 | 47 | monitor-flash.bin: monitor.bin 48 | (printf 'P\004U\252'; cat $+) > $@ 49 | 50 | monitor-flash-16m.bin: monitor-flash.bin 51 | cp $+ $@ 52 | truncate -s 16M $@ 53 | 54 | monitor-flash-32m.bin: monitor-flash.bin 55 | cp $+ $@ 56 | truncate -s 32M $@ 57 | 58 | clean: 59 | rm -f *.o scream.elf scream.bin \ 60 | monitor.elf monitor.bin bootscript.elf bootscript.tmp \ 61 | monitor-flash.bin monitor-flash-16m.bin monitor-flash-32m.bin 62 | -------------------------------------------------------------------------------- /src/bare-metal/bootscript.txt.default: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Copyright (C) J. Neuschäfer 3 | 4 | # Load and run Linux 5 | echo Loading Linux... 6 | cw 40b80000 8000 0x120000 7 | call 8000 0 0xffffffff 0 8 | -------------------------------------------------------------------------------- /src/bare-metal/getuartdiv.s: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Copyright (C) J. Neuschäfer 3 | 4 | # Retrieve the baud rate divisor of UART0 and UART1 5 | # Assumes UART0 @ 0xb8000000, UART1 @ 0xb8000100 6 | # Stores the divisors at 0x0 (UART0), 0x4 (UART1) 7 | .global _start 8 | _start: 9 | mov r1, #0 10 | mov r2, #0xb8000000 11 | mov r7, lr 12 | 13 | bl getdiv 14 | str r0, [r1] 15 | 16 | add r0, #0x100 17 | bl getdiv 18 | str r0, [r1, #4] 19 | 20 | bx r7 21 | 22 | 23 | # getdiv - Retrieve the divisor for the UART @ r2, return value in r0. 24 | getdiv: 25 | # Set Line Control Register / Data Latch Access Bit 26 | ldr r3, [r2, #0xc] 27 | orr r3, #0x80 28 | str r3, [r2, #0xc] 29 | 30 | # Read Divisor Latch 31 | ldrb r0, [r2, #0] 32 | ldrb r3, [r2, #4] 33 | lsl r3, #8 34 | orr r0, r3 35 | 36 | # Clear Line Control Register / Data Latch Access Bit 37 | ldr r3, [r2, #0xc] 38 | bic r3, #0x80 39 | str r3, [r2, #0xc] 40 | 41 | bx lr 42 | -------------------------------------------------------------------------------- /src/bare-metal/interact.py: -------------------------------------------------------------------------------- 1 | #!/usr/share/python3 2 | # SPDX-License-Identifier: MIT 3 | # Copyright (C) J. Neuschäfer 4 | 5 | # Usage: python3 -i ./interact.py 6 | 7 | import serial, time, re, struct, sys, random, socket, os 8 | 9 | KiB = 1 << 10 10 | MiB = 1 << 20 11 | GiB = 1 << 30 12 | 13 | def BIT(x): 14 | return 1 << x 15 | 16 | def MASK(x): 17 | return BIT(x) - 1 18 | 19 | def bswap16(x): 20 | x = (x & 0xff00ff00) >> 8 | (x & 0x00ff00ff) << 8 21 | return x 22 | 23 | def bswap32(x): 24 | x = (x & 0xffff0000) >> 16 | (x & 0x0000ffff) << 16 25 | x = (x & 0xff00ff00) >> 8 | (x & 0x00ff00ff) << 8 26 | return x 27 | 28 | def get_be16(data, offset): 29 | return data[offset] << 8 | data[offset+1] 30 | 31 | def get_be32(data, offset): 32 | return get_be16(data, offset) << 16 | get_be16(data, offset+2) 33 | 34 | def hexdump(data): 35 | if data: 36 | for offset in range(0, len(data), 16): 37 | d = data[offset:offset+16] 38 | line = f'{offset:08x}: ' 39 | line += ' '.join([f'{x:02x}' for x in d]).ljust(49) 40 | line += ''.join([chr(x) if (x >= 0x20 and x <= 0x7f) else '.' for x in d]) 41 | print(line) 42 | 43 | 44 | class Lolmon: 45 | def __init__(self, device): 46 | self.device = device 47 | self.s = serial.Serial(device, baudrate=115200) 48 | self.prompt = b'> ' 49 | self.debug = False 50 | 51 | def connection_test(self): 52 | self.s.write(b'\n') 53 | time.sleep(0.2) 54 | answer = self.s.read_all() 55 | if (b'\r\n' + self.prompt) in answer: 56 | print("lolmon detected!") 57 | 58 | def read_until_prompt(self): 59 | answer = bytearray() 60 | timeout = 1 61 | while True: 62 | if self.s.readable(): 63 | answer += self.s.read_all() 64 | if self.prompt in answer: 65 | return bytes(answer)[:-len(self.prompt)], True 66 | else: 67 | time.sleep(0.05) 68 | timeout -= 0.05 69 | 70 | if timeout < 0: 71 | return bytes(answer), False 72 | 73 | def flush(self): 74 | while self.s.read_all() != b'': 75 | time.sleep(0.05) 76 | self.run_command('') 77 | 78 | def enter_with_echo(self, cmd): 79 | if isinstance(cmd, str): 80 | cmd = cmd.encode('UTF-8') 81 | assert not b'\n' in cmd 82 | 83 | pos = 0 84 | while pos < len(cmd): 85 | chunk = cmd[pos:pos+8] 86 | assert len(chunk) >= 1 87 | self.s.write(chunk) 88 | echo = self.s.read(len(chunk)) 89 | if echo != chunk: 90 | print('Echo error! %s != %s' % (echo, chunk)) 91 | pos += len(chunk) 92 | 93 | def run_command(self, cmd): 94 | try: 95 | if self.debug: 96 | print(':> %s' % cmd) 97 | self.enter_with_echo(cmd) 98 | 99 | self.s.write(b'\n') 100 | assert self.s.read(2) == b'\r\n' 101 | 102 | answer, good = self.read_until_prompt() 103 | if not good: 104 | print('Command \'%s\' timed out:\n%s' % (cmd, answer.decode('UTF-8'))) 105 | return b'' 106 | return answer 107 | except KeyboardInterrupt as e: 108 | time.sleep(0.10) 109 | self.flush() 110 | raise e 111 | 112 | def run_command_noreturn(self, cmd): 113 | if self.debug: 114 | print(':> %s' % cmd) 115 | self.enter_with_echo(cmd) 116 | self.s.write(b'\n') 117 | assert self.s.read(2) == b'\r\n' 118 | 119 | def writeX(self, cmd, size, addr, value): 120 | #print('poke %s %08x %s' % (cmd, addr, value)) 121 | if isinstance(value, bytes): 122 | value = [x for x in value] 123 | if isinstance(value, list): 124 | for v in value: 125 | self.writeX(cmd, size, addr, v) 126 | addr += size 127 | else: 128 | self.run_command("%s %08x %#x" % (cmd, addr, value)) 129 | 130 | def write8(self, addr, value): return self.writeX('wb', 1, addr, value) 131 | def write16(self, addr, value): return self.writeX('wh', 2, addr, value) 132 | def write32(self, addr, value): return self.writeX('ww', 4, addr, value) 133 | 134 | def write_file(self, addr, filename): 135 | with open(filename, 'rb') as f: 136 | data = f.read() 137 | f.close() 138 | self.write8(addr, data) 139 | 140 | def flash(self, memaddr, flashaddr, size): 141 | self.run_command("fl %08x %08x %#x" % (memaddr, flashaddr, size)) 142 | 143 | def memset(self, addr, value, size): 144 | value16 = value << 8 | value 145 | value32 = value16 << 16 | value16 146 | while size > 0: 147 | if addr & 3 != 0 or size < 4: 148 | n = size & 3 or 1 149 | self.write8(addr, [value] * n) 150 | addr += n 151 | size -= n 152 | else: 153 | n = size // 4 154 | self.write32(addr, [value32] * n) 155 | addr += n * 4 156 | size -= n * 4 157 | 158 | def parse_r_output(self, s): 159 | array = [] 160 | s = s.decode('UTF-8') 161 | for line in s.splitlines(): 162 | if re.match('[0-9a-f]{8}: [0-9a-f]+', line): 163 | for n in line[10:].split(' '): 164 | array.append(int(n, base=16)) 165 | return array 166 | 167 | 168 | def readX(self, cmd, size, addr, num): 169 | output = self.run_command("%s %08x %d" % (cmd, addr, num)) 170 | a = self.parse_r_output(output) 171 | if num == 1: return a[0] 172 | elif size==1: return bytes(a) 173 | else: return a 174 | 175 | def read8(self, addr, num=1): return self.readX('rb', 1, addr, num) 176 | def read16(self, addr, num=1): return self.readX('rh', 2, addr, num) 177 | def read32(self, addr, num=1): return self.readX('rw', 4, addr, num) 178 | 179 | def copyX(self, cmd, dest, src, num): 180 | self.run_command("%s %08x %08x %d" % (cmd, src, dest, num)) 181 | 182 | def copy8(self, dest, src, num): self.copyX('cb', dest, src, num) 183 | def copy16(self, dest, src, num): self.copyX('ch', dest, src, num) 184 | def copy32(self, dest, src, num): self.copyX('cw', dest, src, num) 185 | 186 | def make_setclr(rd, wr): 187 | def fn(self, addr, bit, value): 188 | x = rd(self, addr) 189 | if value: wr(self, addr, x | (1 << bit)) 190 | else: wr(self, addr, x & ~(1 << bit)) 191 | return fn 192 | 193 | setclr8 = make_setclr(read8, write8) 194 | setclr16 = make_setclr(read16, write16) 195 | setclr32 = make_setclr(read32, write32) 196 | 197 | def make_dump(cmd): 198 | def fn(self, addr, length): 199 | res = self.run_command('%s %08x %d' % (cmd, addr, length)) 200 | print(res.decode('ascii').strip()) 201 | return fn 202 | 203 | dump8 = make_dump('rb') 204 | dump16 = make_dump('rh') 205 | dump32 = make_dump('rw') 206 | 207 | def call(self, addr, a=0, b=0, c=0, d=0): 208 | self.run_command_noreturn('call %x %d %d %d %d' % (addr, a, b, c, d)) 209 | 210 | def call_linux_and_run_microcom(self, addr): 211 | emc0.stop() # No DMA please! 212 | emc1.stop() 213 | self.call(addr, 0, 0xffffffff, 0) 214 | os.system(f'busybox microcom -s 115200 /dev/ttyUSB0') 215 | 216 | class Block: 217 | def __init__(self, lolmon, base=None): 218 | self.l = lolmon 219 | if base: 220 | self.base = base 221 | 222 | def read8(self, offset): return self.l.read8(self.base + offset) 223 | def read16(self, offset): return self.l.read16(self.base + offset) 224 | def read32(self, offset): return self.l.read32(self.base + offset) 225 | 226 | def write8(self, offset, value): return self.l.write8(self.base + offset, value) 227 | def write16(self, offset, value): return self.l.write16(self.base + offset, value) 228 | def write32(self, offset, value): return self.l.write32(self.base + offset, value) 229 | 230 | def setclr8(self, offset, bit, value): return self.l.setclr8(self.base + offset, bit, value) 231 | def setclr16(self, offset, bit, value): return self.l.setclr16(self.base + offset, bit, value) 232 | def setclr32(self, offset, bit, value): return self.l.setclr32(self.base + offset, bit, value) 233 | 234 | def dump(self): 235 | self.l.dump32(self.base, 0x20) 236 | 237 | 238 | class Clocks(Block): 239 | CLKEN = 0x00 240 | CLKSEL = 0x04 241 | CLKDIV = 0x08 242 | PLLCON0 = 0x0c 243 | PLLCON1 = 0x10 244 | IPSRST = 0x20 245 | 246 | CLKSEL_CPU_SHIFT = 0 247 | CLKSEL_CPU_MASK = 3 248 | CLKSEL_USBPHY_SHIFT = 6 249 | CLKSEL_USBPHY_MASK = 3 250 | CLKSEL_UART_SHIFT = 8 251 | CLKSEL_UART_MASK = 3 252 | 253 | CLKDIV_AHB3_SHIFT = 8 254 | CLKDIV_AHB3_MASK = 3 255 | CLKDIV_UART_SHIFT = 16 256 | CLKDIV_UART_MASK = 15 257 | CLKDIV_AHB_SHIFT = 24 258 | CLKDIV_AHB_MASK = 3 259 | CLKDIV_APB_SHIFT = 26 260 | CLKDIV_APB_MASK = 3 261 | CLKDIV_ADC_SHIFT = 28 262 | CLKDIV_ADC_MASK = 3 263 | 264 | PLLCON_PRST = BIT(13) 265 | PLLCON_INDV_SHIFT = 0 266 | PLLCON_INDV_MASK = 0x3f 267 | PLLCON_OTDV_SHIFT = 8 268 | PLLCON_OTDV_MASK = 0x07 269 | PLLCON_FBDV_SHIFT = 16 270 | PLLCON_FBDV_MASK = 0x1ff 271 | 272 | def reset(self, line, value): 273 | self.setclr32(self.IPSRST, line, value) 274 | 275 | def clken(self, line, value): 276 | self.setclr32(self.CLKEN, line, value) 277 | 278 | def rate_ref(self): 279 | return 48000000 280 | 281 | def pllcon_to_rate(self, pllcon): 282 | if pllcon & self.PLLCON_PRST: 283 | return 0 284 | indv = (pllcon >> self.PLLCON_INDV_SHIFT) & self.PLLCON_INDV_MASK 285 | otdv = (pllcon >> self.PLLCON_OTDV_SHIFT) & self.PLLCON_OTDV_MASK 286 | fbdv = (pllcon >> self.PLLCON_FBDV_SHIFT) & self.PLLCON_FBDV_MASK 287 | return int(self.rate_ref() / (indv+1) * (fbdv+1) / (otdv+1)) 288 | 289 | def rate_pll0(self): return self.pllcon_to_rate(self.read32(self.PLLCON0)) 290 | def rate_pll1(self): return self.pllcon_to_rate(self.read32(self.PLLCON1)) 291 | 292 | def rate_select(self, shift): 293 | sources = [ self.rate_pll0, self.rate_pll1, self.rate_ref ] 294 | return sources[(self.read32(self.CLKSEL) >> shift) & 3]() 295 | 296 | def rate_cpu(self): return self.rate_select(self.CLKSEL_CPU_SHIFT) // 2 297 | def rate_clkout(self): return self.rate_select(self.CLKSEL_CLKOUT_SHIFT) 298 | def rate_usbphy(self): return self.rate_select(self.CLKSEL_USBPHY_SHIFT) 299 | 300 | def rate_uart(self): 301 | div = (self.read32(self.CLKDIV) >> self.CLKDIV_UART_SHIFT) & self.CLKDIV_UART_MASK 302 | return self.rate_select(self.CLKSEL_UART_SHIFT) // (div + 1) 303 | 304 | def div(self, shift): 305 | div = (self.read32(self.CLKDIV) >> shift) & 3 306 | return [ 1, 2, 4, 8 ][div] 307 | 308 | def rate_ahb(self): return self.rate_cpu() // self.div(self.CLKDIV_AHB_SHIFT) 309 | def rate_ahb3(self): return self.rate_ahb() // self.div(self.CLKDIV_AHB3_SHIFT) 310 | def rate_apb(self): return self.rate_ahb() // self.div(self.CLKDIV_APB_SHIFT) 311 | def rate_adc(self): return self.rate_ref() // self.div(self.CLKDIV_ADC_SHIFT) 312 | 313 | def set_div(self, shift, div): 314 | table = { 2**n: n for n in range(4) } 315 | x = self.read32(self.CLKDIV) 316 | x &= ~(3 << shift) 317 | x |= table[div] << shift 318 | self.write32(self.CLKDIV, x) 319 | 320 | def set_sel(self, shift, parent): 321 | table = { 'pll0': 0, 'pll1': 1, 'ref': 2 } 322 | x = self.read32(self.CLKSEL) 323 | x &= ~(3 << shift) 324 | x |= table[parent] << shift 325 | self.write32(self.CLKSEL, x) 326 | 327 | def summary(self): 328 | print(f'Clock summary:') 329 | print(f' REF: {self.rate_ref() :10} Hz') 330 | print(f' PLL0: {self.rate_pll0() :10} Hz') 331 | print(f' PLL1: {self.rate_pll1() :10} Hz') 332 | print(f' CPU: {self.rate_cpu() :10} Hz') 333 | print(f' USBPHY: {self.rate_usbphy():10} Hz') 334 | print(f' UART: {self.rate_uart() :10} Hz') 335 | print(f' AHB: {self.rate_ahb() :10} Hz') 336 | print(f' AHB3: {self.rate_ahb3() :10} Hz') 337 | print(f' APB: {self.rate_apb() :10} Hz') 338 | print(f' ADC: {self.rate_adc() :10} Hz') 339 | 340 | def make_cpu_slow(self): 341 | # Copy PLL0 configuration to PLL1, but divide by 2 342 | pllcon0 = self.read32(self.PLLCON0) 343 | self.write32(self.PLLCON1, pllcon0 | (1 << self.PLLCON_OTDV_SHIFT)) 344 | print(f"PLL0 at {self.rate_pll0()} Hz, PLL1 at {self.rate_pll1()} Hz") 345 | 346 | # Switch CPU clock to PLL1 347 | self.set_sel(self.CLKSEL_CPU_SHIFT, 'pll1') 348 | print(f"AHB3 now at {self.rate_ahb3()}") 349 | 350 | def make_ahb3_fast(self): 351 | ahb = self.rate_ahb() 352 | for div in [1, 2, 4, 8]: 353 | if ahb / div <= 60_000000: 354 | self.set_div(self.CLKDIV_AHB3_SHIFT, div) 355 | break 356 | print(f"AHB3 clock now at {self.rate_ahb3()} Hz") 357 | 358 | def make_cpu_24mhz(self): 359 | self.set_div(self.CLKDIV_AHB_SHIFT, 1) 360 | self.set_div(self.CLKDIV_APB_SHIFT, 1) 361 | clk.set_sel(clk.CLKSEL_CPU_SHIFT, 'ref') 362 | 363 | 364 | class SHM(Block): 365 | def dump(self): 366 | self.l.dump8(self.base, 0x20) 367 | 368 | class RNG(Block): 369 | CMD = 0 370 | DATA = 4 371 | MODE = 8 372 | 373 | # MAC address 374 | class MAC: 375 | def __init__(self, a, b, c, d, e, f): 376 | self.addr = (a, b, c, d, e, f) 377 | 378 | def __repr__(self): 379 | return '%02x:%02x:%02x:%02x:%02x:%02x' % self.addr 380 | 381 | def __getitem__(self, i): 382 | return self.addr[i] 383 | 384 | def to_bytes(self): 385 | return bytes(self.addr) 386 | 387 | MAC.broadcast = MAC(0xff,0xff,0xff,0xff,0xff,0xff) 388 | 389 | class IP: 390 | def __init__(self, a, b, c, d): 391 | self.addr = (a, b, c, d) 392 | 393 | def __repr__(self): 394 | return '%d.%d.%d.%d' % self.addr 395 | 396 | def __getitem__(self, i): 397 | return self.addr[i] 398 | 399 | def to_int(self): 400 | # 10.1.2.3 -> 0x0a010203 401 | return self.addr[0] << 24 | self.addr[1] << 16 | self.addr[2] << 8 | self.addr[3] 402 | 403 | def to_bytes(self): 404 | # IP address in wire format (big endian) 405 | return bytes(self.addr) 406 | 407 | class EMC(Block): 408 | CAMCMR = 0x00 409 | CAMCMR_AUP = BIT(0) 410 | CAMCMR_AMP = BIT(1) 411 | CAMCMR_ABP = BIT(2) 412 | CAMCMR_CCAM = BIT(3) 413 | CAMCMR_ECMP = BIT(4) 414 | CAMEN = 0x04 415 | CAMxM = [0x08 + 0x10 * i for i in range(16)] 416 | CAMxL = [0x0c + 0x10 * i for i in range(16)] 417 | TXDLSA = 0x88 418 | RXDLSA = 0x8c 419 | MCMDR = 0x90 420 | MCMDR_RXON = BIT(0) 421 | MCMDR_ALP = BIT(1) 422 | MCMDR_ARP = BIT(2) 423 | MCMDR_ACP = BIT(3) 424 | MCMDR_AEP = BIT(4) 425 | MCMDR_SPCRC = BIT(5) 426 | MCMDR_TXON = BIT(8) 427 | MCMDR_NDEF = BIT(9) 428 | MCMDR_SDPZ = BIT(16) 429 | MCMDR_EnSQE = BIT(17) 430 | MCMDR_FDUP = BIT(18) 431 | MCMDR_EnMDC = BIT(19) 432 | MCMDR_OPMOD = BIT(20) 433 | MCMDR_LBK = BIT(21) 434 | MCMDR_SWR = BIT(24) 435 | MIID = 0x94 436 | MIIDA = 0x98 437 | MIIDA_BUSY = BIT(17) 438 | FFTCR = 0x9c 439 | TSDR = 0xa0 440 | RSDR = 0xa4 441 | DMARFC = 0xa8 442 | MIEN = 0xac 443 | MISTA = 0xb0 444 | MISTA_RX_MASK = 0x0000ffff 445 | MISTA_TX_MASK = 0xffff0000 446 | MISTA_RXINTR = BIT(0) 447 | MISTA_RXGD = BIT(4) 448 | MISTA_RDU = BIT(10) 449 | MISTA_RXBERR = BIT(11) 450 | MISTA_TXINTR = BIT(16) 451 | MISTA_TXCP = BIT(18) 452 | MISTA_EXDEF = BIT(19) 453 | MISTA_TXABT = BIT(21) 454 | MISTA_TDU = BIT(23) 455 | MISTA_TXBERR = BIT(24) 456 | MGSTA = 0xb4 457 | MPCNT = 0xb8 458 | MRPC = 0xbc 459 | MRPCC = 0xc0 460 | MREPC = 0xc4 461 | DMARFS = 0xc8 462 | CTXDSA = 0xcc 463 | CTXBSA = 0xd0 464 | CRXDSA = 0xd4 465 | CRXBSA = 0xd8 466 | 467 | CAMCMR_DEFAULT = CAMCMR_AUP | CAMCMR_ABP | CAMCMR_AMP | CAMCMR_ECMP 468 | MCMDR_DEFAULT = MCMDR_OPMOD | MCMDR_EnMDC | MCMDR_FDUP | MCMDR_SPCRC 469 | MCMDR_ACTIVE = MCMDR_DEFAULT | MCMDR_TXON | MCMDR_RXON 470 | 471 | ARP_BASE = 0xf0000 # start address of ARP frame 472 | BUFS_BASE = 0x100000 # start address of buffers 473 | BUF_SIZE = 0x800 # enough for 1 packet and descriptor 474 | FRAME_SIZE = 0x600 # max. bytes per frame 475 | BUFS_SIZE = 0x8000 # memory for all buffers per EMC and direction 476 | MTU = 1500 # max. number of bytes that we'd actually put in a frame 477 | 478 | ETHERTYPE_ARP = 0x806 479 | ETHERTYPE_IP = 0x800 480 | 481 | class Buf: 482 | DATA_OFFSET = 0x200 483 | 484 | def __init__(self, base, lolmon): 485 | self.base = base 486 | self.l = lolmon 487 | self.data_base = self.base + self.DATA_OFFSET 488 | self.next = self.base # until another address is provided 489 | 490 | def __repr__(self): 491 | return "%s(0x%x)" % (self.__class__.__name__, self.base) 492 | 493 | def dump(self): 494 | self.l.dump32(self.base, 4) 495 | 496 | class RXBuf(Buf): 497 | SL = 0 498 | BUF_ADDR = 4 499 | RESERVED = 8 500 | NEXTDESC = 12 501 | 502 | class Status: 503 | OWNER_MASK = 0xc0000000 504 | OWNER_EMC = 0x80000000 505 | OWNER_ARM = 0x00000000 506 | MISC_MASK = 0x3fff0000 507 | LEN_MASK = 0x0000ffff 508 | RXGD = BIT(20) 509 | 510 | def __init__(self, raw): 511 | self.raw = raw 512 | self.owner = self.raw & self.OWNER_MASK 513 | self.misc = self.raw & self.MISC_MASK 514 | self.len = self.raw & self.LEN_MASK 515 | 516 | def is_ready(self): 517 | return self.owner == self.OWNER_ARM 518 | 519 | def __repr__(self): 520 | owner = 'ARM' if self.owner == self.OWNER_ARM else 'EMC' 521 | return 'RXBuf.Status(%s, 0x%04x, %d)' % (owner, self.misc, self.len) 522 | 523 | def is_good(self): 524 | return bool(self.raw & self.RXGD) 525 | 526 | def write_initial(self): 527 | self.l.write32(self.base + self.SL, self.Status.OWNER_EMC) 528 | self.l.write32(self.base + self.BUF_ADDR, self.data_base) 529 | self.l.write32(self.base + self.RESERVED, 0) 530 | self.l.write32(self.base + self.NEXTDESC, self.next) 531 | 532 | def rearm(self): 533 | self.l.write32(self.base + self.SL, self.Status.OWNER_EMC) 534 | self.status = None 535 | 536 | def fetch_status(self): 537 | self.status = self.Status(self.l.read32(self.base + self.SL)) 538 | 539 | def fetch_ethertype(self): 540 | return bswap16(self.l.read16(self.data_base + 12)) 541 | 542 | def fetch_data(self): 543 | return self.l.read8(self.data_base, self.status.len) 544 | 545 | def dump_data(self): 546 | self.l.dump8(self.data_base, self.status.len) 547 | 548 | class TXBuf(Buf): 549 | CONTROL = 0 550 | CONTROL_OWNER_EMC = BIT(31) 551 | CONTROL_INTEN = BIT(2) 552 | CONTROL_CRCAPP = BIT(1) 553 | CONTROL_PADEN = BIT(0) 554 | BUF_ADDR = 4 555 | SL = 8 556 | SL_TXCP = BIT(19) 557 | NEXTDESC = 12 558 | 559 | CONTROL_GO = CONTROL_OWNER_EMC | CONTROL_CRCAPP | CONTROL_PADEN 560 | 561 | class Status: 562 | CONTROL_OWNER_EMC = BIT(31) 563 | 564 | def __init__(self, c, sl): 565 | self.c = c 566 | self.sl = sl 567 | 568 | def __repr__(self): 569 | owner = 'EMC' if self.c == self.CONTROL_OWNER_EMC else 'ARM' 570 | return 'TXBuf.Status(%s, c=0x%08x, sl=0x%08x)' % (owner, self.c, self.sl) 571 | 572 | def is_ready(self): 573 | return not(self.c & self.CONTROL_OWNER_EMC) 574 | 575 | def is_good(self): 576 | return bool(self.sl & SL_TXCP) 577 | 578 | def write_initial(self): 579 | self.l.write32(self.base + self.CONTROL, 0) 580 | self.l.write32(self.base + self.BUF_ADDR, self.data_base) 581 | self.l.write32(self.base + self.SL, 0) 582 | self.l.write32(self.base + self.NEXTDESC, self.next) 583 | 584 | def fetch_status(self): 585 | self.status = self.Status(self.l.read32(self.base + self.CONTROL), 586 | self.l.read32(self.base + self.SL)) 587 | 588 | def wait_until_ready(self): 589 | for i in range(100): 590 | self.fetch_status() 591 | if self.status.is_ready(): 592 | return True 593 | print(f'TXBuf.wait_until_ready: Timed out! status = {self.status}') 594 | 595 | def submit(self): 596 | self.l.write32(self.base + self.SL, self.len) 597 | self.l.write32(self.base + self.CONTROL, self.CONTROL_GO) 598 | 599 | def set_data(self, data): 600 | self.len = len(data) 601 | self.l.write8(self.data_base, list(data)) 602 | 603 | def set_data_by_copy(self, addr, length): 604 | self.len = length 605 | self.l.copy8(self.data_base, addr, length) 606 | 607 | def set_data_dma(self, addr, length): 608 | self.l.write32(self.base + self.BUF_ADDR, addr) 609 | self.len = length 610 | 611 | def dump_data(self): 612 | self.l.dump8(self.data_base, self.len) 613 | 614 | def set_cam(self, index, mac): 615 | self.write32(self.CAMxM[index], mac[5] << 24 | mac[4] << 16 | mac[3] << 8 | mac[2]) 616 | self.write32(self.CAMxL[index], mac[1] << 24 | mac[0] << 16) 617 | self.setclr32(self.CAMEN, index, 1) 618 | 619 | def init(self, buf_base=None): 620 | if not buf_base: 621 | if self.base == 0xb0002000: 622 | buf_base = self.BUFS_BASE 623 | self.reset = self.clock = 6 624 | self.mac = MAC(0xaa,0xbb,0xcc,0xdd,0xee,0x01) 625 | self.ip = IP(10,0,1,1) 626 | elif self.base == 0xb0003000: 627 | buf_base = self.BUFS_BASE + 2*self.BUFS_SIZE 628 | self.reset = self.clock = 7 629 | self.mac = MAC(0xaa,0xbb,0xcc,0xdd,0xee,0x02) 630 | self.ip = IP(10,0,1,2) 631 | 632 | # next buffer to use 633 | self.rx_head = 0 634 | self.tx_head = 0 635 | 636 | # reset core 637 | clk.clken(self.clock, 1) 638 | clk.reset(self.clock, 1) 639 | 640 | # allocate buffers 641 | self.rx_buf_base = buf_base 642 | self.tx_buf_base = buf_base + self.BUFS_SIZE 643 | self.rx_bufs = [self.RXBuf(addr, self.l) for addr in 644 | range(self.rx_buf_base, self.rx_buf_base + self.BUFS_SIZE, self.BUF_SIZE)] 645 | self.tx_bufs = [self.TXBuf(addr, self.l) for addr in 646 | range(self.tx_buf_base, self.tx_buf_base + self.BUFS_SIZE, self.BUF_SIZE)] 647 | 648 | # link them up 649 | for i, desc in enumerate(self.rx_bufs): 650 | desc.next = self.rx_bufs[(i + 1) % len(self.rx_bufs)].base 651 | for i, desc in enumerate(self.tx_bufs): 652 | desc.next = self.tx_bufs[(i + 1) % len(self.tx_bufs)].base 653 | 654 | # commit buffers to memory 655 | for desc in self.rx_bufs: desc.write_initial() 656 | for desc in self.tx_bufs: desc.write_initial() 657 | 658 | # initialize core 659 | clk.reset(self.clock, 0) 660 | self.set_cam(0, self.mac) 661 | # Accept unicast, and broadcast, use CAM 662 | self.write32(self.CAMCMR, self.CAMCMR_DEFAULT) 663 | self.write32(self.TXDLSA, self.tx_bufs[0].base) 664 | self.write32(self.RXDLSA, self.rx_bufs[0].base) 665 | self.write32(self.DMARFC, self.FRAME_SIZE) 666 | self.write32(self.MCMDR, self.MCMDR_ACTIVE) 667 | 668 | self.make_arp_packet(self.ARP_BASE) 669 | 670 | def fast_reset(self): 671 | # Rearm descriptors that were missed 672 | ctxdsa = self.read32(self.CTXDSA) 673 | real_tx_head = [i for i, tx in enumerate(emc0.tx_bufs) if tx.base == ctxdsa][0] 674 | head = real_tx_head 675 | while head != self.tx_head: 676 | self.tx_bufs[head].write_initial() 677 | head = (head + 1) % len(self.tx_bufs) 678 | 679 | #for desc in self.tx_bufs: desc.write_initial() 680 | self.rx_head = 0 681 | 682 | # Reset EMC 683 | self.write32(self.MCMDR, self.MCMDR_SWR) 684 | 685 | # Initialize registers 686 | self.write32(self.CAMCMR, self.CAMCMR_DEFAULT) 687 | self.write32(self.TXDLSA, self.tx_bufs[self.tx_head].base) 688 | self.write32(self.RXDLSA, self.rx_bufs[self.rx_head].base) 689 | self.write32(self.DMARFC, self.FRAME_SIZE) 690 | self.write32(self.MCMDR, self.MCMDR_ACTIVE) 691 | 692 | 693 | def make_arp_packet(self, addr): 694 | b = b'' 695 | 696 | # Ethernet header 697 | b += MAC.broadcast.to_bytes() 698 | b += self.mac.to_bytes() 699 | b += struct.pack('>H', self.ETHERTYPE_ARP) 700 | 701 | # ARP structure 702 | b += struct.pack('>HHBBH', 1, self.ETHERTYPE_IP, 6, 4, 2) 703 | b += self.mac.to_bytes() # sender 704 | b += self.ip.to_bytes() 705 | b += MAC.broadcast.to_bytes() # target 706 | b += self.ip.to_bytes() 707 | 708 | # push into memory 709 | self.l.write8(addr, b) 710 | self.arp_packet = addr 711 | self.arp_packet_len = len(b) 712 | 713 | def stop(self): 714 | # initiate software reset 715 | self.write32(self.MCMDR, self.MCMDR_SWR) 716 | while self.read32(self.MCMDR) & self.MCMDR_SWR: 717 | pass 718 | 719 | def dump_rx_descs(self): 720 | for desc in self.rx_bufs: desc.dump() 721 | 722 | def dump_tx_descs(self): 723 | for desc in self.tx_bufs: desc.dump() 724 | 725 | def advance_rx(self): 726 | self.rx_head = (self.rx_head + 1) % len(self.rx_bufs) 727 | 728 | def advance_tx(self): 729 | self.tx_head = (self.tx_head + 1) % len(self.tx_bufs) 730 | 731 | # Get the next RX buffer that is ready, or return None. 732 | # After use, buf.rearm() must be called. 733 | def try_get_rx_buf(self): 734 | self.write32(self.RSDR, 1) 735 | buf = self.rx_bufs[self.rx_head] 736 | buf.fetch_status() 737 | if buf.status.is_ready(): 738 | self.advance_rx() 739 | return buf 740 | 741 | # Get the next RX buffer that is ready. After use, buf.rearm() must be called. 742 | def get_rx_buf(self): 743 | while True: 744 | buf = self.try_get_rx_buf() 745 | if buf: 746 | return buf 747 | 748 | # receive a frame, as data 749 | def rx_frame(self): 750 | buf = self.get_rx_buf() 751 | data = buf.fetch_data() 752 | buf.rearm() 753 | return data 754 | 755 | # try to receive a frame, as data, or return None 756 | def try_rx_frame(self): 757 | data = None 758 | buf = self.try_get_rx_buf() 759 | if buf: 760 | data = buf.fetch_data() 761 | buf.rearm() 762 | return data 763 | 764 | def dump_frames(self): 765 | while True: 766 | buf = self.get_rx_buf() 767 | buf.dump_data() 768 | print() 769 | buf.rearm() 770 | 771 | def get_tx_buf(self): 772 | buf = self.tx_bufs[self.tx_head] 773 | for i in range(100): 774 | buf.fetch_status() 775 | if buf.status.is_ready(): 776 | self.advance_tx() 777 | return buf 778 | print(f'EMC.get_tx_buf timed out! Status: {buf.status}') 779 | 780 | def perform_tx(self): 781 | # TX interrupts 782 | self.write32(self.MISTA, self.MISTA_TX_MASK) 783 | 784 | # Trigger TX 785 | self.write32(self.TSDR, 1) 786 | 787 | looking_for = self.MISTA_TDU | self.MISTA_TXBERR 788 | for i in range(100): 789 | if self.read32(self.MISTA) & looking_for: 790 | break 791 | else: 792 | print(f'TX timeout, MISTA = {self.read32(self.MISTA):08x}') 793 | 794 | # Determine new TX head 795 | ctxdsa = self.read32(self.CTXDSA) 796 | real_tx_head = [i for i, tx in enumerate(emc0.tx_bufs) if tx.base == ctxdsa][0] 797 | 798 | if self.read32(self.MISTA) & self.MISTA_TXBERR: 799 | # TX DMA error. In this case, there are descriptors that were 800 | # previously assigned to the EMC, but not processed and hence not 801 | # assigned back to the CPU. 802 | # 803 | # I was originally going to rearm reassign these descriptors to the CPU, 804 | # set self.tx_head to what CTXDSA says, and move on. But, although 805 | # this solution *should* work, it does not. 806 | # 807 | # So, we reinitialize the hardware here. 808 | self.fast_reset() 809 | return False 810 | else: 811 | return True 812 | 813 | def submit_tx_buf(self, buf): 814 | buf.submit() 815 | return self.perform_tx() 816 | 817 | def tx_frame(self, data): 818 | buf = self.get_tx_buf() 819 | buf.set_data(data) 820 | return self.submit_tx_buf(buf) 821 | 822 | # Read memory using the EMC's DMA view 823 | def dma_read(self, addr, length=1024): 824 | self.setclr32(self.MCMDR, 21, 1) # Enable loopback mode 825 | buf = self.get_tx_buf() 826 | buf.set_data_dma(addr, length) 827 | if not self.submit_tx_buf(buf): 828 | print('TX DMA error') 829 | return 830 | if not buf.wait_until_ready(): 831 | return 832 | print(buf.status) 833 | data = self.try_rx_frame() 834 | if data: 835 | return data 836 | else: 837 | print("no data received!") 838 | 839 | def dma_compare(self, addr, length=1024): 840 | dma = self.dma_read(addr, length) 841 | direct = l.read8(addr, length) 842 | if (dma): 843 | if direct != dma: 844 | print(f'\nMismatch! CPU read @ 0x{addr:08x}:') 845 | hexdump(direct) 846 | print(f'DMA read @ {addr:08x}:') 847 | hexdump(dma) 848 | 849 | def data_chunks(self, data): 850 | ETH_OVERHEAD = 14 851 | UDP_OVERHEAD = 28 852 | TAG_OVERHEAD = 4 853 | chunk_size = self.MTU - ETH_OVERHEAD - UDP_OVERHEAD - TAG_OVERHEAD 854 | chunks = (len(data) + chunk_size - 1) // chunk_size 855 | for i in range(chunks): 856 | yield i, chunks, data[i*chunk_size : (i + 1)*chunk_size] 857 | 858 | # Reply to an ARP packet. We already know that it's ARP. 859 | def handle_arp(self, buf): 860 | data = buf.fetch_data()[14:] 861 | 862 | types = data[0:4] 863 | op = data[6:8] 864 | targetip = data[24:28] 865 | if types == b'\x00\x01\x08\x00' and op == b'\x00\x01' and targetip == self.ip.to_bytes(): 866 | txbuf = self.get_tx_buf() 867 | txbuf.set_data_by_copy(self.arp_packet, self.arp_packet_len) 868 | self.submit_tx_buf(txbuf) 869 | 870 | def arp_loop(self): 871 | while True: 872 | buf = self.get_rx_buf() 873 | et = buf.fetch_ethertype() 874 | print(hex(et)) 875 | if et == self.ETHERTYPE_ARP: 876 | self.handle_arp(buf) 877 | buf.rearm() 878 | 879 | def push_data(self, addr, data): 880 | magic = random.getrandbits(16) 881 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 882 | s.connect((str(self.ip), 450)) 883 | for i, n, chunk in self.data_chunks(data): 884 | packet_done = False 885 | good_tag = struct.pack('>HH', magic, i) 886 | print(f'\rpacket {i + 1}/{n}...', end='') 887 | s.send(good_tag + chunk) 888 | while not packet_done: 889 | buf = self.try_get_rx_buf() 890 | if not buf: 891 | # retransmit only when necessary 892 | s.send(good_tag + chunk) 893 | continue 894 | if not buf.status.is_good(): 895 | buf.rearm() 896 | continue 897 | 898 | header = self.l.read8(buf.data_base, 0x30) 899 | ethertype = get_be16(header, 0xc) 900 | if ethertype == self.ETHERTYPE_ARP: 901 | self.handle_arp(buf) 902 | if ethertype == self.ETHERTYPE_IP: 903 | ip = get_be32(header, 0x1e) 904 | port = get_be16(header, 0x24) 905 | tag = header[0x2a:0x2e] 906 | if ip == self.ip.to_int() and port == 450 and tag == good_tag: 907 | self.l.copy8(addr, buf.data_base + 0x2e, len(chunk)) 908 | addr += len(chunk) 909 | packet_done = True 910 | buf.rearm() 911 | print(' done') 912 | 913 | def push_file(self, addr, filename): 914 | with open(filename, 'rb') as f: 915 | data = f.read() 916 | f.close() 917 | print("Size: %#x bytes" % len(data)) 918 | self.push_data(addr, data) 919 | 920 | def get_mdccr(self): 921 | return (self.read32(self.MIIDA) >> 20) & 0xf 922 | 923 | def set_mdccr(self, value): 924 | miida = self.read32(emc0.MIIDA) & ~(0xf << 20) 925 | self.write32(self.MIIDA, miida | (value << 20)) 926 | 927 | def mdio_do(self, phy, reg, write): 928 | assert phy in range(0x20) 929 | assert reg in range(0x20) 930 | write = int(bool(write)) 931 | miida = self.read32(self.MIIDA) & ~0xffff 932 | miida |= self.MIIDA_BUSY | write << 16 | phy << 8 | reg 933 | self.write32(self.MIIDA, miida) 934 | timeout = time.monotonic() + 0.1 935 | while self.read32(self.MIIDA) & self.MIIDA_BUSY: 936 | if time.monotonic() >= timeout: 937 | print("MDIO transfer timed out") 938 | break 939 | 940 | def mdio_read(self, phy, reg): 941 | self.mdio_do(phy, reg, False) 942 | return self.read32(self.MIID) 943 | 944 | def mdio_write(self, phy, reg, value): 945 | self.write32(self.MIID, value) 946 | self.mdio_do(phy, reg, True) 947 | 948 | def mdio_scan(self): 949 | for phy in range(0x20): 950 | hi = self.mdio_read(phy, 2) 951 | lo = self.mdio_read(phy, 3) 952 | if hi != 0xffff: 953 | print('MDIO @ %d, device %04x:%04x' % (phy, hi, lo)) 954 | 955 | 956 | class GCR(Block): 957 | PDID = 0 958 | PWRON = 4 959 | MFSEL1 = 0xc 960 | MFSEL2 = 0x10 961 | 962 | 963 | class FIU(Block): 964 | FIU_CFG = 0 965 | BURST_CFG = 1 966 | RESP_CFG = 2 967 | CFBB_PROT = 3 968 | FWIN1_LOW = 4 969 | FWIN1_HIGH = 6 970 | FWIN2_LOW = 8 971 | FWIN2_HIGH = 0xa 972 | FWIN3_LOW = 0xc 973 | FWIN3_HIGH = 0xe 974 | FWIN_LOW = { 1: FWIN1_LOW, 2: FWIN2_LOW, 3: FWIN3_LOW } 975 | FWIN_HIGH = { 1: FWIN1_HIGH, 2: FWIN2_HIGH, 3: FWIN3_HIGH } 976 | PROT_LOCK = 0x10 977 | PROT_CLEAR = 0x11 978 | SPI_FL_CFG = 0x14 979 | SPI_TIM = 0x15 980 | UMA_CODE = 0x16 981 | UMA_AB0 = 0x17 982 | UMA_AB1 = 0x18 983 | UMA_AB2 = 0x19 984 | UMA_DB0 = 0x1a 985 | UMA_DB1 = 0x1b 986 | UMA_DB2 = 0x1c 987 | UMA_DB3 = 0x1d 988 | UMA_CTS = 0x1e 989 | CTS_EXEC_DONE = BIT(7) 990 | CTS_DEV_NUM_SHIFT = 5 991 | CTS_RD_WR = BIT(4) 992 | CTS_A_SIZE = BIT(3) 993 | CTS_D_SIZE_SHIFT = 0 994 | UMA_ECTS = 0x1f 995 | 996 | MMFLASH_BASE = 0xc0000000 997 | 998 | def __init__(self, lolmon, base=None): 999 | super().__init__(lolmon, base) 1000 | self.cs = 0 1001 | 1002 | def dump(self): 1003 | self.l.dump8(self.base, 0x20) 1004 | 1005 | def get_fwin(self, i): 1006 | return self.read16(self.FWIN_LOW[i]) * 0x1000, self.read16(self.FWIN_HIGH[i]) * 0x1000 1007 | 1008 | def any_fwin_contains(self, x): 1009 | return any([x in range(*fiu.get_fwin(i)) for i in [1, 2, 3]]) 1010 | 1011 | def set_fwin(self, i, low, high): 1012 | self.write16(self.FWIN_LOW[i], low // 0x1000) 1013 | self.write16(self.FWIN_HIGH[i], high // 0x1000) 1014 | 1015 | def get_uma_code(self): 1016 | return self.read8(self.UMA_CODE) 1017 | 1018 | def set_uma_code(self, code): 1019 | self.write8(self.UMA_CODE, code) 1020 | 1021 | def get_uma_addr(self): 1022 | a = self.read8(self.UMA_AB0) 1023 | a |= self.read8(self.UMA_AB1) << 8 1024 | a |= self.read8(self.UMA_AB2) << 16 1025 | return a 1026 | 1027 | def set_uma_addr(self, a): 1028 | self.write8(self.UMA_AB0, a & 0xff) 1029 | self.write8(self.UMA_AB1, (a >> 8) & 0xff) 1030 | self.write8(self.UMA_AB2, (a >> 16) & 0xff) 1031 | 1032 | def get_uma_data(self): 1033 | return [self.read8(self.UMA_DB0), 1034 | self.read8(self.UMA_DB1), 1035 | self.read8(self.UMA_DB2), 1036 | self.read8(self.UMA_DB3)] 1037 | 1038 | def do_uma(self, write, use_addr, data_len): 1039 | cts = self.CTS_EXEC_DONE | (self.cs << self.CTS_DEV_NUM_SHIFT) | (data_len << self.CTS_D_SIZE_SHIFT) 1040 | if use_addr: 1041 | cts |= self.CTS_A_SIZE 1042 | if write: 1043 | cts |= self.CTS_RD_WR 1044 | self.write8(self.UMA_CTS, cts) 1045 | while self.read8(self.UMA_CTS) & self.CTS_EXEC_DONE: 1046 | print('lol') 1047 | 1048 | # Read chip ID 1049 | def rdid(self): 1050 | self.set_uma_code(0x9f) 1051 | self.do_uma(False, False, 3) 1052 | return self.get_uma_data()[:3] 1053 | 1054 | # Read status register 1055 | def rsr(self): 1056 | self.set_uma_code(0x05) 1057 | self.do_uma(False, False, 1) 1058 | return self.get_uma_data()[0] 1059 | 1060 | # Write Enable 1061 | def wren(self): 1062 | self.set_uma_code(0x06) 1063 | self.do_uma(False, False, 0) 1064 | 1065 | # Sector Erase 1066 | def erase4k(self, addr, cs=0): 1067 | self.wren() 1068 | self.set_uma_code(0x20) 1069 | self.set_uma_addr(addr) 1070 | self.do_uma(False, True, 0) 1071 | 1072 | # Poll until the erase is done. 1073 | # On Winbond: RSR-1.BUSY 1074 | # On Macronix: SR.WIP 1075 | while self.rsr() & 0x01: 1076 | pass 1077 | 1078 | # program at 8-bit width 1079 | def prog8(self, addr, data): 1080 | addr = addr & 0xffffff 1081 | if isinstance(data, list) or isinstance(data, bytes): 1082 | for i, d in enumerate(data): 1083 | self.prog8(addr+i, d) 1084 | else: 1085 | assert self.any_fwin_contains(addr) 1086 | print("prog %06x = %2x" % (addr, data)) 1087 | self.wren() 1088 | self.l.write8(addr | self.MMFLASH_BASE, data) 1089 | 1090 | def prog8_as_needed(self, addr, data): 1091 | addr = addr & 0xffffff 1092 | fdata = self.mm_read(len(data)) 1093 | for i in range(len(data)): 1094 | if fdata[i] != data[i]: 1095 | self.prog8(addr+i, data[i]) 1096 | 1097 | # If the flash has any bits cleared that are set in the new data, we need 1098 | # an erase to set these bits again. 1099 | def page_needs_erase(self, addr, data): 1100 | addr = addr & 0xffffff 1101 | assert addr & 0xfff == 0 1102 | assert len(data) <= 0x1000 1103 | fdata = self.mm_read(len(data)) 1104 | for i in range(len(data)): 1105 | if ~fdata[i] & data[i]: 1106 | return True 1107 | return False 1108 | 1109 | # erase/reprogram a page or more as needed 1110 | def flash(self, addr, data): 1111 | addr = addr & 0xffffff 1112 | assert addr & 0xfff == 0 1113 | for p in range(0, len(data), 0x1000): 1114 | pdata = data[p:p+0x1000] 1115 | if self.page_needs_erase(addr+p, pdata): 1116 | self.erase4k(addr+p) 1117 | self.prog8_as_needed(addr+p, pdata) 1118 | 1119 | def mm_read(self, addr, data_len): 1120 | addr = addr & 0xffffff 1121 | return self.l.read8(addr | self.MMFLASH_BASE, data_len) 1122 | 1123 | def mm_dump(self, addr, data_len): 1124 | addr = addr & 0xffffff 1125 | return self.l.dump8(addr | self.MMFLASH_BASE, data_len) 1126 | 1127 | # perform READ using UMA 1128 | def uma_read(self, addr, data_len=4): 1129 | self.set_uma_code(0x03) 1130 | self.set_uma_addr(addr) 1131 | self.do_uma(False, True, data_len) 1132 | return self.get_uma_data() 1133 | 1134 | # perform FAST READ using UMA. FIU automatically inserts the dummy byte 1135 | def uma_fast_read(self, addr, data_len=4): 1136 | self.set_uma_code(0x0b) 1137 | self.set_uma_addr(addr) 1138 | self.do_uma(False, True, data_len) 1139 | return self.get_uma_data() 1140 | 1141 | def uma_assert(self): 1142 | x = self.read8(self.UMA_ECTS) 1143 | x &= ~BIT(self.cs) 1144 | self.write8(self.UMA_ECTS, x) 1145 | 1146 | def uma_deassert(self): 1147 | x = self.read8(self.UMA_ECTS) 1148 | x |= BIT(self.cs) 1149 | self.write8(self.UMA_ECTS, x) 1150 | 1151 | def hello_la(self): 1152 | # Pulse the CS# line to give my logic analyzer a chance to listen up 1153 | self.uma_assert() 1154 | self.uma_deassert() 1155 | 1156 | def set_read_burst(self, burst): 1157 | table = { 1: 0, 16: 3 } 1158 | x = self.read8(self.BURST_CFG) 1159 | x &= ~3 1160 | x |= table[burst] 1161 | self.write8(self.BURST_CFG, x) 1162 | 1163 | def safe_uma(self, code, write, use_addr, data_len): 1164 | # let the SPI flash think this is a read 1165 | self.uma_assert() 1166 | self.set_uma_code(0x03) 1167 | self.do_uma(False, False, 0) 1168 | 1169 | # now for the main act 1170 | self.set_uma_code(code) 1171 | self.do_uma(write, use_addr, data_len) 1172 | 1173 | # the end 1174 | self.uma_deassert() 1175 | 1176 | def uma_dummy_test(self): 1177 | # The pattern: 1178 | # (R) C 1179 | # (W) C 1180 | # (R) C A 1181 | # (W) C A 1182 | # (R) C D D D D 1183 | # (W) C D D D D 1184 | # (R) C A D D D D 1185 | # (W) C A D D D D 1186 | for code in [0x03, 0x0b]: 1187 | for data_len in [0, 4]: 1188 | for use_addr in [0, 1]: 1189 | for write in [0, 1]: 1190 | self.set_uma_addr(0x112233) 1191 | self.safe_uma(code, write, use_addr, data_len) 1192 | 1193 | def uma_addr_test(self): 1194 | self.set_uma_code(0x03) 1195 | self.write8(self.UMA_AB0, 0xaa) 1196 | self.write8(self.UMA_AB1, 0xbb) 1197 | self.write8(self.UMA_AB2, 0xcc) 1198 | self.do_uma(False, True, 1) 1199 | 1200 | def make_fast(self): 1201 | clk.make_ahb3_fast() 1202 | self.set_read_burst(16) 1203 | self.setclr32(0x14, 6, 1) 1204 | 1205 | def cs3test(self, i): 1206 | # Trying to figure out what determines the output level of CS3 aka. GPIO 2.2 1207 | # Possible influences: 1208 | # - FIU state, UMA_ECTS bit 3 1209 | # - GPIO 2.2 direction 1210 | # - GPIO 2.2 dataout 1211 | # - MFSEL1.5: SCS3SEL 1212 | # Conclusion: 1213 | # - When SCS3SEL is zero, then FIU state is used 1214 | # - When SCS3SEL is one, then GPIO state is used 1215 | 1216 | print(f'{i:020b}') 1217 | fiu.setclr8(fiu.UMA_ECTS, 3, not(i & BIT(0))) 1218 | gpio.write(2, 2, i & BIT(1)) 1219 | gpio.set_dir(2, 2, i & BIT(2)) 1220 | gcr.setclr32(gcr.MFSEL1, 5, i & BIT(3)) 1221 | 1222 | 1223 | class MC(Block): 1224 | RESET_VALUES = [ 1225 | 0x23128300, 0x11122224, 0x40000443, 0x00000000, # +0x00 1226 | 0x0000000d, 0x00150202, 0x00000000, 0x00000000, # +0x10 1227 | 0x00000000, 0x00000000, 0x00000000, 0x00000000, # +0x20 1228 | 0x00000000, 0x00000000, 0x00000000, 0x00000000, # +0x30 1229 | 0x00000000, 0x00000000, 0x00000000, 0x00000000, # +0x40 1230 | 0x00000000, 0x00000000, 0x00000000, 0x00000000, # +0x50 1231 | 0x00000006, 0x00000006 # +0x60 1232 | ] 1233 | 1234 | def apply_reset_values(self): 1235 | for i, value in enumerate(self.RESET_VALUES): 1236 | self.write32(4 * i, value) 1237 | 1238 | 1239 | class Timers(Block): 1240 | TCSR = [ 0x00, 0x04, 0x20, 0x24, 0x40 ] 1241 | TICR = [ 0x08, 0x0c, 0x28, 0x2c, 0x48 ] 1242 | TDR = [ 0x10, 0x14, 0x30, 0x34, 0x50 ] 1243 | TISR = 0x18 1244 | WTCR = 0x1c 1245 | 1246 | TCSR_PRESCALE_MASK = 0xff 1247 | TCSR_CACT = BIT(25) 1248 | TCSR_CRST = BIT(26) 1249 | TCSR_PER = BIT(27) 1250 | TCSR_IE = BIT(29) 1251 | TCSR_CEN = BIT(30) 1252 | TCSR_FREEZE = BIT(31) 1253 | 1254 | def summary(self): 1255 | for i in range(5): 1256 | tcsr = self.read32(self.TCSR[i]) 1257 | ticr = self.read32(self.TICR[i]) 1258 | tdr = self.read32(self.TDR[i]) 1259 | print("Timer %d: %08x %9d %9d" % (i, tcsr, ticr, tdr)) 1260 | tisr = self.read32(self.TISR) 1261 | wtcr = self.read32(self.WTCR) 1262 | print("TISR: %08x, WTCR: %08x" % (tisr, wtcr)) 1263 | 1264 | def testmode(self): 1265 | for i in range(5): 1266 | self.write32(self.TICR[i], 1000000) 1267 | self.write32(self.TCSR[i], 239 | self.TCSR_CACT | self.TCSR_PER | self.TCSR_CEN) 1268 | 1269 | def is_decrementing(self, timer): 1270 | a = self.read32(self.TDR[timer]) 1271 | b = self.read32(self.TDR[timer]) 1272 | return a != b 1273 | 1274 | def test_clock_gates(self): 1275 | self.testmode() 1276 | for gate in [19, 20, 21, 22, 23]: 1277 | clk.clken(gate, False) 1278 | 1279 | for gate in [19, 20, 21, 22, 23]: 1280 | clk.clken(gate, True) 1281 | print(gate, [int(self.is_decrementing(i)) for i in range(5)]) 1282 | 1283 | class GPIO(Block): 1284 | COUNTS = [ 16, 16, 16, 16, 16, 16, 18, 14 ] 1285 | CFG0 = [ 0x14, 0x24, 0x3c, 0x50, 0x64, 0x78, None, 0x90 ] 1286 | DATAOUT = [ 0x1c, 0x34, 0x48, 0x5c, 0x70, 0x84, None, 0x9c ] 1287 | DATAIN = [ 0x20, 0x38, 0x4c, 0x60, 0x74, 0x88, 0x8c, 0xa0 ] 1288 | 1289 | def dump(self): 1290 | self.l.dump32(self.base, 0x40) 1291 | 1292 | def dump_well(self): 1293 | def g(bank, offset): 1294 | if self.get_dir(bank, offset): 1295 | return f'W{self.get_dataout(bank, offset)}' 1296 | else: 1297 | return f'R{self.read(bank, offset)}' 1298 | 1299 | for bank in range(8): 1300 | print(f'Bank {bank}: ' + ' '.join([g(bank, i) for i in range(self.COUNTS[bank])])) 1301 | 1302 | def make_get(regs): 1303 | def get(self, bank, offset): 1304 | if regs[bank]: 1305 | return 0 + bool(self.read32(regs[bank]) & BIT(offset)) 1306 | else: 1307 | return 0 1308 | return get 1309 | 1310 | read = make_get(DATAIN) 1311 | get_dataout = make_get(DATAOUT) 1312 | get_dir = make_get(CFG0) 1313 | 1314 | def write(self, bank, offset, value): 1315 | self.setclr32(self.DATAOUT[bank], offset, value) 1316 | 1317 | def set_dir(self, bank, offset, value): 1318 | self.setclr32(self.CFG0[bank], offset, value) 1319 | 1320 | 1321 | 1322 | USB = KCS = GDMA = AES = UART = SMB = PWM = MFT = Block 1323 | PECI = GFXI = SSPI = AIC = ADC = SDHC = ROM = Block 1324 | 1325 | 1326 | l = Lolmon('/dev/ttyUSB0') 1327 | l.connection_test() 1328 | gcr = GCR(l, 0xb0000000) 1329 | clk = Clocks(l, 0xb0000200) 1330 | mc = MC(l, 0xb0001000) 1331 | emc0 = EMC(l, 0xb0002000) 1332 | emc1 = EMC(l, 0xb0003000) 1333 | gdma = GDMA(l, 0xb0004000) 1334 | usb0 = USB(l, 0xb0005000) 1335 | usb1 = USB(l, 0xb0006000) 1336 | sdhc = SDHC(l, 0xb0007000) 1337 | uart0= UART(l, 0xb8000000) 1338 | uart1= UART(l, 0xb8000100) 1339 | peci = PECI(l, 0xb8000200) 1340 | gfxi = GFXI(l, 0xb8000300) 1341 | sspi = SSPI(l, 0xb8000400) 1342 | tmr = Timers(l, 0xb8001000) 1343 | aic = AIC(l, 0xb8002000) 1344 | gpio = GPIO(l, 0xb8003000) 1345 | mft0 = MFT(l, 0xb8004000) 1346 | mft1 = MFT(l, 0xb8005000) 1347 | smb0 = SMB(l, 0xb8006000) 1348 | smb1 = SMB(l, 0xb8006100) 1349 | smb2 = SMB(l, 0xb8006200) 1350 | smb3 = SMB(l, 0xb8006300) 1351 | smb4 = SMB(l, 0xb8006400) 1352 | smb5 = SMB(l, 0xb8006500) 1353 | pwm = PWM(l, 0xb8007000) 1354 | kcs = KCS(l, 0xb8008000) 1355 | adc = ADC(l, 0xb8009000) 1356 | rng = RNG(l, 0xb800a000) 1357 | aes = AES(l, 0xb800b000) 1358 | fiu = FIU(l, 0xc8000000) 1359 | shm = SHM(l, 0xc8001000) 1360 | rom = ROM(l, 0xffff0000) 1361 | 1362 | emc0.init() 1363 | -------------------------------------------------------------------------------- /src/bare-metal/monitor.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (C) J. Neuschäfer 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define ARRAY_LENGTH(a) (sizeof(a) / sizeof((a)[0])) 9 | #define BIT(x) (1ULL << (x)) 10 | #define min(a,b) (((a) < (b))? (a) : (b)) 11 | 12 | /* MMIO accessors */ 13 | 14 | static uint8_t read8(unsigned long addr) { return *(volatile uint8_t *)addr; } 15 | static uint16_t read16(unsigned long addr) { return *(volatile uint16_t *)addr; } 16 | static uint32_t read32(unsigned long addr) { return *(volatile uint32_t *)addr; } 17 | 18 | static void write8(unsigned long addr, uint8_t value) { *(volatile uint8_t *)addr = value; } 19 | static void write16(unsigned long addr, uint16_t value) { *(volatile uint16_t *)addr = value; } 20 | static void write32(unsigned long addr, uint32_t value) { *(volatile uint32_t *)addr = value; } 21 | 22 | 23 | /* UART driver */ 24 | 25 | #define UART_BASE 0xb8000000 26 | #define MFSEL1 0xb000000c 27 | #define GPIO_BASE 0xb8003000 28 | #define CLK_BASE 0xb0000200 29 | 30 | static void uart_init(void) 31 | { 32 | /* Configure UART clock to a know-good state */ 33 | uint32_t clksel = read32(CLK_BASE + 4); 34 | write32(CLK_BASE + 4, (clksel & ~0x30) | 0x20); // CLKSEL.UARTCKSEL = 48 MHz 35 | uint32_t clken = read32(CLK_BASE + 0); 36 | write32(CLK_BASE + 0, clken | (1 << 11)); // CLKEN.UART0 = enable 37 | 38 | /* 39 | * Set divisor to 13 (24MHz / 16 / 13 = 115384Hz. Close enough.) 40 | * The -2 is a Nuvoton-specific quirk. 41 | */ 42 | write32(UART_BASE + 0x0c, 0x80); // enable divisor latch 43 | write32(UART_BASE + 0x00, 13 - 2); // low byte 44 | write32(UART_BASE + 0x04, 0); // high byte 45 | write32(UART_BASE + 0x0c, 0x03); // disable divisor latch; set 8n1 46 | 47 | /* Clear and initialize UART FIFOs */ 48 | write32(UART_BASE + 0x08, 0x87); // RX trigger = 8 bytes; Reset/enable both FIFOs 49 | 50 | /* Disable timeout interrupt */ 51 | write32(UART_BASE + 0x1c, 0); 52 | 53 | /* Set MFSEL1.BSPSEL to enable UART0 pinmux */ 54 | uint32_t mfsel1 = read32(MFSEL1); 55 | write32(MFSEL1, mfsel1 | (1 << 9)); 56 | 57 | /* Make sure BSP (debug UART) pins (GPIO2.9/10) are not outputs, for good measure */ 58 | uint32_t gpio2cfg0 = read32(GPIO_BASE + 0x3c); 59 | write32(GPIO_BASE + 0x3c, gpio2cfg0 & ~(3 << 9)); 60 | } 61 | 62 | static int uart_can_tx(void) 63 | { 64 | return !!(read32(UART_BASE + 0x14) & 0x20); 65 | } 66 | 67 | static int uart_can_rx(void) 68 | { 69 | return !!(read32(UART_BASE + 0x14) & 1); 70 | } 71 | 72 | static void uart_tx(char ch) 73 | { 74 | while (!uart_can_tx()) 75 | ; 76 | write32(UART_BASE + 0, ch); 77 | } 78 | 79 | static char uart_rx(void) 80 | { 81 | while (!uart_can_rx()) 82 | ; 83 | return read32(UART_BASE + 0); 84 | } 85 | 86 | 87 | /* Timer driver */ 88 | 89 | #define TIMER_BASE 0xb8001000 90 | #define TCSR0 (TIMER_BASE + 0x00) 91 | #define TICR0 (TIMER_BASE + 0x08) 92 | #define TDR0 (TIMER_BASE + 0x10) 93 | #define WTCR (TIMER_BASE + 0x1c) 94 | 95 | static bool timer_is_active() 96 | { 97 | return !!(read32(TCSR0) & (1 << 25)); 98 | } 99 | 100 | static void start_timer(uint32_t usecs) 101 | { 102 | /* Reset timer 0 */ 103 | write32(TCSR0, 1 << 26); 104 | 105 | /* Set initial count */ 106 | write32(TICR0, usecs / 10); 107 | 108 | /* 109 | * Assuming the input clock runs at 24 MHz, set the prescaler to 240 to 110 | * let the timer decrement at 0.1 MHz. 111 | */ 112 | uint32_t tcsr = 240 - 1; 113 | 114 | /* Enable */ 115 | tcsr |= 1 << 30; 116 | 117 | write32(TCSR0, tcsr); 118 | 119 | /* Wait for the timer to become active */ 120 | while (!timer_is_active()) 121 | ; 122 | } 123 | 124 | static bool timeout() 125 | { 126 | /* Timeout is reached when the timer is not active anymore */ 127 | return !timer_is_active(); 128 | } 129 | 130 | static void watchdog_reset() 131 | { 132 | write32(WTCR, 0x82); 133 | } 134 | 135 | static void watchdog_disable() 136 | { 137 | write32(WTCR, 0); 138 | } 139 | 140 | 141 | /* Console I/O functions */ 142 | 143 | /* Print one character. LF is converted to CRLF. */ 144 | static int putchar(int c) 145 | { 146 | if (c == '\n') 147 | uart_tx('\r'); 148 | uart_tx(c); 149 | return c; 150 | } 151 | 152 | /* Print a string. */ 153 | static void putstr(const char *s) 154 | { 155 | for (const char *p = s; *p; p++) 156 | putchar(*p); 157 | } 158 | 159 | /* Print a line. CRLF is added at the end. */ 160 | static int puts(const char *s) 161 | { 162 | putstr(s); 163 | putchar('\n'); 164 | return 0; 165 | } 166 | 167 | /* Print a 8-bit number in hex. */ 168 | static void put_hex8(uint8_t x) 169 | { 170 | static const char hex[16] = "0123456789abcdef"; 171 | 172 | putchar(hex[x >> 4]); 173 | putchar(hex[x & 15]); 174 | } 175 | 176 | /* Print a 16-bit number in hex. */ 177 | static void put_hex16(uint16_t x) 178 | { 179 | put_hex8(x >> 8); 180 | put_hex8(x & 255); 181 | } 182 | 183 | /* Print a 32-bit number in hex. */ 184 | static void put_hex32(uint32_t x) 185 | { 186 | put_hex16(x >> 16); 187 | put_hex16(x & 65535); 188 | } 189 | 190 | /* Get a character from the UART */ 191 | static int getchar(void) 192 | { 193 | return uart_rx(); 194 | } 195 | 196 | 197 | /* String functions */ 198 | 199 | static size_t strlen(const char *s) 200 | { 201 | size_t len = 0; 202 | 203 | for (const char *p = s; *p; p++) 204 | len++; 205 | 206 | return len; 207 | } 208 | 209 | static int strncmp(const char *a, const char *b, size_t n) 210 | { 211 | for (size_t i = 0; i < n && a[i] && b[i]; i++) { 212 | if (a[i] != b[i]) 213 | return (int)a[i] - (int)b[i]; 214 | } 215 | 216 | return 0; 217 | } 218 | 219 | static void *memcpy(void *d, const void *s, size_t n) 220 | { 221 | char *dc = d; 222 | const char *sc = s; 223 | 224 | for (size_t i = 0; i < n; i++) 225 | dc[i] = sc[i]; 226 | 227 | return d; 228 | } 229 | 230 | /* Parse a number, similar to strtol. base 0 means auto-detect */ 231 | static bool parse_int(const char *s, uint32_t base, uint32_t *result) 232 | { 233 | uint32_t x = 0, digit; 234 | const char *p = s; 235 | 236 | if (base == 0) { 237 | if (s[0] == '0' && s[1] == 'x') { 238 | base = 16; 239 | p += 2; 240 | } else { 241 | base = 10; 242 | } 243 | } 244 | 245 | for (; *p; p++) { 246 | if (*p >= '0' && *p <= '9') { 247 | digit = *p - '0'; 248 | } else if (*p >= 'a' && *p <= 'z') { 249 | digit = *p - 'a' + 10; 250 | } else if (*p >= 'A' && *p <= 'Z') { 251 | digit = *p - 'A' + 10; 252 | } else { 253 | putstr("Invalid number "); 254 | puts(s); 255 | return false; 256 | } 257 | 258 | if (digit >= base) { 259 | putstr("Invalid number "); 260 | puts(s); 261 | return false; 262 | } 263 | 264 | x *= base; 265 | x += digit; 266 | } 267 | 268 | *result = x; 269 | return true; 270 | } 271 | 272 | 273 | /* FIU driver */ 274 | 275 | #define MMFLASH_BASE 0xc0000000 276 | #define FIU_BASE 0xc8000000 277 | #define FIU_FWIN1_LOW (FIU_BASE + 4) 278 | #define FIU_FWIN1_HIGH (FIU_BASE + 6) 279 | #define FIU_UMA_CODE (FIU_BASE + 0x16) 280 | #define FIU_UMA_CODE (FIU_BASE + 0x16) 281 | #define FIU_UMA_AB0 (FIU_BASE + 0x17) 282 | #define FIU_UMA_AB1 (FIU_BASE + 0x18) 283 | #define FIU_UMA_AB2 (FIU_BASE + 0x19) 284 | #define FIU_UMA_DB0 (FIU_BASE + 0x1a) 285 | #define FIU_UMA_DB1 (FIU_BASE + 0x1b) 286 | #define FIU_UMA_DB2 (FIU_BASE + 0x1c) 287 | #define FIU_UMA_DB3 (FIU_BASE + 0x1d) 288 | #define FIU_UMA_CTS (FIU_BASE + 0x1e) 289 | 290 | #define CTS_EXEC_DONE BIT(7) 291 | #define CTS_DEV_NUM_SHIFT 5 292 | #define CTS_RD_WR BIT(4) 293 | #define CTS_A_SIZE BIT(3) 294 | #define CTS_D_SIZE_SHIFT 0 295 | 296 | static void fiu_init(void) 297 | { 298 | /* 299 | * TODO: 300 | * - maximize AHB3 ≤ 65 MHz 301 | * - set BURST_CFG.R_BURST = 0b11 (16 bytes read burst) 302 | * - set SPI_FL_CFG.F_READ = 1 (fast read) 303 | * - set SPI_TIM = 0x0b 304 | */ 305 | } 306 | 307 | static void fiu_set_uma_code(uint8_t code) 308 | { 309 | write8(FIU_UMA_CODE, code); 310 | } 311 | 312 | static void fiu_set_uma_addr(size_t a) 313 | { 314 | write8(FIU_UMA_AB0, a & 0xff); 315 | write8(FIU_UMA_AB1, (a >> 8) & 0xff); 316 | write8(FIU_UMA_AB2, (a >> 16) & 0xff); 317 | } 318 | 319 | static void fiu_do_uma(bool write, bool use_addr, size_t data_len) 320 | { 321 | uint8_t cts = CTS_EXEC_DONE | (0 << CTS_DEV_NUM_SHIFT) | (data_len << CTS_D_SIZE_SHIFT); 322 | if (use_addr) 323 | cts |= CTS_A_SIZE; 324 | if (write) 325 | cts |= CTS_RD_WR; 326 | write8(FIU_UMA_CTS, cts); 327 | while (read8(FIU_UMA_CTS) & CTS_EXEC_DONE) 328 | ; 329 | } 330 | 331 | /* Read status register */ 332 | static uint8_t fiu_rsr(void) 333 | { 334 | fiu_set_uma_code(0x05); 335 | fiu_do_uma(false, false, 1); 336 | return read8(FIU_UMA_DB0); 337 | } 338 | 339 | /* Poll the Write-in-progress/BUSY bit */ 340 | static void fiu_poll_wip(void) 341 | { 342 | while (fiu_rsr() & 1) 343 | ; 344 | } 345 | 346 | /* Write Enable */ 347 | static void fiu_wren(void) 348 | { 349 | fiu_set_uma_code(0x06); 350 | fiu_do_uma(false, false, 0); 351 | } 352 | 353 | /* Sector Erase (4 KiB) */ 354 | static void fiu_erase4k(uint32_t addr) 355 | { 356 | fiu_wren(); 357 | fiu_set_uma_code(0x20); 358 | fiu_set_uma_addr(addr); 359 | fiu_do_uma(false, true, 0); 360 | 361 | fiu_poll_wip(); 362 | } 363 | 364 | static void fiu_prog8(uint32_t addr, uint8_t data) 365 | { 366 | fiu_wren(); 367 | write8(addr | MMFLASH_BASE, data); 368 | 369 | fiu_poll_wip(); 370 | 371 | if (read8(addr | MMFLASH_BASE) != data) { 372 | putstr("Flash programming error at "); 373 | put_hex32(addr); 374 | putstr(", "); 375 | put_hex8(read8(addr | MMFLASH_BASE)); 376 | putstr(" != "); 377 | put_hex8(data); 378 | putchar('\n'); 379 | } 380 | } 381 | 382 | static void fiu_prog8_as_needed(uint32_t addr, const uint8_t *data, size_t data_len) 383 | { 384 | for (int i = 0; i < data_len; i++) 385 | if (read8(MMFLASH_BASE + addr+i) != data[i]) 386 | fiu_prog8(addr+i, data[i]); 387 | } 388 | 389 | static bool fiu_page_needs_erase(uint32_t addr, const uint8_t *data, size_t count) 390 | { 391 | /* If the flash has any bits cleared that are set in the new data, we 392 | need an erase to set these bits again. */ 393 | for (size_t i = 0; i < count; i++) 394 | if (~read8(MMFLASH_BASE+addr+i) & data[i]) 395 | return true; 396 | 397 | return false; 398 | } 399 | 400 | static void fiu_flash(const uint8_t *data, uint32_t addr, size_t count) 401 | { 402 | uint16_t fwin1_low = read16(FIU_FWIN1_LOW); 403 | uint16_t fwin1_high = read16(FIU_FWIN1_HIGH); 404 | 405 | write16(FIU_FWIN1_LOW, addr / 0x1000); 406 | write16(FIU_FWIN1_HIGH, (addr + count + 0xfff) / 0x1000); 407 | 408 | for (size_t p = 0; p < count; p += 0x1000) { 409 | size_t chunk = min(0x1000, count - p); 410 | 411 | if (fiu_page_needs_erase(addr+p, data+p, chunk)) 412 | fiu_erase4k(addr+p); 413 | 414 | fiu_prog8_as_needed(addr+p, data+p, chunk); 415 | } 416 | 417 | write16(FIU_FWIN1_LOW, fwin1_low); 418 | write16(FIU_FWIN1_HIGH, fwin1_high); 419 | } 420 | 421 | 422 | /* Command interpreter */ 423 | 424 | struct command { 425 | /* The name of the command, null-terminated if possible */ 426 | char name[4]; 427 | 428 | /* A description of the arguments */ 429 | const char *arguments; 430 | 431 | /* A description of the function */ 432 | const char *description; 433 | 434 | /* The implementation */ 435 | void (*function)(int argc, char **argv); 436 | }; 437 | 438 | static void cmd_echo(int argc, char **argv) 439 | { 440 | for (int i = 1; i < argc; i++) { 441 | putstr(argv[i]); 442 | putchar(' '); 443 | } 444 | putchar('\n'); 445 | } 446 | 447 | static void cmd_read(int argc, char **argv) 448 | { 449 | size_t elems_per_line, increment, elems, addr, pos = 0; 450 | char op = argv[0][1]; 451 | 452 | switch (argc) { 453 | case 2: 454 | elems = 1; 455 | break; 456 | case 3: 457 | if (!parse_int(argv[2], 0, &elems)) 458 | return; 459 | break; 460 | default: 461 | puts("Usage error"); 462 | return; 463 | } 464 | 465 | switch (op) { 466 | case 'b': 467 | elems_per_line = 16; 468 | increment = 1; 469 | break; 470 | case 'h': 471 | elems_per_line = 16; 472 | increment = 2; 473 | break; 474 | case 'w': 475 | elems_per_line = 8; 476 | increment = 4; 477 | break; 478 | default: 479 | return; 480 | } 481 | 482 | if (!parse_int(argv[1], 16, &addr)) 483 | return; 484 | 485 | for (size_t i = 0; i < elems; i++) { 486 | uint32_t value; 487 | 488 | /* Beginning of the line */ 489 | if (pos == 0) { 490 | if (i) 491 | putchar('\n'); 492 | put_hex32(addr); 493 | putstr(": "); 494 | } else { 495 | putchar(' '); 496 | } 497 | 498 | switch (op) { 499 | case 'b': 500 | value = read8(addr); 501 | put_hex8(value); 502 | break; 503 | case 'h': 504 | value = read16(addr); 505 | put_hex16(value); 506 | break; 507 | case 'w': 508 | value = read32(addr); 509 | put_hex32(value); 510 | break; 511 | } 512 | 513 | addr += increment; 514 | if (++pos == elems_per_line) 515 | pos = 0; 516 | } 517 | 518 | putchar('\n'); 519 | } 520 | 521 | static void cmd_write(int argc, char **argv) 522 | { 523 | size_t increment, addr; 524 | char op = argv[0][1]; 525 | 526 | if (argc < 3) { 527 | puts("Usage error"); 528 | return; 529 | } 530 | 531 | switch (op) { 532 | case 'b': 533 | increment = 1; 534 | break; 535 | case 'h': 536 | increment = 2; 537 | break; 538 | case 'w': 539 | increment = 4; 540 | break; 541 | default: 542 | return; 543 | } 544 | 545 | if (!parse_int(argv[1], 16, &addr)) 546 | return; 547 | 548 | for (size_t i = 2; i < argc; i++) { 549 | uint32_t value; 550 | 551 | if (!parse_int(argv[i], 0, &value)) 552 | return; 553 | 554 | switch (op) { 555 | case 'b': 556 | write8(addr, value); 557 | break; 558 | case 'h': 559 | write16(addr, value); 560 | break; 561 | case 'w': 562 | write32(addr, value); 563 | break; 564 | } 565 | 566 | addr += increment; 567 | } 568 | } 569 | 570 | static void cmd_copy(int argc, char **argv) 571 | { 572 | size_t increment, src, dest, count; 573 | char op = argv[0][1]; 574 | 575 | if (argc < 3) { 576 | puts("Usage error"); 577 | return; 578 | } 579 | 580 | switch (op) { 581 | case 'b': 582 | increment = 1; 583 | break; 584 | case 'h': 585 | increment = 2; 586 | break; 587 | case 'w': 588 | increment = 4; 589 | break; 590 | default: 591 | return; 592 | } 593 | 594 | if (!parse_int(argv[1], 16, &src)) 595 | return; 596 | if (!parse_int(argv[2], 16, &dest)) 597 | return; 598 | if (!parse_int(argv[3], 0, &count)) 599 | return; 600 | 601 | for (size_t i = 0; i < count; i++) { 602 | uint32_t value; 603 | 604 | switch (op) { 605 | case 'b': 606 | value = read8(src); 607 | write8(dest, value); 608 | break; 609 | case 'h': 610 | value = read16(src); 611 | write16(dest, value); 612 | break; 613 | case 'w': 614 | value = read32(src); 615 | write32(dest, value); 616 | break; 617 | } 618 | 619 | src += increment; 620 | dest += increment; 621 | } 622 | } 623 | 624 | static void cmd_flash(int argc, char **argv) 625 | { 626 | size_t src, dest, count; 627 | 628 | if (argc != 4) { 629 | puts("Usage error"); 630 | return; 631 | } 632 | 633 | if (!parse_int(argv[1], 16, &src)) 634 | return; 635 | if (!parse_int(argv[2], 16, &dest)) 636 | return; 637 | if (!parse_int(argv[3], 0, &count)) 638 | return; 639 | 640 | /* The destination address must be 4 KiB aligned and fit into 16 MiB. */ 641 | if (dest & 0xff000fff) { 642 | puts("Usage error"); 643 | return; 644 | } 645 | 646 | if (count > 0x1000000 || dest + count > 0x1000000) { 647 | puts("Too big"); 648 | return; 649 | } 650 | 651 | fiu_flash((const uint8_t *)src, dest, count); 652 | } 653 | 654 | void instruction_memory_barrier(void); 655 | static void cmd_imb(int argc, char **argv) 656 | { 657 | instruction_memory_barrier(); 658 | } 659 | 660 | void do_call(uint32_t fn, uint32_t a1, uint32_t a2, uint32_t a3); 661 | static void cmd_call(int argc, char **argv) 662 | { 663 | uint32_t fn, args[3]; 664 | int i; 665 | 666 | if (argc < 2) { 667 | puts("Usage error"); 668 | return; 669 | } 670 | 671 | if (!parse_int(argv[1], 16, &fn)) 672 | return; 673 | 674 | for (i = 0; i < 3 && 2 + i < argc; i++) { 675 | args[i] = 0; 676 | if (2 + i < argc) 677 | parse_int(argv[2 + i], 0, &args[i]); 678 | } 679 | 680 | instruction_memory_barrier(); 681 | 682 | do_call(fn, args[0], args[1], args[2]); 683 | } 684 | 685 | static void source(const char *script); 686 | static void cmd_src(int argc, char **argv) 687 | { 688 | uint32_t script; 689 | 690 | if (argc != 2) { 691 | puts("Usage error"); 692 | return; 693 | } 694 | 695 | if (!parse_int(argv[1], 16, &script)) 696 | return; 697 | 698 | source((const char *)script); 699 | } 700 | 701 | static void cmd_reset(int argc, char **argv) 702 | { 703 | if (argc != 1) { 704 | puts("Usage error"); 705 | return; 706 | } 707 | 708 | watchdog_reset(); 709 | } 710 | 711 | extern const char _bootscript[]; 712 | static void cmd_boot(int argc, char **argv) 713 | { 714 | if (argc != 1) { 715 | puts("Usage error"); 716 | return; 717 | } 718 | 719 | source(_bootscript); 720 | } 721 | 722 | static void cmd_help(int argc, char **argv); 723 | static const struct command commands[] = { 724 | { "help", "[command]", "Show help output for one or all commands", cmd_help }, 725 | { "echo", "[words]", "Echo a few words", cmd_echo }, 726 | { "rb", "address [count]", "Read one or more bytes", cmd_read }, 727 | { "rh", "address [count]", "Read one or more half-words (16-bit)", cmd_read }, 728 | { "rw", "address [count]", "Read one or more words (32-bit)", cmd_read }, 729 | { "wb", "address values", "Write one or more bytes", cmd_write }, 730 | { "wh", "address values", "Write one or more half-words (16-bit)", cmd_write }, 731 | { "ww", "address values", "Write one or more words (32-bit)", cmd_write }, 732 | { "cb", "source destination count", "Copy one or more bytes", cmd_copy }, 733 | { "ch", "source destination count", "Copy one or more half-words (16-bit)", cmd_copy }, 734 | { "cw", "source destination count", "Copy one or more words (32-bit)", cmd_copy }, 735 | { "fl", "source destination count", "Write data to flash; destination must be 4k-aligned", cmd_flash }, 736 | { "imb", "", "Instruction memory barrier", cmd_imb }, 737 | { "call", "address [up to 3 args]", "Call a function by address", cmd_call }, 738 | { "src", "address", "Source/run script at address", cmd_src }, 739 | { "rst", "", "Perform a system reset", cmd_reset }, 740 | { "boot", "", "Continue with the usual boot flow", cmd_boot }, 741 | }; 742 | 743 | static const struct command *find_command(const char *name) 744 | { 745 | if (strlen(name) > 4) 746 | return NULL; 747 | 748 | for (int i = 0; i < ARRAY_LENGTH(commands); i++) 749 | if (!strncmp(name, commands[i].name, 4)) 750 | return &commands[i]; 751 | 752 | return NULL; 753 | } 754 | 755 | static void cmd_help(int argc, char **argv) 756 | { 757 | if (argc > 1) { 758 | for (int i = 1; i < argc; i++) { 759 | const char *name = argv[i]; 760 | 761 | const struct command *cmd = find_command(name); 762 | if (!cmd) { 763 | putstr("Unknown command "); 764 | puts(name); 765 | return; 766 | } 767 | 768 | putstr(name); 769 | putstr(" - "); 770 | puts(cmd->description); 771 | 772 | putstr("Usage: "); 773 | putstr(name); 774 | putchar(' '); 775 | puts(cmd->arguments); 776 | } 777 | } else { 778 | for (int i = 0; i < ARRAY_LENGTH(commands); i++) { 779 | char name[5]; 780 | 781 | memcpy(name, commands[i].name, 4); 782 | name[4] = 0; 783 | 784 | putstr(name); 785 | putstr(" - "); 786 | puts(commands[i].description); 787 | } 788 | } 789 | } 790 | 791 | 792 | /* Main program */ 793 | 794 | /* Read a line from the UART, providing some basic line editing. Ensure NUL-termination */ 795 | static void edit_line(char *line, size_t size) 796 | { 797 | size_t cursor = 0; 798 | 799 | beginning: 800 | putstr("> "); 801 | for (size_t i = 0; i < cursor; i++) 802 | putchar(line[i]); 803 | 804 | while (true) { 805 | char c = getchar(); 806 | 807 | switch ((uint8_t)c) { 808 | case 0x08: /* backspace */ 809 | case 0x7f: 810 | if (cursor) { 811 | cursor--; 812 | putstr("\10 \10"); 813 | } 814 | break; 815 | 816 | case 0x15: /* ^U, NAK: Delete the current input */ 817 | while (cursor) { 818 | cursor--; 819 | putstr("\10 \10"); 820 | } 821 | break; 822 | 823 | case 0x0c: /* ^L: form feed, clear screen */ 824 | putstr("\033[H\033[J"); 825 | goto beginning; 826 | 827 | case '\n': /* newline/enter */ 828 | case '\r': 829 | line[cursor] = 0; 830 | putchar('\n'); 831 | return; 832 | 833 | default: 834 | /* Ignore all ASCII control characters not handled above */ 835 | if (c < 0x20) 836 | break; 837 | 838 | /* Just normal characters */ 839 | if (cursor < size - 1) { 840 | line[cursor] = c; 841 | cursor++; 842 | putchar(c); 843 | } 844 | break; 845 | } 846 | } 847 | } 848 | 849 | static size_t tokenize_line(char **line, char **argv, size_t argv_length) 850 | { 851 | enum { IDLE, WORD } state = IDLE; 852 | size_t argv_index = 0; 853 | char *word_start = NULL, *p; 854 | 855 | for (p = *line; *p && argv_index < argv_length; p++) { 856 | char c = *p; 857 | 858 | /* Once we reach a comment or semicolon, the command is over */ 859 | if(c == '#' || c == ';') { 860 | *p = 0; 861 | if (c == ';') 862 | p++; 863 | break; 864 | } 865 | 866 | switch (state) { 867 | case IDLE: 868 | /* Find the beginning of a word */ 869 | if (c != ' ') { 870 | word_start = p; 871 | state = WORD; 872 | } 873 | break; 874 | case WORD: 875 | /* Find the end of a word */ 876 | if (c == ' ' || c == '\0') { 877 | *p = 0; 878 | state = IDLE; 879 | argv[argv_index++] = word_start; 880 | word_start = NULL; 881 | } 882 | break; 883 | } 884 | } 885 | 886 | /* Update line point for the caller */ 887 | *line = p; 888 | 889 | if (word_start && argv_index < argv_length) 890 | argv[argv_index++] = word_start; 891 | 892 | return argv_index; 893 | } 894 | 895 | /* Execute a single line, which may contain multiple commands separated by semicolon */ 896 | static void execute_line(char *line) 897 | { 898 | char *argv[16]; 899 | int argc; 900 | const struct command *cmd; 901 | 902 | while (true) { 903 | argc = tokenize_line(&line, argv, ARRAY_LENGTH(argv)); 904 | if (argc == 0) 905 | return; 906 | 907 | cmd = find_command(argv[0]); 908 | if (!cmd) { 909 | putstr("Unknown command "); 910 | puts(argv[0]); 911 | return; 912 | } 913 | 914 | cmd->function(argc, argv); 915 | } 916 | } 917 | 918 | /* Execute a command script that may be stored in read-only memory, 919 | and may consist of multiple lines */ 920 | static void source(const char *script) 921 | { 922 | char line[128]; 923 | const char *p; 924 | int pos = 0; 925 | 926 | for (p = script; *p; p++) { 927 | switch (*p) { 928 | case '\n': 929 | case '\r': 930 | if (pos < sizeof(line)) { 931 | line[pos++] = 0; 932 | execute_line(line); 933 | pos = 0; 934 | } else { 935 | line[sizeof(line) - 1] = 0; 936 | putstr("Line too long: "); 937 | puts(line); 938 | } 939 | break; 940 | default: 941 | if (pos < sizeof(line)) { 942 | line[pos++] = *p; 943 | } 944 | break; 945 | } 946 | } 947 | } 948 | 949 | static bool wait_for_key(uint32_t us) 950 | { 951 | start_timer(us); 952 | 953 | while (!timeout()) 954 | if (uart_can_rx()) 955 | return true; 956 | 957 | return false; 958 | } 959 | 960 | static void main_loop(void) 961 | { 962 | char line[128]; 963 | 964 | while(1) { 965 | edit_line(line, sizeof(line)); 966 | execute_line(line); 967 | } 968 | } 969 | 970 | void main(void) 971 | { 972 | watchdog_disable(); 973 | uart_init(); 974 | fiu_init(); 975 | 976 | puts("Press any key to avoid running the default boot script"); 977 | if (!wait_for_key(1000000)) { 978 | source(_bootscript); 979 | } 980 | 981 | puts("Welcome to lolmon"); 982 | main_loop(); 983 | } 984 | 985 | void handle_exception(int number) 986 | { 987 | static const char *const names[8] = { 988 | "Reset", "Undefined", "SWI", "Prefetch abort", 989 | "Data abort", "reserved", "IRQ", "FIQ", 990 | }; 991 | 992 | putchar('\n'); 993 | putstr("Exception "); 994 | put_hex8(number); 995 | putstr(", "); 996 | putstr(names[(number >> 2) & 7]); 997 | putchar('\n'); 998 | 999 | main_loop(); 1000 | } 1001 | -------------------------------------------------------------------------------- /src/bare-metal/monitor.ld: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT */ 2 | /* Copyright (C) J. Neuschäfer */ 3 | 4 | SECTIONS { 5 | . = 0x0000; 6 | 7 | .text : { 8 | *(.text*); 9 | } 10 | 11 | .rodata : { 12 | *(.rodata*); 13 | *(.data.rel.ro*); 14 | } 15 | 16 | . = 0x1800; 17 | .bootscript : { 18 | _bootscript = .; 19 | *(.bootscript); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/bare-metal/scream.s: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Copyright (C) J. Neuschäfer 3 | 4 | # scream on the debug uart. 5 | .global _start 6 | _start: 7 | mov r0, #0xb8000000 8 | mov r1, #'A' 9 | a: strb r1, [r0,#0x00] 10 | b a 11 | -------------------------------------------------------------------------------- /src/bare-metal/start.s: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Copyright (C) J. Neuschäfer 3 | 4 | .global _start 5 | _start: 6 | # Exception vector: 7 | b reset @ 0x0000: Reset 8 | bl exception @ 0x0004: Undefined 9 | bl exception @ 0x0008: SWI 10 | bl exception @ 0x000c: Prefetch abort 11 | bl exception @ 0x0010: Data abort 12 | bl exception @ 0x0014: reserved 13 | bl exception @ 0x0018: IRQ 14 | bl exception @ 0x001c: FIQ 15 | exception: 16 | mov r0, lr @ save exception vector offset 17 | sub r0, #4 18 | 19 | mrs r1, cpsr @ switch to supervisor mode 20 | bic r1, #0x1f 21 | orr r1, #0x13 22 | msr cpsr, r1 23 | 24 | mov sp, #0x2000 25 | bl handle_exception 26 | b loop 27 | 28 | reset: 29 | # Lets mostly follow https://www.kernel.org/doc/Documentation/arm/Booting 30 | # to make sure that Linux boots 31 | # - CPU mode 32 | # All forms of interrupts must be disabled (IRQs and FIQs) 33 | # 34 | # - Caches, MMUs 35 | # The MMU must be off. 36 | # Instruction cache may be on or off. 37 | # Data cache must be off. 38 | 39 | # Disable IRQ and FIQ. 40 | mrs r0, cpsr 41 | bic r0, #0xc0 42 | msr cpsr, r0 43 | 44 | # Disable data cache and MMU, use low vector base 45 | mrc p15, 0, r0, c1, c0, 0 46 | #bic r0, #4 @ DCache 47 | bic r0, #1 @ MMU 48 | bic r0, #0x2000 @ low vectors (@0x00000000) 49 | mcr p15, 0, r0, c1, c0, 0 50 | 51 | # Set the stack pointer to the end of internal RAM @ 0x0 52 | mov sp, #0x2000 53 | 54 | # Copy lolmon to internal RAM @ 0x0 55 | adr r0, _start 56 | mov r1, #0 57 | mov r2, #0x2000 58 | copy: 59 | ldr r3, [r0], #4 60 | ldr r4, [r0], #4 61 | ldr r5, [r0], #4 62 | ldr r6, [r0], #4 63 | str r3, [r1], #4 64 | str r4, [r1], #4 65 | str r5, [r1], #4 66 | str r6, [r1], #4 67 | subs r2, #16 68 | bne copy 69 | 70 | # Switch to internal RAM, in order to be independent from DRAM 71 | bl instruction_memory_barrier 72 | ldr r0, =welcome_to_iram 73 | bx r0 74 | welcome_to_iram: 75 | 76 | bl main 77 | loop: b loop 78 | 79 | 80 | .global instruction_memory_barrier 81 | instruction_memory_barrier: 82 | # See ARM926EJ-S Technical Reference Manual, 9.2 IMB operation 83 | 84 | # 9.2.1 Clean the DCache 85 | dcache_loop: 86 | push {r0, lr} 87 | 88 | mrc p15, 0, r15, c7, c10, 3 89 | bne dcache_loop 90 | 91 | # 9.2.2 Drain write buffer 92 | mcr p15, 0, r0, c7, c10, 4 93 | 94 | # 9.2.3 Synchronize data and instruction streams in level two AHB subsystems 95 | # no idea really, but let's read from uncached memory (TODO) 96 | mov r0, #0x00000000 // not sure if uncached 97 | ldr r0, [r0] 98 | 99 | # 9.2.4 Invalidate the ICache 100 | mcr p15, 0, r0, c7, c5, 0 101 | 102 | # 9.2.5 Flush the prefetch buffer 103 | b new_world 104 | new_world: 105 | 106 | pop {r0, pc} 107 | 108 | 109 | .global do_call 110 | do_call: 111 | # in: r0: function address 112 | # r1-r3: arguments 113 | 114 | push {r0-r4, lr} 115 | 116 | mov r4, r0 117 | mov r0, r1 118 | mov r1, r2 119 | mov r2, r3 120 | 121 | blx r4 122 | 123 | pop {r0-r4, pc} 124 | -------------------------------------------------------------------------------- /src/bare-metal/tlbtest.s: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Copyright (C) J. Neuschäfer 3 | 4 | # TLB flush operation test program 5 | # to see if anything hangs the system 6 | .global _start 7 | _start: 8 | mov r0, #0 9 | mov r1, #0xb8000000 10 | 11 | mov r2, #'A' // checkpoint A 12 | str r2, [r1] 13 | 14 | mcr p15, 0, r0, c8, c7, 0 // Invalidate TLB 15 | 16 | mov r2, #'B' 17 | str r2, [r1] 18 | 19 | mcr p15, 0, r0, c8, c6, 0 // Invalidate data TLB 20 | 21 | mov r2, #'C' 22 | str r2, [r1] 23 | 24 | mcr p15, 0, r0, c8, c5, 0 // Invalidate instruction TLB 25 | 26 | mov r2, #'D' 27 | str r2, [r1] 28 | 29 | # return 30 | bx lr 31 | -------------------------------------------------------------------------------- /src/buildroot/var/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | lolbmc 10 | 11 | 12 | 13 |

lolbmc (PREVIEW)

14 | 15 |

16 | This a baseboard management controller (BMC) with custom firmware 17 | based on familiar Unix tools. 18 |

19 | 20 |

21 | Unlike typical BMC firmware, it doesn't implement 22 | IPMI 23 | or a fancy web interface, or rely on Java/Flash applets. Instead, the protocol of 24 | choice is plain old SSH. 25 |

26 | 27 | 28 |

First login

29 | TODO 30 | 31 |

Changing your password

32 | 33 |
34 | ssh root@host
35 | bmc# passwd
36 | Changing password for root.
37 | ...
38 | ssh root@host persist /etc/shadow
39 | 
40 | 41 |

Adding SSH keys

42 |
43 | ssh-copy-id root@host
44 | ssh root@host persist /root/.ssh/authorized_keys
45 | ssh-copy-id sol@host
46 | ssh-copy-id bmc@host
47 | ssh root@host persist /home/*/.ssh/authorized_keys
48 | 
49 | 50 | 51 |

Power management

52 |
53 | ssh bmc@host power on
54 | 
55 | 56 |

Serial over LAN

57 |

58 | SOL can be entered by typing sol on the shell, 59 | or through ssh sol@host 60 |

61 | 62 | 63 |

Graphical console

64 |

65 | There is a VNC server listening on localhost:5900, which can be accessed through an SSH tunnel. 66 |

67 | 68 | 69 |
70 | The source code for this firmware is available on 71 | GitHub (neuschaefer/wpcm450). 72 |

73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/buildroot/var/www/main.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (C) J. Neuschäfer 3 | 4 | function setThisHost(host) { 5 | var ee = document.getElementsByClassName("thishost"); 6 | var elist = []; 7 | for (var i = 0; i < ee.length; i++) { 8 | elist.push(ee[i]) 9 | } 10 | for (var i in elist) { 11 | elist[i].textContent = host; 12 | elist[i].className = ''; 13 | } 14 | } 15 | 16 | function fixThisHost() { 17 | var host = window.location.hostname; 18 | if (host != '') { 19 | setThisHost(host); 20 | document.title = host + ' - ' + document.title; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/buildroot/var/www/style.css: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT */ 2 | /* Copyright (C) J. Neuschäfer */ 3 | 4 | body { 5 | margin: auto; 6 | font-family: sans-serif; 7 | line-height: 130%; 8 | } 9 | 10 | @media (orientation: landscape) { body { max-width: 800px; } } 11 | @media (orientation: portrait) { body { margin: 15px; } } 12 | 13 | h1 { margin-top: 50px; } 14 | pre { margin-left: 15px; } 15 | 16 | .thishost { font-style: italic; } 17 | 18 | .footer { margin-top: 30px; } 19 | .footer-text { font-size: 70%; } 20 | -------------------------------------------------------------------------------- /src/dellfw/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # Copyright (C) J. Neuschäfer 3 | 4 | trace.so 5 | -------------------------------------------------------------------------------- /src/dellfw/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Copyright (C) J. Neuschäfer 3 | 4 | .PHONY: clean uuencode 5 | 6 | trace.so: trace.c 7 | $(CC) -O2 -Wall -shared -fPIC $+ -ldl -o $@ 8 | 9 | uuencode: trace.so 10 | gzip < $+ | uuencode $+.gz 11 | 12 | clean: 13 | rm -f trace.so* 14 | -------------------------------------------------------------------------------- /src/dellfw/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Dell iDRAC6 firmware tools 5 | 6 | The main thing here is `trace.so`, which can be `LD_PRELOAD`-ed into the 7 | `fullfw` process running on Dell boards, to trace interesting ioctls. 8 | 9 | 10 | ## Build instructions 11 | 12 | Building with a toolchain that uses a recent glibc will not work, because 13 | `trace.so` will contain references to symbol version not present in Dell 14 | firmware (e.g. `printf@GLIBC_2.4` on my system). 15 | 16 | Building with a musl-based toolchain will work fairly well, but you may have to 17 | fix the binary with `patchelf --replace-needed libc.so libc.so.6 trace.so`. 18 | 19 | `make CC=/path/to/arm-linux-gcc` 20 | 21 | 22 | ## Usage 23 | 24 | - `/tmp/DellFS` is a good place to put trace.so. You can upload it with 25 | `make uuencode` and `uudecode && gunzip trace.so.gz`. 26 | - `killall fullfw && LD_PRELOAD=/path/to/trace.so fullfw` 27 | - TODO: appease the `AppMonitor` 28 | - read traces from `/tmp/trace*.log` 29 | 30 | 31 | ## References 32 | 33 | - [Kernel module source code for iDRAC6 1.70](https://github.com/neuschaefer/linux/tree/vendor/dell-idrac6-1.70/drivers/dell) 34 | -------------------------------------------------------------------------------- /src/dellfw/lcd-gif.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: MIT 3 | # Copyright (C) J. Neuschäfer 4 | # 5 | # Turn LCD commands in trace logs into an animated GIF 6 | import re, sys 7 | from PIL import Image, ImageDraw 8 | 9 | re_sspi = re.compile('^\[.*\] SSPI.WR[A-Z]* ([01]), .*\[([0-9]*),([0-9]*)\] *([0-9a-f ]*) -> ([0-9a-f ]*)$') 10 | 11 | def parse_hex(h): 12 | h = h.strip() 13 | if h == '': 14 | return b'' 15 | return bytes([int(x, 16) for x in h.strip().split(' ')]) 16 | 17 | last_row = 0 18 | matrix = {} 19 | 20 | frame_log = [] 21 | interesting_rows = set() 22 | max_width = 0 23 | 24 | def save_frame(): 25 | if 0 not in matrix: 26 | return 27 | 28 | for row in matrix: 29 | data = matrix[row] 30 | if data != b'\0' * len(data): 31 | interesting_rows.add(row) 32 | global max_width 33 | if len(data) > max_width: 34 | max_width = len(data) 35 | frame_log.append(matrix.copy()) 36 | 37 | 38 | def crunch(fn): 39 | f = open(fn, 'r') 40 | last_row = 0 41 | 42 | for line in f.readlines(): 43 | m = re_sspi.match(line) 44 | if not m: 45 | continue 46 | 47 | cs = int(m.group(1)) 48 | send_len = int(m.group(2)) 49 | recv_len = int(m.group(3)) 50 | send_data = parse_hex(m.group(4)) 51 | recv_data = parse_hex(m.group(5)) 52 | 53 | assert len(send_data) == send_len 54 | assert len(recv_data) == recv_len 55 | 56 | if cs == 0 and send_len == 4: 57 | assert send_data[0] == 0x46 58 | assert send_data[1] in range(0xb0, 0xbf) 59 | row = send_data[1] - 0xb0 60 | if row < last_row: 61 | save_frame() 62 | last_row = row 63 | 64 | if cs == 1: 65 | matrix[last_row] = send_data 66 | 67 | def write_gif(fn): 68 | rows = list(interesting_rows) 69 | height = len(rows) * 8 70 | width = max_width 71 | 72 | frames = [] 73 | for matrix in frame_log: 74 | im = Image.new('RGB', (width, height), "blue") 75 | px = im.load() 76 | 77 | for ri, row in enumerate(rows): 78 | data = matrix[row] 79 | for i in range(len(data)): 80 | for j in range(8): 81 | if (1 << j) & data[i]: 82 | px[i, 8*ri+j] = 0xffffff 83 | 84 | frames.append(im) 85 | 86 | if frames == []: 87 | print("No image data found!") 88 | else: 89 | frames[0].save(fn, format="GIF", append_images=frames, save_all=True, duration=10, loop=0) 90 | print(len(frames), "frames saved.") 91 | 92 | if len(sys.argv) == 3: 93 | crunch(sys.argv[1]) 94 | write_gif(sys.argv[2]) 95 | else: 96 | print("Usage: lcd.py trace.log lcd.gif") 97 | -------------------------------------------------------------------------------- /src/dellfw/lcd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: MIT 3 | # Copyright (C) J. Neuschäfer 4 | # 5 | # Analysis of SSPI command logs 6 | import re, sys 7 | 8 | re_sspi = re.compile('^\[.*\] SSPI.WR[A-Z]* ([01]), .*\[([0-9]*),([0-9]*)\] *([0-9a-f ]*) -> ([0-9a-f ]*)$') 9 | 10 | def parse_hex(h): 11 | h = h.strip() 12 | if h == '': 13 | return b'' 14 | return bytes([int(x, 16) for x in h.strip().split(' ')]) 15 | 16 | last_row = 0 17 | matrix = {} 18 | 19 | def render(): 20 | if 0 not in matrix: 21 | return 22 | 23 | print(' ' + '-' * len(matrix[0])) 24 | for row in matrix: 25 | data = matrix[row] 26 | if data == b'\0' * len(data): 27 | continue 28 | 29 | for i in range(8): 30 | line = '' 31 | for byte in data: 32 | if byte & (1 << i): 33 | line += 'o' 34 | else: 35 | line += ' ' 36 | print(line) 37 | 38 | 39 | 40 | def crunch(fn): 41 | f = open(fn, 'r') 42 | 43 | for line in f.readlines(): 44 | m = re_sspi.match(line) 45 | if not m: 46 | continue 47 | 48 | cs = int(m.group(1)) 49 | send_len = int(m.group(2)) 50 | recv_len = int(m.group(3)) 51 | send_data = parse_hex(m.group(4)) 52 | recv_data = parse_hex(m.group(5)) 53 | 54 | assert len(send_data) == send_len 55 | assert len(recv_data) == recv_len 56 | 57 | if cs == 0 and send_len == 4: 58 | assert send_data[0] == 0x46 59 | assert send_data[1] in range(0xb0, 0xbf) 60 | last_row = send_data[1] - 0xb0 61 | 62 | if cs == 1: 63 | matrix[last_row] = send_data 64 | render() 65 | 66 | 67 | if len(sys.argv) == 2: 68 | crunch(sys.argv[1]) 69 | else: 70 | print("Usage: lcd.py trace.log") 71 | -------------------------------------------------------------------------------- /src/dellfw/run-fullfw.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: MIT 3 | # Copyright (C) J. Neuschäfer 4 | # 5 | # run-fullfw.sh - Script that runs fullfw with tracing, and makes sure 6 | # AppMonitor doesn't get upset. 7 | 8 | HERE="$(dirname "$(realpath "$0")")" 9 | 10 | killall fullfw 11 | cd /flash/data0/BMC_Data 12 | LD_PRELOAD="$HERE/trace.so" exec /etc/sysapps_script/S_3150_fullfw_app.sh restart 13 | -------------------------------------------------------------------------------- /src/dellfw/trace.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (C) J. Neuschäfer 3 | // 4 | // trace.so - an ioctl tracer for iDRAC6's fullfw process 5 | #define _GNU_SOURCE 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) 17 | 18 | #define IOCTL_TYPE(x) (((x) & 0xff00) >> 8) 19 | #define IOCTL_TYPENR(x) ((x) & 0xffff) 20 | 21 | #define TYPE_MEM 0xb4 22 | #define MEM_READ 0xb401 23 | #define MEM_WRITE 0xb402 24 | #define MEM_REQUEST 0xb403 25 | #define MEM_RELEASE 0xb404 26 | 27 | #define TYPE_IRQ 0xb9 28 | #define IRQ_DRV_INIT 0xb900 29 | #define IRQ_DYN_INIT 0xb901 30 | #define IRQ_DYN_CONFIG 0xb902 31 | #define IRQ_DYN_CLEAR 0xb903 32 | #define IRQ_GEN_INIT 0xb904 33 | #define IRQ_UM_ISRID 0xb905 34 | 35 | #define TYPE_GPIO 0xb5 36 | #define GPIO_READ 0xb500 37 | #define GPIO_WRITE 0xb501 38 | #define GPIO_CONFIG 0xb502 39 | 40 | #define TYPE_I2C 0xb7 41 | #define I2C_INIT 0xb700 42 | #define I2C_CONFIG 0xb701 43 | #define I2C_WRITE 0xb702 44 | #define I2C_GET_MSG 0xb703 45 | #define I2C_RESET 0xb704 46 | #define I2C_GET_STAT 0xb705 47 | #define I2C_GET_HWSTAT 0xb706 48 | #define I2C_CTRL_HW 0xb707 49 | 50 | #define TYPE_PWM 0xbe 51 | #define PWM_INIT 0xbe00 52 | #define PWM_SET 0xbe01 53 | #define PWM_INFO 0xbe02 54 | #define PWM_DEBUG 0xbe03 55 | 56 | #define TYPE_POST 0xcf 57 | #define POST_INIT 0xcf00 58 | #define POST_READ 0xcf01 59 | #define POST_RESET 0xcf02 60 | 61 | #define TYPE_KCS 0xba 62 | #define KCS_INIT 0xba00 63 | #define KCS_READ 0xba01 64 | #define KCS_WRITE 0xba02 65 | #define KCS_SWSMI 0xba03 66 | #define KCS_SETCBID 0xba04 67 | 68 | #define TYPE_SSPI 0xc5 69 | #define SSPI_WRITE 0xc501 70 | 71 | #define TYPE_ADC 0xbb 72 | #define ADC_READ 0xbb00 73 | 74 | #define TYPE_FAN 0xbd 75 | #define FAN_CONFIG 0xbd00 76 | #define FAN_READ 0xbd01 77 | 78 | #define TYPE_PECI 0xc8 79 | #define PECI_READ 0xc800 80 | #define PECI_QUERY 0xc801 81 | #define PECI_COMMAND 0xc802 82 | 83 | 84 | FILE *log_stream = NULL; 85 | 86 | // A new message 87 | void msg(const char *fmt, ...) 88 | { 89 | va_list ap; 90 | struct timeval tv; 91 | 92 | if (log_stream) { 93 | gettimeofday(&tv, NULL); 94 | fprintf(log_stream, "[%6lu.%03ld] ", 95 | (unsigned long)tv.tv_sec % 1000000, tv.tv_usec / 1000); 96 | 97 | va_start(ap, fmt); 98 | vfprintf(log_stream, fmt, ap); 99 | va_end(ap); 100 | } 101 | } 102 | 103 | // A continuation of a previous message 104 | void cont(const char *fmt, ...) 105 | { 106 | va_list ap; 107 | 108 | if (log_stream) { 109 | va_start(ap, fmt); 110 | vfprintf(log_stream, fmt, ap); 111 | va_end(ap); 112 | } 113 | } 114 | 115 | 116 | #define MEM_WIDTH_8 0 117 | #define MEM_WIDTH_16 1 118 | #define MEM_WIDTH_32 2 119 | 120 | struct mem_info { 121 | uint32_t base_addr; 122 | uint16_t region_size; 123 | uint16_t offset; 124 | void *data_ptr; 125 | uint16_t data_size; 126 | uint8_t data_width; 127 | uint8_t id; 128 | }; 129 | 130 | static void memdump(struct mem_info *mem) 131 | { 132 | uint8_t *p8 = mem->data_ptr; 133 | uint16_t *p16 = mem->data_ptr; 134 | uint32_t *p32 = mem->data_ptr; 135 | int i; 136 | 137 | switch (mem->data_width) { 138 | case MEM_WIDTH_8: 139 | for (i = 0; i < mem->data_size; i++) 140 | cont(" %02x", p8[i]); 141 | break; 142 | case MEM_WIDTH_16: 143 | for (i = 0; i < mem->data_size; i++) 144 | cont(" %04x", p16[i]); 145 | break; 146 | case MEM_WIDTH_32: 147 | for (i = 0; i < mem->data_size; i++) 148 | cont(" %08x", p32[i]); 149 | break; 150 | } 151 | } 152 | 153 | static unsigned long bases[32]; 154 | 155 | static void save_base(const struct mem_info *mem) { 156 | if (mem->id < ARRAY_LENGTH(bases)) 157 | bases[mem->id] = mem->base_addr; 158 | } 159 | 160 | static unsigned long get_address(const struct mem_info *mem) { 161 | unsigned long base = 0; 162 | 163 | if (mem->id < ARRAY_LENGTH(bases)) 164 | base = bases[mem->id]; 165 | 166 | return base + mem->offset; 167 | } 168 | 169 | static void trace_mem(int request, struct mem_info *mem) 170 | { 171 | switch (IOCTL_TYPENR(request)) { 172 | case MEM_REQUEST: 173 | msg(" MEM.REQ%3d %08x:%04x\n", mem->id, mem->base_addr, mem->region_size); 174 | save_base(mem); 175 | break; 176 | case MEM_RELEASE: 177 | msg(" MEM.REL%3d %08x:%04x\n", mem->id, mem->base_addr, mem->region_size); 178 | break; 179 | case MEM_READ: 180 | msg(" MEM.RD %3d %08x -> [%2d]", mem->id, get_address(mem), mem->data_size); 181 | memdump(mem); 182 | cont("\n"); 183 | break; 184 | case MEM_WRITE: 185 | msg(" MEM.WR %3d %08x <- [%2d]", mem->id, get_address(mem), mem->data_size); 186 | memdump(mem); 187 | cont("\n"); 188 | break; 189 | default: 190 | msg(" MEM.UNK %d\n", request & 0xff); 191 | break; 192 | } 193 | } 194 | 195 | 196 | struct irq_info { 197 | uint16_t param1; /* IRQ number */ 198 | uint16_t param2; 199 | uint32_t param3; 200 | const char *isr_name; 201 | }; 202 | 203 | struct irq_usermode_record { 204 | uint16_t num_irq; 205 | uint32_t event_id; 206 | }; 207 | 208 | static void trace_irq(unsigned long request, void *arg) 209 | { 210 | struct irq_info *irq = arg; 211 | struct irq_usermode_record *um = arg; 212 | uint16_t *driver_id = arg; 213 | 214 | switch (IOCTL_TYPENR(request)) { 215 | case IRQ_DRV_INIT: 216 | msg(" IRQ.INIT driver\n"); 217 | break; 218 | case IRQ_DYN_INIT: 219 | msg(" IRQ.INIT dynairq %3d %04x %08x %p\n", 220 | irq->param1, irq->param2, irq->param3, irq->isr_name); 221 | break; 222 | case IRQ_DYN_CONFIG: 223 | msg(" IRQ.CFG dynairq %3d %04x %08x %p\n", 224 | irq->param1, irq->param2, irq->param3, irq->isr_name); 225 | break; 226 | case IRQ_DYN_CLEAR: 227 | msg(" IRQ.CLR dynairq %3d\n", irq->param1); 228 | break; 229 | case IRQ_GEN_INIT: 230 | msg(" IRQ.INIT geneisr driver %d\n", *driver_id); 231 | break; 232 | case IRQ_UM_ISRID: 233 | msg(" IRQ.UM irq %d %d\n", um->num_irq, um->event_id); 234 | break; 235 | default: 236 | msg(" IRQ.UNK %d\n", request & 0xff); 237 | break; 238 | } 239 | } 240 | 241 | 242 | struct gpio_data { 243 | uint8_t command_type; 244 | uint8_t command_num; 245 | uint8_t port_num; 246 | uint8_t pin_num; 247 | void *buf; 248 | }; 249 | 250 | static void trace_gpio(unsigned long request, struct gpio_data *gpio) 251 | { 252 | uint8_t *p8 = gpio->buf; 253 | 254 | switch (IOCTL_TYPENR(request)) { 255 | case GPIO_READ: 256 | msg("GPIO.RD %d %2d -> %d\n", gpio->port_num, gpio->pin_num, *p8); 257 | break; 258 | case GPIO_WRITE: 259 | msg("GPIO.WR %d %2d <- %d\n", gpio->port_num, gpio->pin_num, *p8); 260 | break; 261 | case GPIO_CONFIG: 262 | msg("GPIO.CFG\n"); 263 | break; 264 | default: 265 | msg("GPIO.UNK %d\n", request & 0xff); 266 | break; 267 | } 268 | } 269 | 270 | 271 | struct i2c_bus_info { 272 | uint32_t rec_flag; 273 | uint16_t driver_id; 274 | uint16_t start_count; 275 | uint16_t stop_count; 276 | uint8_t channel; 277 | uint8_t init_mode; 278 | uint8_t mode; 279 | uint8_t dev_addr; 280 | uint8_t freq; 281 | uint8_t error_status; 282 | uint8_t bus_status; 283 | uint8_t hw_ctrl; 284 | uint8_t trans_type; 285 | uint8_t reserved; 286 | }; 287 | 288 | struct i2c_buf_info { 289 | uint8_t *send_buf; 290 | uint8_t *recv_buf; 291 | uint16_t reserved; 292 | uint8_t channel; 293 | uint8_t dev_addr; 294 | uint8_t error_status; 295 | uint8_t send_size; 296 | uint8_t recv_size; 297 | uint8_t trans_type; 298 | }; 299 | 300 | static void trace_i2c(unsigned long request, void *arg) 301 | { 302 | struct i2c_bus_info *bus = arg; 303 | struct i2c_buf_info *buf = arg; 304 | 305 | switch (IOCTL_TYPENR(request)) { 306 | case I2C_INIT: 307 | msg(" I2C.INIT %d ...\n", 308 | bus->channel); 309 | break; 310 | case I2C_WRITE: 311 | msg(" I2C.WR %d ...\n", 312 | buf->channel); 313 | break; 314 | case I2C_GET_HWSTAT: 315 | msg(" I2C.HW %d ...\n", 316 | bus->channel); 317 | break; 318 | default: 319 | msg(" I2C.UNK %d\n", request & 0xff); 320 | break; 321 | } 322 | } 323 | 324 | 325 | struct pwm_dev_config { 326 | uint8_t channel; 327 | uint8_t base_freq; 328 | uint8_t freq_div; 329 | uint8_t duty_cycle; 330 | }; 331 | 332 | static void trace_pwm(unsigned long request, struct pwm_dev_config *pwm) 333 | { 334 | switch (IOCTL_TYPENR(request)) { 335 | case PWM_INIT: 336 | msg(" PWM.INIT %d <- duty %d, freq %d, div %d\n", 337 | pwm->channel, pwm->duty_cycle, pwm->base_freq, pwm->freq_div); 338 | break; 339 | case PWM_SET: 340 | msg(" PWM.SET %d <- duty %d, freq %d, div %d\n", 341 | pwm->channel, pwm->duty_cycle, pwm->base_freq, pwm->freq_div); 342 | break; 343 | case PWM_INFO: 344 | msg(" PWM.INFO %d -> duty %d, freq %d, div %d\n", 345 | pwm->channel, pwm->duty_cycle, pwm->base_freq, pwm->freq_div); 346 | break; 347 | case PWM_DEBUG: 348 | msg(" PWM.DBG %d <- duty %d, freq %d, div %d\n", 349 | pwm->channel, pwm->duty_cycle, pwm->base_freq, pwm->freq_div); 350 | break; 351 | default: 352 | msg(" PWM.UNK %d\n", request & 0xff); 353 | break; 354 | } 355 | } 356 | 357 | 358 | struct bios_post_info { 359 | uint16_t max_read_len; 360 | uint16_t copy_len; 361 | uint8_t addr_lsb; 362 | uint8_t addr_msb; 363 | uint8_t addr_enable; 364 | uint8_t reserved; 365 | uint8_t *buf; 366 | }; 367 | 368 | static void dump_u8_buf(const uint8_t *buf, size_t size) 369 | { 370 | int i; 371 | 372 | for (i = 0; i < size; i++) 373 | cont(" %02x", buf[i]); 374 | } 375 | 376 | static void trace_post(unsigned long request, struct bios_post_info *post) 377 | { 378 | switch (IOCTL_TYPENR(request)) { 379 | case POST_INIT: 380 | msg("POST.INIT %d %02x%02x\n", post->addr_enable, post->addr_msb, post->addr_lsb); 381 | break; 382 | case POST_READ: 383 | msg("POST.RD %d %02x%02x [%d]", 384 | post->addr_enable, post->addr_msb, post->addr_lsb, post->copy_len); 385 | dump_u8_buf(post->buf, post->copy_len); 386 | cont("\n"); 387 | break; 388 | case POST_RESET: 389 | msg("POST.RST %d\n", post->addr_enable); 390 | break; 391 | default: 392 | msg("POST.UNK %d\n", request & 0xff); 393 | break; 394 | } 395 | } 396 | 397 | 398 | struct kcs_info { 399 | uint8_t channel; 400 | uint8_t control; 401 | uint16_t base_addr; 402 | uint8_t write_len; 403 | /* [three bytes of padding] */ 404 | uint8_t *read_len; 405 | uint8_t *data; 406 | uint32_t rx_ok_event; 407 | uint32_t tx_ok_event; 408 | uint32_t tx_fail_event; 409 | uint16_t driver_id; 410 | uint16_t callback_driver_id; 411 | uint32_t callback_event_id; 412 | }; 413 | 414 | static void trace_kcs(unsigned long request, struct kcs_info *kcs) 415 | { 416 | switch (IOCTL_TYPENR(request)) { 417 | case KCS_READ: 418 | msg(" KCS.RD %d [%d]", kcs->channel, *kcs->read_len); 419 | dump_u8_buf(kcs->data, *kcs->read_len); 420 | cont("\n"); 421 | break; 422 | case KCS_WRITE: 423 | msg(" KCS.WR %d [%d]", kcs->channel, kcs->write_len); 424 | dump_u8_buf(kcs->data, kcs->write_len); 425 | cont("\n"); 426 | break; 427 | default: 428 | msg(" KCS.UNK %d\n", request & 0xff); 429 | break; 430 | } 431 | } 432 | 433 | 434 | struct sspi_info { 435 | uint8_t proc_time; 436 | uint8_t mode; 437 | uint8_t chip_select; 438 | uint8_t speed; 439 | uint8_t *send_buf; 440 | uint32_t send_size; 441 | uint8_t *recv_buf; 442 | uint32_t recv_size; 443 | }; 444 | 445 | static void trace_sspi(unsigned long request, struct sspi_info *sspi) 446 | { 447 | switch (IOCTL_TYPENR(request)) { 448 | case SSPI_WRITE: 449 | msg("SSPI.WR %d, time %3d, mode %02x, speed %3d, [%d,%d] ", 450 | sspi->chip_select, sspi->proc_time, sspi->mode, sspi->speed, 451 | sspi->send_size, sspi->recv_size); 452 | dump_u8_buf(sspi->send_buf, sspi->send_size); 453 | cont(" -> "); 454 | dump_u8_buf(sspi->recv_buf, sspi->recv_size); 455 | cont("\n"); 456 | break; 457 | default: 458 | msg("SSPI.UNK %d\n", request & 0xff); 459 | break; 460 | } 461 | } 462 | 463 | 464 | struct adc_data { 465 | uint8_t channel; 466 | uint32_t reading; 467 | }; 468 | 469 | static void trace_adc(unsigned long request, struct adc_data *adc) 470 | { 471 | switch (IOCTL_TYPENR(request)) { 472 | case ADC_READ: 473 | msg(" ADC.RD %3d ->%9d\n", adc->channel, adc->reading); 474 | break; 475 | default: 476 | msg(" ADC.UNK %d\n", request & 0xff); 477 | break; 478 | } 479 | } 480 | 481 | 482 | struct fan_data { 483 | uint8_t channel; 484 | uint8_t pulses_per_rev; 485 | uint16_t speed_rpm; 486 | }; 487 | 488 | static void trace_fan(unsigned long request, struct fan_data *fan) 489 | { 490 | switch (IOCTL_TYPENR(request)) { 491 | case FAN_CONFIG: 492 | msg(" FAN.CFG %2d <- %d ppr\n", fan->channel, fan->pulses_per_rev); 493 | break; 494 | case FAN_READ: 495 | msg(" FAN.RD %2d -> %d rpm\n", fan->channel, fan->speed_rpm); 496 | break; 497 | default: 498 | msg(" FAN.UNK %d\n", request & 0xff); 499 | break; 500 | } 501 | } 502 | 503 | 504 | struct peci_sensor_data { 505 | uint8_t proc_domain; 506 | uint8_t reading; 507 | uint8_t reserved1; 508 | uint8_t reserved2; 509 | }; 510 | 511 | struct peci_command_data { 512 | uint8_t client_addr; 513 | uint8_t write_len; 514 | uint8_t read_len; 515 | uint8_t command_code; 516 | uint8_t command_data[16]; 517 | }; 518 | 519 | static void trace_peci(unsigned long request, void *arg, struct peci_command_data *old_cmd) 520 | { 521 | struct peci_sensor_data *sensor = arg; 522 | struct peci_command_data *cmd = arg; 523 | 524 | switch (IOCTL_TYPENR(request)) { 525 | case PECI_READ: 526 | msg("PECI.RD %02x -> %08x\n", sensor->proc_domain, sensor->reading); 527 | break; 528 | case PECI_QUERY: 529 | msg("PECI.QRY %02x\n", sensor->proc_domain); 530 | break; 531 | case PECI_COMMAND: 532 | msg("PECI.CMD %02x:%02x [%d,%d]", cmd->client_addr, cmd->command_code, 533 | cmd->write_len, cmd->read_len); 534 | dump_u8_buf(old_cmd->command_data, cmd->write_len); 535 | cont(" -> "); 536 | dump_u8_buf(cmd->command_data, cmd->read_len); 537 | cont("\n"); 538 | break; 539 | default: 540 | msg("PECI.UNK %d\n", request & 0xff); 541 | break; 542 | } 543 | } 544 | 545 | 546 | int ioctl(int fd, unsigned long request, ...) 547 | { 548 | unsigned long arg; 549 | va_list ap; 550 | 551 | va_start(ap, request); 552 | arg = va_arg(ap, unsigned long); 553 | va_end(ap); 554 | 555 | // Things that have to be done before the ioctl is allowed to execute. 556 | struct peci_command_data peci_cmd; 557 | switch (IOCTL_TYPENR(request)) { 558 | case PECI_COMMAND: 559 | memcpy(&peci_cmd, (struct peci_command_data *)arg, sizeof(peci_cmd)); 560 | break; 561 | } 562 | 563 | int res = syscall(SYS_ioctl, fd, request, arg); 564 | 565 | switch (IOCTL_TYPE(request)) { 566 | case TYPE_MEM: 567 | trace_mem(request, (struct mem_info *)arg); 568 | break; 569 | case TYPE_IRQ: 570 | trace_irq(request, (void *)arg); 571 | break; 572 | case TYPE_GPIO: 573 | trace_gpio(request, (struct gpio_data *)arg); 574 | break; 575 | case TYPE_I2C: 576 | trace_i2c(request, (void *)arg); 577 | break; 578 | case TYPE_PWM: 579 | trace_pwm(request, (struct pwm_dev_config *)arg); 580 | break; 581 | case TYPE_POST: 582 | trace_post(request, (struct bios_post_info *)arg); 583 | break; 584 | case TYPE_KCS: 585 | trace_kcs(request, (struct kcs_info *)arg); 586 | break; 587 | case TYPE_SSPI: 588 | trace_sspi(request, (struct sspi_info *)arg); 589 | break; 590 | case TYPE_ADC: 591 | trace_adc(request, (struct adc_data *)arg); 592 | break; 593 | case TYPE_FAN: 594 | trace_fan(request, (struct fan_data *)arg); 595 | break; 596 | case TYPE_PECI: 597 | trace_peci(request, (void *)arg, &peci_cmd); 598 | break; 599 | default: 600 | msg(" UNK.ioctl(%d, %08lx, %08lx)\n", fd, request, arg); 601 | } 602 | 603 | return res; 604 | } 605 | 606 | 607 | struct event_data { 608 | uint16_t driver_id; 609 | // uint16_t padding; 610 | uint32_t event_id; 611 | }; 612 | 613 | static void trace_event(struct event_data *event, size_t count, ssize_t res) 614 | { 615 | // aess_eventhandler_read returns zero on success, contrary to how read(2) should work. 616 | if (count != 8 || res != 0) { 617 | msg(" EV.GET: Unusual read from eventhandler FD: %zu %zd\n", count, res); 618 | return; 619 | } 620 | 621 | msg(" EV.GET driver %u, event %u\n", event->driver_id, event->event_id); 622 | } 623 | 624 | 625 | static int eventhandler_fd = -1; 626 | 627 | int open(const char *pathname, int flags, mode_t mode) 628 | { 629 | int res = syscall(SYS_open, pathname, flags, mode); 630 | 631 | if (strcmp(pathname, "/dev/aess_eventhandlerdrv") == 0) 632 | eventhandler_fd = res; 633 | 634 | return res; 635 | } 636 | 637 | ssize_t read(int fd, void *buf, size_t count) 638 | { 639 | ssize_t res = syscall(SYS_read, fd, buf, count); 640 | 641 | if (eventhandler_fd >= 0 && fd == eventhandler_fd) 642 | trace_event(buf, count, res); 643 | 644 | return res; 645 | } 646 | 647 | static void init_trace(void) __attribute__((constructor)); 648 | static void init_trace(void) 649 | { 650 | char filename[100]; 651 | 652 | snprintf(filename, sizeof(filename), "/tmp/trace-%d.log", getpid()); 653 | log_stream = fopen(filename, "w"); 654 | if (log_stream) { 655 | setlinebuf(log_stream); 656 | msg("Hello from trace.so\n"); 657 | } 658 | } 659 | -------------------------------------------------------------------------------- /src/linux/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # Copyright (C) J. Neuschäfer 3 | 4 | gpiodump 5 | memscan 6 | memdump 7 | power 8 | -------------------------------------------------------------------------------- /src/linux/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Copyright (C) J. Neuschäfer 3 | 4 | CROSS_COMPILE := arm-linux-gnueabi- 5 | OBJCOPY := $(CROSS_COMPILE)objcopy 6 | AS := $(CROSS_COMPILE)as 7 | CC := $(CROSS_COMPILE)gcc 8 | LD := $(CROSS_COMPILE)ld 9 | CFLAGS := -Wall -static -Os 10 | 11 | PROGRAMS = memscan memdump gpiodump power 12 | 13 | all: $(PROGRAMS) 14 | 15 | clean: 16 | rm -f $(PROGRAMS) 17 | -------------------------------------------------------------------------------- /src/linux/gpiodump.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (C) J. Neuschäfer 3 | /* 4 | * Monitor GPIO activity 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define GPIO 0xb8003000 17 | 18 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) 19 | 20 | /* Eight ports */ 21 | uint32_t regs[8][5] = { 22 | /* CFG0 CFG1 CFG2 DATAOUT DATAIN */ 23 | { GPIO+0x14, GPIO+0x18, 0, GPIO+0x1c, GPIO+0x20 }, 24 | { GPIO+0x24, GPIO+0x28, GPIO+0x2c, GPIO+0x34, GPIO+0x38 }, 25 | { GPIO+0x3c, GPIO+0x40, GPIO+0x44, GPIO+0x48, GPIO+0x4c }, 26 | { GPIO+0x50, GPIO+0x54, GPIO+0x58, GPIO+0x5c, GPIO+0x60 }, 27 | { GPIO+0x64, GPIO+0x68, GPIO+0x6c, GPIO+0x70, GPIO+0x74 }, 28 | { GPIO+0x78, GPIO+0x7c, GPIO+0x80, GPIO+0x84, GPIO+0x88 }, 29 | { 0, 0, 0, 0, GPIO+0x8c }, 30 | { GPIO+0x90, GPIO+0x94, GPIO+0x98, GPIO+0x9c, GPIO+0xa0 }, 31 | }; 32 | 33 | int main(void) { 34 | int fd = open("/dev/mem", O_RDONLY); 35 | if (fd < 0) { 36 | perror("Failed to open /dev/mem"); 37 | exit(EXIT_FAILURE); 38 | } 39 | 40 | uint8_t *map = mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, GPIO); 41 | if (map == MAP_FAILED) { 42 | perror("Failed to map /dev/mem"); 43 | exit(EXIT_FAILURE); 44 | } 45 | close(fd); 46 | 47 | printf(" CFG0 CFG1 CFG2 DATAOUT DATAIN\n"); 48 | for (int port = 0; port < ARRAY_SIZE(regs); port++) { 49 | printf("[%d] ", port); 50 | for (int i = 0; i < ARRAY_SIZE(regs[port]); i++) { 51 | uint32_t addr = regs[port][i]; 52 | if (addr) { 53 | uint32_t value = *(uint32_t *)(addr - GPIO + map); 54 | printf(" %08x", value); 55 | } else { 56 | printf(" --------"); 57 | } 58 | } 59 | printf("\n"); 60 | } 61 | 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /src/linux/memdump.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (C) J. Neuschäfer 3 | /* 4 | * memdump - dump a memory range to stdout 5 | * Usage: memdump START_ADDRESS SIZE 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #define PAGE 4096ul /* an assumption that will hopefully not bite me. */ 20 | #define PAGE_MASK (PAGE - 1) 21 | 22 | int main(int argc, char **argv) { 23 | if (argc != 3) { 24 | printf("Usage: memscan START_ADDRESS SIZE\n"); 25 | return EXIT_FAILURE; 26 | } 27 | 28 | off_t base = strtoul(argv[1], NULL, 0); 29 | size_t size = strtoul(argv[2], NULL, 0); 30 | if (size == 0) { 31 | printf("Size is zero. Exiting.\n"); 32 | exit(EXIT_FAILURE); 33 | } 34 | 35 | if ((base & PAGE_MASK) || (size & PAGE_MASK)) { 36 | printf("Base or size not 4k-aligned. Exiting.\n"); 37 | exit(EXIT_FAILURE); 38 | } 39 | 40 | int fd = open("/dev/mem", O_RDONLY); 41 | if (fd < 0) { 42 | perror("Failed to open /dev/mem"); 43 | exit(EXIT_FAILURE); 44 | } 45 | 46 | void *map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, base); 47 | if (map == MAP_FAILED) { 48 | perror("Failed to map /dev/mem"); 49 | exit(EXIT_FAILURE); 50 | } 51 | 52 | close(fd); 53 | 54 | fwrite(map, 1, size, stdout); 55 | fflush(stdout); 56 | 57 | return EXIT_SUCCESS; 58 | } 59 | -------------------------------------------------------------------------------- /src/linux/memscan.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (C) J. Neuschäfer 3 | /* 4 | * memscan - scan a range in /dev/mem for changes 5 | * Usage: memscan START_ADDRESS SIZE 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #define PAGE 4096ul /* an assumption that will hopefully not bite me. */ 20 | #define PAGE_MASK (PAGE - 1) 21 | 22 | /* The state of the program */ 23 | struct state { 24 | off_t base; /* Base address of area to scan */ 25 | size_t size; /* Size of area to scan */ 26 | uint8_t *copy; /* Buffer for copy of data */ 27 | uint8_t *map; /* Mapping (page-aligned) */ 28 | }; 29 | 30 | /* Initialize the state */ 31 | static void init(struct state *state, const char *base, const char *size) { 32 | state->base = strtoul(base, NULL, 0); 33 | state->size = strtoul(size, NULL, 0); 34 | if (size == 0) { 35 | printf("Size is zero. Exiting.\n"); 36 | exit(EXIT_FAILURE); 37 | } 38 | 39 | off_t start = state->base; 40 | off_t end = state->base + state->size; 41 | 42 | /* Align start and end to page boundaries for the purposes of mmap */ 43 | start &= ~PAGE_MASK; 44 | end = (end + PAGE - 1) & ~PAGE_MASK; 45 | 46 | state->copy = malloc(state->size); 47 | if (!state->copy) { 48 | perror("Failed to allocate buffer"); 49 | exit(EXIT_FAILURE); 50 | } 51 | 52 | int fd = open("/dev/mem", O_RDONLY); 53 | if (fd < 0) { 54 | perror("Failed to open /dev/mem"); 55 | exit(EXIT_FAILURE); 56 | } 57 | 58 | state->map = mmap(NULL, end - start, PROT_READ, MAP_SHARED, fd, start); 59 | if (state->map == MAP_FAILED) { 60 | perror("Failed to map /dev/mem"); 61 | exit(EXIT_FAILURE); 62 | } 63 | 64 | close(fd); 65 | 66 | memcpy(state->copy, state->map + (state->base & PAGE_MASK), state->size); 67 | } 68 | 69 | /* Compare two chunks of memory */ 70 | static void compare(off_t addr, uint8_t *a, uint8_t *b, size_t size) { 71 | if (memcmp(a, b, size) == 0) { 72 | return; 73 | } 74 | 75 | int linesize = 32; /* bytes per line */ 76 | for (off_t off = 0; off < size; off += linesize) { 77 | if (memcmp(a + off, b + off, linesize) != 0) { 78 | printf("%08lx: ", addr + off); 79 | for (int i = 0; i < linesize; i += 4) { 80 | printf(" %08x", *(uint32_t *)(b + off + i)); 81 | } 82 | printf("\n"); 83 | } 84 | } 85 | fflush(stdout); 86 | } 87 | 88 | /* Read from IO memory */ 89 | void ioread(void *dest, const void *src, size_t n) 90 | { 91 | uint32_t *d = dest; 92 | volatile const uint32_t *s = src; 93 | 94 | for (size_t i = 0; i < n; i += 4) 95 | *d++ = *s++; 96 | } 97 | 98 | #define min(a, b) (((a) < (b))? (a) : (b)) 99 | 100 | /* One round of scanning the memory */ 101 | static void scan(struct state *state) { 102 | uint8_t buf[PAGE]; 103 | uint8_t *map = state->map + (state->base & PAGE_MASK); 104 | 105 | for (off_t off = 0; off < state->size; off += sizeof(buf)) { 106 | size_t chunk = min(sizeof(buf), state->size - off); 107 | 108 | ioread(buf, map + off, chunk); 109 | compare(state->base + off, state->copy + off, buf, chunk); 110 | memcpy(state->copy + off, buf, chunk); 111 | } 112 | } 113 | 114 | /* Main function and busy loop */ 115 | int main(int argc, char **argv) { 116 | if (argc != 3) { 117 | printf("Usage: memscan START_ADDRESS SIZE\n"); 118 | return EXIT_FAILURE; 119 | } 120 | 121 | struct state state; 122 | init(&state, argv[1], argv[2]); 123 | 124 | printf("Scanning at %08lx:%08x\n", state.base, state.size); 125 | fflush(stdout); 126 | while (true) { 127 | scan(&state); 128 | } 129 | 130 | return EXIT_SUCCESS; 131 | } 132 | -------------------------------------------------------------------------------- /src/linux/power.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (C) J. Neuschäfer 3 | /* 4 | * A program to manage host power state on Supermicro X9 boards. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #define SHORTPRESS_MS 400 21 | #define LONGPRESS_S 5 22 | 23 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) 24 | 25 | /* GPIO chip file descriptor */ 26 | int fd; 27 | 28 | /* individual GPIO line numbers */ 29 | int host_powerbtn = -1; 30 | int host_reset = -1; 31 | int host_powersts = -1; 32 | 33 | /* GPIOs */ 34 | struct gpio { 35 | const char *name; 36 | int *line; 37 | }; 38 | 39 | static struct gpio gpios[] = { 40 | { "host_powerbtn", &host_powerbtn }, 41 | { "host_reset", &host_reset }, 42 | { "host_powersts", &host_powersts }, 43 | }; 44 | 45 | static void resolve_line_names(void) 46 | { 47 | struct gpiochip_info chip; 48 | int res; 49 | 50 | res = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &chip); 51 | if (res < 0) { 52 | perror("Failed to request GPIO chip information"); 53 | exit(1); 54 | } 55 | 56 | //printf("Found GPIO chip:\n" 57 | // " %s\n" 58 | // " %s\n" 59 | // " %d lines\n", chip.name, chip.label, chip.lines); 60 | 61 | for (int i = 0; i < chip.lines; i++) { 62 | struct gpioline_info line; 63 | 64 | line.line_offset = i; 65 | res = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &line); 66 | if (res < 0) { 67 | perror("Failed to request GPIO line info"); 68 | exit(1); 69 | } 70 | 71 | if (!line.name[0]) 72 | continue; 73 | 74 | for (int j = 0; j < ARRAY_SIZE(gpios); j++) 75 | if (strcmp(line.name, gpios[j].name) == 0) 76 | *gpios[j].line = i; 77 | } 78 | 79 | bool nope = false; 80 | for (int j = 0; j < ARRAY_SIZE(gpios); j++) { 81 | if (*gpios[j].line == -1) { 82 | printf("Failed to find GPIO line %s\n", gpios[j].name); 83 | nope = true; 84 | } 85 | } 86 | if (nope) 87 | exit(1); 88 | } 89 | 90 | /* Determine host power status */ 91 | bool status(void) 92 | { 93 | struct gpiohandle_request req; 94 | 95 | req.lineoffsets[0] = host_powersts; 96 | req.flags = GPIOHANDLE_REQUEST_INPUT; 97 | strcpy(req.consumer_label, "power status"); 98 | req.lines = 1; 99 | 100 | int res = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req); 101 | if (res < 0) { 102 | perror("Failed to request power status GPIO line"); 103 | exit(1); 104 | } 105 | 106 | struct gpiohandle_data data; 107 | 108 | res = ioctl(req.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); 109 | if (res < 0) { 110 | perror("Failed to read host power status"); 111 | exit(1); 112 | } 113 | 114 | close(req.fd); 115 | 116 | return !!data.values[0]; 117 | } 118 | 119 | void cmd_status(void) 120 | { 121 | if (status()) 122 | puts("on"); 123 | else 124 | puts("off"); 125 | } 126 | 127 | /* Press the virtual power button for a given number of milliseconds */ 128 | void press(int ms) 129 | { 130 | struct gpiohandle_request req; 131 | 132 | req.lineoffsets[0] = host_powerbtn; 133 | req.flags = GPIOHANDLE_REQUEST_OUTPUT | GPIOHANDLE_REQUEST_ACTIVE_LOW; 134 | req.default_values[0] = 0; 135 | strcpy(req.consumer_label, "power button"); 136 | req.lines = 1; 137 | 138 | int res = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req); 139 | if (res < 0) { 140 | perror("Failed to request power button GPIO line"); 141 | exit(1); 142 | } 143 | 144 | struct gpiohandle_data data; 145 | 146 | data.values[0] = 1; 147 | res = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); 148 | if (res < 0) { 149 | perror("Failed to press power button"); 150 | exit(1); 151 | } 152 | 153 | usleep(1000 * ms); 154 | 155 | data.values[0] = 0; 156 | res = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); 157 | if (res < 0) { 158 | perror("Failed to release power button"); 159 | exit(1); 160 | } 161 | 162 | close(req.fd); 163 | } 164 | 165 | static void cmd_longpress(void) 166 | { 167 | press(LONGPRESS_S * 1000); 168 | } 169 | 170 | static void cmd_shortpress(void) 171 | { 172 | press(SHORTPRESS_MS); 173 | } 174 | 175 | void cmd_on(void) 176 | { 177 | if (!status()) 178 | cmd_shortpress(); 179 | } 180 | 181 | void cmd_off(void) 182 | { 183 | if (status()) 184 | cmd_longpress(); 185 | } 186 | 187 | static void wait(bool target) 188 | { 189 | while (status() != target) { 190 | usleep(50 * 1000); 191 | } 192 | } 193 | 194 | static void cmd_shutdown() 195 | { 196 | cmd_shortpress(); 197 | wait(false); 198 | } 199 | 200 | int main(int argc, char **argv) 201 | { 202 | if (argc != 2) { 203 | printf("Usage: %s [ACTION]\n\n", argv[0]); 204 | 205 | printf("Actions:\n"); 206 | printf(" - status query the current status (default)\n"); 207 | printf(" - on turn the power on\n"); 208 | printf(" - off turn the power off\n"); 209 | printf(" - shutdown kindly ask the OS to shut down\n"); 210 | printf(" - shortpress press the power button for a short time (%dms)\n", SHORTPRESS_MS); 211 | printf(" - longpress press the power button for a long time (%ds)\n", LONGPRESS_S); 212 | exit(0); 213 | }; 214 | 215 | fd = open("/dev/gpiochip0", O_RDWR); 216 | if (fd < 0) { 217 | perror("Failed to open /dev/gpiochip0"); 218 | exit(1); 219 | } 220 | 221 | resolve_line_names(); 222 | 223 | const char *action = argv[1]; 224 | 225 | if (!strcmp(action, "status")) 226 | cmd_status(); 227 | else if (!strcmp(action, "on")) 228 | cmd_on(); 229 | else if (!strcmp(action, "off")) 230 | cmd_off(); 231 | else if (!strcmp(action, "shutdown")) 232 | cmd_shutdown(); 233 | else if (!strcmp(action, "shortpress") || !strcmp(action, "boop")) 234 | cmd_shortpress(); 235 | else if (!strcmp(action, "longpress")) 236 | cmd_longpress(); 237 | else 238 | printf("Unknown action %s\n", action); 239 | 240 | return 0; 241 | } 242 | -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # Copyright (C) J. Neuschäfer 3 | 4 | mkimage 5 | -------------------------------------------------------------------------------- /tools/apply-patches.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: MIT 3 | # Copyright (C) J. Neuschäfer 4 | # 5 | # Apply the patches from an AMI GPL source package to a git repo 6 | set -e 7 | 8 | if [ $# != 1 ]; then 9 | echo "usage: $0 path/to/winbond/patches/linux" 10 | exit 1 11 | fi 12 | PATCHDIR="$1" 13 | 14 | 15 | ( 16 | cd "$PATCHDIR" 17 | find . -type f 18 | ) | cut -d/ -f2- | sort | while read filename; do 19 | echo "Applying $filename" 20 | patch -p1 < "$PATCHDIR/$filename" 21 | git add . 22 | git commit -m "Apply $filename" 23 | sleep 1 24 | done 25 | -------------------------------------------------------------------------------- /tools/gen-aten-symbol.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # SPDX-License-Identifier: MIT 3 | # Copyright (C) J. Neuschäfer 4 | import argparse 5 | 6 | parser = argparse.ArgumentParser(description='generate a flash page with the ATEN symbol in it') 7 | parser.add_argument('filename') 8 | 9 | args = parser.parse_args() 10 | 11 | 12 | buf = bytearray(0x10000) 13 | 14 | for i in range(0x10000): 15 | buf[i] = 0xff 16 | 17 | buf[0xffb3:0xffb3+8] = b'ATENs_FW' 18 | 19 | 20 | f = open(args.filename, 'wb') 21 | f.write(buf) 22 | f.close() 23 | -------------------------------------------------------------------------------- /tools/lolconv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # SPDX-License-Identifier: MIT 3 | # Copyright (C) J. Neuschäfer 4 | 5 | # convert a binary into a script for lolmon 6 | # Usage: lolconv 0x1000 foo.bin 7 | import argparse 8 | import struct 9 | 10 | def write_words(base, data): 11 | # Align the size to a multiple of four bytes 12 | while len(data) % 4 != 0: 13 | data += b'\0' 14 | 15 | words = ['0x%08x' % struct.unpack('