What changed, and why it matters
This commit adds support for a new COLDCARD hardware revision, the Mk5. It is a routine product-enablement patch: it teaches the firmware, bootloader, build system, simulator, and signing tools to recognize and run on Mk5 devices. There is no indication of a security vulnerability being fixed or introduced; the changes are about hardware detection, display initialization, graphics, and test coverage.
No security action required. Treat as normal hardware-support release engineering. Reviewers may want to verify that the new MK_5_OK bit does not overlap with any previously used compatibility bits and that Mk5 firmware downgrade/upgrade paths are correctly enforced, but the diff itself does not show a vulnerability.
Security signals we found
No security-relevant signals detected in the diff.
Change is hardware bring-up / product enablement only.
No fixes for memory corruption, authentication bypass, cryptographic flaws, or privilege escalation.
No vendor disclosure of security relevance in commit message or code comments.
Evidence from the diff
The commit introduces Mk5 hardware support across the COLDCARD firmware tree. Key changes include: adding MK_5_OK to the firmware header compatibility bitmap (replacing a reserved RFU bit), detecting Mk5 via the STRAP_MK5 (PE0) pin, enabling a +12V OLED rail on early Mk5 revs, adding Mk5-specific OLED init/scroll commands and simulator graphics, making the build system default to the unified ‘Mk’ target (Mk4/Mk5), and updating tests to include Mk5 alongside Mk4 and Q1. The patch also refactors the SSD1306 driver to separate Mk4 and Mk5 display configurations and moves the busy-bar animation logic into the driver. No security-relevant bug fixes or cryptographic changes are present.
Changed components
stm32/sigheader.hstm32/sigheader.pyshared/version.pyshared/display.pyshared/ssd1306.pystm32/mk4-bootloader/gpio.cstm32/mk4-bootloader/oled.cstm32/mk4-bootloader/gpio.hcli/signit.pyshared/utils.pyunix/simulator.pyunix/variant/version.pytesting framework filesInspect captured patch +728 / −276
diff --git a/cli/signit.py b/cli/signit.py
index 62a2bb4..31ddff2 100755
--- a/cli/signit.py
+++ b/cli/signit.py
@@ -208,8 +208,9 @@ def readback(fname):
if v & MK_2_OK: d.append('Mk2')
if v & MK_3_OK: d.append('Mk3')
if v & MK_4_OK: d.append('Mk4')
+ if v & MK_5_OK: d.append('Mk5')
if v & MK_Q1_OK: d.append('Q1')
- if v & ~(MK_1_OK | MK_2_OK | MK_3_OK | MK_4_OK | MK_Q1_OK):
+ if v & ~(MK_1_OK | MK_2_OK | MK_3_OK | MK_4_OK | MK_5_OK | MK_Q1_OK):
d.append('?other?')
v = nv + '+'.join(d)
elif fld == 'timestamp':
@@ -245,7 +246,7 @@ def readback(fname):
@click.option('--pubkey-num', '-k', type=int, help='Which key # to use for signing', default=0)
@click.option('--high_water', '-h', is_flag=True, help='Mark version as new highwater mark (no downgrades below this version)')
@click.option('--verbose', '-v', default=False, is_flag=True, help='Show numbers related to signature')
-@click.option('--hw-compat', '-m', type=str, metavar='Mk4', help="Set HW compat field (hw_label value)")
+@click.option('--hw-compat', '-m', type=str, metavar='mk', help="Set HW compat field (hw_label value)")
@click.option('--backdate', type=int, metavar='DAYS',
help='Make downgrade attack test version', default=0)
@click.option('--build_dir', '-b', default='l-port/build-COLDCARD')
@@ -278,8 +279,9 @@ def doit(keydir, outfn=None, build_dir=None, high_water=False,
vectors = open(build_dir + '/firmware0.bin', 'rb').read()
body = open(build_dir + '/firmware1.bin', 'rb').read()
- if hw_compat in { 'mk4', '4'}:
- hw_compat = MK_4_OK
+ if hw_compat in { 'mk4', '4', 'mk5', '5', 'mk' }:
+ # Mk4 and 5 can run the same firmware, once Mk5 support was added
+ hw_compat = MK_4_OK | MK_5_OK
elif hw_compat == 'q1':
hw_compat = MK_Q1_OK
elif hw_compat in { 'mk3', '3'}:
diff --git a/graphics/graphics_mk4.py b/graphics/graphics_mk4.py
index 4db6a7d..7b2e752 100644
--- a/graphics/graphics_mk4.py
+++ b/graphics/graphics_mk4.py
@@ -17,6 +17,14 @@ class Graphics:
mk4_nfc_4 = (102, 49, 13, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\x9f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\x8f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00')
+ mk5_nfc_1 = (126, 49, 16, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\xe0\x0e\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\xe0\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\xe0\x0e\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff\xff\xfe0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff\xff\xfe0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\xff\xfe\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\xff\xfe\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
+
+ mk5_nfc_2 = (118, 49, 15, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\xe0\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\xe0\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\xe0\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\xff\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\xff\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00')
+
+ mk5_nfc_3 = (110, 49, 14, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x000\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x000\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x000\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00')
+
+ mk5_nfc_4 = (102, 49, 13, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\x9f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\x8f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00')
+
scroll = (3, 61, 1, 0, b'@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@@\xe0@')
selected = (9, 12, 2, 0, b'\x00\x00\x00\x00\x00\x80\x01\x80\x01\x00\x03\x00\x82\x00\xc6\x00d\x00<\x00\x18\x00\x00\x00')
diff --git a/graphics/mono/mk5_nfc_1.txt b/graphics/mono/mk5_nfc_1.txt
new file mode 100644
index 0000000..ba3527e
--- /dev/null
+++ b/graphics/mono/mk5_nfc_1.txt
@@ -0,0 +1,49 @@
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx xxx xxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxx xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
+ xxx xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
+ xxx xxx xxx xxx yy nn n f c c yyyyyyyyyyy
+ xxx xxx xxx xxx yy nn n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
+ xxx xxx xxx xxx yy n n n f c yyyy yyy
+ xxx xxx xxx xxx yy n n n f c yyyy yyy
+ xxx xxx xxx xxx yy n nn f c yyyyyyyyyyy
+ xxx xxx xxx xxx yy n nn f c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
+ xxx xxx xxx xxx yy yyyyyyyyyyy
+ xxx xxx xxx xxx yy yyyyyyyyyyy
+ xxx xxx xxx xxx yy yyyyyyyyyyy
+ xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
diff --git a/graphics/mono/mk5_nfc_2.txt b/graphics/mono/mk5_nfc_2.txt
new file mode 100644
index 0000000..71317e9
--- /dev/null
+++ b/graphics/mono/mk5_nfc_2.txt
@@ -0,0 +1,49 @@
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx xxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
+ xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
+ xxx xxx xxx yy nn n f c c yyyyyyyyyyy
+ xxx xxx xxx yy nn n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
+ xxx xxx xxx yy n n n f c yyyy yyy
+ xxx xxx xxx yy n n n f c yyyy yyy
+ xxx xxx xxx yy n nn f c yyyyyyyyyyy
+ xxx xxx xxx yy n nn f c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
+ xxx xxx xxx yy yyyyyyyyyyy
+ xxx xxx xxx yy yyyyyyyyyyy
+ xxx xxx xxx yy yyyyyyyyyyy
+ xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
diff --git a/graphics/mono/mk5_nfc_3.txt b/graphics/mono/mk5_nfc_3.txt
new file mode 100644
index 0000000..86386d9
--- /dev/null
+++ b/graphics/mono/mk5_nfc_3.txt
@@ -0,0 +1,49 @@
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
+ xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
+ xxx xxx yy nn n f c c yyyyyyyyyyy
+ xxx xxx yy nn n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
+ xxx xxx yy n n n f c yyyy yyy
+ xxx xxx yy n n n f c yyyy yyy
+ xxx xxx yy n nn f c yyyyyyyyyyy
+ xxx xxx yy n nn f c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
+ xxx xxx yy yyyyyyyyyyy
+ xxx xxx yy yyyyyyyyyyy
+ xxx xxx yy yyyyyyyyyyy
+ xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
diff --git a/graphics/mono/mk5_nfc_4.txt b/graphics/mono/mk5_nfc_4.txt
new file mode 100644
index 0000000..7bdcab9
--- /dev/null
+++ b/graphics/mono/mk5_nfc_4.txt
@@ -0,0 +1,49 @@
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxx xxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxx
+ xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxx xxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
+ xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
+ xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
+ xxx xxx yy nn n f c c yyyyyyyyyyy
+ xxx xxx yy nn n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
+ xxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
+ xxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
+ xxx xxx yy n n n f c yyyy yyy
+ xxx xxx yy n n n f c yyyy yyy
+ xxx xxx yy n nn f c yyyyyyyyyyy
+ xxx xxx yy n nn f c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
+ xxx xxx yy yyyyyyyyyyy
+ xxx xxx yy yyyyyyyyyyy
+ xxx xxx yy yyyyyyyyyyy
+ xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+ xxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
diff --git a/shared/actions.py b/shared/actions.py
index bd59671..996ae36 100644
--- a/shared/actions.py
+++ b/shared/actions.py
@@ -2397,9 +2397,23 @@ async def microsd_2fa(*a):
async def keyboard_test(*a):
# to aid keyboard testing/dev
- from ux import ux_input_text
- await ux_input_text('', max_len=128, scan_ok=True, confirm_exit=False,
- prompt='Keyboard Test', placeholder='(type whatever)')
+ if version.has_qwerty:
+ await ux_input_text('', max_len=128, scan_ok=True, confirm_exit=False,
+ prompt='Keyboard Test', placeholder='(type whatever)')
+ else:
+ from ux_mk4 import ux_input_digits
+ await ux_input_digits('')
+
+async def quick_nfc_test(*a):
+ from selftest import test_nfc
+ await test_nfc()
+
+async def clear_tested_flag(*a):
+ # so can re-create first time power up in
+ # factory case (direct to selftest)
+ settings.remove_key('tested')
+ settings.save()
+ await reset_self()
#
# Q wrappers; these will be present, but are very short on mk4
diff --git a/shared/display.py b/shared/display.py
index 6bfd83f..805f1e3 100644
--- a/shared/display.py
+++ b/shared/display.py
@@ -2,9 +2,8 @@
#
# display.py - OLED rendering
#
-import machine, uzlib, ckcc, utime
+import machine, uzlib, ckcc, utime, version
from ssd1306 import SSD1306_SPI
-from version import is_devmode
import framebuf
from graphics_mk4 import Graphics
from charcodes import OUT_CTRL_TITLE, OUT_CTRL_ADDRESS
@@ -35,11 +34,14 @@ class Display:
dc_pin = Pin('PA8', Pin.OUT)
cs_pin = Pin('PA4', Pin.OUT)
- try:
- self.dis = SSD1306_SPI(128, 64, spi, dc_pin, reset_pin, cs_pin)
- except OSError:
- print("OLED unplugged?")
- raise
+ if version.mk_num == 5:
+ # Early revs (A-D) needed this pin asserted to enable +12v to OLED
+ # - removed in rev E and later boards, but keep here for dev boards
+ # - remove this in 2027
+ vcc_en = Pin('V12EN', Pin.OUT) # aka PC1
+ vcc_en(1)
+
+ self.dis = SSD1306_SPI(128, 64, spi, dc_pin, reset_pin, cs_pin, is_mk5=(version.mk_num==5))
self.last_bar_update = 0
self.clear()
@@ -142,7 +144,7 @@ class Display:
self.icon(128-3, 1, 'scroll')
self.dis.fill_rect(128-2, pos, 1, bh, 1)
- if is_devmode and not ckcc.is_simulator():
+ if version.is_devmode and not ckcc.is_simulator():
self.dis.fill_rect(128-6, 20, 5, 21, 1)
self.text(-2, 21, 'D', font=FontTiny, invert=1)
self.text(-2, 28, 'E', font=FontTiny, invert=1)
@@ -204,61 +206,20 @@ class Display:
def busy_bar(self, enable):
# Render a continuous activity (not progress) bar in lower 8 lines of display
- # - using OLED itself to do the animation, so smooth and CPU free
- # - cannot preserve bottom 8 lines, since we have to destructively write there
- # - assumes normal horz addr mode: 0x20, 0x00
- # - speed_code=>framedelay: 0=5fr, 1=64fr, 2=128, 3=256, 4=3, 5=4, 6=25, 7=2frames
- # unused: assert 0 <= speed_code <= 7
-
- setup = bytes([
- 0x21, 0x00, 0x7f, # setup column address range (start, end): 0-127
- 0x22, 7, 7, # setup page start/end address: page 7=last 8 lines
- ])
- animate = bytes([
- 0x2e, # stop animations in progress
- 0x26, # scroll leftwards (stock ticker mode)
- 0, # placeholder
- 7, # start 'page' (vertical)
- 5, # "speed_code" # scroll speed: 7=fastest, but no order to it
- 7, # end 'page'
- 0, 0xff, # placeholders
- 0x2f # start
- ])
-
- cleanup = bytes([
- 0x2e, # stop animation
- 0x20, 0x00, # horz addr-ing mode
- 0x21, 0x00, 0x7f, # setup column address range (start, end): 0-127
- 0x22, 7, 7, # setup page start/end address: page 7=last 8 lines
- ])
-
+ #
if not enable:
- # stop animation, and redraw old (new) screen
- self.write_cmds(cleanup)
+ self.dis.busy_bar(False, None)
self.show()
else:
-
- # a pattern that repeats nicely mod 128
+ # Need a pattern that repeats nicely mod 128
# - each byte here is a vertical column, 8 pixels tall, MSB at bottom
- data = bytes(0x80 if (x%4)<2 else 0x0 for x in range(128))
-
- if ckcc.is_simulator():
- # just show as static pattern
- t = self.dis.buffer[:-128] + data
- self.dis.write_data(t)
- else:
- self.write_cmds(setup)
- self.dis.write_data(data)
- self.write_cmds(animate)
+ pat = bytes(0x80 if (x%4)<2 else 0x0 for x in range(128))
- def write_cmds(self, cmds):
- for c in cmds:
- self.dis.write_cmd(c)
+ self.dis.busy_bar(True, pat)
def set_brightness(self, val):
# normal = 0x7f, brightness=0xff, dim=0x00 (but they are all very similar)
- self.dis.write_cmd(0x81) # Set Contrast Control
- self.dis.write_cmd(val)
+ return self.dis.contrast(val)
def menu_draw(self, ry, msg, is_sel, is_checked, space_indicators):
# draw a menu item, perhaps selected, checked.
diff --git a/shared/flow.py b/shared/flow.py
index 1aef959..09b4265 100644
--- a/shared/flow.py
+++ b/shared/flow.py
@@ -307,6 +307,8 @@ DebugFunctionsMenu = [
# xxxxxxxxxxxxxxxx
MenuItem("Keyboard Test", f=keyboard_test),
MenuItem('BBQr Demo', f=debug_bbqr_test, predicate=version.has_qwerty),
+ MenuItem("NFC Test", f=quick_nfc_test),
+ MenuItem('Clear Tested', f=clear_tested_flag),
MenuItem('Debug: assert', f=debug_assert),
MenuItem('Debug: except', f=debug_except),
MenuItem('Check: BL FW', f=check_firewall_read),
diff --git a/shared/mk4.py b/shared/mk4.py
index 69b024c..3a3e229 100644
--- a/shared/mk4.py
+++ b/shared/mk4.py
@@ -1,6 +1,6 @@
# (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
-# mk4.py - Mk4 specific code, not needed on earlier devices.
+# mk4.py - Mk4 and Mk5 specific code, not needed on earlier devices.
#
#
import os, sys, pyb, ckcc, version, glob
diff --git a/shared/nfc.py b/shared/nfc.py
index c66c654..115a386 100644
--- a/shared/nfc.py
+++ b/shared/nfc.py
@@ -419,7 +419,8 @@ class NFCHandler:
dis.text(None, -3, line2)
else:
from graphics_mk4 import Graphics
- frames = [getattr(Graphics, 'mk4_nfc_%d'%i) for i in range(1, 5)]
+ from version import mk_num
+ frames = [getattr(Graphics, 'mk%d_nfc_%d'%(mk_num, i)) for i in range(1, 5)]
aborted = True
phase = -1
diff --git a/shared/selftest.py b/shared/selftest.py
index 4550b17..5f105ce 100644
--- a/shared/selftest.py
+++ b/shared/selftest.py
@@ -171,7 +171,7 @@ async def test_secure_element():
dis.clear()
- if version.has_qwerty:
+ if version.has_qwerty or version.mk_num == 5:
dis.text(0, 0, "^^-- Green? " if gg else " ^^-- Red?")
else:
if gg:
diff --git a/shared/ssd1306.py b/shared/ssd1306.py
index 11a1405..c08a064 100644
--- a/shared/ssd1306.py
+++ b/shared/ssd1306.py
@@ -1,6 +1,6 @@
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
-# ssd1306.py - MicroPython SSD1306 OLED driver, I2C and SPI interfaces
+# ssd1306.py - MicroPython SSD1306 OLED driver, with SPI interface
#
# Copied from ../external/micropython/drivers/display/ssd1306.py
#
@@ -28,49 +28,81 @@ SET_VCOM_DESEL = const(0xdb)
SET_CHARGE_PUMP = const(0x8d)
# Subclassing FrameBuffer provides support for graphics primitives
-# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
+# see <http://docs.micropython.org/en/latest/pyboard/library/framebuf.html>
+#
class SSD1306(framebuf.FrameBuffer):
- def __init__(self, width, height, external_vcc):
+ def __init__(self, width, height, is_mk5):
self.width = width
self.height = height
- self.external_vcc = external_vcc
+ self.is_mk5 = is_mk5
self.pages = self.height // 8
- #self.buffer = bytearray(self.pages * self.width)
-
self.buffer = bytearray(1024)
- assert len(self.buffer) == self.pages * self.width
+ #assert len(self.buffer) == self.pages * self.width
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()
def init_display(self):
- for cmd in (
- SET_DISP | 0x00, # off
- # address setting
- SET_MEM_ADDR, 0x00, # horizontal
- # resolution and layout
- SET_DISP_START_LINE | 0x00,
- SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
- SET_MUX_RATIO, self.height - 1,
- SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
- SET_DISP_OFFSET, 0x00,
- SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,
- # timing and driving scheme
- SET_DISP_CLK_DIV, 0xF0,
- SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,
- SET_VCOM_DESEL, 0x30, # 0.83*Vcc
- # display
- SET_CONTRAST, 0xff, # maximum
- SET_ENTIRE_ON, # output follows RAM contents
- SET_NORM_INV, # not inverted
- # charge pump
- SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
- SET_DISP | 0x01): # on
- self.write_cmd(cmd)
+ if not self.is_mk5:
+ # Mk4 and earlier
+ cmds = (
+ SET_DISP | 0x00, # display off
+ # address setting
+ SET_MEM_ADDR, 0x00, # horizontal
+ # resolution and layout
+ SET_DISP_START_LINE | 0x00,
+ SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
+ SET_MUX_RATIO, self.height - 1,
+ SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
+ SET_DISP_OFFSET, 0x00,
+ SET_COM_PIN_CFG, 0x12,
+ # timing and driving scheme
+ SET_DISP_CLK_DIV, 0xF0,
+ SET_PRECHARGE, 0xf1,
+ SET_VCOM_DESEL, 0x30, # 0.83*Vcc
+ # display
+ SET_CONTRAST, 0xff, # maximum
+ SET_ENTIRE_ON, # output follows RAM contents
+ SET_NORM_INV, # not inverted
+ # charge pump
+ SET_CHARGE_PUMP, 0x14)
+ else:
+ # Mk5 has external +12v power supply, and different setup protocol
+
+ cmds = (
+ SET_DISP | 0x00, # display off
+ # address setting
+ SET_MEM_ADDR, 0x00, # horizontal
+ # resolution and layout
+ SET_DISP_START_LINE | 0x00,
+ SET_SEG_REMAP | 0x00, # column addr 0 mapped to SEG127
+ SET_MUX_RATIO, self.height - 1,
+ SET_COM_OUT_DIR | 0x00, # scan from COM[8] to COM[N]
+ SET_DISP_OFFSET, 0x00,
+ SET_COM_PIN_CFG, 0x12,
+ # timing and driving scheme
+ SET_DISP_CLK_DIV, 0xF0,
+ SET_PRECHARGE, 0x22,
+ SET_VCOM_DESEL, 0x40, # per spec sheet
+ # display
+ SET_CONTRAST, 0x85, # NOT maximum, because spec sheet
+ SET_ENTIRE_ON, # output follows RAM contents
+ SET_NORM_INV, # not inverted
+ SET_CHARGE_PUMP, 0x10, # charge pump: DISABLE
+ )
+
+ self.write_cmds(cmds)
+
self.fill(0)
self.show()
+ self.write_cmd(SET_DISP | 0x01)
+
+ def write_cmds(self, cmds):
+ for c in cmds:
+ self.write_cmd(c)
+
def poweroff(self):
self.write_cmd(SET_DISP | 0x00)
@@ -78,6 +110,10 @@ class SSD1306(framebuf.FrameBuffer):
self.write_cmd(SET_DISP | 0x01)
def contrast(self, contrast):
+ # brightness: normal = 0x7f, brightness=0xff, dim=0x00 (but they are all very similar)
+ if self.is_mk5:
+ # - limit to a specific max value from OLED specs used on Mk5
+ contrast = max(contrast, 0x85)
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)
@@ -85,56 +121,113 @@ class SSD1306(framebuf.FrameBuffer):
self.write_cmd(SET_NORM_INV | (invert & 1))
def show(self):
- x0 = 0
- x1 = self.width - 1
- if self.width == 64:
- # displays with width of 64 pixels are shifted by 32
- x0 += 32
- x1 += 32
self.write_cmd(SET_COL_ADDR)
- self.write_cmd(x0)
- self.write_cmd(x1)
+ self.write_cmd(0)
+ self.write_cmd(self.width - 1)
+
self.write_cmd(SET_PAGE_ADDR)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
+
self.write_data(self.buffer)
-SPI_RATE = const(40000000) # max chip can do, still slower than display limit tho
+ def busy_bar(self, enable, pattern):
+ # Render a continuous activity (not progress) bar in lower 8 lines of display
+ # - using OLED itself to do the animation, so smooth and CPU free
+ # - cannot preserve bottom 8 lines, since we have to destructively write there
+ # - assumes normal horz addr mode: 0x20, 0x00
+ # - speed_code=>framedelay: 0=5fr, 1=64fr, 2=128, 3=256, 4=3, 5=4, 6=25, 7=2frames
+ # unused: assert 0 <= speed_code <= 7
+
+ setup = bytes([
+ 0x21, 0x00, 0x7f, # setup column address range (start, end): 0-127
+ 0x22, 7, 7, # setup page start/end address: page 7=last 8 lines
+ ])
+ if not self.is_mk5:
+ animate = bytes([
+ 0x2e, # stop animations in progress
+ 0x26, # scroll leftwards (stock ticker mode)
+ 0, # placeholder
+ 7, # start 'page' (vertical)
+ 5, # "speed_code" # scroll speed: 7=fastest, but no order to it
+ 7, # end 'page'
+ 0, 0x7f, # start/end columns
+ 0x2f # start
+ ])
+ else:
+ # SSD1309? doesn't implement 0x26 but has other commands
+ animate = bytes([
+ 0x2e, # stop animations in progress
+ 0x29, # Vert+Right horz animation setup
+ 1, # A: enable horz scroll
+ 7, # B: start 'page' (vertical)
+ 5, # C: "speed_code" # scroll speed: 7=fastest, but no order to it
+ 7, # D: end 'page'
+ 1, # E: vert scrolling offset (unused)
+ 0, 0x7f, # F,G: start/end columns
+ 0xa3, # Set Vertical scroll Area
+ 0, 0, # A, B: # of rows in fixed vs. scroll area
+ 0x2f # start animating
+ ])
+
+ cleanup = bytes([
+ 0x2e, # stop animation
+ 0x20, 0x00, # horz addr-ing mode
+ 0x21, 0x00, 0x7f, # setup column address range (start, end): 0-127
+ 0x22, 7, 7, # setup page start/end address: page 7=last 8 lines
+ ])
+
+ if not enable:
+ # stop animation, and redraw old (new) screen
+ self.write_cmds(cleanup)
+ else:
+ # needs a pattern that repeats nicely mod 128
+ self.write_cmds(setup)
+ self.write_data(pattern)
+ self.write_cmds(animate)
class SSD1306_SPI(SSD1306):
- def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
- dc.init(dc.OUT, value=0)
- res.init(res.OUT, value=0)
- cs.init(cs.OUT, value=1)
+ def __init__(self, width, height, spi, dc, res, cs, is_mk5=False):
self.spi = spi
self.dc = dc
- self.res = res
self.cs = cs
- self.res(1)
+ self.res = res
+
+ # initial states
+ dc(0)
+ cs(1)
+
+ # reset sequence
+ res(1)
time.sleep_ms(1)
- self.res(0)
+ res(0)
time.sleep_ms(10)
- self.res(1)
- super().__init__(width, height, external_vcc)
+ res(1)
+
+ super().__init__(width, height, is_mk5)
+
+ def _setup_spi(self):
+ # need to re-do this constantly
+ # max chip can do, still slower than display limit tho
+ # - 40Mhz (target) is fine for short-cabled Mk4 (actual is lower?)
+ # - max spec is 10Mhz on Mk5
+ rate = 40_000_000 if not self.is_mk5 else 10_000_000
+ self.spi.init(baudrate=rate, polarity=0, phase=0)
def write_cmd(self, cmd):
- self.spi.init(baudrate=SPI_RATE, polarity=0, phase=0)
+ self._setup_spi()
self.cs(1)
self.dc(0)
self.cs(0)
- try:
- self.spi.write(bytearray([cmd]))
- except:
- print("SPI[cmd]: %r" % self.spi)
+ self.spi.write(bytearray([cmd]))
self.cs(1)
def write_data(self, buf):
- self.spi.init(baudrate=SPI_RATE, polarity=0, phase=0)
+ self._setup_spi()
self.cs(1)
self.dc(1)
self.cs(0)
- try:
- self.spi.write(buf)
- except:
- print("SPI[data]: %r" % self.spi)
+ self.spi.write(buf)
self.cs(1)
+
+# EOF
diff --git a/shared/utils.py b/shared/utils.py
index 93d6029..3bfa4f2 100644
--- a/shared/utils.py
+++ b/shared/utils.py
@@ -383,7 +383,7 @@ def check_firmware_hdr(hdr, binary_size):
# - hdr must be a bytearray(FW_HEADER_SIZE+more)
from sigheader import FW_HEADER_SIZE, FW_HEADER_MAGIC, FWH_PY_FORMAT
- from sigheader import MK_1_OK, MK_2_OK, MK_3_OK, MK_4_OK, MK_Q1_OK
+ from sigheader import MK_1_OK, MK_2_OK, MK_3_OK, MK_4_OK, MK_5_OK, MK_Q1_OK
from ustruct import unpack_from
from version import hw_label
import callgate
@@ -412,6 +412,8 @@ def check_firmware_hdr(hdr, binary_size):
ok = (hw_compat & MK_3_OK)
elif hw_label == 'mk4':
ok = (hw_compat & MK_4_OK)
+ elif hw_label == 'mk5':
+ ok = (hw_compat & MK_5_OK)
elif hw_label == 'q1':
ok = (hw_compat & MK_Q1_OK)
diff --git a/shared/version.py b/shared/version.py
index 3fab78a..ee90a29 100644
--- a/shared/version.py
+++ b/shared/version.py
@@ -80,6 +80,7 @@ def probe_system():
from sigheader import RAM_BOOT_FLAGS, RBF_FACTORY_MODE
import ckcc, callgate, machine
+ from machine import Pin
hw_label = 'mk4'
has_608 = True
@@ -97,7 +98,7 @@ def probe_system():
# detect Q1 based on pins.csv
try:
- machine.Pin('LCD_TEAR') # only defined on Q1 build, will error otherwise
+ Pin('LCD_TEAR') # only defined on Q1 build, will error otherwise
has_qr = True
num_sd_slots = 2
hw_label = 'q1'
@@ -108,6 +109,15 @@ def probe_system():
except ValueError:
pass
+ try:
+ # only defined on Mk4/5 build, will error otherwise; was open on Mk1-4, low on Mk5
+ s0 = Pin('STRAP_MK5', mode=Pin.IN, pull=Pin.PULL_UP)
+ if s0() == 0:
+ hw_label = 'mk5'
+ mk_num = 5
+ except ValueError:
+ pass
+
# Boot loader needs to tell us stuff about how we were booted, sometimes:
# - did we just install a new version, for example (obsolete in mk4)
# - are we running in "factory mode" with flash un-secured?
diff --git a/stm32/COLDCARD_MK4/pins.csv b/stm32/COLDCARD_MK4/pins.csv
index 5a28542..70a861f 100644
--- a/stm32/COLDCARD_MK4/pins.csv
+++ b/stm32/COLDCARD_MK4/pins.csv
@@ -89,3 +89,8 @@ SE2_SDA,PB14
NFC_SCL,PB6
NFC_SDA,PB7
NFC_ED,PC4
+STRAP_MK5,PE0
+STRAP_S1,PE1
+STRAP_S2,PE2
+STRAP_S3,PE3
+V12EN,PC1
diff --git a/stm32/MK-Makefile b/stm32/MK-Makefile
new file mode 100644
index 0000000..1ae8915
--- /dev/null
+++ b/stm32/MK-Makefile
@@ -0,0 +1,42 @@
+# (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC.
+#
+# Build micropython for stm32 (an ARM processor). Also handles signing of resulting firmware images.
+#
+# COLDCARD Mk4 and Mk5 -- OLED display, calculator style.
+#
+
+BOARD = COLDCARD_MK4
+FIRMWARE_BASE = 0x08020000
+BOOTLOADER_BASE = 0x08000000
+HW_MODEL = mk
+PARENT_MKFILE = MK-Makefile
+
+# This is release of the bootloader that will be built into the factory.dfu
+BOOTLOADER_VERSION = 3.2.1
+BOOTLOADER_DIR = mk4-bootloader
+
+LATEST_RELEASE = $(shell ls -t1 ../releases/*-mk-*.dfu ../releases/*-mk4-*.dfu | head -1)
+
+# Our version for this release.
+# - caution, the bootrom will not accept version < 3.0.0
+VERSION_STRING = 5.4.5
+
+# keep near top, because defined default target (all)
+include shared.mk
+
+
+# In another window:
+#
+# openocd -f openocd-mk4.cfg
+#
+# Can do:
+# - "load" which writes the flash (medium speed, lots of output on st-util)
+# - "cont" starts/continues system
+# - "br main" sets breakpoints
+# - "mon reset" to reset micro
+# - and so on
+#
+debug:
+ arm-none-eabi-gdb $(BUILD_DIR)/firmware.elf -x gogo-mk4.gdb
+
+# EOF
diff --git a/stm32/MK4-Makefile b/stm32/MK4-Makefile
deleted file mode 100644
index 43ccdcc..0000000
--- a/stm32/MK4-Makefile
+++ /dev/null
@@ -1,42 +0,0 @@
-# (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC.
-#
-# Build micropython for stm32 (an ARM processor). Also handles signing of resulting firmware images.
-#
-# MARK 4 with different chip and layout
-#
-
-BOARD = COLDCARD_MK4
-FIRMWARE_BASE = 0x08020000
-BOOTLOADER_BASE = 0x08000000
-HW_MODEL = mk4
-PARENT_MKFILE = MK4-Makefile
-
-# This is release of the bootloader that will be built into the factory.dfu
-BOOTLOADER_VERSION = 3.2.1
-BOOTLOADER_DIR = mk4-bootloader
-
-LATEST_RELEASE = $(shell ls -t1 ../releases/*-mk4-*.dfu | head -1)
-
-# Our version for this release.
-# - caution, the bootrom will not accept version < 3.0.0
-VERSION_STRING = 5.4.5
-
-# keep near top, because defined default target (all)
-include shared.mk
-
-
-# In another window:
-#
-# openocd -f openocd-mk4.cfg
-#
-# Can do:
-# - "load" which writes the flash (medium speed, lots of output on st-util)
-# - "cont" starts/continues system
-# - "br main" sets breakpoints
-# - "mon reset" to reset micro
-# - and so on
-#
-debug:
- arm-none-eabi-gdb $(BUILD_DIR)/firmware.elf -x gogo-mk4.gdb
-
-# EOF
diff --git a/stm32/Makefile b/stm32/Makefile
index 6e5cb45..818524f 100644
--- a/stm32/Makefile
+++ b/stm32/Makefile
@@ -5,23 +5,23 @@
#
# Normally you must use:
#
-# make -f MK4-Makefile
+# make -f MK-Makefile
# or
# make -f Q1-Makefile
#
.DEFAULT_GOAL := all
.DEFAULT all:
- $(MAKE) DEBUG_BUILD=1 -f Q1-Makefile $(MAKECMDGOALS)
+ $(MAKE) DEBUG_BUILD=1 -f MK-Makefile $(MAKECMDGOALS)
clean clobber repro:
@echo You should do either:
@echo
@echo " make" -f Q1-Makefile $(MAKECMDGOALS)
@echo "-OR-"
- @echo " make" -f MK4-Makefile $(MAKECMDGOALS)
+ @echo " make" -f MK-Makefile $(MAKECMDGOALS)
rc1 rc2 release:
- make -f Q1-Makefile $(MAKECMDGOALS) && make -f MK4-Makefile $(MAKECMDGOALS)
+ make -f Q1-Makefile $(MAKECMDGOALS) && make -f MK-Makefile $(MAKECMDGOALS)
# EOF
diff --git a/stm32/mk4-bootloader/gpio.c b/stm32/mk4-bootloader/gpio.c
index 7114642..c858f22 100644
--- a/stm32/mk4-bootloader/gpio.c
+++ b/stm32/mk4-bootloader/gpio.c
@@ -8,7 +8,7 @@
#include "gpio.h"
#include "stm32l4xx_hal.h"
-// PA0 - onewire bus for 608a
+// PA0 - onewire bus for 608
#define ONEWIRE_PIN GPIO_PIN_0
#define ONEWIRE_PORT GPIOA
@@ -62,15 +62,20 @@ gpio_setup(void)
// SD active LED: PC7
// USB active LED: PC6
+ // Mk5 (disconnected & unused on Mk4):
+ // PC0: early experiments, unused
+ // PC1: +12v en for OLED
+ // - but by rev E, neither being used, but keep support for older revs
{ GPIO_InitTypeDef setup = {
- .Pin = GPIO_PIN_7 | GPIO_PIN_6,
+ .Pin = GPIO_PIN_7 | GPIO_PIN_6 | GPIO_PIN_0 | GPIO_PIN_1,
.Mode = GPIO_MODE_OUTPUT_PP,
.Pull = GPIO_NOPULL,
.Speed = GPIO_SPEED_FREQ_LOW,
};
+
HAL_GPIO_Init(GPIOC, &setup);
- HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7|GPIO_PIN_6, 0); // turn LEDs off
+ HAL_GPIO_WritePin(GPIOC, setup.Pin, 0); // turn all off
}
// SD card detect switch: PC13
@@ -83,6 +88,18 @@ gpio_setup(void)
HAL_GPIO_Init(GPIOC, &setup);
}
+ // Strapping pins (Mk5, but all disconnected on Mk4): PE0-3
+ // - important to have the internal pull-ups enabled on these
+ // - PE0: low if Mk5
+ { GPIO_InitTypeDef setup = {
+ .Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3,
+ .Mode = GPIO_MODE_INPUT,
+ .Pull = GPIO_PULLUP,
+ .Speed = GPIO_SPEED_FREQ_LOW,
+ };
+ HAL_GPIO_Init(GPIOE, &setup);
+ }
+
#if 0
// TEST CODE -- keep
@@ -125,5 +142,14 @@ gpio_setup(void)
#endif
}
+// is_mk5()
+//
+ bool
+is_mk5(void)
+{
+ // sample the PE0 strapping pin to know if mk4 or 5
+ return !HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_0);
+}
+
// EOF
diff --git a/stm32/mk4-bootloader/gpio.h b/stm32/mk4-bootloader/gpio.h
index 6d78cf2..dd93072 100644
--- a/stm32/mk4-bootloader/gpio.h
+++ b/stm32/mk4-bootloader/gpio.h
@@ -6,10 +6,12 @@
// set directions, lock critical ones, etc.
void gpio_setup(void);
-// sample the DFU button
-inline bool dfu_button_pressed(void) { return false; }
-
#ifdef FOR_Q1_ONLY
// kill system power; instant
extern void turn_power_off(void);
#endif
+
+// sample the strapping pin to know if mk4 or 5
+extern bool is_mk5(void);
+
+// EOF
diff --git a/stm32/mk4-bootloader/oled.c b/stm32/mk4-bootloader/oled.c
index 18823c0..6eded6b 100644
--- a/stm32/mk4-bootloader/oled.c
+++ b/stm32/mk4-bootloader/oled.c
@@ -5,6 +5,7 @@
#include "delay.h"
#include "rng.h"
#include "console.h"
+#include "gpio.h" // for is_mk5()
#include "stm32l4xx_hal.h"
#include <string.h>
@@ -14,7 +15,7 @@
// Reset and config sequence.
//
// As measured! No attempt to understand them here.
-static const uint8_t reset_commands[] = {
+static const uint8_t reset_commands_mk4[] = {
0xae, // display off
0x20, 0x00, // horz addr-ing mode
0x40, // ram display start line: 0
@@ -33,6 +34,28 @@ static const uint8_t reset_commands[] = {
0xaf // display on
};
+// .. similar but a little different on Mk5
+static const uint8_t reset_commands_mk5[] = {
+ 0xae, // display off
+ 0xd5, 0x80, // set display clock divide ratio
+ 0xa8, 0x3f, // set multiplex ratio: 64
+ 0xd3, 0x00, // display offset (vertical shift): 0
+ 0x40, // ram display start line: 0
+ 0xad, 0x8a, // set charge pump (NEW)
+ 0xa0, // column addr 127 mapped to seg0 (NEW was 0xa1)
+ 0xc0, // remapped mode: scan from COMn to COM0 (NEW was 0xc8)
+ 0xda, 0x12, // seq com pin conf: alt com pin
+ 0x81, 0x65, // Contrast: was max, now 0x65 (NEW)
+ 0xd9, 0x22, // set pre-change period (NEW, was 0xf1)
+ 0xdb, 0x40, // Cvomh deselect level (NEW, was 0x30)
+ 0xa6, // normal not inverted
+
+ 0x20, 0x00, // horz addr-ing mode
+ 0xa4, // display ram contents (not all on)
+ //0x8d, 0x14, // enable charge pump (not in use, omitted from their sequence)
+ 0xaf // display on (done after VCC enabled)
+};
+
// Bytes to send before sending the 1024 bytes of pixel data.
//
static const uint8_t before_show[] = {
@@ -50,6 +73,9 @@ static const uint8_t before_show[] = {
#define SPI_SCK GPIO_PIN_5
#define SPI_MOSI GPIO_PIN_7
+// port C, Mk5 only
+#define VCC_EN_PIN GPIO_PIN_1
+
#ifndef DISABLE_OLED
static SPI_HandleTypeDef spi_port;
@@ -177,7 +203,7 @@ oled_setup(void)
HAL_GPIO_Init(GPIOA, &setup);
// lock the RESET pin so that St's DFU code doesn't clear screen
- // it might be trying to use it a MISO signal for SPI loading
+ // it might be trying to use it as a MISO signal for SPI loading
HAL_GPIO_LockPin(GPIOA, RESET_PIN | CS_PIN | DC_PIN);
// 10ms low-going pulse on reset pin
@@ -196,7 +222,14 @@ oled_setup(void)
//SPI1->CR1 = 0x354;
// write a sequence to reset things
- oled_write_cmd_sequence(sizeof(reset_commands), reset_commands);
+ if(is_mk5()) {
+ // note: +12v is always on now, this line supports older revs
+ HAL_GPIO_WritePin(GPIOC, VCC_EN_PIN, 1);
+
+ oled_write_cmd_sequence(sizeof(reset_commands_mk5), reset_commands_mk5);
+ } else {
+ oled_write_cmd_sequence(sizeof(reset_commands_mk4), reset_commands_mk4);
+ }
rng_delay();
}
@@ -429,13 +462,12 @@ oled_factory_busy(void)
// Render a continuous activity (not progress) bar in lower 8 lines of display
// - using OLED itself to do the animation, so smooth and CPU free
// - cannot preserve bottom 8 lines, since we have to destructively write there
- //oled_spi_setup();
static const uint8_t setup[] = {
0x21, 0x00, 0x7f, // setup column address range (start, end): 0-127
0x22, 7, 7, // setup page start/end address: page 7=last 8 lines
};
- static const uint8_t animate[] = {
+ static const uint8_t animate_mk4[] = {
0x2e, // stop animations in progress
0x26, // scroll leftwards (stock ticker mode)
0, // placeholder
@@ -445,6 +477,21 @@ oled_factory_busy(void)
0, 0xff, // placeholders
0x2f // start
};
+ // slightly different on Mk5 display
+ static const uint8_t animate_mk5[] = {
+ 0x2e, // stop animations in progress
+ 0x29, // Vert+Right horz animation setup
+ 1, // A: enable horz scroll
+ 7, // B: start 'page' (vertical)
+ 5, // C: "speed_code" # scroll speed: 7=fastest, but no order to it
+ 7, // D: end 'page'
+ 1, // E: vert scrolling offset (unused)
+ 0, 0x7f, // F,G: start/end columns
+ 0xa3, // Set Vertical scroll Area
+ 0, 0, // A, B: # of rows in fixed vs. scroll area
+ 0x2f // start animating
+ };
+
uint8_t data[128];
for(int x=0; x<128; x++) {
@@ -454,7 +501,11 @@ oled_factory_busy(void)
oled_write_cmd_sequence(sizeof(setup), setup);
oled_write_data(sizeof(data), data);
- oled_write_cmd_sequence(sizeof(animate), animate);
+ if(is_mk5()) {
+ oled_write_cmd_sequence(sizeof(animate_mk5), animate_mk5);
+ } else {
+ oled_write_cmd_sequence(sizeof(animate_mk4), animate_mk4);
+ }
}
// EOF
diff --git a/stm32/shared.mk b/stm32/shared.mk
index a568e05..6cf47be 100644
--- a/stm32/shared.mk
+++ b/stm32/shared.mk
@@ -1,6 +1,6 @@
# (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
-# Shared values and target rules for Mk4 and Q.
+# Shared values and target rules for Mk and Q platforms.
#
# Define these vars to suit board
diff --git a/stm32/sigheader.h b/stm32/sigheader.h
index e0868ea..dec3c99 100644
--- a/stm32/sigheader.h
+++ b/stm32/sigheader.h
@@ -70,8 +70,7 @@ typedef struct {
#define MK_3_OK 0x04
#define MK_4_OK 0x08
#define MK_Q1_OK 0x10
-// RFU:
-#define MK_6_OK 0x20
+#define MK_5_OK 0x20
// (Mk1-3) There is a copy of the header at this location in RAM, copied by bootloader
// **after** it has been verified. If you write to this memory area, you will be reset!
diff --git a/stm32/sigheader.py b/stm32/sigheader.py
index 609bf6f..ef11649 100644
--- a/stm32/sigheader.py
+++ b/stm32/sigheader.py
@@ -54,8 +54,7 @@ MK_2_OK = 0x02
MK_3_OK = 0x04
MK_4_OK = 0x08
MK_Q1_OK = 0x10
-# RFU:
-MK_6_OK = 0x20
+MK_5_OK = 0x20
# (Mk1-3) There is a copy of the header at this location in RAM, copied by bootloader
# **after** it has been verified. If you write to this memory area, you will be reset!
diff --git a/testing/clone_tests.py b/testing/clone_tests.py
index 31ab03a..d4b1e4e 100644
--- a/testing/clone_tests.py
+++ b/testing/clone_tests.py
@@ -9,10 +9,23 @@ from ckcc_protocol.client import ColdcardDevice
def _clone(source, target):
- assert source in ["Q", "Mk4"]
- assert target in ["Q", "Mk4"]
- source_sim_arg, source_is_Q = ("--q1", True) if source == "Q" else ("", False)
- target_sim_arg, target_is_Q = ("--q1", True) if target == "Q" else ("", False)
+ allowed_devices = ["Q", "Mk4", "Mk5"]
+ assert source in allowed_devices
+ assert target in allowed_devices
+
+ source_is_Q = False
+ source_sim_arg = ""
+ if source == "Q":
+ source_sim_arg, source_is_Q = "--q1", True
+ elif source == "Mk4":
+ source_sim_arg, source_is_Q = "--mk4", False
+
+ target_is_Q = False
+ target_sim_arg = ""
+ if target == "Q":
+ target_sim_arg, target_is_Q = "--q1", True
+ elif target == "Mk4":
+ target_sim_arg, target_is_Q = "--mk4", False
# first the TARGET
clean_sim_data() # remove all from previous
@@ -105,7 +118,7 @@ def _clone(source, target):
sim_target.stop()
-@pytest.mark.parametrize("source,target", list(itertools.product(["Q", "Mk4"], repeat=2)))
+@pytest.mark.parametrize("source,target", list(itertools.product(["Q", "Mk4", "Mk5"], repeat=2)))
def test_clone(source, target):
_clone(source, target)
time.sleep(1)
diff --git a/testing/conftest.py b/testing/conftest.py
index b7f6ba5..bf246de 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -1518,19 +1518,22 @@ def is_mark3(dev_hw_label):
def is_mark4(dev_hw_label):
return (dev_hw_label == 'mk4')
+@pytest.fixture(scope='session')
+def is_mark5(dev_hw_label):
+ return (dev_hw_label == 'mk5')
+
@pytest.fixture(scope='session')
def is_q1(dev_hw_label):
return (dev_hw_label == 'q1')
-
@pytest.fixture(scope="session")
def is_headless(request):
return request.config.getoption('--headless')
@pytest.fixture(scope='session')
-def is_mark4plus(is_mark4, is_q1):
- # mark4 PLUS ... so Q1 and Mk4
- return is_mark4 or is_q1
+def is_mark4plus(is_mark4, is_q1, is_mark5):
+ # mark4 PLUS ... so Q1, Mk4 and Mk5
+ return is_mark4 or is_q1 or is_mark5
@pytest.fixture(scope='session')
def mk_num(dev_hw_label):
@@ -1544,35 +1547,29 @@ def mk_num(dev_hw_label):
else:
raise ValueError(v)
-@pytest.fixture(scope='session')
-def only_mk4(is_mark4):
- # NOTE: avoid this, and try to be more specific! ie. NFC vs. QR etc
- if not is_mark4:
- raise pytest.skip("Mk4 only")
-
@pytest.fixture(scope='session')
def only_q1(is_q1):
if not is_q1:
raise pytest.skip("Q only")
@pytest.fixture(scope='session')
-def needs_nfc(is_mark4, is_q1):
- if is_mark4 or is_q1:
+def needs_nfc(is_mark4plus):
+ if is_mark4plus:
return
raise pytest.skip("Needs NFC support")
@pytest.fixture(scope='session')
-def needs_virtdisk(is_mark4, is_q1):
+def needs_virtdisk(is_mark4plus):
# TODO/MAYBE: test if feature enabled in settings?
- if is_mark4 or is_q1:
+ if is_mark4plus:
return
raise pytest.skip("Needs VirtDisk support")
@pytest.fixture(scope='session')
def only_mk4plus(mk_num):
- # Mk4 and Q1
+ # Mk4, Q1 and Mk5
if mk_num < 4:
- raise pytest.skip("Mk4/Q1 only")
+ raise pytest.skip("Mk4/Mk5/Q1 only")
@pytest.fixture(scope='session')
def only_mk3(mk_num):
diff --git a/testing/run_sim_tests.py b/testing/run_sim_tests.py
index 29eb89e..4782cfe 100644
--- a/testing/run_sim_tests.py
+++ b/testing/run_sim_tests.py
@@ -298,7 +298,8 @@ def main():
help="Choose how much to sleep after simulator is started")
parser.add_argument("-m", "--module", action="append", help="Choose only n modules to run")
parser.add_argument("--pdb", action="store_true", help="Go to debugger on failure")
- parser.add_argument("--q1", action="store_true", help="Simulate a Q instead of Mk COLDCARD")
+ parser.add_argument("--q1", action="store_true", help="Simulate a Q instead of Mk5 COLDCARD")
+ parser.add_argument("--mk4", action="store_true", help="Simulate a Mk4 instead of Mk5 COLDCARD")
parser.add_argument("--psbt2", action="store_true", help="`fake_txn` produces PSBTv2")
parser.add_argument("--ff", action="store_true", help="Run the last failures first")
parser.add_argument("--onetime", action="store_true", default=False,
@@ -381,8 +382,11 @@ def main():
# proper `settings.load` _ virtual disk
sim_args = ["--set", "nfc=1", "--set", "vidsk=1"]
+ # by default Mk5 is run
if args.q1 and '--q1' not in sim_args:
sim_args.append('--q1')
+ elif args.mk4 and '--mk4' not in sim_args:
+ sim_args.append("--mk4")
module_args.append((test_module, sim_args, args.pytest_k, args.pdb,
args.ff, args.psbt2, args.q1, args.headless))
@@ -416,8 +420,10 @@ def main():
tmp_dir = "/tmp/cc-simulators"
clean_directory(tmp_dir) # clean it
mk4_log_dir = f"{tmp_dir}/mk4_logs"
+ mk5_log_dir = f"{tmp_dir}/mk5_logs"
q1_log_dir = f"{tmp_dir}/q1_logs"
os.makedirs(mk4_log_dir, exist_ok=True)
+ os.makedirs(mk5_log_dir, exist_ok=True)
os.makedirs(q1_log_dir, exist_ok=True)
q = [] # build priority queue
@@ -455,7 +461,14 @@ def main():
break
sim = ColdcardSimulator(sim_args, segregate=True)
sim.start(start_wait=0)
- ld = q1_log_dir if "--q1" in sim_args else mk4_log_dir
+
+ if "--q1" in sim_args:
+ ld = q1_log_dir
+ elif "--mk4" in sim_args:
+ ld = mk4_log_dir
+ else:
+ ld = mk5_log_dir
+
q_chunks.append((sim, mn, mod_add, k, ld))
time.sleep(5)
@@ -468,7 +481,12 @@ def main():
if k:
cmd_list.extend(["-k", k])
p = subprocess.Popen(cmd_list, preexec_fn=os.setsid, stdout=out_fd, stderr=out_fd)
- mark = "Q" if "q1" in log_dir else "Mk4"
+ if "q1" in log_dir:
+ mark = "Q"
+ elif "mk5" in log_dir:
+ mark = "Mk5"
+ else:
+ mark = "Mk4"
procs.append((mn+mod_add, p, out_fd, sim, mark, time.time()))
print(f'started: {mark:<6}{mn+mod_add:<30}{sim.socket.split("-")[-1].split(".")[0]:<10}')
diff --git a/testing/test_bip322.py b/testing/test_bip322.py
index b3120ff..f561267 100644
--- a/testing/test_bip322.py
+++ b/testing/test_bip322.py
@@ -91,10 +91,13 @@ def verify_msg_bip322_por(cap_story, need_keypress, press_select, press_cancel,
[["p2pkh", None, None]] + ([["p2wpkh", None, 1000000]] * 5) + ([["p2sh-p2wpkh", None, 10000000]] * 5),
])
def test_bip322_por(msg, ins, bip322_txn, start_sign, end_sign, cap_story, need_keypress,
- press_select, verify_msg_bip322_por):
+ press_select, verify_msg_bip322_por, sim_root_dir):
num_ins = len(ins)
amt = sum([i[2] or 0 for i in ins])
psbt, msg_challenge = bip322_txn(ins, msg=msg)
+ with open(f'{sim_root_dir}/debug/last-b322-por.psbt', 'wb') as f:
+ f.write(psbt)
+
start_sign(psbt, finalize=True)
verify_msg_bip322_por(msg.decode(), way="sd")
diff --git a/testing/test_bip39pw.py b/testing/test_bip39pw.py
index 95b3329..c03b8ad 100644
--- a/testing/test_bip39pw.py
+++ b/testing/test_bip39pw.py
@@ -165,7 +165,10 @@ def test_b39p_refused(dev, press_cancel, pw='testing 123'):
@pytest.mark.parametrize('version', range(8))
def test_bip39_pick_words(target, version, cap_menu, pick_menu_item, cap_story,
word_menu_entry, get_pp_sofar, reset_seed_words,
- press_select, only_mk4, go_to_passphrase):
+ press_select, is_q1, go_to_passphrase):
+ if is_q1:
+ raise pytest.skip("not on Q")
+
# Check we can pick words
reset_seed_words()
@@ -192,13 +195,16 @@ def test_bip39_pick_words(target, version, cap_menu, pick_menu_item, cap_story,
@pytest.mark.parametrize('target', ['123', '1', '4'*32, '12'*8])
@pytest.mark.parametrize('backspaces', [1, 0, 12])
-def test_bip39_add_nums(target, backspaces, pick_menu_item, cap_story, only_mk4,
+def test_bip39_add_nums(target, backspaces, pick_menu_item, cap_story, is_q1,
cap_menu, word_menu_entry, get_pp_sofar, need_keypress,
press_select, press_cancel, go_to_passphrase):
# Check we can pick numbers (appended)
# - also the "clear all" menu item
+ if is_q1:
+ raise pytest.skip("not on Q")
+
go_to_passphrase()
pick_menu_item('Add Numbers')
diff --git a/testing/test_ccc.py b/testing/test_ccc.py
index 4af2aea..cd5b3ac 100644
--- a/testing/test_ccc.py
+++ b/testing/test_ccc.py
@@ -22,12 +22,12 @@ from psbt import BasicPSBT
SERVER_PUBKEY = '0231301ec4acec08c1c7d0181f4ffb8be70d693acccc86cccb8f00bf2e00fcabfd'
@pytest.fixture
-def goto_ccc_menu(goto_home, pick_menu_item, is_mark4):
+def goto_ccc_menu(goto_home, pick_menu_item, is_q1):
def doit():
goto_home()
pick_menu_item("Advanced/Tools")
pick_menu_item("Spending Policy")
- pick_menu_item("Co-Sign Multi." if is_mark4 else "Co-Sign Multisig (CCC)")
+ pick_menu_item("Co-Sign Multisig (CCC)" if is_q1 else "Co-Sign Multi.")
return doit
diff --git a/testing/test_hobble.py b/testing/test_hobble.py
index cdf2a22..96b129c 100644
--- a/testing/test_hobble.py
+++ b/testing/test_hobble.py
@@ -55,8 +55,7 @@ goto_top_menu()
@pytest.mark.parametrize('en_nfc', [ True, False] )
@pytest.mark.parametrize('en_multisig', [ True, False] )
def test_menu_contents(set_hobble, pick_menu_item, cap_menu, en_okeys, en_notes, settings_set,
- need_some_notes, is_q1, is_mark4, en_nfc, sim_exec, en_multisig,
- vdisk_disabled):
+ need_some_notes, is_q1, en_nfc, sim_exec, en_multisig, vdisk_disabled):
# just enough to pass/fail the menu predicates!
settings_set('seedvault', True)
diff --git a/testing/test_hsm.py b/testing/test_hsm.py
index fb9af23..f5e99ca 100644
--- a/testing/test_hsm.py
+++ b/testing/test_hsm.py
@@ -114,7 +114,10 @@ def compute_policy_hash(policy):
return b2a_hex(sha256(json_.encode()).digest()).decode()
@pytest.fixture(autouse=True)
-def enable_hsm_commands(dev, sim_exec, only_mk4):
+def enable_hsm_commands(dev, sim_exec, is_q1):
+ if is_q1:
+ raise pytest.skip("Q does not have HSM support")
+
cmd = 'from glob import settings; settings.set("hsmcmd", 1)'
sim_exec(cmd)
yield
diff --git a/testing/test_sign.py b/testing/test_sign.py
index 9a4620e..a8797d6 100644
--- a/testing/test_sign.py
+++ b/testing/test_sign.py
@@ -134,21 +134,13 @@ def test_psbt_proxy_parsing(fn, sim_execfile, sim_exec, src_root_dir, sim_root_d
assert oo == rb
@pytest.mark.unfinalized
-def test_speed_test(dev, fake_txn, is_mark3, is_mark4, start_sign, end_sign,
- press_select, press_cancel, sim_root_dir, is_q1):
+def test_speed_test(dev, fake_txn, start_sign, end_sign, press_select, press_cancel, sim_root_dir):
# measure time to sign a larger txn
- if is_mark4 or is_q1:
- # Mk4: expect
- # 20/250 => 15.5s (or 10.0 if seed is cached)
- # 200/500 => 96.3s
- num_in = 20
- num_out = 250
- elif is_mark3:
- num_in = 20
- num_out = 250
- else:
- num_in = 9
- num_out = 100
+ # Mk4: expect
+ # 20/250 => 15.5s (or 10.0 if seed is cached)
+ # 200/500 => 96.3s
+ num_in = 20
+ num_out = 250
psbt = fake_txn(num_in, num_out, dev.master_xpub, segwit_in=True)
@@ -179,9 +171,7 @@ if 0:
# see <https://bitcoin.stackexchange.com/questions/11542>
# - how big woudl PSBT be?
# - not a great test case because so slow.
- def test_mega_txn(fake_txn, is_mark4, start_sign, end_sign, dev):
- if not is_mark4:
- raise pytest.xfail('no way')
+ def test_mega_txn(fake_txn, start_sign, end_sign, dev):
psbt = fake_txn(5569, 1, dev.master_xpub)
@@ -3634,10 +3624,13 @@ def test_txn_nVersion_zero(segwit, fake_txn, start_sign, cap_story, goto_home):
goto_home()
def hack(psbt):
- t = CTransaction()
- t.deserialize(BytesIO(psbt.txn))
- t.nVersion = 0
- psbt.txn = t.serialize()
+ if psbt.is_v2():
+ psbt.txn_version = 0
+ else:
+ t = CTransaction()
+ t.deserialize(BytesIO(psbt.txn))
+ t.nVersion = 0
+ psbt.txn = t.serialize()
psbt = fake_txn(1, 2, segwit_in=segwit, change_outputs=[0], psbt_hacker=hack)
start_sign(psbt)
diff --git a/testing/test_sssp.py b/testing/test_sssp.py
index 85f4e06..e043f30 100644
--- a/testing/test_sssp.py
+++ b/testing/test_sssp.py
@@ -11,7 +11,7 @@ from ckcc.protocol import CCProtocolPacker
@pytest.fixture
-def goto_sssp_menu(goto_home, pick_menu_item, is_mark4):
+def goto_sssp_menu(goto_home, pick_menu_item):
def doit():
goto_home()
pick_menu_item("Advanced/Tools")
diff --git a/testing/test_upgrades.py b/testing/test_upgrades.py
index b89eac0..984a479 100644
--- a/testing/test_upgrades.py
+++ b/testing/test_upgrades.py
@@ -92,18 +92,12 @@ def upgrade_by_sd(open_microsd, cap_story, pick_menu_item, goto_home, press_sele
@pytest.mark.parametrize('mode', ['compat', 'incompat'])
@pytest.mark.parametrize('transport', ['sd', 'usb'])
def test_hacky_upgrade(mode, cap_story, transport, dev, sim_exec, make_firmware, upload_file,
- upgrade_by_sd, press_cancel, is_q1):
+ upgrade_by_sd, press_cancel, is_q1, is_mark5):
if mode == 'compat':
- data = make_firmware("q1" if is_q1 else 4)
+ data = make_firmware("q1" if is_q1 else (5 if is_mark5 else 4))
elif mode == 'incompat':
- if is_q1:
- data = make_firmware(4)
- else:
- with pytest.raises(RuntimeError) as err:
- make_firmware(3)
- assert "too big for our USB upgrades" in str(err)
- return
+ data = make_firmware(4 if is_q1 else "q1")
hdr = data[FW_HEADER_OFFSET:FW_HEADER_OFFSET+FW_HEADER_SIZE]
diff --git a/testing/test_vdisk.py b/testing/test_vdisk.py
index b6db2d3..c7a5e01 100644
--- a/testing/test_vdisk.py
+++ b/testing/test_vdisk.py
@@ -214,7 +214,7 @@ def test_virtdisk_signing(encoding, num_outs, partial, try_sign_virtdisk, fake_t
if 0:
@pytest.mark.parametrize('num_outs', [ 1, 20, 250])
- def test_virtdisk_after(num_outs, fake_txn, try_sign, nfc_read, need_keypress, cap_story, only_mk4):
+ def test_virtdisk_after(num_outs, fake_txn, try_sign, nfc_read, need_keypress, cap_story):
# Read signing result (transaction) over NFC, decode it.
psbt = fake_txn(1, num_outs)
orig, result = try_sign(psbt, accept=True, finalize=True)
diff --git a/unix/README.md b/unix/README.md
index 03e79d7..c3a122d 100644
--- a/unix/README.md
+++ b/unix/README.md
@@ -33,6 +33,7 @@ wallet (on testnet, always with the same seed). But there are other options:
- `--mk2` => emulate mark2 hardware (older micro, etc), default is current-gen (mark4)
- `--mk3` => emulate mark3 hardware
- `--mk4` => emulate mark4 hardware
+- `--mk5` => emulate mark5 hardware (default)
- `--q1` => emulate Q1 hardware
- `--addr` => go to the address explorer at startup
- `--xw` => go to the wallet export submenu
diff --git a/unix/mk5-images/background.png b/unix/mk5-images/background.png
new file mode 100755
index 0000000..8702e20
Binary files /dev/null and b/unix/mk5-images/background.png differ
diff --git a/unix/mk5-images/led-green.png b/unix/mk5-images/led-green.png
new file mode 100644
index 0000000..8058d52
Binary files /dev/null and b/unix/mk5-images/led-green.png differ
diff --git a/unix/mk5-images/led-red.png b/unix/mk5-images/led-red.png
new file mode 100644
index 0000000..7bc306b
Binary files /dev/null and b/unix/mk5-images/led-red.png differ
diff --git a/unix/simulator.py b/unix/simulator.py
index c636377..2852b83 100755
--- a/unix/simulator.py
+++ b/unix/simulator.py
@@ -439,15 +439,6 @@ class LCDSimulator(SimulatedScreen):
self.draw_single_led(spriterenderer, 465, 315)
class OLEDSimulator(SimulatedScreen):
- # top-left coord of OLED area; size is 1:1 with real pixels... 128x64 pixels
- OLED_ACTIVE = (46, 85)
-
- # keypad touch buttons
- KEYPAD_LEFT = 52
- KEYPAD_TOP = 216
- KEYPAD_PITCH = 73
-
- background_img = 'mk4-images/background.png'
def __init__(self, factory):
self.movie = None
@@ -462,12 +453,8 @@ class OLEDSimulator(SimulatedScreen):
sdl2.ext.fill(s, self.bg)
self.mv = sdl2.ext.pixels2d(self.sprite, transpose=False)
-
- # for genuine/caution lights and other LED's
- self.led_red = factory.from_image("mk4-images/led-red.png")
- self.led_green = factory.from_image("mk4-images/led-green.png")
- self.led_sdcard = factory.from_image("mk4-images/led-sd.png")
- self.led_usb = factory.from_image("mk4-images/led-usb.png")
+
+ self.load_leds(factory)
def new_contents(self, readable):
# got bytes for new update.
@@ -509,13 +496,59 @@ class OLEDSimulator(SimulatedScreen):
SD_LED = 0x2
USB_LED = 0x4
- spriterenderer.render(self.led_green if (active_set & GEN_LED) else self.led_red)
+ spriterenderer.render(self.led_genuine if (active_set & GEN_LED) else self.led_unsafe)
if active_set & SD_LED:
spriterenderer.render(self.led_sdcard)
if active_set & USB_LED:
spriterenderer.render(self.led_usb)
+class Mk4OLEDSimulator(OLEDSimulator):
+ # top-left coord of OLED area; size is 1:1 with real pixels... 128x64 pixels
+ OLED_ACTIVE = (46, 85)
+
+ # keypad touch buttons
+ KEYPAD_LEFT = 52
+ KEYPAD_TOP = 216
+ KEYPAD_PITCH = 73
+
+ background_img = 'mk4-images/background.png'
+
+ def load_leds(self, factory):
+ # for genuine/caution lights and other LED's
+ # - these are pre-positioned where they need to end up
+ self.led_unsafe = factory.from_image("mk4-images/led-red.png")
+ self.led_genuine = factory.from_image("mk4-images/led-green.png")
+ self.led_sdcard = factory.from_image("mk4-images/led-sd.png")
+ self.led_usb = factory.from_image("mk4-images/led-usb.png")
+
+class Mk5OLEDSimulator(OLEDSimulator):
+ OLED_ACTIVE = (28, 41)
+
+ # keypad touch buttons
+ KEYPAD_LEFT = 28
+ KEYPAD_TOP = 125
+ KEYPAD_PITCH = 42
+
+ background_img = 'mk5-images/background.png'
+
+ def load_leds(self, factory):
+ # position each carefully
+ r = factory.from_image("mk5-images/led-red.png")
+ g = factory.from_image("mk5-images/led-green.png")
+
+ self.led_unsafe = r.subsprite(r.area)
+ self.led_genuine = g.subsprite(g.area)
+ self.led_sdcard = g.subsprite(g.area)
+ self.led_usb = g.subsprite(g.area)
+
+ self.led_unsafe.position = (14, -9)
+ self.led_genuine.position = (-1, -9)
+
+ self.led_sdcard.position = (-14, 23)
+ self.led_usb.position = (65, 283)
+
+
def load_shared_mod(name, path):
# load indicated file.py as a module
# from <https://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path>
@@ -783,7 +816,15 @@ Q1 specials:
factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
- simdis = (OLEDSimulator if not is_q1 else LCDSimulator)(factory)
+ if is_q1:
+ simdis = LCDSimulator(factory)
+ elif ('--mk4' in sys.argv):
+ # retro look
+ simdis = Mk4OLEDSimulator(factory)
+ else:
+ # default: Mk5
+ simdis = Mk5OLEDSimulator(factory)
+
bg = factory.from_image(simdis.background_img)
window = sdl2.ext.Window("Coldcard Simulator", size=bg.size, position=(100, 100))
@@ -952,6 +993,7 @@ Q1 specials:
if not pressed:
numpad_tx.write(b'\0') # all up signal
+
while running:
events = sdl2.ext.get_events()
for event in events:
diff --git a/unix/variant/ssd1306.py b/unix/variant/ssd1306.py
index bd3d5f7..385ac55 100644
--- a/unix/variant/ssd1306.py
+++ b/unix/variant/ssd1306.py
@@ -26,10 +26,10 @@ SET_CHARGE_PUMP = const(0x8d)
# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
- def __init__(self, width, height, external_vcc):
+ def __init__(self, width, height, is_mk5):
self.width = width
self.height = height
- self.external_vcc = external_vcc
+ self.is_mk5 = is_mk5
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
@@ -72,12 +72,20 @@ class SSD1306(framebuf.FrameBuffer):
self.write_cmd(self.pages - 1)
self.write_data(self.buffer)
+
+ def busy_bar(self, enable, pattern):
+ # Render a continuous activity (not progress) bar in lower 8 lines of display
+ if enable:
+ # just show as static pattern
+ t = self.buffer[:-128] + pattern
+ self.write_data(t)
+
class SSD1306_SPI(SSD1306):
- def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
+ def __init__(self, width, height, spi, dc, res, cs, is_mk5=False):
import sys
self.pipe = open(int(sys.argv[1]), 'wb')
- super().__init__(width, height, external_vcc)
+ super().__init__(width, height, is_mk5)
def write_cmd(self, cmd):
pass
diff --git a/unix/variant/version.py b/unix/variant/version.py
index 62875be..0eee86d 100644
--- a/unix/variant/version.py
+++ b/unix/variant/version.py
@@ -32,8 +32,8 @@ def get_header_value(fld_name):
return b'\x18\x07\x11\x19S\x08\x00\x00'
return 0
-# default is Mk4 hardware
-hw_label = 'mk4'
+# default is Mk5 hardware
+hw_label = 'mk5'
has_608 = True
has_membrane = True
supports_hsm = True
@@ -47,7 +47,7 @@ has_qwerty = False
is_edge = False
if '--mk1' in sys.argv:
- # doubt this works still
+ # doubt this works anymore
hw_label = 'mk1'
has_608 = False
has_membrane = False
@@ -72,6 +72,9 @@ if '--mk3' in sys.argv:
has_nfc = False
supports_hsm = False
+if '--mk4' in sys.argv:
+ hw_label = 'mk4'
+
mk_num = int(hw_label[2:])
if '--q1' in sys.argv:
@@ -81,6 +84,7 @@ if '--q1' in sys.argv:
has_battery = True
has_qwerty = True
supports_hsm = False
+ mk_num = 4
from public_constants import MAX_TXN_LEN, MAX_UPLOAD_LEN
from public_constants import MAX_TXN_LEN_MK4, MAX_UPLOAD_LEN_MK4
Why this scored 21/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.