├── .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
16 | This a baseboard management controller (BMC) with custom firmware
17 | based on familiar Unix tools.
18 |
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 |
58 | SOL can be entered by typing
65 | There is a VNC server listening on localhost:5900, which can be accessed through an SSH tunnel.
66 | lolbmc (PREVIEW)
14 |
15 | 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 | sol
on the shell,
59 | or through ssh sol@host
60 | Graphical console
64 |
70 |
72 |