Skip to content

Commit 0df8326

Browse files
hoshinolinajannau
authored andcommitted
drm/asahi: crashdump: Add crash dumper module
Signed-off-by: Asahi Lina <lina@asahilina.net>
1 parent 1c51814 commit 0df8326

2 files changed

Lines changed: 264 additions & 0 deletions

File tree

drivers/gpu/drm/asahi/asahi.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
mod alloc;
77
mod buffer;
88
mod channel;
9+
mod crashdump;
910
mod debug;
1011
mod driver;
1112
mod event;

drivers/gpu/drm/asahi/crashdump.rs

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
// SPDX-License-Identifier: GPL-2.0-only OR MIT
2+
3+
//! GPU crash dump formatter
4+
//!
5+
//! Takes a raw dump of firmware/kernel mapped pages from `pgtable` and formats it into
6+
//! an ELF core dump suitable for dumping into userspace.
7+
8+
use core::mem::size_of;
9+
10+
use kernel::{error::Result, page::Page, prelude::*, types::Owned};
11+
12+
use crate::hw;
13+
use crate::pgtable::{self, DumpedPage, Prot, UAT_PGSZ};
14+
use crate::util::align;
15+
use kernel::uapi;
16+
17+
pub(crate) struct CrashDump {
18+
headers: KVVec<u8>,
19+
pages: KVVec<Owned<Page>>,
20+
}
21+
22+
const NOTE_NAME_AGX: &str = &"AGX";
23+
const NOTE_AGX_DUMP_INFO: u32 = 1;
24+
25+
const NOTE_NAME_RTKIT: &str = &"RTKIT";
26+
const NOTE_RTKIT_CRASHLOG: u32 = 1;
27+
28+
#[repr(C)]
29+
pub(crate) struct AGXDumpInfo {
30+
initdata_address: u64,
31+
chip_id: u32,
32+
gpu_gen: hw::GpuGen,
33+
gpu_variant: hw::GpuVariant,
34+
gpu_rev: hw::GpuRevision,
35+
total_active_cores: u32,
36+
firmware_version: [u32; 6],
37+
}
38+
39+
struct ELFNote {
40+
name: &'static str,
41+
ty: u32,
42+
data: KVVec<u8>,
43+
}
44+
45+
pub(crate) struct CrashDumpBuilder {
46+
page_dump: KVVec<DumpedPage>,
47+
notes: KVec<ELFNote>,
48+
}
49+
50+
// Helper to convert ELF headers into byte slices
51+
// TODO: Hook this up into kernel::AsBytes somehow
52+
unsafe trait AsBytes: Sized {
53+
fn as_bytes(&self) -> &[u8] {
54+
// SAFETY: This trait is only implemented for types with no padding bytes
55+
unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, size_of::<Self>()) }
56+
}
57+
fn slice_as_bytes(slice: &[Self]) -> &[u8] {
58+
// SAFETY: This trait is only implemented for types with no padding bytes
59+
unsafe {
60+
core::slice::from_raw_parts(
61+
slice.as_ptr() as *const u8,
62+
slice.len() * size_of::<Self>(),
63+
)
64+
}
65+
}
66+
}
67+
68+
// SAFETY: This type has no padding
69+
unsafe impl AsBytes for uapi::Elf64_Ehdr {}
70+
// SAFETY: This type has no padding
71+
unsafe impl AsBytes for uapi::Elf64_Phdr {}
72+
// SAFETY: This type has no padding
73+
unsafe impl AsBytes for uapi::Elf64_Nhdr {}
74+
// SAFETY: This type has no padding
75+
unsafe impl AsBytes for AGXDumpInfo {}
76+
77+
const FIRMWARE_ENTRYPOINT: u64 = 0xFFFFFF8000000000u64;
78+
79+
impl CrashDumpBuilder {
80+
pub(crate) fn new(page_dump: KVVec<DumpedPage>) -> Result<CrashDumpBuilder> {
81+
Ok(CrashDumpBuilder {
82+
page_dump,
83+
notes: KVec::new(),
84+
})
85+
}
86+
87+
pub(crate) fn add_agx_info(
88+
&mut self,
89+
cfg: &hw::HwConfig,
90+
dyncfg: &hw::DynConfig,
91+
initdata_address: u64,
92+
) -> Result {
93+
let mut info = AGXDumpInfo {
94+
chip_id: cfg.chip_id,
95+
gpu_gen: dyncfg.id.gpu_gen,
96+
gpu_variant: dyncfg.id.gpu_variant,
97+
gpu_rev: dyncfg.id.gpu_rev,
98+
total_active_cores: dyncfg.id.total_active_cores,
99+
firmware_version: [0; 6],
100+
initdata_address,
101+
};
102+
info.firmware_version[..dyncfg.firmware_version.len().min(6)]
103+
.copy_from_slice(&dyncfg.firmware_version);
104+
105+
let mut data = KVVec::new();
106+
data.extend_from_slice(info.as_bytes(), GFP_KERNEL)?;
107+
108+
self.notes.push(
109+
ELFNote {
110+
name: NOTE_NAME_AGX,
111+
ty: NOTE_AGX_DUMP_INFO,
112+
data,
113+
},
114+
GFP_KERNEL,
115+
)?;
116+
Ok(())
117+
}
118+
119+
pub(crate) fn add_crashlog(&mut self, crashlog: &[u8]) -> Result {
120+
let mut data = KVVec::new();
121+
data.extend_from_slice(&crashlog, GFP_KERNEL)?;
122+
123+
self.notes.push(
124+
ELFNote {
125+
name: NOTE_NAME_RTKIT,
126+
ty: NOTE_RTKIT_CRASHLOG,
127+
data,
128+
},
129+
GFP_KERNEL,
130+
)?;
131+
132+
Ok(())
133+
}
134+
135+
pub(crate) fn finalize(self) -> Result<CrashDump> {
136+
let CrashDumpBuilder { page_dump, notes } = self;
137+
138+
let mut ehdr: uapi::Elf64_Ehdr = Default::default();
139+
140+
ehdr.e_ident[uapi::EI_MAG0 as usize..=uapi::EI_MAG3 as usize].copy_from_slice(b"\x7fELF");
141+
ehdr.e_ident[uapi::EI_CLASS as usize] = uapi::ELFCLASS64 as u8;
142+
ehdr.e_ident[uapi::EI_DATA as usize] = uapi::ELFDATA2LSB as u8;
143+
ehdr.e_ident[uapi::EI_VERSION as usize] = uapi::EV_CURRENT as u8;
144+
ehdr.e_type = uapi::ET_CORE as u16;
145+
ehdr.e_machine = uapi::EM_AARCH64 as u16;
146+
ehdr.e_version = uapi::EV_CURRENT as u32;
147+
ehdr.e_entry = FIRMWARE_ENTRYPOINT;
148+
ehdr.e_ehsize = core::mem::size_of::<uapi::Elf64_Ehdr>() as u16;
149+
ehdr.e_phentsize = core::mem::size_of::<uapi::Elf64_Phdr>() as u16;
150+
151+
let phdr_offset = core::mem::size_of::<uapi::Elf64_Ehdr>();
152+
153+
// PHDRs come after the ELF header
154+
ehdr.e_phoff = phdr_offset as u64;
155+
156+
let mut phdrs = KVVec::new();
157+
158+
// First PHDR is the NOTE section
159+
phdrs.push(
160+
uapi::Elf64_Phdr {
161+
p_type: uapi::PT_NOTE,
162+
p_flags: uapi::PF_R,
163+
p_align: 1,
164+
..Default::default()
165+
},
166+
GFP_KERNEL,
167+
)?;
168+
169+
// Generate the page phdrs. The offset will be fixed up later.
170+
let mut off: usize = 0;
171+
let mut next = None;
172+
let mut pages: KVVec<Owned<Page>> = KVVec::new();
173+
174+
for mut page in page_dump {
175+
let vaddr = page.iova;
176+
let paddr = page.pte & pgtable::PTE_ADDR_BITS;
177+
let flags = Prot::from_pte(page.pte).elf_flags();
178+
let valid = page.data.is_some();
179+
let cur = (vaddr, paddr, flags, valid);
180+
if Some(cur) != next {
181+
phdrs.push(
182+
uapi::Elf64_Phdr {
183+
p_type: uapi::PT_LOAD,
184+
p_offset: if valid { off as u64 } else { 0 },
185+
p_vaddr: vaddr,
186+
p_paddr: paddr,
187+
p_filesz: if valid { UAT_PGSZ as u64 } else { 0 },
188+
p_memsz: UAT_PGSZ as u64,
189+
p_flags: flags,
190+
p_align: UAT_PGSZ as u64,
191+
..Default::default()
192+
},
193+
GFP_KERNEL,
194+
)?;
195+
if valid {
196+
off += UAT_PGSZ;
197+
}
198+
} else {
199+
let ph = phdrs.last_mut().unwrap();
200+
ph.p_memsz += UAT_PGSZ as u64;
201+
if valid {
202+
ph.p_filesz += UAT_PGSZ as u64;
203+
off += UAT_PGSZ;
204+
}
205+
}
206+
if let Some(data_page) = page.data.take() {
207+
pages.push(data_page, GFP_KERNEL)?;
208+
}
209+
next = Some((
210+
vaddr + UAT_PGSZ as u64,
211+
paddr + UAT_PGSZ as u64,
212+
flags,
213+
valid,
214+
));
215+
}
216+
217+
ehdr.e_phnum = phdrs.len() as u16;
218+
219+
let note_offset = phdr_offset + size_of::<uapi::Elf64_Phdr>() * phdrs.len();
220+
221+
let mut note_data: KVVec<u8> = KVVec::new();
222+
223+
for note in notes {
224+
let hdr = uapi::Elf64_Nhdr {
225+
n_namesz: note.name.len() as u32 + 1,
226+
n_descsz: note.data.len() as u32,
227+
n_type: note.ty,
228+
};
229+
note_data.extend_from_slice(hdr.as_bytes(), GFP_KERNEL)?;
230+
note_data.extend_from_slice(note.name.as_bytes(), GFP_KERNEL)?;
231+
note_data.push(0, GFP_KERNEL)?;
232+
while note_data.len() & 3 != 0 {
233+
note_data.push(0, GFP_KERNEL)?;
234+
}
235+
note_data.extend_from_slice(&note.data, GFP_KERNEL)?;
236+
while note_data.len() & 3 != 0 {
237+
note_data.push(0, GFP_KERNEL)?;
238+
}
239+
}
240+
241+
// NOTE section comes after the PHDRs
242+
phdrs[0].p_offset = note_offset as u64;
243+
phdrs[0].p_filesz = note_data.len() as u64;
244+
245+
// Align data section to the page size
246+
let data_offset = align(note_offset + note_data.len(), UAT_PGSZ);
247+
248+
// Fix up data PHDR offsets
249+
for phdr in &mut phdrs[1..] {
250+
phdr.p_offset += data_offset as u64;
251+
}
252+
253+
// Build ELF header buffer
254+
let mut headers: KVVec<u8> = KVVec::from_elem(0, data_offset, GFP_KERNEL)?;
255+
256+
headers[0..size_of::<uapi::Elf64_Ehdr>()].copy_from_slice(ehdr.as_bytes());
257+
headers[phdr_offset..phdr_offset + phdrs.len() * size_of::<uapi::Elf64_Phdr>()]
258+
.copy_from_slice(AsBytes::slice_as_bytes(&phdrs));
259+
headers[note_offset..note_offset + note_data.len()].copy_from_slice(&note_data);
260+
261+
Ok(CrashDump { headers, pages })
262+
}
263+
}

0 commit comments

Comments
 (0)