Repository navigation
Expand file tree
/
Copy pathNativeUfs.cpp
More file actions
2289 lines (2206 loc) · 104 KB
/
Copy pathNativeUfs.cpp
File metadata and controls
2289 lines (2206 loc) · 104 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
// SPDX-License-Identifier: BSD-2-Clause-Patent
// Native sheng SM8550 UFS bring-up. Default-off: no MMIO is touched unless
// native-ufs-probe=1 is present. This is a register probe, not a storage driver.
#include <IOKit/IOService.h>
#include <IOKit/IOPlatformExpert.h>
#include <IOKit/storage/IOBlockStorageDevice.h>
#include <libkern/crypto/sha2.h>
#include <vm/vm_kern.h>
#include <vm/vm_map.h>
#include <sys/param.h>
#if defined(NATIVE_VERIFIED_BLOCK) && NATIVE_VERIFIED_BLOCK
#include "verified_root.h"
#include "VerifiedBlockPolicy.h"
extern "C" {
extern const struct vr_crypto vr_xnu_crypto;
extern const uint8_t verified_root_key[];
}
#endif
#include <libkern/c++/OSNumber.h>
#include <libkern/c++/OSIterator.h>
#include <IOKit/IOBSD.h>
#include <kern/thread.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOBufferMemoryDescriptor.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/IOLib.h>
#include <libkern/c++/OSData.h>
#include <libkern/c++/OSString.h>
#include <libkern/libkern.h>
#include <pexpert/pexpert.h>
#include <kern/debug.h>
#include "GccUfs.h"
#include "RootDiagnostics.h"
#include "TransferPoll.h"
#include "SmmuTakeover.h"
static UfsRootDiagnostics gRootDiagnostic;
static bool gRootDiagnosticEnabled;
static void rootDiagnosticFailure (const char *reason);
extern "C" {
// Optional passive observer; absent in ordinary builds. It may run while a
// window lock is held, so its contract forbids blocking or transport access.
void boot_progress_poll (void) __attribute__((weak));
volatile uint32_t native_ufs_probe_calls = 0;
volatile uint32_t native_ufs_matched = 0;
volatile uint32_t native_ufs_probe_requested = 0;
volatile uint32_t native_ufs_mapped = 0;
volatile uint32_t native_ufs_status = 0;
// Root start: 1 entered, 2 host up, 3 layout found; the last soft-failure reason (a static string), for the boot page.
volatile uint32_t native_ufs_root_step = 0;
const char *volatile native_ufs_root_failure = nullptr;
volatile uint32_t native_ufs_cap = 0;
volatile uint32_t native_ufs_version = 0;
volatile uint32_t native_ufs_hcs = 0;
volatile uint32_t native_ufs_hce = 0;
volatile uint32_t native_ufs_utrl_ready = 0;
volatile uint32_t native_ufs_uic_status = 0;
volatile uint32_t native_ufs_uic_result = 0;
}
class NativeUfs : public IOService {
OSDeclareDefaultStructors (NativeUfs)
OSSharedPtr<IOMemoryMap> fMap;
OSSharedPtr<IOBufferMemoryDescriptor> fCmdDesc;
void inspect (void);
void adoptAndRead (unsigned level);
void dumpFirmwareList (void);
void inspectSmmu (void);
void inspectContextBanks (void);
bool startRoot (IOService *provider);
static bool waitSet (uintptr_t base, unsigned offset, uint32_t mask, unsigned loops);
public:
IOService *probe (IOService *provider, SInt32 *score) override;
bool start (IOService *provider) override;
void stop (IOService *provider) override;
};
OSDefineMetaClassAndStructors (NativeUfs, IOService)
static uint32_t read32 (uintptr_t base, unsigned offset)
{
return *reinterpret_cast<volatile uint32_t *> (base + offset);
}
static void write32 (uintptr_t base, unsigned offset, uint32_t value)
{
*reinterpret_cast<volatile uint32_t *> (base + offset) = value;
}
static bool contains (const char *data, size_t length, const char *needle)
{
size_t wanted = 0;
while (needle[wanted]) {
wanted++;
}
for (size_t at = 0; at + wanted <= length; at++) {
size_t index = 0;
while (index < wanted && data[at + index] == needle[index]) {
index++;
}
if (index == wanted) {
return true;
}
}
return false;
}
IOService *NativeUfs::probe (IOService *provider, SInt32 *score)
{
native_ufs_probe_calls = native_ufs_probe_calls + 1;
if (OSDynamicCast (IOPlatformExpertDevice, provider)) {
// Second personality (KernelConfigTables: IOMatchCategory NativeUfsRoot): attaches to the
// root device, which starts before the platform expert publishes IOResources, so the
// hardware probes run before the original AMFI (whose start panics without a PMGR).
native_ufs_matched = 1;
if (score) {
*score = 500;
}
return this;
}
OSObject *property = provider->getProperty ("compatible");
OSData *data = OSDynamicCast (OSData, property);
OSString *string = OSDynamicCast (OSString, property);
bool match = false;
if (data) {
const char *text = static_cast<const char *> (data->getBytesNoCopy ());
match = text && contains (text, data->getLength (), "qcom,sm8550-ufshc");
} else if (string) {
const char *text = string->getCStringNoCopy ();
match = text && contains (text, string->getLength (), "qcom,sm8550-ufshc");
}
if (!match) {
return nullptr;
}
native_ufs_matched = 1;
if (score) {
*score = 1000;
}
return this;
}
bool NativeUfs::waitSet (uintptr_t base, unsigned offset, uint32_t mask, unsigned loops)
{
for (unsigned index = 0; index < loops; index++) {
if ((read32 (base, offset) & mask) == mask) {
return true;
}
IODelay (1);
}
return false;
}
// Read-only inspection (native-ufs-probe=2): reports what firmware left behind.
// The physical windows are the sheng board DT's own GCC (0x100000) and UFS host
// (0x1d84000, 0x3000) ranges. The controller is read only if the GCC says its
// GDSC is powered and the AXI/AHB branches are running; nothing is written.
static uintptr_t mapPhys (uint64_t phys, uint64_t length, OSSharedPtr<IOMemoryMap> &keep)
{
IODeviceMemory *memory = IODeviceMemory::withRange (phys, length);
if (!memory) {
return 0;
}
keep = OSSharedPtr<IOMemoryMap> (memory->map (kIOMapAnywhere | kIOMapInhibitCache), OSNoRetain);
memory->release ();
return keep ? static_cast<uintptr_t> (keep->getVirtualAddress ()) : 0;
}
void NativeUfs::inspect (void)
{
OSSharedPtr<IOMemoryMap> gccMap, memPhyMap, hcMap;
uintptr_t gcc = mapPhys (GCC_BASE + 0x77000, 0x1000, gccMap);
uintptr_t memPhy = mapPhys (GCC_BASE + 0x9e000, 0x1000, memPhyMap);
if (!gcc || !memPhy) {
panic ("NATIVE_UFS inspect: GCC map failed gcc=%p memphy=%p", (void *) gcc, (void *) memPhy);
}
uint32_t gdsc = read32 (gcc, GCC_UFS_PHY_GDSCR - 0x77000);
uint32_t axi = read32 (gcc, GCC_UFS_PHY_AXI_ENABLE - 0x77000);
uint32_t ahb = read32 (gcc, GCC_UFS_PHY_AHB_ENABLE - 0x77000);
uint32_t unipro = read32 (gcc, GCC_UFS_PHY_UNIPRO_CORE_ENABLE - 0x77000);
uint32_t txs = read32 (gcc, GCC_UFS_PHY_TX_SYMBOL0_ENABLE - 0x77000);
uint32_t rxs = read32 (gcc, GCC_UFS_PHY_RX_SYMBOL0_ENABLE - 0x77000);
uint32_t aux = read32 (gcc, GCC_UFS_PHY_PHY_AUX_ENABLE - 0x77000);
uint32_t memGdsc = read32 (memPhy, 0);
// GDSCR bit31 PWR_ON; CBCR bit31 CLK_OFF.
bool powered = (gdsc & 0x80000000U) != 0;
// Hardware 2026-10-17: AXI running, AHB enabled but CLK_OFF (bit31). With bit1 (HW gating)
// set the branch wakes on access, so enable bits, not CLK_OFF, gate the read-only probe.
bool busClocks = !(axi & 0x80000000U) && (ahb & 0x1U) && (axi & 0x1U);
uint32_t cap = 0, ver = 0, hcs = 0, hce = 0, utrlba = 0, is = 0;
unsigned readHc = 0;
if (powered && busClocks) {
uintptr_t hc = mapPhys (UFS_HC_BASE, UFS_HC_SIZE, hcMap);
if (hc) {
cap = read32 (hc, 0x00);
ver = read32 (hc, 0x08);
is = read32 (hc, 0x20);
hcs = read32 (hc, 0x30);
hce = read32 (hc, 0x34);
utrlba = read32 (hc, 0x50);
readHc = 1;
}
}
panic ("NATIVE_UFS inspect\n"
"gdsc=%08x memphygdsc=%08x\n"
"axi=%08x ahb=%08x unipro=%08x\n"
"txsym=%08x rxsym=%08x aux=%08x\n"
"hc_read=%u cap=%08x ver=%08x\n"
"is=%08x hcs=%08x hce=%08x utrlba=%08x",
gdsc, memGdsc, axi, ahb, unipro, txs, rxs, aux, readHc, cap, ver, is, hcs, hce, utrlba);
}
// Adopt the controller the firmware left running (native-ufs-probe=3) and issue
// read-only SCSI commands through one UTP transfer request slot. Nothing is written
// to the storage. The controller is not reset and the link is not restarted; the
// firmware's request list is stopped (refused if a request is outstanding) and replaced
// by ours. Interrupts stay masked and completion is polled.
namespace {
struct UfsResult {
uint32_t ocs; // UTRD overall command status; 0xf = never completed
uint32_t response; // UPIU response byte
uint32_t scsi; // SCSI status byte
uint32_t sense; // sense key<<16 | ASC<<8 | ASCQ from the response UPIU's autosense data
uint32_t senseLen; // sense data length reported by the device
uint32_t seg; // UPIU data segment length
};
static void be32 (uint8_t *at, uint32_t value)
{
at[0] = value >> 24; at[1] = value >> 16; at[2] = value >> 8; at[3] = value;
}
static uint32_t getBe32 (const uint8_t *at)
{
return (uint32_t (at[0]) << 24) | (uint32_t (at[1]) << 16) | (uint32_t (at[2]) << 8) | at[3];
}
}
static void cacheClean (const void *start, size_t length)
{
uintptr_t address = reinterpret_cast<uintptr_t> (start) & ~uintptr_t (63);
uintptr_t end = reinterpret_cast<uintptr_t> (start) + length;
for (; address < end; address += 64) {
__asm__ volatile ("dc cvac, %0" :: "r" (address) : "memory");
}
__asm__ volatile ("dsb sy" ::: "memory");
}
static void cacheCleanInvalidate (const void *start, size_t length)
{
uintptr_t address = reinterpret_cast<uintptr_t> (start) & ~uintptr_t (63);
uintptr_t end = reinterpret_cast<uintptr_t> (start) + length;
for (; address < end; address += 64) {
__asm__ volatile ("dc civac, %0" :: "r" (address) : "memory");
}
__asm__ volatile ("dsb sy" ::: "memory");
}
// The region is mapped as device memory: only aligned element-sized stores are allowed
// (bzero's block zeroing faulted on hardware, 2026-10-17).
static void zero32 (void *start, size_t length)
{
volatile uint32_t *word = static_cast<volatile uint32_t *> (start);
for (size_t index = 0; index < length / 4; index++) {
word[index] = 0;
}
}
// Give the UFS stream a pass-through mapping the way Linux does on this firmware.
// Hardware 2026-10-17: after hand-off apps_smmu has sCR0 = 0x002f0406 (USFCFG=1, unmatched
// streams fault), no stream-match entry for the UFS host (ID 0x60, `iommus = <&apps_smmu 0x60
// 0x0>` in the board DT), a TYPE=BYPASS S2CR reads back as FAULT, and sCR0 writes are ignored.
// Linux (drivers/iommu/arm/arm-smmu/arm-smmu-qcom.c, qcom_smmu_cfg_probe + qcom_smmu_write_s2cr,
// "bypass_quirk") meets exactly this: it reserves the LAST context bank (num_context_banks-1),
// writes SCTLR=0 (stage 1 disabled) and CBAR.TYPE=S1_TRANS_S2_BYPASS there, and maps a bypass
// stream as S2CR TYPE=TRANSLATE with CBNDX = that bank. S2CR is written before SMR
// (arm_smmu_write_sme). ARM IHI 0062D.c table 3-1 additionally requires VA64=1
// for inputs above 32 bits even with SCTLR.M=0. Storage DMA path only; this
// is physical pass-through, not a page-table or Adreno 4 KiB qualification.
extern "C" volatile uint32_t native_ufs_extra_stream;
extern "C" uint8_t *native_ufs_extra_va;
extern "C" volatile uint32_t native_ufs_extra_uncached;
extern "C" volatile uint32_t native_ufs_extra_iova, native_ufs_extra_bytes;
extern "C" volatile uint32_t native_ufs_extra_atos[8];
extern "C" volatile uint32_t native_ufs_dpu_smmu[24];
extern "C" volatile uint32_t native_ufs_dpu_state;
extern "C" volatile uint32_t native_ufs_dpu_check[8];
// The display bank's stage 1 table: L1, then L2 for the first GiB, then kDpuSlots * kDpuSlotTables L3 tables (one per 2 MiB of a slot).
constexpr uint32_t kDpuWindow = 0x10000000U, kDpuSlots = 4, kDpuSlotBytes = 32U << 20, kDpuSlotTables = kDpuSlotBytes >> 21;
static uint64_t *gDpuTables;
static uintptr_t gDpuCb;
static IOLock *gDpuLock;
static uintptr_t gDpuLiveCb;
// FSR fault bits (TF .. UUT, SS, MULTI); 0x600 is the format field.
constexpr uint32_t kDpuFaults = 0xc00001feU;
static void dpuClean (const void *start, size_t length)
{
for (size_t off = 0; off < length; off += 64)
__asm__ volatile ("dc civac, %0" :: "r" (static_cast<const uint8_t *> (start) + off) : "memory");
__asm__ volatile ("dsb sy" ::: "memory");
}
static bool dpuTlbFlush (uintptr_t cb)
{
write32 (cb, 0x618, 0); write32 (cb, 0x7f0, 0); // TLBIALL, TLBSYNC
__asm__ volatile ("dsb sy" ::: "memory");
for (unsigned spin = 0; spin < 100000; ++spin) if (!(read32 (cb, 0x7f4) & 1)) return true;
return false;
}
static void dpuEnable (uintptr_t smmu, uintptr_t cb, uint32_t bank, uint32_t oldSctlr)
{
(void) smmu;
const size_t bytes = (2 + kDpuSlots * kDpuSlotTables) * 4096;
IOBufferMemoryDescriptor *tables = IOBufferMemoryDescriptor::inTaskWithOptions (
kernel_task, static_cast<IOOptionBits> (kIODirectionInOut) | static_cast<IOOptionBits> (kIOMemoryPhysicallyContiguous) |
static_cast<IOOptionBits> (kIOMemoryMapperNone), bytes, 4096);
gDpuLock = IOLockAlloc ();
if (!tables || !gDpuLock) {native_ufs_dpu_state = 0xdead0002U; return;}
uint64_t *l1 = static_cast<uint64_t *> (tables->getBytesNoCopy ()), *l2 = l1 + 512;
const uint64_t pa = tables->getPhysicalSegment (0, nullptr, kIOMemoryMapperNone);
bzero (l1, bytes);
// Normal non-cacheable (attribute 0 = 0x44), not shareable, access flag, readable and writable at both privilege levels.
const uint64_t attrs = (1ULL << 10) | (1ULL << 6);
// i564: the first fetch faulted at once with FSYNR0 level 0, so the walk may start at level 0 although T0SZ 32 means level 1. One table
// serves every level: [0] points back at itself (level 0 -> level 1, level 1 -> level 2 for the window), [2] and [3] are the 1 GiB
// blocks, [128 + t] the window's level 3 tables. The second page stays unused.
(void) l2;
l1[0] = pa | 3;
l1[2] = 0x80000000ULL | attrs | 1;
l1[3] = 0xc0000000ULL | attrs | 1;
for (uint32_t t = 0; t < kDpuSlots * kDpuSlotTables; ++t) l1[(kDpuWindow >> 21) + t] = (pa + (2 + t) * 4096ULL) | 3;
dpuClean (l1, bytes);
const uint32_t old[5] = {read32 (cb, 0x20), read32 (cb, 0x24), read32 (cb, 0x10), read32 (cb, 0x30), read32 (cb, 0x38)};
gDpuLiveCb = cb;
write32 (cb, 0x20, static_cast<uint32_t> (pa)); write32 (cb, 0x24, static_cast<uint32_t> (pa >> 32));
write32 (cb, 0x10, 5); // TCR2: 48-bit output
write32 (cb, 0x30, 32U | (1U << 23)); // T0SZ 32, non-cacheable walks, 4 KiB granule, no TTBR1
write32 (cb, 0x38, 0x44); // MAIR attribute 0: normal non-cacheable
__asm__ volatile ("dsb sy" ::: "memory");
volatile uint32_t *c = native_ufs_dpu_check;
c[0] = read32 (cb, 0x20); c[1] = read32 (cb, 0x30); c[2] = read32 (cb, 0x58);
const auto undo = [&] (uint32_t why) {
write32 (cb, 0x0, oldSctlr);
__asm__ volatile ("dsb sy; isb" ::: "memory");
write32 (cb, 0x20, old[0]); write32 (cb, 0x24, old[1]); write32 (cb, 0x10, old[2]); write32 (cb, 0x30, old[3]); write32 (cb, 0x38, old[4]);
const uint32_t fsr = read32 (cb, 0x58);
if (fsr & (1U << 30)) write32 (cb, 0x8, 1); // RESUME: terminate a stalled transaction
if (fsr & kDpuFaults) write32 (cb, 0x58, fsr); // write one to clear
dpuTlbFlush (cb);
native_ufs_dpu_state = why;
};
if (c[0] != static_cast<uint32_t> (pa) || c[1] != (32U | (1U << 23))) {undo (0xdead0003U); return;} // the bank ignores our writes
if (!dpuTlbFlush (cb)) {undo (0xdead0004U); return;}
write32 (cb, 0x0, 1U | (1U << 5) | (1U << 6)); // SCTLR: M, CFRE, CFIE; a fault terminates instead of stalling the display
__asm__ volatile ("dsb sy; isb" ::: "memory");
// No check translation here (i563: IDR0 bit 26 ATOSNS, the SMMU has no ATOS), so watch the display's own fetches for 50 ms (6 frames
// at 120 Hz) and turn translation off again at the first fault.
uint32_t fsr = 0, us = 0;
for (; us < 50000; us += 500) {
fsr = read32 (cb, 0x58);
if (fsr & kDpuFaults) break;
IODelay (500);
}
c[3] = fsr; c[7] = us;
if (fsr & kDpuFaults) {
c[4] = read32 (cb, 0x60); c[5] = read32 (cb, 0x64); c[6] = read32 (cb, 0x68);
undo (0xdead0005U); return;
}
gDpuTables = l1; gDpuCb = cb;
native_ufs_dpu_state = 0x80000000U | bank;
}
static uintptr_t gExtraSmmu, gExtraCb;
static unsigned gExtraBank;
static void smmuBypassStream (uint16_t stream, OSSharedPtr<IOMemoryMap> &keep, uint32_t *report,
uint64_t dma, uint64_t bytes)
{
uintptr_t smmu = mapPhys (0x15000000ULL, 0xd0000, keep);
if (!smmu) {
panic ("NATIVE_UFS smmu: map failed");
}
UfsSmmuPlan plan = planUfsSmmu (stream, dma, bytes, [smmu](unsigned offset) { return read32 (smmu, offset); });
gRootDiagnostic.smmuId1 = read32 (smmu, 0x24);
gRootDiagnostic.smmuId2 = read32 (smmu, 0x28);
gRootDiagnostic.smmuSlot = plan.slot;
if (plan.error) {
gRootDiagnostic.stage = "SMMU planning";
gRootDiagnostic.result = static_cast<uint32_t> (kIOReturnNotReady);
gRootDiagnostic.smr = plan.smr; gRootDiagnostic.s2cr = plan.s2cr;
rootDiagnosticFailure (plan.error);
panic ("NATIVE_UFS smmu: %s slot=%u smr=%x s2cr=%x", plan.error, plan.slot, plan.smr, plan.s2cr);
}
unsigned bank = plan.bank, index = plan.slot;
uintptr_t cb = smmu + (plan.pages + bank) * 4096;
const char *failure = configureUfsSmmu (plan, stream,
[smmu](unsigned offset) { return read32 (smmu, offset); },
[smmu](unsigned offset, uint32_t value) {
write32 (smmu, offset, value);
__asm__ volatile ("dsb sy" ::: "memory");
});
uint32_t smr = read32 (smmu, 0x800 + 4 * index), s2cr = read32 (smmu, 0xc00 + 4 * index);
uint32_t cbar = read32 (smmu, 0x1000 + 4 * bank), sctlr = read32 (cb, 0x0);
gRootDiagnostic.smr = smr; gRootDiagnostic.s2cr = s2cr;
gRootDiagnostic.cbar = cbar; gRootDiagnostic.sctlr = sctlr;
gRootDiagnostic.smmuId2 = read32 (smmu, 0x28);
gRootDiagnostic.cba2r = read32 (smmu, 0x1800 + 4 * bank);
if (failure) {
gRootDiagnostic.stage = "SMMU readback";
gRootDiagnostic.result = static_cast<uint32_t> (kIOReturnNotReady);
rootDiagnosticFailure (failure);
panic ("NATIVE_UFS smmu: %s idx=%u bank=%u cba2r=%x", failure, index, bank,
gRootDiagnostic.cba2r);
}
report[0] = index | (bank << 16);
report[1] = cbar;
report[2] = sctlr;
}
void NativeUfs::adoptAndRead (unsigned level)
{
OSSharedPtr<IOMemoryMap> hcMap;
uintptr_t hc = mapPhys (UFS_HC_BASE, UFS_HC_SIZE, hcMap);
if (!hc) {
panic ("NATIVE_UFS adopt: controller map failed");
}
// The controller's own request list is the one region its DMA is known to reach
// (hardware 2026-10-17: a kernel-allocated buffer at 0xf080f000 reset the system when the
// first command was issued, and nothing below 0xc8000000 is allocatable). The firmware
// zeroed it; use 16 KiB from UTRLBA as ordinary cached memory (the DT marks the host
// dma-coherent). Test instrument only: the kernel may also own these pages.
uint64_t fwList = (uint64_t (read32 (hc, 0x54)) << 32) | read32 (hc, 0x50);
if (!fwList || (fwList & 0xfff)) {
panic ("NATIVE_UFS adopt: no usable firmware list base=%llx", fwList);
}
IOMemoryDescriptor *region = IOMemoryDescriptor::withPhysicalAddress (fwList, 16384, kIODirectionInOut);
OSSharedPtr<IOMemoryMap> regionMap;
if (region) {
regionMap = OSSharedPtr<IOMemoryMap> (region->map (kIOMapAnywhere), OSNoRetain);
region->release ();
}
if (!regionMap) {
panic ("NATIVE_UFS adopt: firmware list map failed base=%llx", fwList);
}
uint8_t *virt = reinterpret_cast<uint8_t *> (regionMap->getVirtualAddress ());
uint64_t phys = fwList;
zero32 (virt, 16384);
cacheClean (virt, 16384);
// Layout: [0,4096) request list (slot 0 used), [4096,5120) command + response UPIU +
// PRDT, [8192,16384) data.
const uint32_t ucdOffset = 4096, rspOffset = 512, prdtOffset = 1024, dataOffset = 8192;
uint32_t hcsBefore = read32 (hc, 0x30);
uint32_t doorbellBefore = read32 (hc, 0x58);
if (doorbellBefore != 0 || read32 (hc, 0x78) != 0) {
panic ("NATIVE_UFS adopt: firmware request outstanding doorbell=%08x hcs=%08x", doorbellBefore, hcsBefore);
}
OSSharedPtr<IOMemoryMap> smmuMap;
uint32_t smmuReport[3] = { 0, 0, 0 };
write32 (hc, 0x24, 0); // mask interrupts
write32 (hc, 0x60, 0); // stop the firmware's request list
__asm__ volatile ("dsb sy" ::: "memory");
unsigned stopLoops = 10000;
while ((read32 (hc, 0x60) & 1) && stopLoops--) IODelay (10);
if (read32 (hc, 0x60) & 1) panic ("NATIVE_UFS adopt: request list did not stop");
smmuBypassStream (0x60, smmuMap, smmuReport, phys, 16384);
write32 (hc, 0x50, static_cast<uint32_t> (phys));
write32 (hc, 0x54, static_cast<uint32_t> (phys >> 32));
write32 (hc, 0x20, 0xffffffffU); // clear stale status
write32 (hc, 0x60, 1);
if (!waitSet (hc, 0x30, 0x2, 100000)) {
panic ("NATIVE_UFS adopt: request list not ready hcs=%08x", read32 (hc, 0x30));
}
if (level == 3) {
panic ("NATIVE_UFS adopt: list replaced\n"
"phys=%llx hcs=%08x->%08x\n"
"is=%08x db=%08x rsr=%08x ba=%08x bau=%08x ie=%08x",
phys, hcsBefore, read32 (hc, 0x30), read32 (hc, 0x20), read32 (hc, 0x58),
read32 (hc, 0x60), read32 (hc, 0x50), read32 (hc, 0x54), read32 (hc, 0x24));
}
auto command = [&] (uint8_t lun, const uint8_t *cdb, unsigned cdbLength, bool toHost, uint32_t length) -> UfsResult {
uint8_t *utrd = virt;
uint8_t *ucd = virt + ucdOffset;
zero32 (utrd, 32);
zero32 (ucd, 1024 + 16);
zero32 (virt + dataOffset, 8192);
ucd[0] = 0x01; // COMMAND UPIU
ucd[1] = toHost ? 0x40 : 0x00; // flags: read
ucd[2] = lun;
ucd[3] = 0; // task tag
be32 (ucd + 12, length); // expected data transfer length
for (unsigned index = 0; index < cdbLength; index++) {
ucd[16 + index] = cdb[index];
}
uint64_t ucdPhys = phys + ucdOffset;
uint32_t direction = length ? (toHost ? 2U : 1U) : 0U;
*reinterpret_cast<uint32_t *> (utrd + 0) = 0x10000000U | (direction << 25);
*reinterpret_cast<uint32_t *> (utrd + 8) = 0x0fU; // OCS: not completed
*reinterpret_cast<uint32_t *> (utrd + 16) = static_cast<uint32_t> (ucdPhys);
*reinterpret_cast<uint32_t *> (utrd + 20) = static_cast<uint32_t> (ucdPhys >> 32);
*reinterpret_cast<uint32_t *> (utrd + 24) = (512U >> 2) | ((rspOffset >> 2) << 16); // response length | offset (dwords)
uint32_t prdEntries = length ? 1U : 0U;
*reinterpret_cast<uint32_t *> (utrd + 28) = prdEntries | ((prdtOffset >> 2) << 16);
if (length) {
uint64_t dataPhys = phys + dataOffset;
uint8_t *prd = ucd + prdtOffset;
*reinterpret_cast<uint32_t *> (prd + 0) = static_cast<uint32_t> (dataPhys);
*reinterpret_cast<uint32_t *> (prd + 4) = static_cast<uint32_t> (dataPhys >> 32);
*reinterpret_cast<uint32_t *> (prd + 12) = length - 1;
}
cacheClean (virt, 16384);
write32 (hc, 0x20, 0xffffffffU);
write32 (hc, 0x58, 1); // ring slot 0
UfsResult result = { 0xf, 0xff, 0xff, 0, 0, 0 };
bool done = false;
for (unsigned loops = 0; loops < 100000; loops++) {
if (!(read32 (hc, 0x58) & 1)) {
done = true;
break;
}
IODelay (1);
}
cacheCleanInvalidate (virt, 16384);
if (!done) {
return result;
}
result.ocs = *reinterpret_cast<volatile uint32_t *> (utrd + 8) & 0xff;
const uint8_t *rsp = ucd + rspOffset;
result.response = rsp[6];
result.scsi = rsp[7];
// Response UPIU: 12-byte header, residual count, 16 reserved bytes, then the data segment
// (2-byte sense length + sense data) at offset 32.
result.seg = (uint32_t (rsp[10]) << 8) | rsp[11];
result.senseLen = (uint32_t (rsp[32]) << 8) | rsp[33];
result.sense = ((rsp[36] & 0xfU) << 16) | (uint32_t (rsp[46]) << 8) | rsp[47];
return result;
};
// NOP OUT (kind 0) and Query read requests (kind 1: opcode 0x05 READ FLAG, 0x03 READ ATTRIBUTE).
// Read-only: no data segment, no state change on the device. Returns the UPIU response byte and
// the 32-bit value field (attributes) or flag byte via *value; ocs 0xf means no completion.
auto query = [&] (unsigned kind, uint8_t opcode, uint8_t idn, uint32_t *value, uint32_t *respByte) -> uint32_t {
uint8_t *utrd = virt;
uint8_t *ucd = virt + ucdOffset;
zero32 (utrd, 32);
zero32 (ucd, 1024 + 16);
if (kind == 0) {
ucd[0] = 0x00; // NOP OUT
} else {
ucd[0] = 0x16; // QUERY REQUEST
ucd[5] = 0x01; // query function (header dword 1, byte 1): standard read request
ucd[12] = opcode;
ucd[13] = idn;
}
ucd[3] = 1; // task tag
uint64_t ucdPhys = phys + ucdOffset;
*reinterpret_cast<uint32_t *> (utrd + 0) = 0x10000000U;
*reinterpret_cast<uint32_t *> (utrd + 8) = 0x0fU;
*reinterpret_cast<uint32_t *> (utrd + 16) = static_cast<uint32_t> (ucdPhys);
*reinterpret_cast<uint32_t *> (utrd + 20) = static_cast<uint32_t> (ucdPhys >> 32);
*reinterpret_cast<uint32_t *> (utrd + 24) = (512U >> 2) | ((rspOffset >> 2) << 16);
*reinterpret_cast<uint32_t *> (utrd + 28) = (prdtOffset >> 2) << 16;
cacheClean (virt, 16384);
write32 (hc, 0x20, 0xffffffffU);
write32 (hc, 0x58, 1);
bool done = false;
for (unsigned loops = 0; loops < 100000; loops++) {
if (!(read32 (hc, 0x58) & 1)) {
done = true;
break;
}
IODelay (1);
}
cacheCleanInvalidate (virt, 16384);
if (!done) {
return 0xf;
}
const uint8_t *rsp = ucd + rspOffset;
*respByte = (uint32_t (rsp[0]) << 8) | rsp[6]; // response type << 8 | query/NOP response
*value = getBe32 (rsp + 20);
return *reinterpret_cast<volatile uint32_t *> (utrd + 8) & 0xff;
};
// Device state before waking it. Hardware 2026-10-17: NOP OUT answers, fDeviceInit = 0 (done),
// bCurrentPowerMode = 0x33 (UFS-PowerDown) and every LU returns NOT READY. Linux resumes the
// device with START STOP UNIT (power condition ACTIVE) to the UFS Device well-known LU 0xD0
// (ufshcd_set_dev_pwr_mode); no user data is touched.
uint32_t powerBefore = 0, powerAfter = 0, queryResp = 0, ignoredValue = 0;
uint32_t wakeStatus = 0xee;
if (level == 5 || level == 9) {
query (1, 0x03, 0x02, &powerBefore, &queryResp); // READ ATTRIBUTE bCurrentPowerMode
uint8_t ssu[6] = { 0x1b, 0, 0, 0, 0x10, 0 }; // START STOP UNIT, power condition ACTIVE
UfsResult w = command (0xd0, ssu, 6, false, 0);
wakeStatus = (w.ocs << 16) | (w.response << 8) | w.scsi;
for (unsigned settle = 0; settle < 50; settle++) {
IODelay (1000);
}
query (1, 0x03, 0x02, &powerAfter, &queryResp);
(void) ignoredValue;
}
if (level == 4) {
uint8_t tur0[6] = { 0, 0, 0, 0, 0, 0 };
UfsResult t = command (0, tur0, 6, false, 0);
const uint8_t *rsp0 = virt + ucdOffset + rspOffset;
panic ("NATIVE_UFS adopt: TEST UNIT READY lun0\n"
"ocs=%x resp=%02x scsi=%02x\n"
"phys=%llx hcs=%08x->%08x smmu idx/bank=%08x cbar=%08x\n"
"is=%08x db=%08x rsr=%08x\n"
"rsp=%08x %08x",
t.ocs, t.response, t.scsi, phys, hcsBefore, read32 (hc, 0x30), smmuReport[0], smmuReport[1],
read32 (hc, 0x20), read32 (hc, 0x58), read32 (hc, 0x60), getBe32 (rsp0), getBe32 (rsp0 + 4));
}
uint32_t hcsAfter = 0;
const unsigned maxLun = 6;
uint32_t status[maxLun], last[maxLun], blockSize[maxLun], senseOf[maxLun];
for (unsigned lun = 0; lun < maxLun; lun++) {
status[lun] = 0xee; last[lun] = 0; blockSize[lun] = 0; senseOf[lun] = 0;
uint8_t tur[6] = { 0x00, 0, 0, 0, 0, 0 };
UfsResult r = { 0xf, 0xff, 0xff, 0, 0, 0 };
for (unsigned attempt = 0; attempt < 3; attempt++) {
r = command (static_cast<uint8_t> (lun), tur, 6, false, 0);
if (lun == 0 && attempt == 0 && r.ocs == 0xf) {
// Never completed: stop here and report instead of waiting out every command.
const uint8_t *rsp = virt + ucdOffset + rspOffset;
panic ("NATIVE_UFS adopt: first command did not complete\n"
"phys=%llx hcs=%08x->%08x\n"
"is=%08x db=%08x rsr=%08x ba=%08x\n"
"utrd2=%08x rsp=%08x %08x",
phys, hcsBefore, read32 (hc, 0x30),
read32 (hc, 0x20), read32 (hc, 0x58), read32 (hc, 0x60), read32 (hc, 0x50),
*reinterpret_cast<volatile uint32_t *> (virt + 8), getBe32 (rsp), getBe32 (rsp + 4));
}
if (r.ocs == 0 && r.scsi == 0) {
break;
}
}
uint8_t cap[10] = { 0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
r = command (static_cast<uint8_t> (lun), cap, 10, true, 8);
status[lun] = (r.ocs << 16) | (r.response << 8) | r.scsi;
senseOf[lun] = r.sense;
if (r.ocs == 0 && r.scsi == 0) {
last[lun] = getBe32 (virt + dataOffset);
blockSize[lun] = getBe32 (virt + dataOffset + 4);
}
}
if (level == 9) {
// Read-only GPT survey of LUN 0 (4096-byte blocks): header fields and CRCs for before/after
// comparison, then every non-empty entry among the first 64. Nothing is written.
if (blockSize[0] != 4096) {
panic ("NATIVE_UFS gpt: unexpected LUN0 block size %08x", blockSize[0]);
}
uint8_t rd[10] = { 0x28, 0, 0, 0, 0, 1, 0, 0, 1, 0 };
UfsResult h = command (0, rd, 10, true, 4096);
if (h.ocs != 0 || h.scsi != 0) {
panic ("NATIVE_UFS gpt: header read failed ocs=%x resp=%x scsi=%x", h.ocs, h.response, h.scsi);
}
const uint8_t *hdr = virt + dataOffset;
auto le32 = [] (const uint8_t *at) -> uint32_t {
return uint32_t (at[0]) | (uint32_t (at[1]) << 8) | (uint32_t (at[2]) << 16) | (uint32_t (at[3]) << 24);
};
auto le64 = [&] (const uint8_t *at) -> uint64_t {
return uint64_t (le32 (at)) | (uint64_t (le32 (at + 4)) << 32);
};
uint32_t sig0 = le32 (hdr), sig1 = le32 (hdr + 4), hdrCrc = le32 (hdr + 16), entCrc = le32 (hdr + 88);
uint64_t curLba = le64 (hdr + 24), bakLba = le64 (hdr + 32), firstUsable = le64 (hdr + 40), lastUsable = le64 (hdr + 48);
uint64_t entLba = le64 (hdr + 72);
uint32_t entCount = le32 (hdr + 80), entSize = le32 (hdr + 84);
uint32_t guid0 = le32 (hdr + 56), guid1 = le32 (hdr + 60), guid2 = le32 (hdr + 64), guid3 = le32 (hdr + 68);
if (sig0 != 0x20494645U || sig1 != 0x54524150U || entSize != 128 || entLba > 64) {
panic ("NATIVE_UFS gpt: unexpected header sig=%08x %08x esz=%u elba=%llx", sig0, sig1, entSize, entLba);
}
uint32_t shown = entCount > 64 ? 64 : entCount;
uint32_t blocks = (shown * 128 + 4095) / 4096;
uint8_t rd2[10] = { 0x28, 0, 0, 0, 0, uint8_t (entLba), 0, 0, uint8_t (blocks), 0 };
UfsResult e = command (0, rd2, 10, true, blocks * 4096);
if (e.ocs != 0 || e.scsi != 0) {
panic ("NATIVE_UFS gpt: entries read failed ocs=%x scsi=%x", e.ocs, e.scsi);
}
const uint8_t *ents = virt + dataOffset;
static char text[3400];
int used = snprintf (text, sizeof (text),
"NATIVE_UFS gpt LUN0 hdr crc=%08x ents crc=%08x cnt=%u\n"
"cur=%llx backup=%llx usable=%llx..%llx elba=%llx\n"
"disk guid %08x %08x %08x %08x last=%08x\n",
hdrCrc, entCrc, entCount, curLba, bakLba, firstUsable, lastUsable, entLba,
guid0, guid1, guid2, guid3, last[0]);
unsigned column = 0, listed = 0;
for (uint32_t index = 0; index < shown && used < int (sizeof (text)) - 80; index++) {
const uint8_t *entry = ents + index * 128;
bool empty = true;
for (unsigned b = 0; b < 16; b++) {
if (entry[b]) { empty = false; break; }
}
if (empty) {
continue;
}
char name[14];
for (unsigned c = 0; c < 13; c++) {
uint8_t lo = entry[56 + 2 * c], hi = entry[57 + 2 * c];
name[c] = (hi == 0 && lo >= 32 && lo < 127) ? char (lo) : (lo ? '?' : 0);
if (!name[c]) { name[c] = 0; break; }
}
name[13] = 0;
used += snprintf (text + used, sizeof (text) - used, "%u:%s %llx-%llx%s", index, name,
le64 (entry + 32), le64 (entry + 40), (++column % 3) ? " " : "\n");
listed++;
}
snprintf (text + used, sizeof (text) - used, "\nlisted=%u", listed);
panic ("%s", text);
}
// INQUIRY of LUN 0: vendor/product bytes 8..23.
uint8_t inquiry[6] = { 0x12, 0, 0, 0, 36, 0 };
UfsResult inq = command (0, inquiry, 6, true, 36);
uint32_t inqWord[4] = { 0, 0, 0, 0 };
for (unsigned index = 0; index < 4; index++) {
inqWord[index] = getBe32 (virt + dataOffset + 8 + index * 4);
}
// LBA 1 of LUN 0 holds the GPT header when the block size is 4096 (or 512).
uint32_t gptStatus = 0xee, sig0 = 0, sig1 = 0;
if (blockSize[0] >= 512 && blockSize[0] <= 4096) {
uint8_t read10[10] = { 0x28, 0, 0, 0, 0, 1, 0, 0, 1, 0 };
UfsResult g = command (0, read10, 10, true, blockSize[0]);
gptStatus = (g.ocs << 16) | (g.response << 8) | g.scsi;
if (g.ocs == 0 && g.scsi == 0) {
sig0 = *reinterpret_cast<uint32_t *> (virt + dataOffset);
sig1 = *reinterpret_cast<uint32_t *> (virt + dataOffset + 4);
}
}
hcsAfter = read32 (hc, 0x30);
panic ("NATIVE_UFS adopt+read\n"
"hcs %08x->%08x pwr %08x->%08x ssu=%06x\n"
"L0 st=%06x last=%08x bs=%08x sns=%06x\n"
"L1 st=%06x last=%08x bs=%08x sns=%06x\n"
"L2 st=%06x last=%08x bs=%08x sns=%06x\n"
"L3 st=%06x last=%08x bs=%08x sns=%06x\n"
"L4 st=%06x last=%08x bs=%08x sns=%06x\n"
"L5 st=%06x last=%08x bs=%08x sns=%06x\n"
"inq st=%06x %08x %08x %08x %08x\n"
"inq sns=%06x slen=%x seg=%x\n"
"rsp0 %08x %08x %08x %08x\n"
"rsp8 %08x %08x %08x %08x\n"
"gpt st=%06x sig=%08x %08x",
hcsBefore, hcsAfter, powerBefore, powerAfter, wakeStatus,
status[0], last[0], blockSize[0], senseOf[0], status[1], last[1], blockSize[1], senseOf[1],
status[2], last[2], blockSize[2], senseOf[2], status[3], last[3], blockSize[3], senseOf[3],
status[4], last[4], blockSize[4], senseOf[4], status[5], last[5], blockSize[5], senseOf[5],
(inq.ocs << 16) | (inq.response << 8) | inq.scsi, inqWord[0], inqWord[1], inqWord[2], inqWord[3],
inq.sense, inq.senseLen, inq.seg,
getBe32 (virt + ucdOffset + rspOffset), getBe32 (virt + ucdOffset + rspOffset + 4),
getBe32 (virt + ucdOffset + rspOffset + 8), getBe32 (virt + ucdOffset + rspOffset + 12),
getBe32 (virt + ucdOffset + rspOffset + 32), getBe32 (virt + ucdOffset + rspOffset + 36),
getBe32 (virt + ucdOffset + rspOffset + 40), getBe32 (virt + ucdOffset + rspOffset + 44),
gptStatus, sig0, sig1);
}
// Read-only dump of the request list the firmware left (native-ufs-probe=6): shows where
// the firmware placed its descriptors, to compare with an allocation the controller's DMA
// is known to reach. Nothing is written and no register is touched except reads.
static uint32_t dumpWord (const volatile uint32_t *base, unsigned index)
{
return base[index];
}
void NativeUfs::dumpFirmwareList (void)
{
OSSharedPtr<IOMemoryMap> hcMap, listMap, ucdMap;
uintptr_t hc = mapPhys (UFS_HC_BASE, UFS_HC_SIZE, hcMap);
if (!hc) {
panic ("NATIVE_UFS dump: controller map failed");
}
uint64_t listPhys = (uint64_t (read32 (hc, 0x54)) << 32) | read32 (hc, 0x50);
uint32_t db = read32 (hc, 0x58), rsr = read32 (hc, 0x60), hcs = read32 (hc, 0x30);
IOMemoryDescriptor *listDesc = IOMemoryDescriptor::withPhysicalAddress (listPhys, 4096, kIODirectionIn);
if (!listDesc) {
panic ("NATIVE_UFS dump: list descriptor failed phys=%llx", listPhys);
}
listMap = OSSharedPtr<IOMemoryMap> (listDesc->map (kIOMapAnywhere), OSNoRetain);
listDesc->release ();
if (!listMap) {
panic ("NATIVE_UFS dump: list map failed phys=%llx", listPhys);
}
const volatile uint32_t *utrd = reinterpret_cast<const volatile uint32_t *> (listMap->getVirtualAddress ());
uint64_t ucdPhys = (uint64_t (dumpWord (utrd, 5)) << 32) | dumpWord (utrd, 4);
uint32_t ucd0 = 0, ucd1 = 0, ucd2 = 0, ucd3 = 0;
if (ucdPhys) {
IOMemoryDescriptor *ucdDesc = IOMemoryDescriptor::withPhysicalAddress (ucdPhys, 4096, kIODirectionIn);
if (ucdDesc) {
ucdMap = OSSharedPtr<IOMemoryMap> (ucdDesc->map (kIOMapAnywhere), OSNoRetain);
ucdDesc->release ();
if (ucdMap) {
const volatile uint32_t *ucd = reinterpret_cast<const volatile uint32_t *> (ucdMap->getVirtualAddress ());
ucd0 = dumpWord (ucd, 0); ucd1 = dumpWord (ucd, 1); ucd2 = dumpWord (ucd, 2); ucd3 = dumpWord (ucd, 3);
}
}
}
panic ("NATIVE_UFS dump: firmware list\n"
"list=%llx hcs=%08x db=%08x rsr=%08x\n"
"utrd0 %08x %08x %08x %08x\n"
"utrd0 %08x %08x %08x %08x\n"
"utrd1 %08x %08x %08x %08x\n"
"ucd=%llx %08x %08x %08x %08x",
listPhys, hcs, db, rsr,
dumpWord (utrd, 0), dumpWord (utrd, 1), dumpWord (utrd, 2), dumpWord (utrd, 3),
dumpWord (utrd, 4), dumpWord (utrd, 5), dumpWord (utrd, 6), dumpWord (utrd, 7),
dumpWord (utrd, 8), dumpWord (utrd, 9), dumpWord (utrd, 10), dumpWord (utrd, 11),
ucdPhys, ucd0, ucd1, ucd2, ucd3);
}
// Read-only dump of the apps SMMU (apps_smmu@15000000) global register space and the stream
// match entry for the UFS stream ID 0x60 (native-ufs-probe=7). SMMUv2 layout: sCR0 0x0,
// IDR0 0x20, sGFSR 0x48, SMR(n) 0x800+4n, S2CR(n) 0xc00+4n. Nothing is written.
void NativeUfs::inspectSmmu (void)
{
OSSharedPtr<IOMemoryMap> map;
uintptr_t smmu = mapPhys (0x15000000ULL, 0x1000, map);
if (!smmu) {
panic ("NATIVE_UFS smmu: map failed");
}
uint32_t scr0 = read32 (smmu, 0x00), idr0 = read32 (smmu, 0x20), idr1 = read32 (smmu, 0x24);
uint32_t gfsr = read32 (smmu, 0x48);
unsigned count = idr0 & 0xff;
if (count > 128) {
count = 128;
}
unsigned match = 0xffff, valid = 0, typeCount[4] = { 0, 0, 0, 0 };
uint32_t matchSmr = 0, matchS2cr = 0;
for (unsigned index = 0; index < count; index++) {
uint32_t smr = read32 (smmu, 0x800 + 4 * index);
uint32_t s2cr = read32 (smmu, 0xc00 + 4 * index);
if (smr & 0x80000000U) {
valid++;
typeCount[(s2cr >> 16) & 3]++;
uint32_t mask = (smr >> 16) & 0x7fff;
if (match == 0xffff && (((smr ^ 0x60U) & 0x7fffU) & ~mask) == 0) {
match = index;
matchSmr = smr;
matchS2cr = s2cr;
}
}
}
panic ("NATIVE_UFS smmu\n"
"scr0=%08x idr0=%08x idr1=%08x gfsr=%08x\n"
"nsmr=%u valid=%u types t0=%u t1=%u t2=%u\n"
"sid0x60 match=%x smr=%08x s2cr=%08x",
scr0, idr0, idr1, gfsr, count, valid, typeCount[0], typeCount[1], typeCount[2],
match, matchSmr, matchS2cr);
}
// Read-only context-bank survey (native-ufs-probe=8): CBAR/CBA2R in gr1 and SCTLR/TTBR0 in the
// context banks, to find a bank the hypervisor leaves to us. Nothing is written.
void NativeUfs::inspectContextBanks (void)
{
OSSharedPtr<IOMemoryMap> map;
uintptr_t smmu = mapPhys (0x15000000ULL, 0xd0000, map);
if (!smmu) {
panic ("NATIVE_UFS cb: map failed");
}
uint32_t idr1 = read32 (smmu, 0x24);
unsigned numCb = idr1 & 0xff;
unsigned globalPages = 1U << (((idr1 >> 28) & 7) + 1);
if (globalPages > 128 || numCb > 128 || (globalPages + numCb) * 0x1000 > 0xd0000) {
panic ("NATIVE_UFS cb: unexpected geometry idr1=%08x", idr1);
}
unsigned cbarZero = 0, sctlrZero = 0, mSet = 0, bothZero = 0;
uint32_t firstZero = 0xffff;
for (unsigned index = 0; index < numCb; index++) {
uint32_t cbar = read32 (smmu, 0x1000 + 4 * index);
uint32_t sctlr = read32 (smmu + (globalPages + index) * 0x1000, 0x0);
cbarZero += cbar == 0;
sctlrZero += sctlr == 0;
mSet += (sctlr & 1) != 0;
if (cbar == 0 && sctlr == 0) {
bothZero++;
if (firstZero == 0xffff) {
firstZero = index;
}
}
}
uintptr_t cb0 = smmu + globalPages * 0x1000, cb1 = cb0 + 0x1000, cbLast = cb0 + (numCb - 1) * 0x1000;
panic ("NATIVE_UFS cb survey\n"
"ncb=%u pages=%u cbar0=%u sctlr0=%u msset=%u both0=%u first0=%x\n"
"cbar[0]=%08x cbar[1]=%08x cbar[%u]=%08x\n"
"sctlr[0]=%08x sctlr[1]=%08x sctlr[last]=%08x\n"
"cba2r[0]=%08x cba2r[1]=%08x",
numCb, globalPages, cbarZero, sctlrZero, mSet, bothZero, firstZero,
read32 (smmu, 0x1000), read32 (smmu, 0x1004), numCb - 1, read32 (smmu, 0x1000 + 4 * (numCb - 1)),
read32 (cb0, 0), read32 (cb1, 0), read32 (cbLast, 0),
read32 (smmu, 0x1800), read32 (smmu, 0x1804));
}
// ======================================================================================
// Root media: the live controller as a read-only block device.
//
// One shared host (a single UTP request slot, polled; interrupts stay masked) serves every
// block device. The DMA region is ordinary cached, physically contiguous kernel memory with
// explicit cache maintenance around each command (the stream is mapped through the reserved
// apps_smmu bypass context bank by smmuBypassStream, so physical addresses are used as-is).
// Nothing here writes to the storage: only INQUIRY-class and READ(10) commands are built, plus
// the single START STOP UNIT that wakes the device from the power-down the firmware left.
// ======================================================================================
namespace {
#include "RootTransportLayout.h"
struct UfsHost {
OSSharedPtr<IOMemoryMap> hcMap, smmuMap;
uintptr_t hc;
IOBufferMemoryDescriptor *dma;
uint8_t *virt;
uint64_t phys;
IOLock *lock;
bool ready;
bool poisoned = false;
uintptr_t smmu = 0, cb = 0;
};
UfsHost gHost = { OSSharedPtr<IOMemoryMap> (), OSSharedPtr<IOMemoryMap> (), 0, nullptr, nullptr, 0, nullptr, false };
static IOReturn recordTransport (IOReturn result, uint32_t ocs)
{
gRootDiagnostic.result = static_cast<uint32_t> (result);
gRootDiagnostic.ocs = ocs;
if (result != kIOReturnSuccess && gRootDiagnosticEnabled) {
// Read only the already mapped HC's status/registers used by this driver.
gRootDiagnostic.hcs = read32 (gHost.hc, 0x30);
gRootDiagnostic.hce = read32 (gHost.hc, 0x34);
gRootDiagnostic.doorbell = read32 (gHost.hc, 0x58);
gRootDiagnostic.interrupt = read32 (gHost.hc, 0x20);
gRootDiagnostic.list = uint64_t (read32 (gHost.hc, 0x50)) |
(uint64_t (read32 (gHost.hc, 0x54)) << 32);
gRootDiagnostic.runStop = read32 (gHost.hc, 0x60);
gRootDiagnostic.ahit = read32 (gHost.hc, 0x18);
for (unsigned i = 0; i < 5; ++i) gRootDiagnostic.uic[i] = read32 (gHost.hc, 0x38 + 4 * i);
if (gHost.smmu && gHost.cb) {
gRootDiagnostic.gfsr = read32 (gHost.smmu, 0x48);
gRootDiagnostic.gsyn = read32 (gHost.smmu, 0x54);
gRootDiagnostic.fsr = read32 (gHost.cb, 0x58);
gRootDiagnostic.fsyn = read32 (gHost.cb, 0x68);
gRootDiagnostic.far = uint64_t (read32 (gHost.cb, 0x60)) |
(uint64_t (read32 (gHost.cb, 0x64)) << 32);
}
}
return result;
}
static void cacheCivac (const void *start, size_t length)
{
uintptr_t address = reinterpret_cast<uintptr_t> (start) & ~uintptr_t (63);
uintptr_t end = reinterpret_cast<uintptr_t> (start) + length;
for (; address < end; address += 64) {
__asm__ volatile ("dc civac, %0" :: "r" (address) : "memory");
}
__asm__ volatile ("dsb sy" ::: "memory");
}
// Device-owned cache lines must not be written back over DMA results. All
// callers use complete 64-byte lines from the dedicated page-aligned buffer.
static void cacheInvalidate (const void *start, size_t length)
{
__asm__ volatile ("dsb sy" ::: "memory");
uintptr_t address = reinterpret_cast<uintptr_t> (start);
for (uintptr_t end = address + length; address < end; address += 64) {
__asm__ volatile ("dc ivac, %0" :: "r" (address) : "memory");
}
__asm__ volatile ("dsb sy" ::: "memory");
}
static void hostWrite (unsigned offset, uint32_t value)
{
__asm__ volatile ("dsb sy" ::: "memory");
write32 (gHost.hc, offset, value);
__asm__ volatile ("dsb sy" ::: "memory");
}
#include "RootQueue.h"
#include "RootCommands.h"
// Bring the live controller up for reading: map, SMMU pass-through for the UFS stream, take over
// the request list, wake the device from UFS-PowerDown and wait for LUN 0 to be ready.
// `why` receives a short reason on failure.
static bool hostBringUp (const char **why)
{
if (gHost.poisoned) {
*why = "transport quarantined";
return false;
}
if (gHost.ready) {
return true;
}
gRootDiagnostic.stage = "controller map";
gHost.hc = mapPhys (UFS_HC_BASE, UFS_HC_SIZE, gHost.hcMap);
if (!gHost.hc) {
*why = "controller map";
return false;
}
if (gRootDiagnosticEnabled) {
gRootDiagnostic.cap = read32 (gHost.hc, 0x00);
gRootDiagnostic.version = read32 (gHost.hc, 0x08);
gRootDiagnostic.hcs = read32 (gHost.hc, 0x30);
gRootDiagnostic.hce = read32 (gHost.hc, 0x34);
}
gRootDiagnostic.stage = "DMA allocation";
gHost.dma = IOBufferMemoryDescriptor::inTaskWithOptions (