AMD GPU KMD 代码分析

地址:https://github.com/ROCm/ROCK-Kernel-Driver

驱动

Total Xmind

流程示意图
flowchart TB
    amdgpu_driver_load_kms --> amdgpu_device_init -->  amdgpu_device_ip_early_init

    subgraph amdgpu_device_ip_early_init
        direction TB
        amdgpu_discovery_set_ip_blocks --> amdgpu_amdkfd_device_probe
        amdgpu_amdkfd_device_probe --> early_init
        subgraph early_init
            direction TB
            uvd_v7_0_early_init  --> ... --> vce_v4_0_early_init
        end
    end

    amdgpu_device_init --> amdgpu_device_ip_init
    
    amdgpu_device_ip_init --> sw_init
    subgraph sw_init
        direction TB
        gmc_v9_0_sw_init --> gmc_v9_0_mc_init --> amdgpu_bo_init --> gmc_v9_0_gart_init --> vega20_ih_sw_init --> psp_sw_init --> pp_sw_init --> gfx_v9_0_sw_init --> uvd_v7_0_sw_init --> vce_v4_0_sw_init
    end

    amdgpu_device_ip_init --> hw_init
    subgraph hw_init
        direction TB
        gmc_v9_0_hw_init --> psp_hw_init --> dm_hw_init --> gfx_v9_0_hw_init --> sdma_v4_0_hw_init --> vce_v4_0_hw_init
    end

    amdgpu_device_ip_init --> amdgpu_amdkfd_device_init --> kgd2kfd_device_init
    subgraph kgd2kfd_device_init
        direction TB
        kfd_gtt_sa_init --> kfd_doorbell_init --> device_queue_manager_init
    end

    amdgpu_device_init --> amdgpu_device_ip_late_init
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
amd/amdgpu/amdgpu_drv.c: amdgpu_init()
[22906.629931] [drm] amdgpu kernel modesetting enabled.
[22906.629932] [drm] amdgpu test for PM4 packet version: 5.18.13
[22906.629932] [drm] OS DRM version: 5.4.0
--> amdgpu_register_atpx_handler()
--> amdgpu_acpi_detect()
--> amd/amdgpu/amdgpu_amdkfd.c: amdgpu_amdkfd_init()
--> amd/amdkfd/kfd_module.c: kgd2kfd_init()
--> <if IS_ENABLED(CONFIG_HSA_AMD)>: amd/amdkfd/kfd_module.c: kfd_init()
--> amd/amdkfd/kfd_chardev.c: kfd_chardev_init()
--> register_chrdev(0, kfd_dev_name, &kfd_fops)
--> class_create(THIS_MODULE, kfd_dev_name)
--> device_create(kfd_class, NULL, MKDEV(kfd_char_dev_major, 0), NULL, kfd_dev_name)
--> amd/amdkfd/kfd_topology.c: kfd_topology_init()
--> <#ifdef CONFIG_ACPI>: ret = kfd_create_crat_image_acpi(&crat_image, &image_size)
[22906.630016] amdgpu: CRAT table not found
--> amd/amdkfd/kfd_crat.c: kfd_create_crat_image_virtual(&crat_image, &image_size, COMPUTE_UNIT_CPU, NULL, proximity_domain)
[22906.630017] [244191] amdgpu: CRAT size is 128
--> amd/amdkfd/kfd_crat.c: kfd_create_vcrat_image_cpu(pcrat_image, size)
[22906.630018] amdgpu: Virtual CRAT table created for CPU
--> amd/amdkfd/kfd_crat.c: kfd_parse_crat_table(crat_image, &temp_topology_device_list, proximity_domain)
[22906.630019] [244191] amdgpu: Parsing CRAT table with 1 nodes
--> kfd_parse_subtype()
--> kfd_parse_subtype_cu()
[22906.630019] [244191] amdgpu: Found CU entry in CRAT table with proximity_domain=0 caps=0
--> kfd_populated_cu_info_cpu()
[22906.630020] [244191] amdgpu: CU CPU: cores=12 id_base=0
--> kfd_debug_print_topology()
[22906.630027] amdgpu: Topology: Add CPU node
--> kfd_ipc_init()
--> kfd_process_create_wq()
--> kfd_init_peer_direct()
[22906.630082] [244191] amdgpu: Try to initialize PeerDirect support
[22906.631966] [244191] amdgpu: PeerDirect interface was not detected
--> kfd_procfs_init()
--> kfd_debugfs_init()
--> <#ifndef DEFINE_SRCU>: kfd_init_processes_srcu()
--> amdgpu_amdkfd_gpuvm_init_mem_limits()
[22906.631979] [244191] amdgpu: Kernel memory limit 12427M, TTM limit 4971M
--> pci_register_driver(&amdgpu_kms_pci_driver)





static struct pci_driver amdgpu_kms_pci_driver = {
.name = DRIVER_NAME,
.id_table = pciidlist,
.probe = amdgpu_pci_probe,
.remove = amdgpu_pci_remove,
.shutdown = amdgpu_pci_shutdown,
.driver.pm = &amdgpu_pm_ops,
.err_handler = &amdgpu_pci_err_handler,
#ifdef HAVE_PCI_DRIVER_DEV_GROUPS
.dev_groups = amdgpu_sysfs_groups,
#endif
};


amd/amdgpu/amdgpu_drv.c: amdgpu_pci_probe()
--> drm_aperture_remove_conflicting_pci_framebuffers()
--> _kcl_remove_conflicting_pci_framebuffers()
--> remove_conflicting_pci_framebuffers()
[22906.632001] amdgpu 0000:03:00.0: remove_conflicting_pci_framebuffers: bar 0: 0x2400000000 -> 0x27ffffffff
[22906.632001] amdgpu 0000:03:00.0: remove_conflicting_pci_framebuffers: bar 2: 0x2200000000 -> 0x22001fffff
[22906.632002] amdgpu 0000:03:00.0: remove_conflicting_pci_framebuffers: bar 5: 0xf7b00000 -> 0xf7b7ffff
--> ...
--> amd/amdgpu/amdgpu_kms.c: amdgpu_driver_load_kms()
--> amd/amdgpu/amdgpu_device.c: amdgpu_device_init()
[22906.632089] [drm] initializing kernel modesetting (VEGA20 0x1002:0x66AF 0x1002:0x081E 0xC1).
[22906.632099] [drm] register mmio base: 0xF7B00000
[22906.632100] [drm] register mmio size: 524288
--> amdgpu_device_ip_early_init()
--> amdgpu_discovery_set_ip_blocks()
--> amdgpu_discovery_set_common_ip_blocks()
--> ...
--> amdgpu_discovery_set_sdma_ip_blocks()
--> amdgpu_device_ip_block_add(adev, &sdma_v4_0_ip_block)
--> amdgpu_discovery_set_mm_ip_blocks()
--> amdgpu_device_ip_block_add(adev, &uvd_v7_0_ip_block);
--> amdgpu_discovery_set_mes_ip_blocks()
--> amdgpu_device_ip_block_add()
[22906.632143] [drm] add ip block number 0 <soc15_common>
[22906.632143] [drm] add ip block number 1 <gmc_v9_0>
[22906.632144] [drm] add ip block number 2 <vega20_ih>
[22906.632144] [drm] add ip block number 3 <psp>
[22906.632144] [drm] add ip block number 4 <powerplay>
[22906.632145] [drm] add ip block number 5 <dm>
[22906.632145] [drm] add ip block number 6 <gfx_v9_0>
[22906.632146] [drm] add ip block number 7 <sdma_v4_0>
[22906.632146] [drm] add ip block number 8 <uvd_v7_0>
[22906.632147] [drm] add ip block number 9 <vce_v4_0>
--> amdgpu_amdkfd_device_probe() ★
--> adev->ip_blocks[i].version->funcs->early_init()
--> uvd_v7_0_ip_funcs.early_init() = uvd_v7_0_early_init()
--> uvd_v7_0_set_ring_funcs()
[22906.726994] [drm] UVD(0) is enabled in VM mode
[22906.726995] [drm] UVD(1) is enabled in VM mode
uvd_v7_0_set_enc_ring_funcs()
[22906.726995] [drm] UVD(0) ENC is enabled in VM mode
[22906.726995] [drm] UVD(1) ENC is enabled in VM mode
--> vce_v4_0_ip_funcs.early_init() = vce_v4_0_early_init()
--> vce_v4_0_set_ring_funcs()
[22906.726996] [drm] VCE enabled in VM mode
--> amdgpu_get_bios()
[22906.726968] amdgpu 0000:03:00.0: amdgpu: Fetched VBIOS from ROM BAR
--> amdgpu_atombios_init()
--> amdgpu_atom_parse()
[22906.726969] amdgpu: ATOM BIOS: 113-D3600200-106
--> amdgpu_gmc_tmz_set()
[22906.726996] amdgpu 0000:03:00.0: amdgpu: Trusted Memory Zone (TMZ) feature not supported
--> amdgpu_device_doorbell_init()
--> amdgpu_device_ip_init()
--> amdgpu_ras_init()
--> amdgpu_ras_check_supported()
[22906.727016] amdgpu 0000:03:00.0: amdgpu: MEM ECC is not presented.
[22906.727016] amdgpu 0000:03:00.0: amdgpu: SRAM ECC is not presented.
[22906.727019] amdgpu 0000:03:00.0: amdgpu: RAS INFO: ras initialized successfully, hardware ability[4] ras_mask[4]
--> adev->ip_blocks[i].version->funcs->sw_init((void *)adev)
gmc_v9_0_ip_funcs.sw_init = gmc_v9_0_sw_init()
--> amdgpu_vm_adjust_size()
[22906.727024] [drm] vm size is 262144 GB, 4 levels, block size is 9-bit, fragment size is 9-bit
--> gmc_v9_0_mc_init()
--> gmc_v9_0_vram_gtt_location()
--> amdgpu_gmc_vram_location(adev, mc, base);
[22906.727028] amdgpu 0000:03:00.0: amdgpu: VRAM: 16368M 0x0000008000000000 - 0x00000083FEFFFFFF (16368M used)
--> amdgpu_gmc_gart_location(adev, mc);
[22906.727028] amdgpu 0000:03:00.0: amdgpu: GART: 512M 0x0000000000000000 - 0x000000001FFFFFFF
--> amdgpu_gmc_agp_location(adev, mc);
[22906.727029] amdgpu 0000:03:00.0: amdgpu: AGP: 267894784M 0x0000008400000000 - 0x0000FFFFFFFFFFFF
--> amdgpu_bo_init()
[22906.727033] [drm] Detected VRAM RAM=16368M, BAR=16384M
[22906.727034] [drm] RAM width 4096bits HBM\
--> amdgpu_ttm_init()
[22906.730061] [drm] amdgpu: 16368M of VRAM memory ready
[22906.730062] [drm] amdgpu: 15985M of GTT memory ready.
--> gmc_v9_0_gart_init()
--> amdgpu_gart_init()
[22906.730070] [drm] GART: num cpu pages 131072, num gpu pages 131072
vega20_ih_ip_funcs.sw_init = vega20_ih_sw_init()
--> amdgpu_irq_init()
[22906.730197] [244191] amdgpu 0000:03:00.0: amdgpu: using MSI/MSI-X.
psp_ip_funcs.sw_init = psp_sw_init()
--> psp_get_runtime_db_entry()
[22906.730299] amdgpu 0000:03:00.0: amdgpu: PSP runtime database doesn't exist
[22906.730305] amdgpu 0000:03:00.0: amdgpu: PSP runtime database doesn't exist
pp_ip_funcs.sw_init = pp_sw_init()
--> hwmgr_sw_init()
[22906.730357] amdgpu: [powerplay] hwmgr_sw_init smu backed is vega20_smu
gfx_v9_0_ip_funcs.sw_init = gfx_v9_0_sw_init()
--> gfx_v9_0_mec_init()
--> amdgpu_gfx_compute_queue_acquire()
[22906.730665] [244191] amdgpu 0000:03:00.0: amdgpu: mec queue bitmap weight=8
uvd_v7_0_ip_funcs.sw_init = uvd_v7_0_sw_init()
--> amdgpu_uvd_sw_init()
[22906.731269] [drm] Found UVD firmware ENC: 1.2 DEC: .43 Family ID: 19
[22906.731284] [drm] PSP loading UVD firmware
vce_v4_0_ip_funcs.sw_init = vce_v4_0_sw_init()
--> amdgpu_vce_sw_init()
[22906.732037] [drm] Found VCE firmware Version: 57.6 Binary ID: 4
[22906.732061] [drm] PSP loading VCE firmware
--> adev->ip_blocks[i].version->funcs->hw_init((void *)adev)
gmc_v9_0_ip_funcs.hw_init = gmc_v9_0_hw_init()
--> gmc_v9_0_gart_enable()
[22906.730140] [drm] PCIE GART of 512M enabled.
[22906.730140] [drm] PTB located at 0x0000008000000000

psp_ip_funcs.hw_init = psp_hw_init()
--> psp_load_fw()
--> psp_hw_start()
--> psp_tmr_load()
[22906.752341] [drm] reserve 0x400000 from 0x83fec00000 for PSP TMR
--> psp_hdcp_initialize()
[22906.886713] amdgpu 0000:03:00.0: amdgpu: HDCP: optional hdcp ta ucode is not available
--> psp_dtm_initialize()
[22906.886716] amdgpu 0000:03:00.0: amdgpu: DTM: optional dtm ta ucode is not available
--> psp_rap_initialize()
[22906.886719] amdgpu 0000:03:00.0: amdgpu: RAP: optional rap ta ucode is not available
--> psp_securedisplay_initialize()
[22906.886721] amdgpu 0000:03:00.0: amdgpu: SECUREDISPLAY: securedisplay ta ucode is not available

amdgpu_dm_funcs.hw_init = dm_hw_init()
--> amdgpu_dm_init()
--> dc_create(&init_data)
--> dc_construct(dc, init_params)
--> dc_clk_mgr_create(dc->ctx, dc->res_pool->pp_smu, dc->res_pool->dccg)
--> dceXXX_clk_mgr_construct()
--> ...
--> vbios_funcs.get_spread_spectrum_info = bios_parser_get_spread_spectrum_info()
--> get_ss_info_v4_1()
[22906.890253] [244191] amdgpu: [BIOS]:gpuclk_ss_percentage (unit of 0.001 percent): 0
[22906.890388] [244191] amdgpu: [BIOS]:AS_SIGNAL_TYPE_DISPLAY_PORT ss_percentage: 450
--> create_links(dc, init_params->num_virtual_links)
--> link_create()
--> dc_link_construct(link, init_params))
--> dc_link_construct_legacy()
--> link->dc->res_pool->funcs->link_enc_create(dc_ctx, &enc_init_data)
.link_enc_create = dceXXX_link_encoder_create
--> dceXXX_link_encoder_construct()
--> bp_funcs->get_encoder_cap_info(enc110->base.ctx->dc_bios, enc110->base.id, &bp_cap_info)
vbios_funcs.get_encoder_cap_info = bios_parser_get_encoder_cap_info()
bios_parser_get_encoder_cap_info()
[22906.890474] [244191] amdgpu: [BIOS]:record->encodercaps 0xf for object_id 0xd
[22906.890476] [244191] amdgpu: [BIOS]: info->DP_IS_USB_C 0
[22906.890501] [244191] amdgpu: [BIOS]:record->encodercaps 0xf for object_id 0xf
[22906.890503] [244191] amdgpu: [BIOS]: info->DP_IS_USB_C 0
[22906.890527] [244191] amdgpu: [BIOS]:record->encodercaps 0xf for object_id 0xf
[22906.890529] [244191] amdgpu: [BIOS]: info->DP_IS_USB_C 0
[22906.890553] [244191] amdgpu: [BIOS]:record->encodercaps 0xf for object_id 0xd
[22906.890555] [244191] amdgpu: [BIOS]: info->DP_IS_USB_C 0
[22906.890570] [drm] Display Core initialized with v3.2.212!

gfx_v9_0_ip_funcs.hw_init = gfx_v9_0_hw_init()
--> gfx_v9_0_cp_resume()
--> gfx_v9_0_kcq_resume()
--> amdgpu_gfx_enable_kcq()
[22906.894733] [drm] kiq ring mec 2 pipe 1 q 0

sdma_v4_0_ip_funcs.hw_init = sdma_v4_0_hw_init()
--> amdgpu_dpm_set_powergating_by_smu()
--> p_dpm_funcs.set_powergating_by_smu = pp_set_powergating_by_smu()
--> pp_dpm_powergate_uvd()
--> if (hwmgr->hwmgr_func->powergate_uvd == NULL)
vega20_hwmgr_funcs.powergate_uvd = vega20_power_gate_uvd()
vega20_power_gate_uvd()
--> vega20_enable_disable_uvd_dpm()
[22906.936879] [244191] amdgpu: [powerplay] [EnableDisableUVDDPM] feature DPM UVD already enabled!
uvd_v6_0_ip_funcs.hw_init = uvd_v6_0_hw_init()
[22906.937023] [drm] UVD and UVD ENC initialized successfully.

// --> amdgpu_dpm_set_powergating_by_smu()
// --> p_dpm_funcs.set_powergating_by_smu = pp_set_powergating_by_smu()
// --> pp_dpm_powergate_vce()
// --> vega20_hwmgr_funcs.powergate_vce = vega20_power_gate_vce()
// --> vega20_enable_disable_vce_dpm()
// [22907.135666] [244191] amdgpu: [powerplay] [EnableDisableVCEDPM] feature VCE DPM already enabled!
vce_v4_0_ip_funcs.hw_init = vce_v4_0_hw_init()
[22907.135956] [drm] VCE initialized successfully.

// smu_v11_0_i2c_algo.master_xfer = smu_v11_0_i2c_xfer
// smu_v11_0_i2c_xfer()
// smu_v11_0_i2c_write_data()
// smu_v11_0_i2c_transmit()
// smu_v11_0_i2c_poll_tx_status()
// [22907.141263] [drm] TX was terminated, IC_TX_ABRT_SOURCE val is:1000001
// [22907.141357] [drm:smu_v11_0_i2c_xfer.cold [amdgpu]] *ERROR* Received I2C_NAK_7B_ADDR_NOACK !!!
// [22907.141410] [drm:smu_v11_0_i2c_xfer [amdgpu]] *ERROR* WriteI2CData() - I2C error occurred :1

--> amdgpu_ras_recovery_init()
--> amdgpu_ras_eeprom_init()
[22907.142252] [drm:amdgpu_ras_eeprom_init [amdgpu]] *ERROR* Failed to read EEPROM table header, res:-5
[22907.142811] amdgpu 0000:03:00.0: amdgpu: Failed to initialize ras recovery! (-5)
--> amdgpu_amdkfd_device_init()
--> kgd2kfd_device_init()
[22907.144489] kfd kfd: amdgpu: Allocated 3969056 bytes on gart
--> kfd_gtt_sa_init()
[22907.144490] [244191] amdgpu: gtt_sa_num_of_chunks = 7752, gtt_sa_bitmap = 000000005240393f
--> kfd_doorbell_init()
[22907.144498] [244191] amdgpu: Doorbell initialization:
[22907.144499] [244191] amdgpu: doorbell base == 0x2200002000
[22907.144499] [244191] amdgpu: doorbell_base_dw_offset == 0x00000800
[22907.144499] [244191] amdgpu: doorbell_process_limit == 0x000000FF
[22907.144500] [244191] amdgpu: doorbell_kernel_offset == 0x2200002000
[22907.144500] [244191] amdgpu: doorbell aperture size == 0x00200000
[22907.144501] [244191] amdgpu: doorbell kernel address == 00000000ec5ee4d9
--> device_queue_manager_init()
[22907.144543] [244191] amdgpu: Loading device queue manager
--> initialize_nocpsch()/initialize_cpsch()
[22907.144638] [244191] amdgpu: num of pipes: 4
--> init_sdma_bitmaps()
[22907.144638] amdgpu: sdma_bitmap: ffff
--> init_mqd_managers()
--> asic_ops->mqd_manager_init = mqd_manager_init_v9()
mqd_manager_init_v9()
mqd->load_mqd = kfd_hiq_load_mqd_kiq;
kfd_hiq_load_mqd_kiq()
gfx_v9_kfd2kgd.hiq_mqd_load = kgd_gfx_v9_hiq_mqd_load()
--> dqm->ops.create_queue = create_queue_nocpsch;
--> dqm->ops.start = start_cpsch;
--> kfd_resume(kfd)
--> kfd->dqm->ops.start(kfd->dqm)
// dqm->ops.start = start_cpsch;
start_cpsch()
--> pm_init()
--> kernel_queue_init()
--> kq_initialize()
[22907.144642] [244191] amdgpu: Initializing queue type 2 size 2048
--> kfd_get_kernel_doorbell()
[22907.144642] [244191] amdgpu: Get kernel queue doorbell
doorbell offset == 0x00000800
doorbell index == 0x0
--> kfd_gtt_sa_allocate() * 4
[22907.144643] [244191] amdgpu: Allocated mem_obj = 00000000c6362840 for size = 2048
[22907.144644] [244191] amdgpu: Found = 0
[22907.144644] [244191] amdgpu: gpu_addr = 00000000ef7ebed7, cpu_addr = 0000000052309c93
[22907.144645] [244191] amdgpu: range_start = 0, range_end = 3
[22907.144645] [244191] amdgpu: Allocated mem_obj = 000000007ae05bdc for size = 4096
[22907.144646] [244191] amdgpu: Found = 4
[22907.144646] [244191] amdgpu: gpu_addr = 000000008cbe4b16, cpu_addr = 000000008ec6d2d0
[22907.144647] [244191] amdgpu: range_start = 4, range_end = 11
[22907.144647] [244191] amdgpu: Allocated mem_obj = 000000001c98cd7f for size = 4
[22907.144648] [244191] amdgpu: Found = 12
[22907.144648] [244191] amdgpu: gpu_addr = 00000000fdd101a7, cpu_addr = 00000000704ebcb5
[22907.144648] [244191] amdgpu: Single bit
[22907.144649] [244191] amdgpu: Allocated mem_obj = 0000000023ca5260 for size = 8
[22907.144649] [244191] amdgpu: Found = 13
[22907.144650] [244191] amdgpu: gpu_addr = 0000000076cf5431, cpu_addr = 0000000034d2bd4c
[22907.144650] [244191] amdgpu: Single bit
--> if (init_queue(&kq->queue, &prop) != 0)
// update_mqd()
// [22907.144651] [244191] amdgpu: cp_hqd_pq_control 0x508
// restore_mqd()
// [22907.144652] [244191] amdgpu: cp_hqd_pq_doorbell_control 0x2000
[22907.144652] [244191] amdgpu: Assigning hiq to hqd
--> kq->mqd_mgr->load_mqd(kq->mqd_mgr, kq->queue->mqd, kq->queue->pipe, kq->queue->queue, &kq->queue->properties, NULL);
// mqd->load_mqd = kfd_hiq_load_mqd_kiq;
--> return mm->dev->kfd2kgd->hiq_mqd_load(mm->dev->adev, mqd, pipe_id,
queue_id, p->doorbell_off);
// gfx_v9_kfd2kgd.hiq_mqd_load = kgd_gfx_v9_hiq_mqd_load()
kgd_gfx_v9_hiq_mqd_load()
[22907.144654] [244191] amdgpu: kfd: set HIQ, mec:2, pipe:0, queue:0.
--> amdgpu_ring_write(kiq_ring, PACKET3(PACKET3_MAP_QUEUES, 5));
--> amdgpu_ring_write(kiq_ring,
PACKET3_MAP_QUEUES_QUEUE_SEL(0) | /* Queue_Sel */
PACKET3_MAP_QUEUES_VMID(m->cp_hqd_vmid) | /* VMID */
PACKET3_MAP_QUEUES_QUEUE(queue_id) |
PACKET3_MAP_QUEUES_PIPE(pipe) |
PACKET3_MAP_QUEUES_ME((mec - 1)) |
PACKET3_MAP_QUEUES_QUEUE_TYPE(0) | /*queue_type: normal compute queue */
PACKET3_MAP_QUEUES_ALLOC_FORMAT(0) | /* alloc format: all_on_one_pipe */
PACKET3_MAP_QUEUES_ENGINE_SEL(1) | /* engine_sel: hiq */
PACKET3_MAP_QUEUES_NUM_QUEUES(1)); /* num_queues: must be 1 */
--> amdgpu_ring_write(kiq_ring,
PACKET3_MAP_QUEUES_DOORBELL_OFFSET(doorbell_off));
--> amdgpu_ring_write(kiq_ring, m->cp_mqd_base_addr_lo);
--> amdgpu_ring_write(kiq_ring, m->cp_mqd_base_addr_hi);
--> amdgpu_ring_write(kiq_ring, m->cp_hqd_pq_wptr_poll_addr_lo);
--> amdgpu_ring_write(kiq_ring, m->cp_hqd_pq_wptr_poll_addr_hi);
--> amdgpu_ring_commit(kiq_ring);
--> print_queue()
[22907.144655] [244191] amdgpu: Printing queue:
[22907.144656] [244191] amdgpu: Queue Type: 2
[22907.144656] [244191] amdgpu: Queue Size: 2048
[22907.144656] [244191] amdgpu: Queue percent: 100
[22907.144657] [244191] amdgpu: Queue Address: 0xCD1000
[22907.144657] [244191] amdgpu: Queue Id: 0
[22907.144658] [244191] amdgpu: Queue Process Vmid: 0
[22907.144658] [244191] amdgpu: Queue Read Pointer: 0x0000000000cd2800
[22907.144658] [244191] amdgpu: Queue Write Pointer: 0x0000000000cd2a00
[22907.144659] [244191] amdgpu: Queue Doorbell Pointer: 0x00000000ec5ee4d9
[22907.144659] [244191] amdgpu: Queue Doorbell Offset: 2048
[22907.144660] [244191] amdgpu: Queue MQD Address: 0x000000009cf9a951
[22907.144660] [244191] amdgpu: Queue MQD Gart: 0x48F000
[22907.144661] [244191] amdgpu: Queue Process Address: 0xffffffffffffffea
[22907.144661] [244191] amdgpu: Queue Device Address: 0x00000000bf68ccc7

--> set_sched_resources()
[22907.144662] [244191] amdgpu: Scheduling resources:
vmid mask: 0x FF00
queue mask: 0xFCFCFCFC
--> pm_send_set_resources()
--> kq_acquire_packet_buffer()
[22907.144663] [244191] amdgpu: rptr: 0
[22907.144663] [244191] amdgpu: wptr: 0
[22907.144664] [244191] amdgpu: queue_address 0x0000000052309c93
--> kq_submit_packet()
--> write_kernel_doorbell()
[22907.144665] [244191] amdgpu: writing 8 to doorbell address 00000000ec5ee4d9
[22907.144665] [244191] amdgpu: Allocating fence memory

--> kfd_gtt_sa_allocate()
[22907.144666] [244191] amdgpu: Allocated mem_obj = 00000000f2493a7d for size = 8
[22907.144666] [244191] amdgpu: Found = 14
[22907.144666] [244191] amdgpu: gpu_addr = 000000008e289627, cpu_addr = 00000000d0a54834
[22907.144667] [244191] amdgpu: Single bit

--> amdgpu_amdkfd_get_local_mem_info()
[22907.144668] [244191] amdgpu: Address base: 0x0000002400000000 public 0x3ff000000 private 0x0
--> kfd_topology_add_device()
[22907.144679] [244191] amdgpu: Adding new GPU (ID: 0x44d3) to topology
--> kfd_create_crat_image_virtual()
--> kfd_create_vcrat_image_gpu()
--> kfd_fill_gpu_memory_affinity()
[22907.144681] [244191] amdgpu: Fill gpu memory affinity - type 0x1 size 0x3ff000000
--> kfd_fill_gpu_direct_io_link_to_cpu()
--> kfd_find_numa_node_in_srat()
[22907.144683] amdgpu: SRAT table not found
[22907.144683] amdgpu: Virtual CRAT table created for GPU
--> kfd_parse_crat_table()
[22907.144684] [244191] amdgpu: Parsing CRAT table with 1 nodes
--> kfd_parse_subtype()
--> kfd_parse_subtype_cu()
[22907.144685] [244191] amdgpu: Found CU entry in CRAT table with proximity_domain=1 caps=0
--> kfd_populated_cu_info_gpu()
[22907.144685] [244191] amdgpu: CU GPU: id_base=-2147479552
--> kfd_parse_subtype_mem()
[22907.144685] [244191] amdgpu: Found memory entry in CRAT table with proximity_domain=1
--> kfd_parse_subtype_iolink()
[22907.144686] [244191] amdgpu: Found IO link entry in CRAT table with id_from=1, id_to 0
--> kfd_fill_cache_non_crat_info()
[22907.144696] [244191] amdgpu: Added [109] GPU cache entries
--> kfd_fill_mem_clk_max_info()
--> amdgpu_amdkfd_get_local_mem_info()
[22907.144790] [244191] amdgpu: Address base: 0x0000002400000000 public 0x3ff000000 private 0x0
--> kfd_debug_print_topology()
[22907.144800] amdgpu: Topology: Add dGPU node [0x66af:0x1002]
[22907.144801] kfd kfd: amdgpu: added device 1002:66af
[22907.144801] [244191] amdgpu: Starting kfd with the following scheduling policy 0
[22907.144811] amdgpu 0000:03:00.0: amdgpu: SE 4, SH per SE 1, CU per SH 16, active_cu_number 60
--> amdgpu_device_ip_late_init()
adev->ip_blocks[i].version->funcs->late_init
gmc_v9_0_ip_funcs.late_init() = gmc_v9_0_late_init
gmc_v9_0_late_init()
--> amdgpu_gmc_allocate_vm_inv_eng()
[22907.144863] amdgpu 0000:03:00.0: amdgpu: ring gfx uses VM inv eng 0 on hub 0
[22907.144864] amdgpu 0000:03:00.0: amdgpu: ring comp_1.0.0 uses VM inv eng 1 on hub 0
[22907.144864] amdgpu 0000:03:00.0: amdgpu: ring comp_1.1.0 uses VM inv eng 4 on hub 0
[22907.144865] amdgpu 0000:03:00.0: amdgpu: ring comp_1.2.0 uses VM inv eng 5 on hub 0
[22907.144865] amdgpu 0000:03:00.0: amdgpu: ring comp_1.3.0 uses VM inv eng 6 on hub 0
[22907.144866] amdgpu 0000:03:00.0: amdgpu: ring comp_1.0.1 uses VM inv eng 7 on hub 0
[22907.144866] amdgpu 0000:03:00.0: amdgpu: ring comp_1.1.1 uses VM inv eng 8 on hub 0
[22907.144867] amdgpu 0000:03:00.0: amdgpu: ring comp_1.2.1 uses VM inv eng 9 on hub 0
[22907.144867] amdgpu 0000:03:00.0: amdgpu: ring comp_1.3.1 uses VM inv eng 10 on hub 0
[22907.144868] amdgpu 0000:03:00.0: amdgpu: ring kiq_2.1.0 uses VM inv eng 11 on hub 0
[22907.144868] amdgpu 0000:03:00.0: amdgpu: ring sdma0 uses VM inv eng 0 on hub 1
[22907.144868] amdgpu 0000:03:00.0: amdgpu: ring page0 uses VM inv eng 1 on hub 1
[22907.144869] amdgpu 0000:03:00.0: amdgpu: ring sdma1 uses VM inv eng 4 on hub 1
[22907.144870] amdgpu 0000:03:00.0: amdgpu: ring page1 uses VM inv eng 5 on hub 1
[22907.144870] amdgpu 0000:03:00.0: amdgpu: ring uvd_0 uses VM inv eng 6 on hub 1
[22907.144870] amdgpu 0000:03:00.0: amdgpu: ring uvd_enc_0.0 uses VM inv eng 7 on hub 1
[22907.144871] amdgpu 0000:03:00.0: amdgpu: ring uvd_enc_0.1 uses VM inv eng 8 on hub 1
[22907.144871] amdgpu 0000:03:00.0: amdgpu: ring uvd_1 uses VM inv eng 9 on hub 1
[22907.144872] amdgpu 0000:03:00.0: amdgpu: ring uvd_enc_1.0 uses VM inv eng 10 on hub 1
[22907.144872] amdgpu 0000:03:00.0: amdgpu: ring uvd_enc_1.1 uses VM inv eng 11 on hub 1
[22907.144873] amdgpu 0000:03:00.0: amdgpu: ring vce0 uses VM inv eng 12 on hub 1
[22907.144873] amdgpu 0000:03:00.0: amdgpu: ring vce1 uses VM inv eng 13 on hub 1
[22907.144874] amdgpu 0000:03:00.0: amdgpu: ring vce2 uses VM inv eng 14 on hub 1

--> amdgpu_pmu_init()
--> init_pmu_entry_by_type_and_add()
[22907.151940] amdgpu: Detected AMDGPU DF Counters. # of Counters = 8.
[22907.151950] amdgpu: Detected AMDGPU 2 Perf Events.
// [22907.152889] [drm] Initialized amdgpu 3.48.0 20150101 for 0000:03:00.0 on minor 1
// [22907.155820] [drm] Cannot find any crtc or sizes

队列创建

流程示意图
flowchart TB
    subgraph kfdtest
        direction TB
        id0["HSAKMT_STATUS Create()"] --> id1["memset()"] --> id2["hsaKmtCreateQueue()
        
        Params:
        NodeId,
        type,
        DEFAULT_QUEUE_PERCENTAGE,
        DEFAULT_PRIORITY,
        m_QueueBuf->As<unsigned int*>(),
        m_QueueBuf->Size(),
        NULL,
        &m_Resources"]
    end

    subgraph Thunk
        direction TB
        id3["handle_concrete_asic();"] --> id4["args.read_pointer_address = QueueResource->QueueRptrValue;
            args.write_pointer_address = QueueResource->QueueWptrValue;
            args.ring_base_address = (uintptr_t)QueueAddress;
            args.ring_size = QueueSizeInBytes;
            args.queue_percentage = QueuePercentage;
            args.queue_priority = priority_map[Priority+3];"] --> id5["kmtIoctl(kfd_fd, AMDKFD_IOC_CREATE_QUEUE, &args)"]
    end

    subgraph Driver
        direction TB
        subgraph kfd_ioctl_create_queue
            direction TB
            set_queue_properties_from_user --> kfd_process_device_data_by_id --> kfd_bind_process_to_device --> pqm_create_queue
            subgraph pqm_create_queue
                direction TB
                kfd_get_process_device_data --> init_user_queue --> kfd_process_drain_interrupts --> create_queue --> create_queue_cpsch
                subgraph create_queue_cpsch
                    direction TB
                    allocate_doorbell --> init_mqd --> list_add --> increment_queue_count --> execute_queues_cpsch --> deallocate_doorbell
                end
            end
        end
    end

    kfdtest --> Thunk
    Thunk --> Driver
    style id2 text-align:left
    style id4 text-align:left
execute_queues_cpsch 流程
flowchart TB
    subgraph execute_queues_cpsch
        direction TB
        subgraph unmap_queues_cpsch
            direction TB
            pm_send_unmap_queue --> pm_send_query_status --> amdkfd_fence_wait_timeout --> pm_release_ib
        end

        subgraph map_queues_cpsch
            direction TB
            subgraph pm_send_runlist
                direction TB
                pm_create_runlist_ib --> kq_acquire_packet_buffer --> pm_runlist_v9 --> kq_submit_packet
            end
        end

        unmap_queues_cpsch --> map_queues_cpsch
    end
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
// ----------------------------------------------------------------------------------------------------------------------------------
// kfdtest
// ----------------------------------------------------------------------------------------------------------------------------------
class BaseQueue
--> HSAKMT_STATUS Create(unsigned int NodeId, unsigned int size = DEFAULT_QUEUE_SIZE, HSAuint64 *pointers = NULL);
--> memset(&m_Resources, 0, sizeof(m_Resources));
--> hsaKmtCreateQueue(NodeId, type, DEFAULT_QUEUE_PERCENTAGE, DEFAULT_PRIORITY, m_QueueBuf->As<unsigned int*>(), m_QueueBuf->Size(), NULL, &m_Resources);
// ----------------------------------------------------------------------------------------------------------------------------------
// Thunk
// ----------------------------------------------------------------------------------------------------------------------------------
--> struct kfd_ioctl_create_queue_args args = {0};
--> handle_concrete_asic(q, &args, NodeId, Event, QueueResource->ErrorReason);
--> args.read_pointer_address = QueueResource->QueueRptrValue;
args.write_pointer_address = QueueResource->QueueWptrValue;
args.ring_base_address = (uintptr_t)QueueAddress;
args.ring_size = QueueSizeInBytes;
args.queue_percentage = QueuePercentage;
args.queue_priority = priority_map[Priority+3];
--> err = kmtIoctl(kfd_fd, AMDKFD_IOC_CREATE_QUEUE, &args);
// ----------------------------------------------------------------------------------------------------------------------------------
// Driver
// ----------------------------------------------------------------------------------------------------------------------------------
--> static int kfd_ioctl_create_queue(struct file *filep, struct kfd_process *p, void *data)
--> set_queue_properties_from_user(&q_properties, args);
--> q_properties->is_interop = false;
q_properties->is_gws = false;
q_properties->queue_percent = args->queue_percentage;
q_properties->priority = args->queue_priority;
q_properties->queue_address = args->ring_base_address;
q_properties->queue_size = args->ring_size;
q_properties->read_ptr = (uint32_t *) args->read_pointer_address;
q_properties->write_ptr = (uint32_t *) args->write_pointer_address;
q_properties->eop_ring_buffer_address = args->eop_buffer_address;
q_properties->eop_ring_buffer_size = args->eop_buffer_size;
q_properties->ctx_save_restore_area_address = args->ctx_save_restore_address;
q_properties->ctx_save_restore_area_size = args->ctx_save_restore_size;
q_properties->ctl_stack_size = args->ctl_stack_size;
//...
--> kfd_process_device_data_by_id()
--> kfd_bind_process_to_device()
--> pqm_create_queue(&p->pqm, dev, filep, &q_properties, &queue_id, NULL, NULL, NULL, &doorbell_offset_in_process);
--> struct kfd_process_device *pdd = kfd_get_process_device_data()
// PM4Queue.hpp: Type is HSA_QUEUE_COMPUTE --> KFD_IOC_QUEUE_TYPE_COMPUTE --> KFD_QUEUE_TYPE_COMPUTE
// SDMAQueue.hpp: Type is HSA_QUEUE_SDMA --> KFD_IOC_QUEUE_TYPE_SDMA --> KFD_QUEUE_TYPE_SDMA
--> init_user_queue(pqm, dev, &q, properties, f, *qid);
--> kfd_process_drain_interrupts(pdd);
--> retval = dev->dqm->ops.create_queue(dev->dqm, q, &pdd->qpd, q_data, restore_mqd, restore_ctl_stack);
// dqm->ops.create_queue = create_queue_cpsch;
--> create_queue_cpsch()
--> allocate_doorbell()
--> mqd_mgr->init_mqd(mqd_mgr, &q->mqd, q->mqd_mem_obj, &q->gart_mqd_addr, &q->properties);
--> list_add(&q->list, &qpd->queues_list);
--> increment_queue_count(dqm, qpd, q);
--> execute_queues_cpsch(dqm, KFD_UNMAP_QUEUES_FILTER_DYNAMIC_QUEUES, 0, USE_DEFAULT_GRACE_PERIOD);
--> retval = unmap_queues_cpsch(dqm, filter, filter_param, grace_period, false);
--> retval = pm_send_unmap_queue(&dqm->packet_mgr, filter, filter_param, reset);
--> pm_send_query_status(&dqm->packet_mgr, dqm->fence_gpu_addr, KFD_FENCE_COMPLETED);
--> retval = amdkfd_fence_wait_timeout(dqm->fence_addr, KFD_FENCE_COMPLETED, queue_preemption_timeout_ms);
--> pm_release_ib(&dqm->packet_mgr);
--> map_queues_cpsch(dqm);
--> pm_send_runlist(&dqm->packet_mgr, &dqm->queues);
--> retval = pm_create_runlist_ib(pm, dqm_queues, &rl_gpu_ib_addr, &rl_ib_size);
--> retval = kq_acquire_packet_buffer(pm->priv_queue, ket_size_dwords, &rl_buffer);
--> retval = pm->pmf->runlist(pm, rl_buffer, rl_gpu_ib_addr, rl_ib_size / sizeof(uint32_t), false);
--> pm_runlist_v9()
--> kq_submit_packet(pm->priv_queue);
--> write_kernel_doorbell()
--> writel(value, db);
--> __io_bw();
__raw_writel((u32 __force)__cpu_to_le32(value), addr);
__io_aw();
--> dqm->active_runlist = true;
--> deallocate_doorbell(qpd, q);


/** Ioctl table */
static const struct amdkfd_ioctl_desc amdkfd_ioctls[] = {
//...
AMDKFD_IOCTL_DEF(AMDKFD_IOC_CREATE_QUEUE,
kfd_ioctl_create_queue, 0),
//...
}

amdgpu_amdkfd_device_init()
--> adev->kfd.init_complete = kgd2kfd_device_init(adev->kfd.dev, adev_to_drm(adev), &gpu_resources);
--> kfd->dqm = device_queue_manager_init(kfd);
--> case KFD_SCHED_POLICY_HWS_NO_OVERSUBSCRIPTION:
/* initialize dqm for cp scheduling */
dqm->ops.create_queue = create_queue_cpsch;
dqm->ops.initialize = initialize_cpsch;
dqm->ops.start = start_cpsch;
dqm->ops.stop = stop_cpsch;
dqm->ops.pre_reset = pre_reset;
dqm->ops.destroy_queue = destroy_queue_cpsch;
dqm->ops.update_queue = update_queue;
dqm->ops.register_process = register_process;
dqm->ops.unregister_process = unregister_process;
dqm->ops.uninitialize = uninitialize;
dqm->ops.create_kernel_queue = create_kernel_queue_cpsch;
dqm->ops.destroy_kernel_queue = destroy_kernel_queue_cpsch;
dqm->ops.set_cache_memory_policy = set_cache_memory_policy;
dqm->ops.process_termination = process_termination_cpsch;
dqm->ops.evict_process_queues = evict_process_queues_cpsch;
dqm->ops.restore_process_queues = restore_process_queues_cpsch;
dqm->ops.get_wave_state = get_wave_state;
dqm->ops.reset_queues = reset_queues_cpsch;
dqm->ops.get_queue_checkpoint_info = get_queue_checkpoint_info;
dqm->ops.checkpoint_mqd = checkpoint_mqd;
break;
--> kfd_resume()
--> kfd->dqm->ops.start(kfd->dqm);
--> retval = pm_init(&dqm->packet_mgr, dqm);
--> pm->priv_queue = kernel_queue_init(dqm->dev, KFD_QUEUE_TYPE_HIQ);
--> kq_initialize(kq, dev, type, KFD_KERNEL_QUEUE_SIZE)
--> kq->mqd_mgr->init_mqd(kq->mqd_mgr, &kq->queue->mqd,
kq->queue->mqd_mem_obj,
&kq->queue->gart_mqd_addr,
&kq->queue->properties);
--> set_sched_resources(dqm)
--> return pm_send_set_resources(&dqm->packet_mgr, &res);
--> retval = pm->pmf->set_resources(pm, buffer, res);
--> pm_set_resources_v9()

初始化

MEC,又称 Ucode,或者microcode,是 CP 的重要 firmware。

在AMD GPU的初始化过程中,CP(Command Processor)模块的初始化是一个关键步骤,它涉及加载和配置微代码(microcode)以支持指令调度、命令解析、DMA(Direct Memory Access)以及图形和计算任务的管理。以下是该流程的详细介绍,包括KMD(Kernel Mode Driver)如何将Ucode写入CP模块的步骤。

  1. CP模块初始化概述

    CP模块是AMD GPU的一个重要组件,主要负责以下任务:

    • 管理和调度指令(包括图形和计算命令)
    • 处理和解析命令流(Command Stream)
    • 协调硬件调度单元(Hardware Scheduler)
    • 管理上下文切换和线程预处理(Thread Preemption)

    在初始化时,CP模块需要被配置好其微代码,以便正确解析和调度由内核驱动(KMD)或用户驱动(UMD)发送的指令流。

  2. 微代码(Microcode)的作用

    微代码是CP模块执行的固件程序,它定义了CP模块如何解释并调度各种图形和计算任务。微代码通常包含以下几类指令:

    • 图形指令解析和调度逻辑
    • 计算指令和DMA传输的管理
    • 上下文切换(Context Switching)和优先级调度策略

    在KMD初始化CP时,通常会先通过驱动程序将微代码加载到GPU的内存中,然后通过特定寄存器配置微代码的执行位置。

  3. CP模块的初始化流程
    以下是一个典型的AMD GPU CP模块初始化流程:

    1. 微代码加载

      • 驱动程序会将微代码文件(例如gfx_cp.bin)加载到系统内存中。这个文件通常通过KMD或固件接口(Firmware Interface)进行获取,包含预编译好的指令集合。
    2. 微代码传输到GPU内存

      • 驱动程序通过MMIO(Memory-Mapped I/O)或PCIe访问机制,将微代码从系统内存传输到GPU的内存(通常是显存或CP模块本地存储器中)。
    3. CP微代码配置

      • 驱动程序通过配置CP的寄存器来指定微代码的位置和大小。例如,通过以下寄存器进行配置:
        • CP_MEC_ME1_UCODE_ADDR:指定CP微代码的加载地址。
        • CP_MEC_ME1_UCODE_DATA:逐字节或逐段将微代码数据写入指定地址。
      • 在配置时,通常需要先指定微代码基地址(例如显存或本地寄存器的地址),然后循环地写入微代码内容。对于CP模块,微代码加载通常采用单字(32-bit)或双字(64-bit)方式。
    4. 微代码加载确认和校验

      • 驱动程序在完成微代码写入后,会配置相关寄存器标志位以启动微代码。例如:
        • 设置 CP_MEC_CNTL 寄存器的启动位(start bit)来触发CP模块执行微代码。
      • 驱动程序会通过读取寄存器状态(如检查校验和寄存器)来确认微代码是否加载成功。
    5. 命令处理配置

      • 一旦微代码被成功加载和校验,CP模块会被配置为接收和解析来自KMD的图形或计算命令流。此时,KMD会配置相应的调度策略和上下文切换策略。
  4. KMD如何写入微代码(Ucode)
    KMD(Kernel Mode Driver)在进行CP模块的微代码初始化时,会采用以下具体步骤:

    1. 获取微代码数据

      • 从驱动中定义的静态数组(或从设备固件库中)获取微代码的数据。微代码通常以二进制格式存在于驱动程序中,文件名可能是 gfx_cp.bin 或者通过加载固件接口(Firmware Loader)动态获取。
    2. 设置CP微代码写入地址

      • 配置 CP_MEC_ME1_UCODE_ADDRCP_ME_RAM_WADDR 等寄存器,以指向CP模块内部RAM或GPU内存中微代码的目标写入地址。
    3. 逐字写入微代码数据

      • 使用循环结构逐字(word by word)地将微代码数据写入 CP_MEC_ME1_UCODE_DATACP_ME_RAM_DATA 寄存器。每次写入后,地址指针会自动递增。
      1
      2
      3
      4
      for (i = 0; i < microcode_size; i++) {
      WREG32(CP_ME_RAM_WADDR, i); // 设置CP RAM的写入地址
      WREG32(CP_ME_RAM_DATA, microcode_data[i]); // 写入微代码数据
      }
    4. 启动微代码执行

      • 配置 CP_RB_CNTLCP_MEC_CNTL 寄存器,以启动CP模块开始执行微代码。
    5. 校验加载状态

      • 驱动程序通过读取 CP_ME_STATUS 或类似的状态寄存器来确认微代码是否正确启动和执行。
  5. 关键寄存器的说明
    在CP模块微代码加载过程中,以下寄存器通常需要配置:

    • CP_ME_RAM_WADDR:指向微代码在CP模块中的起始地址。
    • CP_ME_RAM_DATA:用来写入微代码的具体数据。
    • CP_MEC_CNTL:控制CP模块的启动和停止。
    • CP_RB_CNTL:用于配置Ring Buffer和命令调度策略。
    • CP_MEC_ME1_UCODE_ADDR / CP_MEC_ME1_UCODE_DATA:配置MEC(Micro Engine Controller)微代码的加载地址和数据。

AMD GPU的CP模块初始化涉及复杂的微代码加载和配置过程。KMD通常通过内核模式驱动程序将微代码从系统内存传输到GPU内存中,再通过配置相关寄存器来启动微代码的执行。正确的微代码加载和配置对于GPU指令解析和任务调度至关重要。在实际开发中,可以使用调试工具(如amdgpu-progdb)来检查CP模块的寄存器状态,以确保微代码正确加载并成功运行。

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
static int gfx_v9_0_early_init(void *handle)
{
struct amdgpu_device *adev = (struct amdgpu_device *)handle;

adev->gfx.funcs = &gfx_v9_0_gfx_funcs;

adev->gfx.num_gfx_rings = GFX9_NUM_GFX_RINGS;
adev->gfx.num_compute_rings = min(amdgpu_gfx_get_num_kcq(adev),
AMDGPU_MAX_COMPUTE_RINGS);
gfx_v9_0_set_spm_funcs(adev);
gfx_v9_0_set_kiq_pm4_funcs(adev);
gfx_v9_0_set_ring_funcs(adev);
gfx_v9_0_set_irq_funcs(adev);
gfx_v9_0_set_gds_init(adev);
gfx_v9_0_set_rlc_funcs(adev);
#if HYGON_KMD_SUPPORT
gfx_v9_0_set_rd_stream(adev);
#endif

/* init rlcg reg access ctrl */
gfx_v9_0_init_rlcg_reg_access_ctrl(adev);

return gfx_v9_0_init_microcode(adev);
}

static int gfx_v9_0_init_microcode(struct amdgpu_device *adev)
{
char ucode_prefix[30];
int r;

DRM_DEBUG("\n");

amdgpu_ucode_ip_version_decode(adev, GC_HWIP, ucode_prefix, sizeof(ucode_prefix));
#ifdef HYGON_KMD_VEGA20
/* No CPG in Arcturus */
if (adev->gfx.num_gfx_rings) {
r = gfx_v9_0_init_cp_gfx_microcode(adev, ucode_prefix);
if (r)
return r;
}
#endif
r = gfx_v9_0_init_rlc_microcode(adev, ucode_prefix);
if (r)
return r;

r = gfx_v9_0_init_cp_compute_microcode(adev, ucode_prefix);
if (r)
return r;

return r;
}

static int gfx_v9_0_init_cp_compute_microcode(struct amdgpu_device *adev,
const char *chip_name)
{
char fw_name[30];
int err;

snprintf(fw_name, sizeof(fw_name), "hydcu/%s_mec.bin", chip_name);
err = request_firmware(&adev->gfx.mec_fw, fw_name, adev->dev);
if (err)
goto out;
amdgpu_gfx_cp_init_microcode(adev, AMDGPU_UCODE_ID_CP_MEC1);
amdgpu_gfx_cp_init_microcode(adev, AMDGPU_UCODE_ID_CP_MEC1_JT);

if (gfx_v9_0_load_mec2_fw_bin_support(adev)) {
snprintf(fw_name, sizeof(fw_name), "hydcu/%s_mec2.bin", chip_name);

/* ignore failures to load */
err = amdgpu_ucode_request(adev, &adev->gfx.mec2_fw, fw_name);
if (!err) {
amdgpu_gfx_cp_init_microcode(adev, AMDGPU_UCODE_ID_CP_MEC2);
amdgpu_gfx_cp_init_microcode(adev, AMDGPU_UCODE_ID_CP_MEC2_JT);
} else {
err = 0;
amdgpu_ucode_release(&adev->gfx.mec2_fw);
}
} else {
adev->gfx.mec2_fw_version = adev->gfx.mec_fw_version;
adev->gfx.mec2_feature_version = adev->gfx.mec_feature_version;
}


gfx_v9_0_check_if_need_gfxoff(adev);
gfx_v9_0_check_fw_write_wait(adev);

out:
if (err)
amdgpu_ucode_release(&adev->gfx.mec_fw);
return err;
}

void amdgpu_gfx_cp_init_microcode(struct amdgpu_device *adev,
uint32_t ucode_id)
{
const struct gfx_firmware_header_v1_0 *cp_hdr;
const struct gfx_firmware_header_v2_0 *cp_hdr_v2_0;
struct amdgpu_firmware_info *info = NULL;
const struct firmware *ucode_fw;
unsigned int fw_size;

switch (ucode_id) {
//
case AMDGPU_UCODE_ID_CP_MEC1:
cp_hdr = (const struct gfx_firmware_header_v1_0 *)
adev->gfx.mec_fw->data;
adev->gfx.mec_fw_version =
le32_to_cpu(cp_hdr->header.ucode_version);
adev->gfx.mec_feature_version =
le32_to_cpu(cp_hdr->ucode_feature_version);
ucode_fw = adev->gfx.mec_fw;
fw_size = le32_to_cpu(cp_hdr->header.ucode_size_bytes) -
le32_to_cpu(cp_hdr->jt_size) * 4;
break;
case AMDGPU_UCODE_ID_CP_MEC1_JT:
cp_hdr = (const struct gfx_firmware_header_v1_0 *)
adev->gfx.mec_fw->data;
ucode_fw = adev->gfx.mec_fw;
fw_size = le32_to_cpu(cp_hdr->jt_size) * 4;
break;
//
default:
break;
}

if (adev->firmware.load_type == AMDGPU_FW_LOAD_PSP) {
info = &adev->firmware.ucode[ucode_id];
info->ucode_id = ucode_id;
info->fw = ucode_fw;
adev->firmware.fw_size += ALIGN(fw_size, PAGE_SIZE);
}
}

gfx_v9_0_hw_init() --> gfx_v9_0_cp_resume() --> gfx_v9_0_cp_compute_load_microcode();
static int gfx_v9_0_cp_compute_load_microcode(struct amdgpu_device *adev)
{
const struct gfx_firmware_header_v1_0 *mec_hdr;
const __le32 *fw_data;
unsigned i;
u32 tmp;

if (!adev->gfx.mec_fw)
return -EINVAL;

gfx_v9_0_cp_compute_enable(adev, false);

mec_hdr = (const struct gfx_firmware_header_v1_0 *)adev->gfx.mec_fw->data;
amdgpu_ucode_print_gfx_hdr(&mec_hdr->header);

fw_data = (const __le32 *)
(adev->gfx.mec_fw->data +
le32_to_cpu(mec_hdr->header.ucode_array_offset_bytes));
tmp = 0;
tmp = REG_SET_FIELD(tmp, CP_CPC_IC_BASE_CNTL, VMID, 0);
tmp = REG_SET_FIELD(tmp, CP_CPC_IC_BASE_CNTL, CACHE_POLICY, 0);
WREG32_SOC15(GC, 0, mmCP_CPC_IC_BASE_CNTL, tmp);

WREG32_SOC15(GC, 0, mmCP_CPC_IC_BASE_LO,
adev->gfx.mec.mec_fw_gpu_addr & 0xFFFFF000);
WREG32_SOC15(GC, 0, mmCP_CPC_IC_BASE_HI,
upper_32_bits(adev->gfx.mec.mec_fw_gpu_addr));

/* MEC1 */
WREG32_SOC15(GC, 0, mmCP_MEC_ME1_UCODE_ADDR,
mec_hdr->jt_offset);
for (i = 0; i < mec_hdr->jt_size; i++)
WREG32_SOC15(GC, 0, mmCP_MEC_ME1_UCODE_DATA,
le32_to_cpup(fw_data + mec_hdr->jt_offset + i));

WREG32_SOC15(GC, 0, mmCP_MEC_ME1_UCODE_ADDR,
adev->gfx.mec_fw_version);
/* Todo : Loading MEC2 firmware is only necessary if MEC2 should run different microcode than MEC1. */

return 0;
}

static void gfx_v9_0_cp_compute_enable(struct amdgpu_device *adev, bool enable)
{
if (enable) {
WREG32_SOC15_RLC(GC, 0, mmCP_MEC_CNTL, 0);
} else {
WREG32_SOC15_RLC(GC, 0, mmCP_MEC_CNTL,
(CP_MEC_CNTL__MEC_ME1_HALT_MASK | CP_MEC_CNTL__MEC_ME2_HALT_MASK));
adev->gfx.kiq.ring.sched.ready = false;
}
udelay(50);
}

内存管理

常见概念介绍:

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
struct amdgpu_gmc {
/* FB(帧缓冲区)的物理地址和大小(供 CPU 映射使用)。
* 这与 `vram_start` 和 `vram_end` 字段不同,后者是 GPU 视角的地址,
* 而 `aper_base` 是 CPU 视角的物理地址。
*/
resource_size_t aper_size; // FB 孔径的大小
resource_size_t aper_base; // FB 孔径的起始地址(CPU 视角)

/* 对于某些显存 <= 32MB 的芯片,需要在 MC(内存控制器)中
* 修改 VRAM 大小以适应帧缓冲区位置。
*/
u64 mc_vram_size; // 显存(VRAM)大小(MC 视角)
u64 visible_vram_size; // CPU 可见的显存大小

/* AGP(加速图形端口)孔径的起始和结束地址(MC 地址空间中)。
* 驱动程序通过设置 MC_VM_AGP_BOT/TOP 寄存器在 MC 地址空间中找到一个 AGP 区域。
* 在 VMID0 上,逻辑地址等于 MC 地址。AGP 孔径映射到物理总线地址或 IOVA 地址。
* AGP 主要用于模拟帧缓冲区或者作为系统内存中的页表(主要用于 APU)。
*/
u64 agp_size; // AGP 孔径大小
u64 agp_start; // AGP 孔径起始地址
u64 agp_end; // AGP 孔径结束地址

/* GART(图形地址重映射表)孔径的起始和结束地址(MC 地址空间中)。
* 驱动程序通过设置 VM_CONTEXT0_PAGE_TABLE_START/END_ADDR 寄存器在 MC 地址空间中
* 找到一个 GART 区域。对于 VMID0 的逻辑地址,在 GART 区域中的地址通过 GPU VM
* 的 GART 页表转换,访问分页的系统内存。
*/
u64 gart_size; // GART 孔径大小
u64 gart_start; // GART 孔径起始地址
u64 gart_end; // GART 孔径结束地址

/* 当前 GPU 设备的帧缓冲区孔径。不同于 `fb_start`,
* 该字段只表示本地 GPU 设备的孔径范围。
* 如果驱动程序使用 FB 孔径访问显存,则 `fb_start` 从
* MC_VM_FB_LOCATION_BASE 中获取(由 vbios 设置)。
* 如果驱动程序使用 GART 表访问 VMID0 的帧缓冲区,
* 驱动程序会在 VMID0 虚拟地址空间中找到一个 SYSVM 孔径,
* 其中第一部分是显存,第二部分是 GART(映射到系统内存)。
*/
u64 vram_start; // 本地 GPU 显存孔径起始地址
u64 vram_end; // 本地 GPU 显存孔径结束地址

/* FB(帧缓冲区)区域。在单 GPU 配置中,它等同于本地显存区域。
* 在 XGMI(多 GPU 集群)配置中,该区域覆盖同一集群中所有 GPU。
* 集群中的每个 GPU 具有相同的 FB 视图,GPU0 的显存从偏移 (0 * 段大小) 开始,
* GPU1 从偏移 (1 * 段大小) 开始,以此类推。
*/
u64 fb_start; // 帧缓冲区起始地址
u64 fb_end; // 帧缓冲区结束地址

unsigned vram_width; // 显存总线宽度(位宽)
u64 real_vram_size; // 实际可用的显存大小
int vram_mtrr; // MTRR(内存类型范围寄存器)设置
u64 mc_mask; // 内存控制器的地址掩码

const struct firmware *fw; // MC 固件指针
uint32_t fw_version; // 固件版本号
struct amdgpu_irq_src vm_fault; // 虚拟内存故障源
uint32_t vram_type; // 显存类型(GDDR5、HBM 等)
uint8_t vram_vendor; // 显存供应商(例如三星、海力士等)
uint32_t ecc_status; // ECC(错误纠正码)状态
uint32_t srbm_soft_reset; // SRBM(系统请求总线管理器)软复位寄存器
bool prt_warning; // PRT(部分资源事务)警告标志

uint32_t sdpif_register; // SDPIF(串行数据处理接口)寄存器

/* 各种孔径(用于多 GPU 配置时的地址分配) */
u64 shared_aperture_start; // 共享孔径起始地址
u64 shared_aperture_end; // 共享孔径结束地址
u64 private_aperture_start; // 私有孔径起始地址
u64 private_aperture_end; // 私有孔径结束地址

/* 用于保护并发的无效化操作 */
spinlock_t invalidate_lock; // 自旋锁,保护并发内存无效化操作
bool translate_further; // 是否需要进一步地址转换

struct kfd_vm_fault_info *vm_fault_info; // 虚拟内存故障信息
atomic_t vm_fault_info_updated; // 故障信息更新标志

struct amdgpu_gmc_fault fault_ring[AMDGPU_GMC_FAULT_RING_SIZE]; // 内存故障环形缓冲区
struct {
uint64_t idx:AMDGPU_GMC_FAULT_RING_ORDER; // 故障环形缓冲区索引
} fault_hash[AMDGPU_GMC_FAULT_HASH_SIZE]; // 故障哈希表
uint64_t last_fault:AMDGPU_GMC_FAULT_RING_ORDER; // 上次内存故障索引

#ifdef HYGON_KMD_SUPPORT
struct amdgpu_gmc_va_hash va_hash_table[MAX_VA_HASH_SIZE]; // VA(虚拟地址)哈希表(Hygon 芯片支持)
#endif

bool tmz_enabled; // 是否启用 TMZ(受信内存区)

const struct amdgpu_gmc_funcs *gmc_funcs; // GMC 功能函数指针

struct amdgpu_xgmi xgmi; // XGMI(GPU 互联)结构体
struct amdgpu_irq_src ecc_irq; // ECC 中断源
int noretry; // 重试机制控制

uint32_t vmid0_page_table_block_size; // VMID0 页表块大小
uint32_t vmid0_page_table_depth; // VMID0 页表深度
struct amdgpu_bo *pdb0_bo; // VMID0 的页目录基址块
void *ptr_pdb0; // 页目录基址块的 CPU 映射地址

u64 noretry_flags; // 内存事务重试标志
};

/*
* GPU 全局内存控制器(GMC)结构体、函数及辅助工具。
*/
struct amdgpu_gmc_funcs {
/* 通过 MMIO(内存映射 I/O)刷新 VM(虚拟内存) TLB(页表缓冲区) */
void (*flush_gpu_tlb)(struct amdgpu_device *adev, uint32_t vmid,
uint32_t vmhub, uint32_t flush_type);

/* 通过 PASID(进程地址空间 ID)刷新 VM TLB */
int (*flush_gpu_tlb_pasid)(struct amdgpu_device *adev, uint16_t pasid,
uint32_t flush_type, bool all_hub);

/* 通过 GPU 指令环(ring buffer)刷新 VM TLB */
uint64_t (*emit_flush_gpu_tlb)(struct amdgpu_ring *ring, unsigned vmid,
uint64_t pd_addr);

/* 修改 VMID(虚拟内存 ID)到 PASID(进程地址空间 ID)的映射关系 */
void (*emit_pasid_mapping)(struct amdgpu_ring *ring, unsigned vmid,
unsigned pasid);

/* 启用/禁用 PRT(部分资源事务)支持 */
void (*set_prt)(struct amdgpu_device *adev, bool enable);

/* 将内存类型(mtype)映射到硬件标志 */
uint64_t (*map_mtype)(struct amdgpu_device *adev, uint32_t flags);

/* 获取给定 MC(内存控制器)地址的页目录条目(PDE) */
void (*get_vm_pde)(struct amdgpu_device *adev, int level,
u64 *dst, u64 *flags);

/* 获取用于 BO(缓冲对象)VA(虚拟地址)映射的 PTE(页表项)标志 */
void (*get_vm_pte)(struct amdgpu_device *adev,
struct amdgpu_bo_va_mapping *mapping,
uint64_t *flags);
};

struct amdgpu_gmc 是 AMDGPU 驱动中用于表示 AMD GPU 全局内存控制器(Global Memory Controller, GMC)配置的结构体。该结构体主要负责管理 GPU 内存的各种配置和状态,包括 VRAM(视频内存)、GART(图形地址重映射表)、AGP(加速图形端口)等。每个字段都有其特定用途,用于处理 GPU 内存分配、访问、和故障管理等任务。

  1. 内存孔径字段(Aperture Fields):
    • aper_baseaper_size
      • 代表从 CPU 角度看到的帧缓冲区(Frame Buffer, FB)孔径的物理地址和大小。这与 GPU 视角的地址不同,后者由 vram_start/vram_end 表示。
    • vram_startvram_end
      • 定义 GPU 本地 VRAM 的起始和结束地址。
    • fb_startfb_end
      • 描述 Frame Buffer 区域的起始和结束地址。在多 GPU 设置中(例如 XGMI 集群),该区域覆盖所有 GPU 的显存,每个 GPU 可能会有不同的偏移量。
    • agp_start、agp_end、agp_size
      • 指定 AGP(加速图形端口)孔径,主要用于早期 GPU 或集成 APU 的内存访问。
    • gart_start、gart_end、gart_size
      • 描述 GART(图形地址重映射表)孔径,用于访问分页的系统内存(例如在虚拟内存场景下)。
  2. 内存大小和属性字段:
    • mc_vram_size
      • 指定从内存控制器(Memory Controller, MC)视角看到的 VRAM 大小。
    • visible_vram_size
      • 定义 CPU 可见的 VRAM 大小,这可能小于或等于 mc_vram_size
    • real_vram_size
      • 表示 GPU 实际可用的 VRAM 总大小。
    • vram_width
      • 表示 VRAM 的总宽度(位宽),影响显存带宽。
  3. 故障处理(Fault Handling)字段:
    • vm_fault
      • 描述虚拟内存(VM)故障的来源。
    • vm_fault_infovm_fault_info_updated
      • 存储虚拟内存故障的详细信息,并跟踪故障信息的更新。
    • fault_ring
      • 用于记录和处理 GPU 内存故障的环形缓冲区。
  4. 孔径共享和地址转换字段:
    • shared_aperture_startshared_aperture_end
      • 多设备共享的孔径范围,用于 XGMI 等多 GPU 互联场景。
    • private_aperture_startprivate_aperture_end
      • 该设备独有的孔径范围。
    • translate_further
      • 布尔类型,指示地址转换是否需要进一步进行。
  5. 固件和配置信息:
    • fwfw_version
      • 指向 MC 固件和固件版本号。
    • ecc_status
      • 记录 ECC(错误纠正码)状态,用于检查显存中是否发生了位错误。
    • tmz_enabled
      • 指示是否启用了 TMZ(受信内存区),这是一种用于保护特定内存区域的安全功能。
  6. 锁和同步字段:
    • invalidate_lock
      • 自旋锁,用于保护内存无效化操作的并发访问。
  7. 页表配置字段:
    • vmid0_page_table_block_sizevmid0_page_table_depth
      • VMID0 页表的配置设置,决定页表的粒度和深度。
  8. 多 GPU 和互联支持字段:
    • xgmi
      • 表示 XGMI(高速 GPU 互联)结构体,用于管理多 GPU 的互联配置和状态。
  9. 其他字段:
    • sdpif_register
      • SDPIF(串行数据处理接口)寄存器,用于与其他组件通信。
    • noretrynoretry_flags
      • 内存事务重试机制的控制字段。

amdgpu_gmc 结构体被设计用于详细描述 AMD GPU 内存的各个部分,包括显存、系统内存和地址重映射表的配置。它可以管理不同 GPU 之间的内存地址空间,并为不同内存类型分配特定的地址范围。此外,它还包含故障处理字段以记录内存访问错误,并且支持多 GPU 的内存地址转换和互联配置。这些字段确保 GPU 内存管理的高效性和可靠性,是实现高性能计算和图形处理的基础。

DRM 是 Linux 内核中的一个子系统,负责管理图形硬件设备。它为图形处理器(GPU)和显示设备提供低级别的图形资源管理和硬件加速支持。主要用于显示服务器(如 X11 或 Wayland)和图形库(如 Mesa 3D)与 GPU 之间的交互。

  1. 内存管理:
    • DRM 管理 GPU 使用的显存(VRAM)和系统内存(GTT)。它处理内存的分配、映射和释放,确保 GPU 和 CPU 可以高效地共享和使用内存资源。
  2. 上下文管理:
    • DRM 支持图形上下文的创建和管理,使多个图形应用程序可以同时运行并独立渲染其内容。这对于多任务处理和确保应用程序之间的隔离至关重要。
  3. 图形硬件控制:
    • DRM 直接与图形硬件(如 GPU、显示控制器)交互,执行低级别操作,如显示模式设置(Mode Setting)、 Frame Buffer 管理(Framebuffer Management)、硬件加速命令的排队和执行等。
  4. 安全和访问控制:
    • DRM 提供了对图形硬件的安全访问控制,确保只有授权的应用程序能够访问 GPU 资源。这在多用户环境中非常重要。
  5. 显示管理:
    • DRM 包含了显示管理的功能,包括管理多个显示器、设置显示模式(分辨率、刷新率等)、处理热插拔事件等。它为图形栈中的更高级别组件(如 X Server 或 Wayland Compositor)提供了统一的接口。

DRM 的组件

  • DRM 驱动程序:
    • 这是特定于硬件的内核模块,负责与特定的 GPU 和显示硬件交互。每个支持 DRM 的 GPU 都有一个相应的 DRM 驱动程序,例如 amdgpu(AMD GPU)、i915(Intel GPU)、nouveau(NVIDIA 开源驱动)等。
  • GEM (Graphics Execution Manager):
    • 这是 DRM 的一部分,负责内存对象的管理,特别是图形缓冲区的分配、映射和释放。
  • KMS (Kernel Mode Setting):
    • KMS 是 DRM 的一个子系统,用于在内核中设置显示模式(分辨率、颜色深度、刷新率等),而不是由用户空间应用程序设置。KMS 确保显示管理的安全性和稳定性,尤其是在多用户系统中。

DRM 的实际应用

在 Linux 系统中,DRM 是现代图形栈的核心组成部分。它提供了 GPU 资源的底层管理,支持 OpenGL、Vulkan 和其他图形 API 的硬件加速,确保图形应用程序能够高效运行。显示服务器如 X11 和 Wayland,图形库如 Mesa 3D,都依赖于 DRM 提供的功能来与 GPU 进行交互。

TTM 一个内存管理子系统,最初由 Tungsten Graphics 开发,目的是为 Linux 内核中的图形设备提供统一的内存管理。TTM 主要处理显存(VRAM)和系统内存(GART 或者系统RAM)之间的数据交换。它可以动态地管理和分配这些内存区域,支持显卡的高效使用。TTM 具有以下几个关键功能:

  • 内存管理:为图形硬件分配内存,支持在显存和系统内存之间的动态切换。
  • 分页:在显存不足的情况下,TTM 允许将一些内存页面从显存转移到系统内存,从而腾出显存空间。
  • 地址映射:通过使用页表和其他映射技术,TTM 可以将物理内存映射到 GPU 可访问的虚拟地址空间。

GEM 是 Linux 内核中用于图形内存管理的一个框架,特别是用于与图形处理单元(GPU)交互。GEM 主要由 Intel 为其 i915 DRM 驱动程序开发,但后来被其他驱动程序采用或借鉴,用于管理 GPU 上的图形内存。

  1. 图形内存对象管理:
    • GEM 负责分配、管理和释放 GPU 使用的图形内存对象。这些对象可以包括缓冲区(例如 Frame Buffer )、纹理、顶点缓冲区等,这些都是 GPU 进行图形渲染的基础。
  2. 内存映射:
    • GEM 允许用户空间程序将 GPU 内存映射到 CPU 地址空间。这意味着用户空间程序可以直接访问和修改 GPU 内存内容,例如更新纹理数据或将渲染结果从 GPU 传输回 CPU 进行后续处理。
  3. 同步与缓冲区共享:
    • 在一个系统中,多个进程或线程可能需要访问同一块 GPU 内存。GEM 提供了同步机制,确保这些访问是安全且有序的。
    • GEM 还允许共享缓冲区对象,这意味着不同的进程或 GPU 阶段可以共享和重用相同的图形内存对象,提高资源利用率和性能。
  4. 用户空间接口:
    • GEM 提供了一组 ioctl(输入输出控制)接口,供用户空间程序调用。这些接口包括内存对象的创建、销毁、映射和同步操作等,使得用户空间程序能够与内核中的 GEM 模块交互,管理 GPU 内存。
  5. 调度与执行管理:
    • GEM 在某些情况下也负责调度图形命令的执行,确保 GPU 可以高效地处理多个渲染任务。它通过管理图形命令流和确保内存对象的可用性来优化 GPU 的使用。

GEM 的引入极大地简化了 GPU 内存管理,使得图形应用程序可以更容易地管理 GPU 资源。通过提供一个通用的内存管理框架,GEM 使得不同的 GPU 驱动程序可以更一致地管理内存,减少了在不同硬件和驱动程序之间进行移植时的复杂性。在 Linux 内核中,除了 GEM 之外,还有其他用于 GPU 内存管理的机制,例如 TTM(Translation Table Maps)。TTM 是另一个内存管理框架,主要由 AMD 开发,并在一些开源驱动中使用。

  • GEM vs. TTM:
    • GEM:较简单,适用于较轻量级的图形内存管理任务,特别是在资源管理需求较低的环境中。
    • TTM:更复杂,支持更高级的功能,如内存分页和交换,更适合需要大量内存管理的高性能图形应用程序。

一些 GPU 驱动程序可能会基于这两者的组合来实现最优的内存管理方案。

ROCm 中的 SVM 实现主要存在于 Thunk 中的用户模式下。分配内存时,Thunk 使用 mmap (…, PROT_NONE, …) 为内存分配虚拟地址空间。GPU 可以处理 48 位虚拟地址。缓冲区应映射到主机(如果它是主机可访问的缓冲区)和所有适用 GPU 上的该虚拟地址。

KFD 内存分配 ioctl 有一个用于虚拟地址的参数 kfd_ioctl_alloc_memory_of_gpu_args.va_addr。也就是说,Thunk 应该在要求 KFD 分配内存之前分配虚拟地址空间。KFD 在分配时将虚拟地址与内存关联起来。内存的未来映射应该使用该虚拟地址。如果适用,主机映射由 Thunk 使用 mmap(va_addr, …, MAP_FIXED, …) 完成,在第一个参数中指定先前分配的虚拟地址。

通过 ROCm API 分配的内存应映射到所有设备(主机和目标)上的相同虚拟地址。可以在设备上使用相同的指针来引用相同的内存。包含指针的数据结构可以在设备之间共享。

  • MC 地址和 GPU 的物理地址:在为 GPU 编写驱动程序代码时,区分 MC(GPU 的内存控制器)和物理地址非常重要。GPU 物理地址从零开始,一直到最大视频内存大小。另一方面,MC 地址具有 48 位地址空间。内存孔径地址范围寄存器大多在 MC 地址空间中定义。通过定义这些寄存器的值,可以确定虚拟内存地址空间。
    • F400000000 MC 地址在 VBIOS 中定义,当 GPU 自行启动时,应将该值写入寄存器 MC_VM_FB_LOCATION_BASE,驱动程序将该值读出为“系统定义值”,并且该值应该是预定义的。因此,该值不是 GPU 硬件中的硬编码值,您可以根据需要更改该值。
    • 如何根据 MC 地址计算 GPU 的物理地址,可以按照以下公式计算物理地址:FB 的物理地址 = (McAddr - ‘FB aperture start address’) + PHYS_FB_OFFSET
      • McAddr:在本例中为 MC 地址 F4_0000_0008。
      • FB aperture start address:由寄存器 MC_VM_FB_LOCATION_BASE 定义的 F4_0000_0000
      • PHYS_FB_OFFSET:它定义了 UMA 地址中视频内存的物理偏移量,因此在独立 GPU 中它应始终为 0。
    • 因此,您可以得到 (F4_0000_0008) 的物理地址为 (F4_0000_0008 – F4_0000_0000) + 0 = 0x8

在填充页表的时候需要用到物理地址来组成PTE和PDE,所以了解如何计算物理地址很重要,但请注意有些寄存器是定义在MC地址中的,有些是定义在物理地址中的,不要混淆。

系统孔径(Aperture):系统孔径由两部分组成, Frame Buffer 孔径和 AGP 孔径。大多数情况下,系统孔径与 Frame Buffer 孔径一致,即“本地 Frame Buffer ”或“视频内存”。孔径由两个寄存器“mmMC_VM_SYSTEM_APERTURE_LOW_ADDR”和“mmMC_VM_SYSTEM_APERTURE_HIGH_ADDR”定义。

MC 地址布局不是固定的,它实际上取决于使用情况和不同的平台。对于 Linux,地址布局有两个孔径,系统孔径和 Gart 孔径。

  • 系统孔径从 F400000000 开始,到视频内存的最大大小结束。
  • Gart 孔径目前定义在系统孔径之后,但同样,这不是硬件定义的值,您可以根据需要更改布局。您可以在“孔径”以外的任何地方定义,并且不要与其他孔径重叠。

GPU MC 地址空间由两部分组成,高位部分(0xFFFF_8000_0000_0000, 0xFFFF_FFFF_FFFF_FFF),低位部分(0x0000_0000_0000_0000, 0x0000_7FFF_FFFF_FFFF),范围由 48 位地址空间第 47 位的有符号扩展定义,当第 47 位 == 0 时,可以将 0 扩展至 64 位,并应获得低位部分的上限,当第 47 位 == 1 时,可以将 1 扩展至 64 位,并应获得高位地址的下限。这两部分之间的地址称为“洞”,当 GPU 接收到洞内的任何地址时,它都是无效地址。

“Memory Address Space.xls”中引用了 Remote GPU FB 孔径,这是 VBIOS 和 SYSTEM BIOS 中额外支持在 gpu 中导出“Large-Bar”的,“Large-Bar”是指主机可以访问本地 Frame Buffer 的概念,这意味着 PCIE bar 空间是 64bit,系统可以分配一个覆盖本地 Frame Buffer 的地址空间。在这种模式下,GPU 可以通过 PCIE 相互访问,因为本地 Frame Buffer 对彼此是可见的。

GART 页表 在Linux开源驱动中,Gart表位于“可见内存”中。Gart表是一级页表。PAGE_TABLE_DEPTH定义为实际级别减一。

但有一个例外,这就是“LDS孔径”和“私有(临时)孔径”,有配置寄存器来指定这两个孔径,因此在计算着色器中,可以通过“FLAT*”指令访问这两个孔径。 分配给“LDS孔径”和“私有孔径”一部分的“洞”内的地址可以直接在“FLAT*”指令中使用,GPU可以通过该地址访问LDS或私有孔径。

GPUVM 使用模型 在 GPU 中,有 16 个称为 VMID 的 vm 域,VMID0 称为“系统域”,只有内核模式驱动程序可以使用 VMID0,它被配置为“gart 模型”,但其他 VMID 都暴露给用户模式驱动程序,从使用模型来看,VMID1 到 VMID15 是相等的。

虽然 GPU 是可配置的,但您可以为每个 VMID 分配不同的 vm 空间,但通常情况下,您会平等地配置其余 15 个 VMID。

Frame Buffer 用于显示,并且 gart 表必须在内部可见,固件则不是,固件应在驱动程序初始化 PSP 模块时上传到专用 ROM 上。 “VF 可见 Frame Buffer ”,VF 指的是虚拟功能,这是虚拟化中的一个概念。

“HDP”代表“主机数据路径”,它是主机在不知道 Frame Buffer 中实际内存布局的情况下访问 Frame Buffer 的方式。

VMID使用及分配策略 除0以外的VMID被视为平等,在开源linux驱动中,VMID1~VMID7分配给图形使用,VMID8~VMID15分配给计算使用,但这不是硬件限制,您可以重新定义使用。

页表级别也可以从1级到4级配置,所以也取决于使用情况,在Linux开源驱动中,您可以为每个VMID分配64GB的虚拟地址,因为如果覆盖太大的虚拟空间,页表将非常大,从而消耗视频内存。

所以,综上所述,VMID0是为启用GART表而设计的系统域,页表是一级,GART空间的大小是可配置的。 VMID1~VMID15 是用户模式虚拟机,每个虚拟机都有 64GB 的虚拟内存空间,同样,这是根据使用情况可配置的,gfx 驱动中是 64GB,但是 Rocm 驱动从 rocm 2.0 开始为用户启用了整个 48 位虚拟内存空间。用户模式驱动可以分配本地 Frame Buffer 或 gart 空间,更新到 PTE 中。

页表中有两种类型的块:

  • 页目录块 (PDB)
  • 页表块 (PTB)

在这些类型的块中,有两种类型的条目:

  • PDB 中的页目录条目 (PDE)
  • PTB 中的页表条目 (PTE) 页表由一定数量的级别构成,最底层是页表块,其上每一级都是页目录块。

PDB 由一组 64 位页面目录条目 (PDE) 组成。中间 PDB(不是最顶层的 PDB)最多应有 512 个 PDE;也就是说,它们最多占用单个对齐的 4kb 内存页面。最顶层的 PDB 可以小或大,以表示所需的虚拟地址空间;最顶层的 PDB 可以占用多个 4kb 内存页面,但组成页面必须相邻。每个 PDE 指向 8 个 PDE 或 8 个 PTE 的缓存行的物理基址,具体取决于其在页表层次结构中的位置。

PTE

出于代码和寄存器文档命名目的,PDE 级别进一步描述如下:

  • PDE0 - 指向 8 个 PTE 的起始缓存行
  • PDE1 - 指向 8 个 PDE0 的起始缓存行
  • PDE2 - 指向 8 个 PDE1 的起始缓存行

64 位 PDE 的格式为:

关联页表块大小为 0 的传统页表块 (PTB) 包含 512 个 64 位页表条目 (PTE),占用 4KB 内存空间。每个 PTE 指向一个 4KB 可寻址内存页,PTB 中的所有 512 个 PTE 共同寻址 2MB 内存空间(512 x 4KB = 2MB)。

PTE

定义在:amdgpu-5.7.0-0\amd\amdgpu\amdgpu_vm.h

  • M – Mtype
  • F – 进一步转换
  • L – 日志
  • P - PTE
  • SW – 软件
  • T – 平铺 (PRT)
  • W - 写入
  • R - 读取
  • X - 执行
  • Z - tmZ
  • C – 可缓存/监听
  • S - 系统
  • V - 有效

64 位 PTE 的格式为:

MTYPE 旨在控制基于描述符或指针的内存访问的 TC 缓存行为。此寄存器中指定的 Mtype 适用于没有关联描述符来指定 mtype 的内存访问。KMD 旨在根据寻址模式为非零 VMID 编程默认 mtype。对于 GPUVM32 和 64 寻址模式,KMD 应将默认 mtype 编程为 cached_NON_Coherent(NC),对于 HSA32 和 64 寻址模式,默认 mtype 编程为 Cached Coherent(CC)。VMID 0 的 Mtype 编程为 Uncached。

以下是 GPU 上的 mType 值:

  • 0 - NC;缓存,非连贯
  • 1 - WC;写入组合
  • 2 - CC;缓存,连贯
  • 3 - UC;未缓存的

V 位:V 位控制 PTE 是否为有效的 GPUVM 页面,如果将 V 位设置为 0,GPU MC 应该请求 ATC(IOMMU)进行进一步转换,因为它是主机页面而不是 GPU 页面,这用于实现共享虚拟内存。

C 位:指示内存类型是监听还是非监听。在 Linux 驱动程序中,根据页面是否缓存来设置位。

S 位:指示页面地址是否引用主机页面。在 Linux 驱动程序中,在为 GPU 访问分配主机页面时,始终在 PTE 中启用此位。

详细剖析见:

PTE(Page Table Entry,页表项)和 PDE(Page Directory Entry,页目录项)是虚拟内存管理系统中用于内存地址转换的重要概念,尤其是在 x86 架构和类似的内存管理单元(MMU)中广泛使用。在现代操作系统中,虚拟内存通过分页机制实现,将虚拟地址空间映射到物理地址空间。这种映射通常分为多级,即通过多个层次的表逐步解析虚拟地址,最终找到对应的物理地址。

PTE:

  • 页表项是虚拟内存系统中最低级别的映射条目,用于将虚拟页面映射到物理页面。
  • 结构:PTE 通常包含以下信息:
    • 物理页框地址:指向物理内存中的一个页面的起始地址。
    • 状态标志:如有效位(valid bit)、可读/可写标志(read/write bit)、用户/内核模式标志(user/kernel mode bit)等。
    • 特权和缓存控制:可能包含一些关于该页面特权级别的标志和缓存控制位。
  • 功能:当 CPU 需要访问某个虚拟地址时,PTE 会被查找到,以获取该地址对应的物理地址,从而进行实际的数据访问。

PDE:

  • 页目录项是在多级分页结构中用于指向下一级页表的条目,通常是第一级映射。
  • 结构:PDE 通常包含以下信息:
    • 页表基地址:指向下一级页表的物理地址。
    • 状态标志:类似 PTE,也有一些控制和状态标志,如有效位、权限标志等。
  • 功能:PDE 的作用是指向页表(即包含 PTE 的数据结构)。在多级分页机制中,CPU 会先查找 PDE 来确定具体使用哪个页表,然后再通过该页表中的 PTE 找到最终的物理地址。

多级页表管理:

  • 一级分页(单级页表):整个虚拟地址空间通过一个单一的页表映射到物理地址。
  • 多级分页(例如两级或四级页表):虚拟地址通过多个级别的表来逐步解析。最常见的是两级或四级页表结构。
    • 二级页表:包含页目录(Page Directory)和页表(Page Table)。虚拟地址首先通过页目录项(PDE)找到页表,然后通过页表项(PTE)找到对应的物理页框。
    • 四级页表:进一步扩展,添加了页全局目录(Page Global Directory,PGD)和页上级目录(Page Upper Directory,PUD),用于处理更大的虚拟地址空间。

BO 是在图形驱动程序中用于管理内存缓冲区的抽象对象。在 AMD 的驱动程序中,BO 通常与图形资源(如纹理、 Frame Buffer 、顶点缓冲区等)相关联。这些缓冲区对象通过 TTM 或其他内存管理机制来管理和分配实际的内存资源。BO 具备以下特性:

  • 内存分配:通过 BO 接口,图形驱动程序可以为各种图形资源分配和管理内存。
  • 引用计数:BO 通常带有引用计数机制,以确保在缓冲区不再需要时可以安全地释放内存。
  • 内存映射:BO 可以被映射到用户空间,使得应用程序可以直接访问这些图形资源。

GART 是一个关键的内存管理机制,主要用于在系统内存(通常是主机的 RAM)和 GPU(图形处理单元)的地址空间之间进行地址映射和管理。

  1. 地址映射:
    • GART 是一种内存管理单元(MMU),用于将系统内存中的物理地址映射到 GPU 虚拟地址空间(GPUVA)。这种映射允许 GPU 直接访问系统内存中的数据,而不必将所有数据都存储在有限的显存(VRAM)中。
    • 通过 GART,系统内存的一部分被虚拟化为 GPU 可以访问的地址范围,从而扩展了 GPU 的可用内存空间。
  2. 页面调度与切换:
    • GART 支持对大页面和小页面的管理,使得 GPU 能够灵活地访问系统内存中的不同大小的内存块。
    • 当 GPU 需要访问不在显存中的数据时,GART 可以通过页表切换将这些数据从系统内存加载到 GPU 可访问的地址空间。
  3. 内存一致性与缓存:
    • GART 负责确保 CPU 和 GPU 之间的数据一致性。它通过控制地址映射,确保当 CPU 修改了某些系统内存区域后,GPU 能够及时看到这些修改。
    • GART 还涉及到缓存控制,确保在不同情况下正确处理缓存一致性问题。

在 AMD GPU 架构中,GART 通常用于以下场景:

  • 共享内存资源:当 GPU 需要频繁访问大量的系统内存数据时,使用 GART 允许这些数据直接映射到 GPU 的地址空间,而无需不断地在 VRAM 和系统内存之间移动数据。
  • 数据传输:在数据传输过程中,GART 可以有效地管理从系统内存到 GPU 地址空间的数据路径,提高数据传输效率。

在实际的应用场景中,GART 被用于管理那些无法全部放入 VRAM 的数据,尤其是在需要处理大量数据或当 VRAM 资源受限的情况下。例如,在大型图形应用程序或游戏中,GART 允许 GPU 访问那些需要动态加载和处理的资源,如纹理、几何数据等。

以下是具体细节的KMD代码实现分析:

内存申请

kfd_ioctl_alloc_memory_of_gpu

用于在 GPU 虚拟内存(GPUVM)中分配内存。该函数处理内存的分配、初始化和管理,确保 GPU 和 CPU 之间的内存映射和访问符合预期。

参数解析

  • adev: 指向 amdgpu_device 结构体的指针,表示当前的 AMD GPU 设备。
  • va: 虚拟地址,表示分配的内存在 GPU 虚拟地址空间中的位置。
  • size: 要分配的内存的大小,以字节为单位。
  • drm_priv: 表示与当前进程相关的 DRM 私有数据,通常是一个指向 amdgpu_vm(虚拟内存)结构的指针。
  • mem: 指向指针的指针,函数返回时会指向一个已分配的 kgd_mem 结构体,表示分配的 GPU 内存。
  • offset: 指向 64 位整数的指针,函数返回时会包含与分配的内存相关的偏移量(通常用于 mmap)。
  • flags: 内存分配标志,指示如何分配内存(例如分配到 VRAM、GTT 等)。
  • criu_resume: 布尔值,指示是否在 CRIU 恢复上下文中分配内存。

主要执行流程

  1. 虚拟内存初始化:
    • 函数首先从 drm_priv 中提取出 amdgpu_vm(虚拟内存管理)结构。
    • 根据 flags 确定分配内存的类型(VRAM、GTT、用户指针等),并设置相应的内存分配标志。
  2. 分配内存区域:
    • 如果标志表明内存应分配在 VRAM 中,函数会设置 domainalloc_domain 为 VRAM,并根据标志确定其他分配选项,如 CPU 访问要求等。
    • 如果标志指示内存应分配在 GTT(显卡地址转换表)中,函数会相应设置 domainalloc_domain 为 GTT。
    • 函数还会处理其他特殊情况,如用户指针(USERPTR)或 DOORBELL 内存。
  3. 分配 kgd_mem 结构:
    • 函数为 kgd_mem 结构分配内存,并初始化它的一些基本字段,如分配标志和同步对象。
  4. 内存预留与分配:
    • 函数会调用 amdgpu_amdkfd_reserve_mem_limit 预留内存限制,以确保足够的内存可供分配。
    • 使用 amdgpu_gem_object_create 创建实际的缓冲区对象(BO),这代表了分配的 GPU 内存。
  5. 内存映射与管理:
    • 如果是用户指针内存(USERPTR),函数会初始化用户页表。
    • 如果是 DOORBELLMMIO_REMAP 内存,函数会将 BO 固定在 GTT 中。
    • 最后,函数会将偏移量返回给调用者,以便于后续的内存映射。
  6. 错误处理与回滚:
    • 函数包含多个错误处理路径。如果在内存分配或初始化过程中遇到问题,它会清理已分配的资源,并返回相应的错误代码。

关键概念

  • BO (Buffer Object): 在图形驱动程序中用于表示 GPU 上的内存缓冲区。
  • GTT (Graphics Translation Table): 用于管理系统内存与 GPU 显存之间的映射。
  • VRAM: 显存,GPU 上的高速内存,用于存储需要快速访问的数据,如纹理、 Frame Buffer 等。
  • User Pointer (USERPTR): 允许用户空间的指针直接映射到 GPU 的地址空间,通常用于高效的数据共享。

该函数的作用

这个函数的作用是确保在适当的内存域(VRAM、GTT 等)中分配 GPU 内存,并处理复杂的内存映射需求,同时提供足够的灵活性以应对不同的内存分配场景(如用户指针、DOORBELL 等)。它是 AMD GPU 驱动程序中的一个重要部分,确保了 GPU 内存的高效管理和使用。

内存映射

kfd_ioctl_map_memory_to_gpu

pde

amdgpu_vm_update_pdes

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
amdgpu_vm_pde_update --> vm->update_funcs->update --> amdgpu_vm_sdma_update --> amdgpu_vm_sdma_set_ptes / amdgpu_vm_sdma_copy_ptes

// amdgpu_vm_pt_clear --> vm->update_funcs->update --> amdgpu_vm_sdma_update
// amdgpu_vm_pte_update_flags --> vm->update_funcs->update --> amdgpu_vm_sdma_update

const struct amdgpu_vm_update_funcs amdgpu_vm_sdma_funcs = {
.map_table = amdgpu_vm_sdma_map_table,
.prepare = amdgpu_vm_sdma_prepare,
.update = amdgpu_vm_sdma_update,
.commit = amdgpu_vm_sdma_commit
};

/**
* amdgpu_vm_sdma_set_ptes - helper to call the right asic function
*
* @p: see amdgpu_vm_update_params definition
* @bo: PD/PT to update
* @pe: byte offset of the PDE/PTE, relative to start of PDB/PTB
* @addr: dst addr to write into pe
* @count: number of page entries to update
* @incr: increase next addr by incr bytes
* @flags: hw access flags
*
* Traces the parameters and calls the right asic functions
* to setup the page table using the DMA.
*/
static void amdgpu_vm_sdma_set_ptes(struct amdgpu_vm_update_params *p,
struct amdgpu_bo *bo, uint64_t pe,
uint64_t addr, unsigned count,
uint32_t incr, uint64_t flags)
{
struct amdgpu_ib *ib = p->job->ibs;

pe += amdgpu_gmc_sign_extend(amdgpu_bo_gpu_offset_no_check(bo));
trace_amdgpu_vm_set_ptes(pe, addr, count, incr, flags, p->immediate);
if (count < 3) {
amdgpu_vm_write_pte(p->adev, ib, pe, addr | flags,
count, incr);
} else {
amdgpu_vm_set_pte_pde(p->adev, ib, pe, addr,
count, incr, flags);
}
}

/**
* amdgpu_vm_sdma_copy_ptes - copy the PTEs from mapping
*
* @p: see amdgpu_vm_update_params definition
* @bo: PD/PT to update
* @pe: addr of the page entry
* @count: number of page entries to copy
*
* Traces the parameters and calls the DMA function to copy the PTEs.
*/
static void amdgpu_vm_sdma_copy_ptes(struct amdgpu_vm_update_params *p,
struct amdgpu_bo *bo, uint64_t pe,
unsigned count)
{
struct amdgpu_ib *ib = p->job->ibs;
uint64_t src = ib->gpu_addr;

src += p->num_dw_left * 4;

pe += amdgpu_gmc_sign_extend(amdgpu_bo_gpu_offset_no_check(bo));
trace_amdgpu_vm_copy_ptes(pe, src, count, p->immediate);

amdgpu_vm_copy_pte(p->adev, ib, pe, src, count);
}

#define amdgpu_vm_copy_pte(adev, ib, pe, src, count) ((adev)->vm_manager.vm_pte_funcs->copy_pte((ib), (pe), (src), (count)))
#define amdgpu_vm_write_pte(adev, ib, pe, value, count, incr) ((adev)->vm_manager.vm_pte_funcs->write_pte((ib), (pe), (value), (count), (incr)))
#define amdgpu_vm_set_pte_pde(adev, ib, pe, addr, count, incr, flags) ((adev)->vm_manager.vm_pte_funcs->set_pte_pde((ib), (pe), (addr), (count), (incr), (flags)))

static const struct amdgpu_vm_pte_funcs sdma_v4_0_vm_pte_funcs = {
.copy_pte_num_dw = 7,
.copy_pte = sdma_v4_0_vm_copy_pte,

.write_pte = sdma_v4_0_vm_write_pte,
.set_pte_pde = sdma_v4_0_vm_set_pte_pde,
};

/**
* sdma_v4_0_vm_copy_pte - update PTEs by copying them from the GART
*
* @ib: indirect buffer to fill with commands
* @pe: addr of the page entry
* @src: src addr to copy from
* @count: number of page entries to update
*
* Update PTEs by copying them from the GART using sDMA (VEGA10).
*/
static void sdma_v4_0_vm_copy_pte(struct amdgpu_ib *ib,
uint64_t pe, uint64_t src,
unsigned count)
{
unsigned bytes = count * 8;

ib->ptr[ib->length_dw++] = SDMA_PKT_HEADER_OP(SDMA_OP_COPY) |
SDMA_PKT_HEADER_SUB_OP(SDMA_SUBOP_COPY_LINEAR);
ib->ptr[ib->length_dw++] = bytes - 1;
ib->ptr[ib->length_dw++] = 0; /* src/dst endian swap */
ib->ptr[ib->length_dw++] = lower_32_bits(src);
ib->ptr[ib->length_dw++] = upper_32_bits(src);
ib->ptr[ib->length_dw++] = lower_32_bits(pe);
ib->ptr[ib->length_dw++] = upper_32_bits(pe);

}

/**
* sdma_v4_0_vm_write_pte - update PTEs by writing them manually
*
* @ib: indirect buffer to fill with commands
* @pe: addr of the page entry
* @value: dst addr to write into pe
* @count: number of page entries to update
* @incr: increase next addr by incr bytes
*
* Update PTEs by writing them manually using sDMA (VEGA10).
*/
static void sdma_v4_0_vm_write_pte(struct amdgpu_ib *ib, uint64_t pe,
uint64_t value, unsigned count,
uint32_t incr)
{
unsigned ndw = count * 2;

ib->ptr[ib->length_dw++] = SDMA_PKT_HEADER_OP(SDMA_OP_WRITE) |
SDMA_PKT_HEADER_SUB_OP(SDMA_SUBOP_WRITE_LINEAR);
ib->ptr[ib->length_dw++] = lower_32_bits(pe);
ib->ptr[ib->length_dw++] = upper_32_bits(pe);
ib->ptr[ib->length_dw++] = ndw - 1;
for (; ndw > 0; ndw -= 2) {
ib->ptr[ib->length_dw++] = lower_32_bits(value);
ib->ptr[ib->length_dw++] = upper_32_bits(value);
value += incr;
}
}

/**
* sdma_v4_0_vm_set_pte_pde - update the page tables using sDMA
*
* @ib: indirect buffer to fill with commands
* @pe: addr of the page entry
* @addr: dst addr to write into pe
* @count: number of page entries to update
* @incr: increase next addr by incr bytes
* @flags: access flags
*
* Update the page tables using sDMA (VEGA10).
*/
static void sdma_v4_0_vm_set_pte_pde(struct amdgpu_ib *ib,
uint64_t pe,
uint64_t addr, unsigned count,
uint32_t incr, uint64_t flags)
{
/* for physically contiguous pages (vram) */
ib->ptr[ib->length_dw++] = SDMA_PKT_HEADER_OP(SDMA_OP_PTEPDE);
ib->ptr[ib->length_dw++] = lower_32_bits(pe); /* dst addr */
ib->ptr[ib->length_dw++] = upper_32_bits(pe);
ib->ptr[ib->length_dw++] = lower_32_bits(flags); /* mask */
ib->ptr[ib->length_dw++] = upper_32_bits(flags);
ib->ptr[ib->length_dw++] = lower_32_bits(addr); /* value */
ib->ptr[ib->length_dw++] = upper_32_bits(addr);
ib->ptr[ib->length_dw++] = incr; /* increment size */
ib->ptr[ib->length_dw++] = 0;
ib->ptr[ib->length_dw++] = count - 1; /* number of entries */
}

当缓存中未写入的数据量达到一定水平时,控制器会定期将缓存数据写入驱动器。此写入过程称为“刷新”。
控制器使用两种算法来刷新缓存:基于需求和基于年龄。控制器使用基于需求的算法,直到缓存数据量低于缓存刷新阈值。默认情况下,当 80% 的缓存正在使用时,刷新开始。
在系统管理器中,您可以设置“启动需求缓存刷新”阈值,以最好地支持环境中使用的 I/O 类型。在主要进行写入操作的环境中,您应该将“启动需求缓存刷新”百分比设置为较高,以增加任何新的写入请求都可以由缓存处理而无需转到磁盘的可能性。高百分比设置会限制缓存刷新的次数,以便更多数据保留在缓存中,从而增加更多缓存命中的机会。
在 I/O 不稳定(数据突发)的环境中,您可以使用较低的缓存刷新,以便系统在数据突发之间频繁刷新缓存。在处理各种负载的多样化 I/O 环境中,或者当负载类型未知时,将阈值设置为 50% 是一个不错的中间值。请注意,如果选择的启动百分比低于 80%,则可能会看到性能下降,因为主机读取所需的数据可能不可用。选择较低的百分比还会增加维持缓存级别所需的磁盘写入次数,从而增加系统开销。
基于年龄的算法指定写入数据在有资格刷新到磁盘之前可以保留在缓存中的时间段。控制器使用基于年龄的算法,直到达到缓存刷新阈值。默认值为 10 秒,但此时间段仅在非活动期间计算。您无法在系统管理器中修改刷新时间;相反,您必须使用命令行界面 (CLI) 中的“设置存储阵列”命令。

1
2
3
4
5
6
7
8
9
10
11
static void gfx_v9_0_kiq_invalidate_tlbs(struct amdgpu_ring *kiq_ring,
uint16_t pasid, uint32_t flush_type,
bool all_hub)
{
amdgpu_ring_write(kiq_ring, PACKET3(PACKET3_INVALIDATE_TLBS, 0));
amdgpu_ring_write(kiq_ring,
PACKET3_INVALIDATE_TLBS_DST_SEL(1) |
PACKET3_INVALIDATE_TLBS_ALL_HUB(all_hub) |
PACKET3_INVALIDATE_TLBS_PASID(pasid) |
PACKET3_INVALIDATE_TLBS_FLUSH_TYPE(flush_type));
}

amdgpu_amdkfd_flush_gpu_tlb_pasid

amdgpu_amdkfd_flush_gpu_tlb_pasid –> amdgpu_gmc_flush_gpu_tlb_pasid –> gmc_v9_0_flush_gpu_tlb_pasid –> gfx_v9_0_kiq_invalidate_tlbs

其中,kfd_ioctl_unmap_memory_from_gpu / svm_range_unmap_from_gpus 采用 TLB_FLUSH_HEAVYWEIGHT 策略

第二路径:
gmc_v9_0_flush_gpu_tlb_pasid –> gfx_v9_0_kiq_invalidate_tlbs

gmc_v9_0_flush_gpu_tlb_pasid –> gmc_v9_0_flush_gpu_tlb(无pasid flush)

gmc_v9_0_hw_init –> gmc_v9_0_flush_gpu_tlb

amdgpu_gmc_flush_gpu_tlb –> gmc_v9_0_flush_gpu_tlb

amdgpu_gmc_flush_gpu_tlb

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
int amdgpu_tmz = -1; /* auto */

/**
* DOC: tmz (int)
* Trusted Memory Zone (TMZ) is a method to protect data being written
* to or read from memory.
*
* The default value: 0 (off). TODO: change to auto till it is completed.
*/
MODULE_PARM_DESC(tmz, "Enable TMZ feature (-1 = auto (default), 0 = off, 1 = on)");
module_param_named(tmz, amdgpu_tmz, int, 0444);

/**
* amdgpu_gmc_tmz_set -- check and set if a device supports TMZ
* @adev: amdgpu_device pointer
*
* Check and set if an the device @adev supports Trusted Memory
* Zones (TMZ).
*/
void amdgpu_gmc_tmz_set(struct amdgpu_device *adev)
{
switch (adev->asic_type) {
default:
adev->gmc.tmz_enabled = false;
dev_info(adev->dev, "Trusted Memory Zone (TMZ) feature not supported\n");
break;
}
}
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
/**
* amdgpu_ib_schedule - schedule an IB (Indirect Buffer) on the ring
*
* @ring: ring index the IB is associated with
* @num_ibs: number of IBs to schedule
* @ibs: IB objects to schedule
* @job: job to schedule
* @f: fence created during this submission
*
* Schedule an IB on the associated ring (all asics).
* Returns 0 on success, error on failure.
*
* On SI, there are two parallel engines fed from the primary ring,
* the CE (Constant Engine) and the DE (Drawing Engine). Since
* resource descriptors have moved to memory, the CE allows you to
* prime the caches while the DE is updating register state so that
* the resource descriptors will be already in cache when the draw is
* processed. To accomplish this, the userspace driver submits two
* IBs, one for the CE and one for the DE. If there is a CE IB (called
* a CONST_IB), it will be put on the ring prior to the DE IB. Prior
* to SI there was just a DE IB.
*/
int amdgpu_ib_schedule(struct amdgpu_ring *ring, unsigned num_ibs,
struct amdgpu_ib *ibs, struct amdgpu_job *job,
struct dma_fence **f)
{
/* Setup initial TMZiness and send it off.
*/
secure = false;
if (job && ring->funcs->emit_frame_cntl) {
secure = ib->flags & AMDGPU_IB_FLAGS_SECURE;
amdgpu_ring_emit_frame_cntl(ring, true, secure);
}
}

#define amdgpu_ring_emit_frame_cntl(r, b, s) (r)->funcs->emit_frame_cntl((r), (b), (s))


#define PACKET3_FRAME_CONTROL 0x90
# define FRAME_TM (1 << 0)
# define FRAME_CMD(x) ((x) << 28)
/*
* x=0: tmz_begin
* x=1: tmz_end
*/

static void gfx_v9_0_ring_emit_frame_cntl(struct amdgpu_ring *ring, bool start,
bool secure)
{
uint32_t v = secure ? FRAME_TMZ : 0;

amdgpu_ring_write(ring, PACKET3(PACKET3_FRAME_CONTROL, 0));
amdgpu_ring_write(ring, v | FRAME_CMD(start ? 0 : 1));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* amdgpu_ttm_copy_mem_to_mem - Helper function for copy
* @adev: amdgpu device
* @src: buffer/address where to read from
* @dst: buffer/address where to write to
* @size: number of bytes to copy
* @tmz: if a secure copy should be used
* @resv: resv object to sync to
* @f: Returns the last fence if multiple jobs are submitted.
*
* The function copies @size bytes from {src->mem + src->offset} to
* {dst->mem + dst->offset}. src->bo and dst->bo could be same BO for a
* move and different for a BO to BO copy.
*
*/
int amdgpu_ttm_copy_mem_to_mem(struct amdgpu_device *adev,
const struct amdgpu_copy_mem *src,
const struct amdgpu_copy_mem *dst,
uint64_t size, bool tmz,
struct dma_resv *resv,
struct dma_fence **f)

--> amdgpu_copy_buffer() --> amdgpu_emit_copy_buffer() --> sdma_v4_0_emit_copy_buffer()

amdgpu_ttm_copy_mem_to_mem

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
/*define for tmz field*/
#define SDMA_PKT_COPY_LINEAR_HEADER_tmz_offset 0
#define SDMA_PKT_COPY_LINEAR_HEADER_tmz_mask 0x00000001
#define SDMA_PKT_COPY_LINEAR_HEADER_tmz_shift 18
#define SDMA_PKT_COPY_LINEAR_HEADER_TMZ(x) (((x) & SDMA_PKT_COPY_LINEAR_HEADER_tmz_mask) << SDMA_PKT_COPY_LINEAR_HEADER_tmz_shift)

int amdgpu_copy_buffer(struct amdgpu_ring *ring, uint64_t src_offset,
uint64_t dst_offset, uint32_t byte_count,
struct dma_resv *resv,
struct dma_fence **fence, bool direct_submit,
bool vm_needs_flush, bool tmz)

--> amdgpu_emit_copy_buffer(adev, &job->ibs[0], src_offset,
dst_offset, cur_size_in_bytes, tmz);

#define amdgpu_emit_copy_buffer(adev, ib, s, d, b, t) (adev)->mman.buffer_funcs->emit_copy_buffer((ib), (s), (d), (b), (t))

void (*emit_copy_buffer)(struct amdgpu_ib *ib,
/* src addr in bytes */
uint64_t src_offset,
/* dst addr in bytes */
uint64_t dst_offset,
/* number of byte to transfer */
uint32_t byte_count,
bool tmz);

/**
* sdma_v4_0_emit_copy_buffer - copy buffer using the sDMA engine
*
* @ib: indirect buffer to copy to
* @src_offset: src GPU address
* @dst_offset: dst GPU address
* @byte_count: number of bytes to xfer
* @tmz: if a secure copy should be used
*
* Copy GPU buffers using the DMA engine (VEGA10/12).
* Used by the amdgpu ttm implementation to move pages if
* registered as the asic copy callback.
*/
static void sdma_v4_0_emit_copy_buffer(struct amdgpu_ib *ib,
uint64_t src_offset,
uint64_t dst_offset,
uint32_t byte_count,
bool tmz)
{
ib->ptr[ib->length_dw++] = SDMA_PKT_HEADER_OP(SDMA_OP_COPY) |
SDMA_PKT_HEADER_SUB_OP(SDMA_SUBOP_COPY_LINEAR) |
SDMA_PKT_COPY_LINEAR_HEADER_TMZ(tmz ? 1 : 0);
ib->ptr[ib->length_dw++] = byte_count - 1;
ib->ptr[ib->length_dw++] = 0; /* src/dst endian swap */
ib->ptr[ib->length_dw++] = lower_32_bits(src_offset);
ib->ptr[ib->length_dw++] = upper_32_bits(src_offset);
ib->ptr[ib->length_dw++] = lower_32_bits(dst_offset);
ib->ptr[ib->length_dw++] = upper_32_bits(dst_offset);
}
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
static void amdgpu_ttm_gart_bind(struct amdgpu_device *adev,
struct ttm_buffer_object *tbo,
uint64_t flags)
{
if (amdgpu_bo_encrypted(abo))
flags |= AMDGPU_PTE_TMZ;
}

--> amdgpu_gart_bind(adev, gtt->offset, page_idx, gtt->ttm.dma_address, flags);

/**
* amdgpu_gart_bind - bind pages into the gart page table
*
* @adev: amdgpu_device pointer
* @offset: offset into the GPU's gart aperture
* @pages: number of pages to bind
* @dma_addr: DMA addresses of pages
* @flags: page table entry flags
*
* Binds the requested pages to the gart page table
* (all asics).
* Returns 0 for success, -EINVAL for failure.
*/
void amdgpu_gart_bind(struct amdgpu_device *adev, uint64_t offset,
int pages, dma_addr_t *dma_addr,
uint64_t flags)

--> amdgpu_gart_map(adev, offset, pages, dma_addr, flags, adev->gart.ptr); --> amdgpu_gmc_set_pte_pde(adev, dst, t, page_base, flags);

amdgpu_gart_map

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
/**
* amdgpu_ttm_map_buffer - Map memory into the GART windows
* @bo: buffer object to map
* @mem: memory object to map
* @mm_cur: range to map
* @window: which GART window to use
* @ring: DMA ring to use for the copy
* @tmz: if we should setup a TMZ enabled mapping
* @size: in number of bytes to map, out number of bytes mapped
* @addr: resulting address inside the MC address space
*
* Setup one of the GART windows to access a specific piece of memory or return
* the physical address for local memory.
*/
static int amdgpu_ttm_map_buffer(struct ttm_buffer_object *bo,
struct ttm_resource *mem,
struct amdgpu_res_cursor *mm_cur,
unsigned window, struct amdgpu_ring *ring,
bool tmz, uint64_t *size, uint64_t *addr)

--> amdgpu_gart_map(adev, 0, num_pages, dma_addr, flags, cpu_addr);

/**
* amdgpu_gart_map - map dma_addresses into GART entries
*
* @adev: amdgpu_device pointer
* @offset: offset into the GPU's gart aperture
* @pages: number of pages to bind
* @dma_addr: DMA addresses of pages
* @flags: page table entry flags
* @dst: CPU address of the gart table
*
* Map the dma_addresses into GART entries (all asics).
* Returns 0 for success, -EINVAL for failure.
*/
void amdgpu_gart_map(struct amdgpu_device *adev, uint64_t offset,
int pages, dma_addr_t *dma_addr, uint64_t flags,
void *dst)

--> amdgpu_gmc_set_pte_pde(adev, dst, t, page_base, flags);

/**
* amdgpu_gmc_set_pte_pde - update the page tables using CPU
*
* @adev: amdgpu_device pointer
* @cpu_pt_addr: cpu address of the page table
* @gpu_page_idx: entry in the page table to update
* @addr: dst addr to write into pte/pde
* @flags: access flags
*
* Update the page tables using CPU.
*/
int amdgpu_gmc_set_pte_pde(struct amdgpu_device *adev, void *cpu_pt_addr,
uint32_t gpu_page_idx, uint64_t addr,
uint64_t flags)
{
void __iomem *ptr = (void *)cpu_pt_addr;
uint64_t value;

/*
* The following is for PTE only. GART does not have PDEs.
*/
value = addr & 0x0000FFFFFFFFF000ULL;
value |= flags;
writeq(value, ptr + (gpu_page_idx * 8));

return 0;
}
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
// amdgpu-5.7.0-0\include\uapi\drm\amdgpu_drm.h:

/* Flag that BO will be encrypted and that the TMZ bit should be
* set in the PTEs when mapping this buffer via GPUVM or
* accessing it with various hw blocks
*/
#define AMDGPU_GEM_CREATE_ENCRYPTED (1 << 10)

/* Flag the IB as secure (TMZ)
*/
#define AMDGPU_IB_FLAGS_SECURE (1 << 5)

#define AMDGPU_IDS_FLAGS_TMZ 0x4


/**
* amdgpu_bo_encrypted - test if the BO is encrypted
* @bo: pointer to a buffer object
*
* Return true if the buffer object is encrypted, false otherwise.
*/
static inline bool amdgpu_bo_encrypted(struct amdgpu_bo *bo)
{
return bo->flags & AMDGPU_GEM_CREATE_ENCRYPTED;
}

/*
* GEM ioctls.
*/
int amdgpu_gem_create_ioctl(struct drm_device *dev, void *data,
struct drm_file *filp) {
if (!amdgpu_is_tmz(adev) && (flags & AMDGPU_GEM_CREATE_ENCRYPTED)) {
DRM_NOTE_ONCE("Cannot allocate secure buffer since TMZ is disabled\n");
return -EINVAL;
}
}

static inline bool amdgpu_is_tmz(struct amdgpu_device *adev)
{
return adev->gmc.tmz_enabled;
}
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
/* RV+ */
#define AMDGPU_PTE_TMZ (1ULL << 3)

/**
* amdgpu_vm_bo_update - update all BO mappings in the vm page table
*
* @adev: amdgpu_device pointer
* @bo_va: requested BO and VM object
* @clear: if true clear the entries
*
* Fill in the page table entries for @bo_va.
*
* Returns:
* 0 for success, -EINVAL for failure.
*/
int amdgpu_vm_bo_update(struct amdgpu_device *adev, struct amdgpu_bo_va *bo_va,
bool clear)
{
if (bo) {
flags = amdgpu_ttm_tt_pte_flags(adev, bo->tbo.ttm, mem);

if (amdgpu_bo_encrypted(bo))
flags |= AMDGPU_PTE_TMZ;

// ...
--> amdgpu_vm_update_range --> amdgpu_vm_ptes_update
}

/**
* amdgpu_vm_ptes_update - make sure that page tables are valid
*
* @params: see amdgpu_vm_update_params definition
* @start: start of GPU address range
* @end: end of GPU address range
* @dst: destination address to map to, the next dst inside the function
* @flags: mapping flags
*
* Update the page tables in the range @start - @end.
*
* Returns:
* 0 for success, -EINVAL for failure.
*/
int amdgpu_vm_ptes_update(struct amdgpu_vm_update_params *params,
uint64_t start, uint64_t end,
uint64_t dst, uint64_t flags, struct list_head *free_list)
1
2
3
4
5
6
7
8
9
10
11
12
13
#define GDS_VM_PROTECTION_FAULT__TMZ__SHIFT
#define GDS_VM_PROTECTION_FAULT__TMZ_MASK

// addressBlock: mmhub_l1tlb_vml1dec:1
#define mmVML1_1_MC_VM_MX_L1_TLB0_STATUS_DEFAULT 0x00000000
#define mmVML1_1_MC_VM_MX_L1_TLB1_STATUS_DEFAULT 0x00000000
#define mmVML1_1_MC_VM_MX_L1_TLB2_STATUS_DEFAULT 0x00000000
#define mmVML1_1_MC_VM_MX_L1_TLB3_STATUS_DEFAULT 0x00000000
#define mmVML1_1_MC_VM_MX_L1_TLB4_STATUS_DEFAULT 0x00000000
#define mmVML1_1_MC_VM_MX_L1_TLB5_STATUS_DEFAULT 0x00000000
#define mmVML1_1_MC_VM_MX_L1_TLB6_STATUS_DEFAULT 0x00000000
#define mmVML1_1_MC_VM_MX_L1_TLB7_STATUS_DEFAULT 0x00000000
#define mmVML1_1_MC_VM_MX_L1_TMZ_CNTL_DEFAULT 0x00000000

拓展