-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmmio.cpp
More file actions
2121 lines (1967 loc) · 80.5 KB
/
mmio.cpp
File metadata and controls
2121 lines (1967 loc) · 80.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "mmio.h"
#include "../emu_state.h"
#include "i2c3.h"
#include "memory_map.h"
#include "se_engine.h"
#include "tegra_bl.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <sys/stat.h>
#include <map>
#include <unistd.h>
#include <vector>
#define BIT(n) (1U << (n))
static uint32_t pmc_scratch0 = 0;
static uint32_t pmc_scratch37 = 0;
static std::map<uint64_t, uint32_t> mmio_regs;
/*
* Central MMIO dispatcher.
*
* Unicorn hooks for unmapped memory access route here.
* We dispatch to the appropriate peripheral handler based on address range.
*/
// ==================== GPIO ====================
// Button state is stored in EmuState and read via GPIO registers.
// VOL_UP = GPIO_X6 (port X, pin 6), VOL_DOWN = GPIO_X7, POWER = GPIO_X0 (PMC)
uint32_t gpio_read(EmuState *state, uint64_t addr) {
uint32_t offset = (uint32_t)(addr - GPIO_BASE);
// hekate reads buttons via btn_read() which accesses GPIO port X
// Port X is in Bank 6. Offset for Port X starts at 0x530.
// CNF=0x530, OE=0x534, OUT=0x538, IN=0x53C.
if (offset == 0x53C) {
uint32_t val = 0xFF; // All pins high (buttons not pressed, active low)
if (state->btn_vol_up)
val &= ~(1 << 6); // VOL_UP = PX6
if (state->btn_vol_down)
val &= ~(1 << 7); // VOL_DOWN = PX7
printf("[gpio] R: Port X IN = 0x%02X\n", val);
return val;
}
if (offset == 0x61C) {
// Port Z IN. Bit 1 active-low = SD card detect.
uint32_t val = state->sd_inserted.load() ? 0x00 : (1u << 1);
return val;
}
// Tegra X1 GPIO is organized as 8 banks of 256 bytes; within each
// bank a port occupies a 4-byte slot at offsets:
// CNF +0x00..0x0F (GPIO vs SPIO mode select per pin)
// OE +0x10..0x1F (output enable per pin)
// OUT +0x20..0x2F (driven value per pin)
// IN +0x30..0x3F (sampled value per pin)
// The write hook stores every word into mmio_regs already, so reads
// for CNF / OE / OUT just hand back what the payload last wrote.
// For IN we don't model external drivers, so we mirror the latched
// OUT value (one slot earlier, i.e. addr - 0x10) -- that makes
// gpio_write(PORT, PIN, HIGH); gpio_read(PORT, PIN);
// round-trip for ports the payload drives itself (PV0/PV1 backlight
// enable, PV2 panel reset, PK3 Joy-Con charge enable, PA5 5V rail
// enable, etc.).
uint32_t bank_off = offset & 0xFF;
if (offset < 0x800) {
if (bank_off < 0x30) {
// CNF / OE / OUT: hand back the cached write.
uint32_t v = mmio_regs.count(addr) ? mmio_regs[addr] : 0;
printf("[gpio] R: offset 0x%X (CNF/OE/OUT cache) = 0x%08X\n", offset, v);
return v;
}
if (bank_off < 0x40) {
// IN: mirror of the matching OUT one slot earlier.
uint64_t out_addr = addr - 0x10;
uint32_t v = mmio_regs.count(out_addr) ? mmio_regs[out_addr] : 0;
printf("[gpio] R: offset 0x%X (IN, mirror of OUT @ 0x%X) = 0x%08X\n",
offset, (uint32_t)(offset - 0x10), v);
return v;
}
}
printf("[gpio] R: offset 0x%X = 0\n", offset);
return 0;
}
void gpio_write(EmuState *state, uint64_t addr, uint32_t val) {
uint32_t offset = (uint32_t)(addr - GPIO_BASE);
printf("[gpio] W: offset 0x%X = 0x%08X\n", offset, val);
(void)state;
}
// ==================== I2C ====================
// hekate uses I2C5 for MAX77620 (PMIC) and MAX17050 (fuel gauge)
// We stub the I2C transaction to return simulated values.
// I2C register offsets
#define I2C_CNFG 0x00
#define I2C_CMD_ADDR0 0x04
#define I2C_CMD_DATA1 0x0C
#define I2C_STATUS 0x1C
// MAX77620 addresses and registers
#define MAX77620_I2C_ADDR 0x3C
#define MAX77620_RTC_ADDR 0x68
// MAX17050 fuel gauge
#define MAX17050_ADDR 0x36
#define MAX17050_REP_SOC 0x06
#define MAX17050_VCELL 0x09
static uint8_t i2c_slave_addr = 0;
static uint8_t i2c_reg_addr = 0;
// (Touch controller is the STMFTS at I2C_3 / slave 0x49, handled separately
// in t210/i2c3.cpp. Nothing else lives at I2C_1 / slave 0x4C besides TMP451.)
#define MAX77620_REG_ONOFFSTAT 0x15
#define MAX77620_ONOFFSTAT_EN0 BIT(2)
// ---- Packet-mode I2C (BM92T36 USB-PD on I2C_1 @ 0x18) ----
// MAX17050/MAX77620/BQ24193 use the simple "normal" path (CMD_DATA1 reads).
// BM92T36 uses Hekate's i2c_xfer_packet, which streams a multi-word header
// through TX_FIFO and reads data back through RX_FIFO. This is a small FSM
// that watches TX_FIFO writes, captures the slave/register/direction, and
// pre-fills an RX buffer when a read header arrives.
struct PacketState {
int hdr_idx = 0; // word index since last PROT magic
uint8_t dev_addr = 0;
uint32_t payload_size = 0; // bytes
bool is_read = false;
uint8_t reg_addr = 0; // captured from prior write phase
uint8_t rx_buf[64] = {0};
uint32_t rx_size = 0;
uint32_t rx_pos = 0;
};
static PacketState pkt_i2c1;
static PacketState pkt_i2c5;
#define I2C_PACKET_PROT_I2C (1u << 4)
#define I2C_HEADER_READ (1u << 19)
static void bm92t36_fill_rx(EmuState *state, uint8_t reg, uint8_t *buf, uint32_t size) {
// All multi-byte values are little-endian on the wire (Hekate reassembles
// with `(buf[1] << 8) | buf[0]`). FW_TYPE is the exception — Hekate uses
// `(buf[0] << 4) | buf[1]` and expects 0x36, so buf[0]=3, buf[1]=6.
auto put = [&](uint32_t i, uint8_t v) { if (i < size) buf[i] = v; };
switch (reg) {
case 0x03: // STATUS1: bit 7 = cable inserted
put(0, state->usb_pd_inserted.load() ? 0x80 : 0x00);
break;
case 0x4B: // FW_TYPE_REG -> VER_36 = 0x36
put(0, 0x03); put(1, 0x06);
break;
case 0x4D: // MAN_ID_REG -> MAN_ROHM = 0x04B5
put(0, 0xB5); put(1, 0x04);
break;
case 0x4E: // DEV_ID_REG -> DEV_BM92T = 0x03B0
put(0, 0xB0); put(1, 0x03);
break;
case 0x08: // READ_PDOS_SRC: byte0 = PDO-bytes count, then 4-byte PDOs
case 0x28: { // CURRENT_PDO: same layout, 1 PDO
// Synthesize a single Fixed-type PDO from the EmuState values.
// pd_object_t bitfields (LSB->MSB): amp:10, volt:10, info:10, type:2.
uint32_t amp_lsb = (uint32_t)(state->usb_pd_amperage_ma.load() / 10) & 0x3FF;
uint32_t volt_lsb = (uint32_t)(state->usb_pd_voltage_mv.load() / 50) & 0x3FF;
uint32_t pdo = amp_lsb | (volt_lsb << 10);
put(0, 4);
put(1, (uint8_t)(pdo >> 0));
put(2, (uint8_t)(pdo >> 8));
put(3, (uint8_t)(pdo >> 16));
put(4, (uint8_t)(pdo >> 24));
break;
}
default:
break;
}
}
static void packet_populate_rx(EmuState *state, bool on_i2c5, PacketState &p) {
p.rx_pos = 0;
p.rx_size = std::min((uint32_t)sizeof(p.rx_buf), p.payload_size);
memset(p.rx_buf, 0, sizeof(p.rx_buf));
if (!on_i2c5 && p.dev_addr == 0x18) {
bm92t36_fill_rx(state, p.reg_addr, p.rx_buf, p.rx_size);
}
// Add other packet-mode slaves here as needed.
}
uint32_t i2c_read(EmuState *state, uint64_t addr) {
bool on_i2c5 = (addr >= I2C5_BASE);
uint32_t base = on_i2c5 ? I2C5_BASE : I2C1_BASE;
uint32_t offset = (uint32_t)(addr - base);
PacketState &pkt = on_i2c5 ? pkt_i2c5 : pkt_i2c1;
switch (offset) {
case 0x1C: // I2C_STATUS
return 0; // Transaction complete, no error, not busy
case 0x8C: // I2C_CONFIG_LOAD
return 0; // MSTR_CONFIG_LOAD (bit 0) cleared = load complete
case 0x68: // I2C_INT_STATUS
return (1 << 11); // BUS_CLEAR_DONE (bit 11)
case 0x54: { // I2C_RX_FIFO (packet-mode receive)
uint32_t word = 0;
uint32_t n = std::min((uint32_t)4, pkt.rx_size - pkt.rx_pos);
for (uint32_t i = 0; i < n; i++) {
word |= (uint32_t)pkt.rx_buf[pkt.rx_pos++] << (i * 8);
}
return word;
}
case 0x58: // I2C_PACKET_TRANSFER_STATUS
// Hekate waits for ((status >> 4) & 0xFFF) == size-1 after each phase.
// Return the last-captured payload size shifted; phase always completes
// synchronously in our emulator.
return (pkt.payload_size ? (pkt.payload_size - 1) : 0) << 4;
case 0x60: { // I2C_FIFO_STATUS
// Bits[3:0] = RX_FIFO_FULL_CNT (entries available, each entry = 4 bytes).
if (pkt.rx_size > pkt.rx_pos) {
uint32_t words = (pkt.rx_size - pkt.rx_pos + 3) / 4;
return words & 0xF;
}
return 0;
}
case 0x10: // I2C_CMD_DATA2 (bytes 4-7) — no slaves currently need a >4 byte response
return 0;
case 0x0C: // I2C_CMD_DATA1
// TMP451 SoC/PCB thermal sensor (slave 0x4C on I2C_1).
// Hekate reads (per bdk/thermal/tmp451.c) the integer °C from
// PCB: 0x00, SoC: 0x01
// and the fractional byte from
// SoC dec: 0x10, PCB dec: 0x15
// The fractional byte's high nibble is units of 1/16 °C; Hekate decodes
// tenths_of_C = ((dec >> 4) * 625) / 100
// So encoding from a UI value of °C×10:
// lsb = (c10 * 8) / 5 // total LSBs (1 LSB = 1/16 °C = 0.625 c10)
// int_byte = lsb >> 4
// dec_byte = (lsb & 0xF) << 4
if (!on_i2c5 && i2c_slave_addr == 0x4C) {
auto encode_lsb = [](int16_t c10) -> uint16_t {
return (uint16_t)((int32_t)c10 * 8 / 5);
};
uint16_t soc_lsb = encode_lsb(state->soc_temp_c10.load());
uint16_t pcb_lsb = encode_lsb(state->pcb_temp_c10.load());
switch (i2c_reg_addr) {
case 0x00: return (pcb_lsb >> 4) & 0xFF; // PCB int (local)
case 0x01: return (soc_lsb >> 4) & 0xFF; // SoC int (remote)
case 0x10: return (uint8_t)((soc_lsb & 0xF) << 4); // SoC dec
case 0x15: return (uint8_t)((pcb_lsb & 0xF) << 4); // PCB dec
default: return 0;
}
}
// MAX17050 fuel gauge (slave 0x36 on I2C_1).
// All raw encodings here are the inverse of Hekate's max17050_get_property
// formulas in bdk/power/max17050.c. Switch hardware uses Rsense=5mΩ with
// CGAIN=2 → ADJ_RSENSE = 10mΩ, which sets the per-LSB units below.
if (!on_i2c5 && i2c_slave_addr == 0x36) {
switch (i2c_reg_addr) {
case 0x05: { // RepCap — 0.5 mAh/LSB (= mAh * 2)
return (uint16_t)(state->bat_capacity_mah.load() * 2);
}
case 0x06: { // RepSOC — %·256, Hekate displays (raw >> 8)
return (uint16_t)(state->bat_soc_pct.load() << 8);
}
case 0x07: { // Age — %·256, Hekate displays (raw >> 8)
return (uint16_t)((uint16_t)state->bat_age_pct.load() << 8);
}
case 0x08: { // TEMP — °C/256 signed, UI is °C·10
int32_t scaled = (int32_t)state->bat_temp_c10.load() * 256 / 10;
return (uint16_t)(int16_t)scaled;
}
case 0x09: // VCELL — 0.625 mV/LSB on the upper 13 bits, i.e. (raw >> 3) * 625 / 1000 = mV
case 0x19: // AvgVCELL — same encoding
case 0xFB: { // OCVInternal — same encoding
uint16_t mv = (i2c_reg_addr == 0xFB) ? state->bat_ocv_mv.load() : state->bat_vcell_mv.load();
return (uint16_t)(((uint32_t)mv * 8000) / 625);
}
case 0x0A: // Current — 156.25 µA/LSB signed, UI is mA
case 0x0B: { // AvgCurrent — same encoding
int32_t raw = (int32_t)state->bat_current_ma.load() * 64 / 10;
return (uint16_t)(int16_t)raw;
}
case 0x10: { // FullCAP — 0.5 mAh/LSB
return (uint16_t)(state->bat_full_cap_mah.load() * 2);
}
case 0x17: { // Cycles
return state->bat_cycles.load();
}
case 0x18: { // DesignCap — 0.5 mAh/LSB
return (uint16_t)(state->bat_design_cap_mah.load() * 2);
}
case 0x1B: { // MinMaxVolt — packed (max << 8) | min, units of 20 mV
uint16_t lo = (uint16_t)(state->bat_min_volt_mv.load() / 20) & 0xFF;
uint16_t hi = (uint16_t)(state->bat_max_volt_mv.load() / 20) & 0xFF;
return (hi << 8) | lo;
}
case 0x21: // DevName — must be 0x00AC for max17050_get_version() to succeed
return 0x00AC;
case 0x3A: { // V_empty — (raw >> 7) * 10 = mV
return (uint16_t)(((uint32_t)state->bat_v_empty_mv.load() / 10) << 7);
}
default:
return 0;
}
}
// MAX77620 PMIC (slave 0x3C on I2C_5).
if (on_i2c5 && i2c_slave_addr == 0x3C) {
switch (i2c_reg_addr) {
case 0x15: { // ONOFFSTAT — EN0 bit reflects power button
return state->btn_power.load() ? (1 << 2) : 0;
}
case 0x5B: return state->pmic_silicon_rev.load() & 0xF; // CID3: low nibble shown as "v%d"
case 0x5C: return state->pmic_otp.load(); // CID4: 0x35 Erista, 0x53 Mariko
case 0x5D: return 0; // CID5: ES version
default: return 0;
}
}
// MAX77621 CPU/GPU regulator (slave 0x1B on I2C_5). Hekate reads CHIPID1
// (reg 0x04) and prints the byte verbatim as the version number.
if (on_i2c5 && i2c_slave_addr == 0x1B) {
if (i2c_reg_addr == 0x04) return state->cpu_pmic_version.load();
return 0;
}
// BQ24193 charger (slave 0x6B on I2C_1).
// Each register is reverse-encoded from a decoded EmuState value (mA / mV
// / °C) so the user-facing tweak reads in real units; the formulas mirror
// bq24193_get_property() in bdk/power/bq24193.c.
if (!on_i2c5 && i2c_slave_addr == 0x6B) {
auto encode_input_current = [](uint16_t ma) -> uint8_t {
// Table-quantized: pick nearest legal bucket.
static const uint16_t tbl[8] = {100,150,500,900,1200,1500,2000,3000};
uint8_t best = 0; int best_d = 0x7FFFFFFF;
for (uint8_t i = 0; i < 8; i++) {
int d = (int)tbl[i] - (int)ma; if (d < 0) d = -d;
if (d < best_d) { best_d = d; best = i; }
}
return best;
};
auto clamp_div = [](uint16_t v, uint16_t base, uint16_t step, uint8_t maxbits) -> uint8_t {
if (v < base) v = base;
uint16_t units = (v - base) / step;
uint16_t cap = (1u << maxbits) - 1;
if (units > cap) units = cap;
return (uint8_t)units;
};
switch (i2c_reg_addr) {
case 0x00: { // InputSource: VINDPM[6:3] | INLIMIT[2:0]
uint8_t ilim = encode_input_current(state->chg_input_current_ma.load());
uint8_t vindpm = clamp_div(state->chg_input_voltage_mv.load(), 3880, 80, 4);
return (uint32_t)((vindpm << 3) | ilim);
}
case 0x01: { // PORConfig: keep CHGCONFIG=charger-en, set SYSMIN[3:1]
uint8_t sysmin = clamp_div(state->chg_system_min_mv.load(), 3000, 100, 3);
return (uint32_t)((1u << 4) | (sysmin << 1));
}
case 0x02: { // ChrgCurr: ICHG[7:2]
uint8_t ichg = clamp_div(state->chg_fast_current_ma.load(), 512, 64, 6);
return (uint32_t)(ichg << 2);
}
case 0x04: { // ChrgVolt: VREG[7:2]
uint8_t vreg = clamp_div(state->chg_charge_voltage_mv.load(), 3504, 16, 6);
return (uint32_t)(vreg << 2);
}
case 0x06: { // IRCompThermal: THERM[1:0]
uint8_t c = state->chg_thermal_c.load();
uint8_t therm = (c >= 110) ? 3 : (c >= 90) ? 2 : (c >= 70) ? 1 : 0;
return (uint32_t)therm;
}
case 0x08: { // Status: bits[7:6]=VBUS, [5:4]=CHRG, [2]=PG
uint32_t v = 0;
v |= (uint32_t)(state->chg_vbus_stat.load() & 0x3) << 6;
v |= (uint32_t)(state->chg_chrg_stat.load() & 0x3) << 4;
if (state->chg_power_good.load()) v |= (1 << 2);
return v;
}
case 0x09: { // FaultReg: bits[2:0] = THERM_STAT (charger NTC thermistor).
// Hekate decodes this independently of MAX17050's TEMP, but on real
// hardware both sensors track the battery, so derive it from the
// battery temp slider for consistency. Code map (per gui_info.c):
// 0=Normal, 2=Warm, 3=Cool, 5=Cold, 6=Hot
int16_t t10 = state->bat_temp_c10.load();
uint8_t therm;
if (t10 < 0) therm = 5; // Cold
else if (t10 < 100) therm = 3; // Cool
else if (t10 < 450) therm = 0; // Normal
else if (t10 < 500) therm = 2; // Warm
else therm = 6; // Hot
return therm;
}
case 0x0A: return 0x2F; // VendorPart — must be 0x2F for bq24193_get_version()
default: return 0;
}
}
return 0;
default:
return 0;
}
}
void i2c_write(EmuState *state, uint64_t addr, uint32_t val) {
bool on_i2c5 = (addr >= I2C5_BASE);
uint32_t base = on_i2c5 ? I2C5_BASE : I2C1_BASE;
uint32_t offset = (uint32_t)(addr - base);
PacketState &pkt = on_i2c5 ? pkt_i2c5 : pkt_i2c1;
switch (offset) {
case I2C_CMD_ADDR0:
i2c_slave_addr = (val >> 1) & 0x7F;
break;
case I2C_CMD_DATA1:
i2c_reg_addr = val & 0xFF;
// Hekate's i2c_send_byte goes through _i2c_send_normal which packs
// [reg, value, ...] into CMD_DATA1 (low byte = reg, next byte = value).
// Catch ONOFFCNFG1 writes to MAX77620 here so the emulator reacts to
// the payload's POWER_OFF / SFT_RST same as real hardware.
if (on_i2c5 && i2c_slave_addr == MAX77620_I2C_ADDR && i2c_reg_addr == 0x41) {
uint8_t v = (val >> 8) & 0xFF;
if (v & 0x02) { // ONOFFCNFG1_PWR_OFF
printf("[emu] MAX77620 PWR_OFF received - exiting\n");
fflush(stdout);
state->running = false;
} else if (v & 0x80) { // ONOFFCNFG1_SFT_RST
printf("[emu] MAX77620 SFT_RST received - rebooting payload\n");
fflush(stdout);
state->reboot_requested = true;
}
}
break;
case 0x50: { // I2C_TX_FIFO (packet-mode dispatch)
// Each packet starts with the PROT magic word; subsequent words follow a
// fixed layout: [size-1, header, payload...]. The header carries dev_addr
// and a READ flag; for write phases the first payload byte is the slave's
// register address that the matching read phase will target.
if (val == I2C_PACKET_PROT_I2C) {
pkt.hdr_idx = 1; // word 0 (PROT) just consumed
return;
}
int idx = pkt.hdr_idx++;
if (idx == 1) {
pkt.payload_size = (val & 0xFFF) + 1;
} else if (idx == 2) {
pkt.dev_addr = (val >> 1) & 0x7F;
pkt.is_read = (val & I2C_HEADER_READ) != 0;
i2c_slave_addr = pkt.dev_addr; // mirror for any cross-path lookups
if (pkt.is_read) {
packet_populate_rx(state, on_i2c5, pkt);
}
} else if (!pkt.is_read && idx == 3) {
// First (and for our slaves, only) payload byte = register address.
pkt.reg_addr = val & 0xFF;
} else if (!pkt.is_read && idx == 4) {
// Second payload byte = value being written. Watch for PMIC commands
// that mean "shut the SoC down" or "reset" so the emulator can react
// the same way real hardware would.
if (on_i2c5 && pkt.dev_addr == 0x3C && pkt.reg_addr == 0x41) {
uint8_t v = val & 0xFF;
if (v & 0x02) { // ONOFFCNFG1_PWR_OFF
printf("[emu] MAX77620 PWR_OFF received - exiting\n");
fflush(stdout);
state->running = false;
} else if (v & 0x80) { // ONOFFCNFG1_SFT_RST
printf("[emu] MAX77620 SFT_RST received - rebooting payload\n");
fflush(stdout);
state->reboot_requested = true;
}
}
}
break;
}
case 0x5C: // I2C_FIFO_CONTROL — TX/RX flush at the start of each xfer
if (val & 0x3) {
pkt.hdr_idx = 0;
pkt.rx_size = 0;
pkt.rx_pos = 0;
// reg_addr intentionally preserved across the write→read transition
}
break;
}
}
// ==================== Display ====================
uint32_t display_read(EmuState *state, uint64_t addr) {
uint32_t offset = (uint32_t)(addr - DISPLAY_A_BASE);
if (offset == 0x800 * 4 || offset == 0x2000)
return (uint32_t)state->fb_addr;
return 0;
}
void display_write(EmuState *state, uint64_t addr, uint32_t val) {
if (addr < 0x54200000 || addr >= 0x54240000)
return;
uint32_t offset = (uint32_t)(addr - 0x54200000);
uint32_t index = offset / 4;
// DC_CMD_DISPLAY_WINDOW_HEADER (offset 0x042*4 = 0x108).
// Tracks which window's registers are currently selected.
if (offset == 0x108)
state->dc_window_sel = val;
if (offset >= 0x1C00 && offset < 0x1E00) {
uint32_t win_off = offset - 0x1C00;
if (win_off == 0x00)
state->pre_rot = val;
if (win_off == 0x14) {
state->pre_w = (val & 0x1FFF);
state->pre_h = ((val >> 16) & 0x1FFF);
}
if (win_off == 0x18)
state->pre_stride = val & 0xFFFF;
if (win_off == 0x2C) {
state->pre_sw = ((val & 0x7) == 2) ? 2 : 0;
// Tegra DC surface kind: tile in low bits; bits 4–7 = log2(block height in
// GOBs) for block-linear (values 1..5 → 2,4,8,16,32 GOBs).
unsigned log2bh = (val >> 4) & 0xF;
if (log2bh >= 1 && log2bh <= 5)
state->pre_bh = 1u << log2bh;
else
state->pre_bh = 0;
}
} else if (offset >= 0x2000 && offset < 0x2100) {
uint32_t buf_off = offset - 0x2000;
if (buf_off == 0x00) {
state->pre_addr = val;
if (val == 0xF6200000) {
printf("[display] WARNING: Guest wrote 0xF6200000 to pre_addr!\n");
// Print the guest PC to see where this comes from
uint32_t pc = 0;
uc_reg_read(state->uc, UC_ARM_REG_PC, &pc);
printf("[display] Guest PC: 0x%X\n", pc);
uint8_t code[16] = {0};
if (uc_mem_read(state->uc, pc - 8, code, 16) == UC_ERR_OK) {
printf("[display] Code: ");
for (int i=0; i<16; i++) printf("%02X ", code[i]);
printf("\n");
}
}
}
if (buf_off == 0x2C) {
state->pre_sw = ((val & 0x7) == 2) ? 2 : 0;
unsigned log2bh = (val >> 4) & 0xF;
if (log2bh >= 1 && log2bh <= 5)
state->pre_bh = 1u << log2bh;
else
state->pre_bh = 0;
}
} else if (index == 0x85) { // SIZE legacy
state->pre_w = (val & 0x1FFF);
state->pre_h = ((val >> 16) & 0x1FFF);
} else if (index == 0x41 || offset == 0x104) { // DC_CMD_STATE_CONTROL
// Latch when activation request bits are set (WIN_A_ACT_REQ=bit1,
// GENERAL_ACT_REQ=bit0)
if (val & 0x3) {
bool is_window_a = (state->dc_window_sel & 0x10) != 0; // Bit 4 = Window A
bool is_window_d = (state->dc_window_sel & 0x80) != 0; // Bit 7 = Window D
if (is_window_a && state->pre_addr) {
// Save Window A parameters as primary display surface.
state->winA_addr = state->pre_addr;
state->winA_w = state->pre_w;
state->winA_h = state->pre_h;
state->winA_stride = state->pre_stride;
state->winA_sw = state->pre_sw;
state->winA_rot = state->pre_rot;
state->winA_bh = state->pre_bh;
}
// Always latch to fb_* for display — but prefer Window A over Window D
// since Window D is just a transparent overlay in Hekate.
if (is_window_a || !is_window_d) {
state->fb_addr = state->pre_addr;
state->fb_width = state->pre_w;
state->fb_height = state->pre_h;
state->fb_stride = state->pre_stride;
state->fb_swizzle = state->pre_sw;
state->fb_rotation = state->pre_rot;
if (state->pre_bh)
state->fb_bh = state->pre_bh;
}
// If Window D is latched alone, don't override — keep Window A's surface.
state->display_dirty = true;
printf("[display] LATCH (ACT_REQ): 0x%llX (%dx%d), WinSel: 0x%X, Sw: %d, "
"Str: %d\n",
(unsigned long long)state->fb_addr, state->fb_width,
state->fb_height, state->dc_window_sel, state->fb_swizzle,
state->fb_stride);
}
}
}
// Forward decls — these handlers live further down this file but are reached
// from misc_read's catch-all routing for DSI accesses.
uint32_t dsi_read(EmuState *state, uint64_t addr);
void dsi_write(EmuState *state, uint64_t addr, uint32_t val);
uint32_t misc_read(EmuState *state, uint64_t addr) {
// PINMUX (APB_MISC pad config range) — every write lands in the global
// mmio_regs map at line 1726, so we just hand it back. Returning 0 here
// (the old behavior) silently dropped the muxed function bits, which
// broke any probe that read back PINMUX_AUX_* to confirm a pin's mode.
if (addr >= PINMUX_BASE && addr < PINMUX_BASE + PINMUX_SIZE) {
return mmio_regs.count(addr) ? mmio_regs[addr] : 0;
}
// APB_MISC_GP_HIDREV - hardware revision.
// Bits 11:8 = chip ID (0x21 for both T210 / T210B01),
// Bits 7:4 = major rev (1 = Erista T210, 2 = Mariko T210B01),
// Bits 3:0 = minor.
// Hekate's hw_get_chip_id() does `(HIDREV >> 4) & 0xF` and compares against
// GP_HIDREV_MAJOR_T210B01 (=2) to decide h_cfg.t210b01.
if (addr == APB_MISC_BASE + 0x804)
return state->is_mariko.load() ? 0x20 : 0x10;
// UART
if (addr >= 0x70006000 && addr < 0x70006500) {
uint32_t port = (addr - 0x70006000) / 0x40;
uint32_t offset = (addr - 0x70006000) % 0x40;
if (offset == 0x14) {
// LSR: THRE | TMTY (0x60) always ready, plus RDR (bit 0) when our
// host-side RX FIFO has bytes queued by the console window.
uint32_t lsr = 0x60;
if (port < EmuState::N_UARTS && !state->uart_rx_fifo[port].empty())
lsr |= 0x01;
return lsr;
}
if (offset == 0x00) {
// RBR (read alias of THR/DLL when DLAB=0). Pop the next host-injected
// byte; if the FIFO is empty return 0 (the payload should have
// checked LSR.RDR first).
if (port < EmuState::N_UARTS && !state->uart_rx_fifo[port].empty()) {
uint8_t b = state->uart_rx_fifo[port].front();
state->uart_rx_fifo[port].pop_front();
return b;
}
}
return 0;
}
// DSI
if (addr >= DSI_BASE && addr < DSI_BASE + DSI_SIZE) {
return dsi_read(state, addr);
}
// MC
if (addr >= MC_BASE && addr < MC_BASE + MC_SIZE) {
uint32_t offset = (uint32_t)(addr - MC_BASE);
if (offset == 0x65C)
return 0x40000000; // MC_IRAM_BOM: set to bridge IRAM boundary
return 0;
}
// PWM controller (LCD backlight on PWM0, optional fan on PWM1). Same
// story as PINMUX above — the writes are captured by the global cache
// already; return whatever was last written so the payload can read
// back its own duty cycle / enable bit.
if (addr >= PWM_BASE && addr < PWM_BASE + 0x1000) {
return mmio_regs.count(addr) ? mmio_regs[addr] : 0;
}
// TSEC (Tegra Security Co-processor) — base 0x54500000 per Hekate bdk/soc/t210.h.
// The TSEC microcontroller's firmware blob runs HDCP-based key derivation that
// produces the per-console TSEC key. We don't emulate the Falcon CPU, so we
// satisfy the polling protocol Lockpick / Hekate's tsec_query() uses:
// * TSEC_DMATRFCMD (0x1118) reads → return DMATRFCMD_IDLE so the DMA-wait exits.
// * TSEC_STATUS (0x1044) reads → return 0xB0B0B0B0 (success magic).
// The keyslot values that TSEC firmware would have produced are instead
// pre-loaded into the SE keytable via --prod-keys (slots 12/13/14).
if (addr >= TSEC_BASE && addr < TSEC_BASE + TSEC_SIZE) {
uint32_t offset = (uint32_t)(addr - TSEC_BASE);
if (offset == 0x1118) return (1u << 1); // TSEC_DMATRFCMD_IDLE
if (offset == 0x1044) return 0xB0B0B0B0; // TSEC_STATUS = success magic
return 0;
}
// KFUSE (Key Fuse / HDCP keys) — base 0x7000FC00 per Hekate bdk/soc/t210.h.
// Hekate's kfuse_wait_ready() spins on KFUSE_STATE bit 16 (DONE) with no
// timeout (bdk/soc/kfuse.c). Return DONE|CRCPASS so the wait exits and the
// 144-word KFUSE_KEYS read produces zeros (fine for the boot path; HDCP
// is unused in the emulator).
if (addr >= 0x7000FC00 && addr < 0x7000FD00) {
uint32_t offset = (uint32_t)(addr - 0x7000FC00);
if (offset == 0x80) // KFUSE_STATE
return (1u << 16) | (1u << 17); // DONE | CRCPASS
if (offset == 0x8C) // KFUSE_KEYS (auto-incrementing zeros)
return 0;
return 0;
}
// SDMMC Controllers
if ((addr >= SDMMC1_BASE && addr < SDMMC1_BASE + 0x1000) ||
(addr >= SDMMC4_BASE && addr < SDMMC4_BASE + 0x1000)) {
uint32_t base = (addr >= SDMMC4_BASE) ? SDMMC4_BASE : SDMMC1_BASE;
uint32_t offset = addr - base;
uint32_t *rsp =
(base == SDMMC4_BASE) ? state->sdmmc4_rsp : state->sdmmc_rsp;
uint32_t &norintsts = (base == SDMMC4_BASE) ? state->sdmmc4_norintsts
: state->sdmmc_norintsts;
uint16_t blksize =
(base == SDMMC4_BASE) ? state->sdmmc4_blksize : state->sdmmc_blksize;
uint16_t blkcnt =
(base == SDMMC4_BASE) ? state->sdmmc4_blkcnt : state->sdmmc_blkcnt;
uint16_t trnmod =
(base == SDMMC4_BASE) ? state->sdmmc4_trnmod : state->sdmmc_trnmod;
uint32_t result = 0;
if (offset == 0x00)
result = (base == SDMMC4_BASE) ? state->sdmmc4_sysad : state->sdmmc_sysad;
else if (offset == 0x04)
result = (blkcnt << 16) | blksize;
else if (offset == 0x0C)
result = trnmod;
else if (offset == 0x58)
result = (uint32_t)((base == SDMMC4_BASE) ? state->sdmmc4_adma_addr
: state->sdmmc_adma_addr);
else if (offset == 0x5C)
result = (uint32_t)(((base == SDMMC4_BASE) ? state->sdmmc4_adma_addr
: state->sdmmc_adma_addr) >>
32);
else if (offset == 0x24) {
// PRESENT_STATE. SDMMC1 honours the SD-insert toggle; SDMMC4 (eMMC)
// is always present.
if (base == SDMMC1_BASE && !state->sd_inserted.load())
result = 0; // no card present
else
result = 0x01F70000; // CARD_PRESENT | CD_STABLE | CD_LVL | DAT_LINE_LEVEL
}
else if (offset == 0x2C)
result = 0x0003; // SDHCI_CLOCK_INT_EN | SDHCI_CLOCK_INT_STABLE
else if (offset == 0x40)
result =
0x376CD08C; // Full Tegra capabilities (64-bit, SDMA, ADMA2, etc.)
else if (offset == 0x44)
result = 0x10002F73; // CAP1
else if (offset == 0x30)
result = ((base == SDMMC4_BASE ? state->sdmmc4_errintsts
: state->sdmmc_errintsts)
<< 16) |
norintsts;
else if (offset == 0x32)
result = (base == SDMMC4_BASE) ? state->sdmmc4_errintsts
: state->sdmmc_errintsts;
else if (offset >= 0x10 && offset <= 0x1C)
result = rsp[(offset - 0x10) / 4];
else if (offset == 0x1EC)
result = 0x00000001; // AUTOCAL_STS
else if (offset == 0xFE)
result = 0x0303; // SDHCI Version 4.0
printf("[sdmmc%c] R: 0x%02lX = 0x%08X (PC=0x%llX)\n",
(base == SDMMC4_BASE) ? '4' : '1', (unsigned long)offset,
result, (unsigned long long)state->insn_count);
return result;
}
return 0;
}
void misc_write(uc_engine *uc, EmuState *state, uint64_t addr, int64_t value,
int size) {
uint32_t val = (uint32_t)value;
// SDMMC Controllers
if ((addr >= SDMMC1_BASE && addr < SDMMC1_BASE + 0x1000) ||
(addr >= SDMMC4_BASE && addr < SDMMC4_BASE + 0x1000)) {
uint32_t base = (addr >= SDMMC4_BASE) ? SDMMC4_BASE : SDMMC1_BASE;
uint32_t offset = addr - base;
printf("[sdmmc%c] W: 0x%02X = 0x%08X (size %d)\n",
(base == SDMMC4_BASE) ? '4' : '1', offset, val, size);
uint32_t &arg =
(base == SDMMC4_BASE) ? state->sdmmc4_arg : state->sdmmc_arg;
uint32_t *rsp =
(base == SDMMC4_BASE) ? state->sdmmc4_rsp : state->sdmmc_rsp;
uint32_t &norintsts = (base == SDMMC4_BASE) ? state->sdmmc4_norintsts
: state->sdmmc_norintsts;
uint32_t &errintsts = (base == SDMMC4_BASE) ? state->sdmmc4_errintsts
: state->sdmmc_errintsts;
uint32_t &sysad =
(base == SDMMC4_BASE) ? state->sdmmc4_sysad : state->sdmmc_sysad;
uint8_t &hostctl =
(base == SDMMC4_BASE) ? state->sdmmc4_hostctl : state->sdmmc_hostctl;
uint16_t &blksize =
(base == SDMMC4_BASE) ? state->sdmmc4_blksize : state->sdmmc_blksize;
uint16_t &blkcnt =
(base == SDMMC4_BASE) ? state->sdmmc4_blkcnt : state->sdmmc_blkcnt;
uint16_t &trnmod =
(base == SDMMC4_BASE) ? state->sdmmc4_trnmod : state->sdmmc_trnmod;
uint64_t &adma_addr = (base == SDMMC4_BASE) ? state->sdmmc4_adma_addr
: state->sdmmc_adma_addr;
if (offset == 0x00)
sysad = val;
if (offset == 0x28)
hostctl = val & 0x1E;
if (offset == 0x2F) {
// Software Reset. Clear immediately to signify completion.
mmio_regs[base + 0x2C] &= ~(val << 24);
}
if (offset == 0x2C && size == 4) {
// If reset bits were set in 4-byte write, clear them for subsequent reads
mmio_regs[base + 0x2C] &= ~0x07000000;
}
if (offset == 0x04) {
if (size == 4) {
blksize = val & 0x0FFF;
blkcnt = val >> 16;
} else
blksize = val & 0x0FFF;
}
if (offset == 0x06)
blkcnt = val;
if (offset == 0x08)
arg = val;
if (offset == 0x0C)
trnmod = (size == 4) ? (val & 0xFFFF) : val;
if (offset == 0x58) {
if (size == 8)
adma_addr = value;
else
adma_addr = (adma_addr & 0xFFFFFFFF00000000ULL) | val;
}
if (offset == 0x5C)
adma_addr = (adma_addr & 0x00000000FFFFFFFFULL) | ((uint64_t)val << 32);
if (offset == 0x30) {
norintsts &= ~(val & 0xFFFF);
errintsts &= ~(val >> 16);
}
if (offset == 0x32)
errintsts &= ~val;
if (offset == 0x0E || (offset == 0x0C && size == 4)) {
uint32_t cmdreg = (offset == 0x0C) ? (val >> 16) : val;
uint32_t cmd = (cmdreg >> 8) & 0x3F;
bool is_read = (trnmod & 0x0010); // Bit 4 of TRNMOD is Read/Write
if (base == SDMMC1_BASE) {
printf("[sdmmc] W: offset 0x%X = 0x%08X (PC=0x%08llX)\n", offset, val,
(unsigned long long)state->insn_count);
if (offset == 0x0E || (offset == 0x0C && size == 4)) {
printf("[sdmmc] CMD%d: arg=0x%08X, blkcnt=%d, trnmod=0x%04X\n", cmd,
arg, blkcnt, trnmod);
}
fflush(stdout);
}
uint32_t r1_base = 0x00000000;
if (base == SDMMC1_BASE && state->last_cmd_was_55)
r1_base |= 0x0100;
if (base == SDMMC4_BASE && state->last_cmd4_was_55)
r1_base |= 0x0100;
// SDMMC1 with no SD card: signal CMD_TIMEOUT_ERROR for every command.
// Hekate's sd_init_retry sees the timeout, falls back to checking
// gpio_read(PORT_Z, 1), and bails with "Failed to init SD card".
if (base == SDMMC1_BASE && !state->sd_inserted.load()) {
norintsts |= 0x0001;
errintsts |= (1 << 0);
rsp[0] = rsp[1] = rsp[2] = rsp[3] = 0;
return;
}
// Set Command Complete for all commands
norintsts |= 0x0001;
switch (cmd) {
case 0:
break;
case 8:
rsp[0] = (base == SDMMC4_BASE) ? 0x00000900 : arg;
// eMMC SEND_EXT_CSD: 512-byte data transfer to DMA dst. Without this,
// sdmmc_storage_init_mmc times out waiting for transfer complete and
// bails with storage->initialized = 0 — which silently breaks all
// later sdmmc_storage_read calls (returns 0 without issuing CMD18).
if (base == SDMMC4_BASE) {
uint8_t ext_csd[512] = {0};
// Set a few fields BDK actually parses (most others can be 0):
// EXT_CSD_REV = 192 (offset 192)
// EXT_CSD_CARD_TYPE = 196 (HS-52 + HS200 + HS400 supported)
// EXT_CSD_SEC_CNT = 212..215 (sector count, little-endian u32)
ext_csd[192] = 7; // eMMC v5.0
ext_csd[196] = 0x57; // HS400_1.8V | HS200_1.8V | HS_52 | HS_DDR
// 32GB worth of sectors (0x3A380000 = 977MB; for the user's actual
// dump size we'd want the real value, but BDK only uses sec_cnt for
// sanity, not for read addressing).
uint32_t sec_cnt = 64 * 1024 * 1024; // 32GB / 512B = 64M sectors
ext_csd[212] = sec_cnt & 0xFF;
ext_csd[213] = (sec_cnt >> 8) & 0xFF;
ext_csd[214] = (sec_cnt >> 16) & 0xFF;
ext_csd[215] = (sec_cnt >> 24) & 0xFF;
uint64_t dma_addr = 0;
// Tegra's SDMMC uses register 0x58 as the SDMA system-address
// register (Hekate writes the destination buffer pointer directly
// there in sdmmc_driver.c::_sdmmc_dma_init). adma_addr in our state
// captures that write, so it IS the destination, not a descriptor
// pointer. Fall back to sysad (SDHCI standard 0x00) if 0x58 wasn't
// programmed.
dma_addr = adma_addr ? adma_addr : sysad;
if (dma_addr) uc_mem_write(uc, dma_addr, ext_csd, 512);
norintsts |= 0x0002; // TRANSFER_COMPLETE
}
break;
case 1:
rsp[0] = 0xC0FF8000;
break;
case 2: { // SEND_CID
// Build the 16-byte CID from EmuState atomics, then pack into the
// 4 R2 response registers. Hekate's _get_rsp shifts the 4 registers
// left 8 bits (CRC strip) when assembling raw_cid, so we put the
// CID bytes into rspreg pre-shifted right by 8.
uint8_t cid[16] = {0};
if (base == SDMMC1_BASE) {
// SD CID per _sd_storage_parse_cid (bdk/storage/sdmmc.c).
uint64_t pn = state->sd_cid_prod_name.load();
cid[0] = state->sd_cid_manfid.load();
cid[1] = (state->sd_cid_oemid.load() >> 8) & 0xFF;
cid[2] = state->sd_cid_oemid.load() & 0xFF;
cid[3] = pn & 0xFF;
cid[4] = (pn >> 8) & 0xFF;
cid[5] = (pn >> 16) & 0xFF;
cid[6] = (pn >> 24) & 0xFF;
cid[7] = (pn >> 32) & 0xFF;
cid[8] = ((state->sd_cid_hwrev.load() & 0xF) << 4)
| (state->sd_cid_fwrev.load() & 0xF);
uint32_t sn = state->sd_cid_serial.load();
cid[9] = (sn >> 24) & 0xFF;
cid[10] = (sn >> 16) & 0xFF;
cid[11] = (sn >> 8) & 0xFF;
cid[12] = sn & 0xFF;
uint8_t yr = (uint8_t)(state->sd_cid_year.load() - 2000);
cid[13] = (yr >> 4) & 0x0F;
cid[14] = ((yr & 0x0F) << 4) | (state->sd_cid_month.load() & 0x0F);
} else {
// eMMC CID per _mmc_storage_parse_cid (MMC v4: 8-bit oemid + 6-byte
// prod_name + prv + 32-bit serial + 4-bit month + 4-bit year offset
// from 2013 because we report ext_csd.rev >= 5).
uint64_t pn = state->emmc_cid_prod_name.load();
cid[0] = state->emmc_cid_manfid.load();
cid[1] = 0;
cid[2] = state->emmc_cid_oemid.load();
cid[3] = pn & 0xFF;
cid[4] = (pn >> 8) & 0xFF;
cid[5] = (pn >> 16) & 0xFF;
cid[6] = (pn >> 24) & 0xFF;
cid[7] = (pn >> 32) & 0xFF;
cid[8] = (pn >> 40) & 0xFF;
cid[9] = state->emmc_cid_prv.load();
uint32_t sn = state->emmc_cid_serial.load();
cid[10] = (sn >> 24) & 0xFF;
cid[11] = (sn >> 16) & 0xFF;
cid[12] = (sn >> 8) & 0xFF;
cid[13] = sn & 0xFF;
uint8_t yr = (uint8_t)(state->emmc_cid_year.load() - 2013);
cid[14] = ((state->emmc_cid_month.load() & 0x0F) << 4) | (yr & 0x0F);
}
rsp[3] = (cid[0] << 16) | (cid[1] << 8) | cid[2];
rsp[2] = (cid[3] << 24) | (cid[4] << 16) | (cid[5] << 8) | cid[6];
rsp[1] = (cid[7] << 24) | (cid[8] << 16) | (cid[9] << 8) | cid[10];
rsp[0] = (cid[11] << 24) | (cid[12] << 16) | (cid[13] << 8) | cid[14];
break;
}
case 3: // SEND_RELATIVE_ADDR
if (base == SDMMC1_BASE) {
// SD: R6 response — RCA in upper 16 bits, status in lower 16.
rsp[0] = 0x00010000 | (3 << 9); // RCA=1, State=stby(3)
} else {
// eMMC: R1 response — status only. RCA was set by host via the arg.
// Bit 16 is R1_CID_CSD_OVERWRITE (error) — must NOT be set, or
// Hekate's _sdmmc_storage_check_card_status() rejects the response
// and sdmmc_storage_init_mmc bails before CMD9 (SEND_CSD).
rsp[0] = (3 << 9); // State=stby(3) only
}
break;
case 9: // SEND_CSD
// R2 response. Hekate's sdmmc_get_rsp shifts each rspreg left by 8
// (to account for the 7-bit CRC strip + start bit), so my rsp[3] here
// ultimately becomes rsp[0] (the top dword of the 128-bit CSD) at
// unstuff_bits time, but with bits remapped: original bit N of rspreg3
// ends up at bit N+8 of unstuff_bits' logical CSD.
// mmca_vsn is at CSD bits 122-125. After Hekate's shuffle, that's
// bits 18-21 of my rspreg3. Set those to 0100 (=4) so storage->csd.
// mmca_vsn >= CSD_SPEC_VER_4 and sdmmc_storage_init_mmc reaches
// storage->initialized = 1 instead of the early-return at line 688.
rsp[0] = 0x400E0032;
rsp[1] = 0x5B590000;
rsp[2] = 0x00007F80;
rsp[3] = 0x16504000; // bits 18-21 = 0100 → mmca_vsn = 4
break;
case 13: { // SEND_STATUS / ACMD13 (SD_STATUS)
bool is_acmd = (base == SDMMC1_BASE) ? state->last_cmd_was_55
: state->last_cmd4_was_55;
if (is_acmd) {
uint8_t ss[64] = {0};
ss[0] = 0x80; // 4-bit support (bit 511:510 = 10)
uint64_t dma_addr = 0;
if (trnmod & 0x0001) {
if ((hostctl & 0x18) == 0x10) { // ADMA2
uint8_t desc[12];