obkrnl/dmem/
mod.rs

1use crate::MemoryInfo;
2use crate::config::{Dipsw, PAGE_SHIFT};
3use crate::context::config;
4use alloc::sync::Arc;
5use core::num::NonZero;
6
7/// Implementation of Direct Memory system.
8pub struct Dmem {
9    mode: usize,
10    config: &'static DmemConfig,
11    game_end: u64,
12}
13
14impl Dmem {
15    /// See `initialize_dmem` on the Orbis for a reference.
16    ///
17    /// # Reference offsets
18    /// | Version | Offset |
19    /// |---------|--------|
20    /// |PS4 11.00|0x3F5C20|
21    pub fn new(mi: &mut MemoryInfo) -> Arc<Self> {
22        // Reserve address for msgbuf.
23        let haddr = Self::reserve_phys(mi, 0x1000000.try_into().unwrap(), 1);
24
25        if haddr == 0 {
26            panic!("no available memory for high-address memory");
27        }
28
29        // TODO: Invoke bootparam_get_ddr3_capacity.
30        let mode = Self::load_mode(mi);
31
32        if (0x80C6C0C4u64 & (1 << mode)) != 0 {
33            panic!("game DMEM size is not configured");
34        }
35
36        // Allocate game DMEM.
37        let dc = DMEM_CONFIGS[mode].as_ref().unwrap();
38        let game = Self::reserve_phys(mi, dc.game_size, 0x8000000);
39
40        if game == 0 {
41            panic!("not enough memory for game DMEM");
42        }
43
44        // TODO: There is an unknown call here.
45        let game_end = game + dc.game_size.get();
46
47        if (0x7F393733u64 & (1 << mode)) != 0 {
48            // Get alignment for mini-app DMEM.
49            let align = if config().unknown_dmem1() == 0 {
50                0x8000000i64
51            } else {
52                0x200000i64
53            };
54
55            // Allocate mini-app DMEM.
56            let size = dc.mini_size;
57            let mini = match dc.mini_shared {
58                true => (-align) as u64 & (game_end - (dc.fmem_max.get() + size)),
59                false => todo!(),
60            };
61
62            if mini == 0 {
63                panic!("not enough memory for mini-app DMEM");
64            }
65
66            // TODO: Invoke pmap_change_attr.
67        }
68
69        if (0x7F393F3Bu64 & (1 << mode)) != 0 {
70            let size = dc.vsh_size;
71            let vsh = Self::reserve_phys(mi, size, 0x200000);
72
73            if vsh == 0 {
74                panic!("not enough memory for VSH DMEM");
75            }
76
77            // TODO: There are some write to unknow variable here.
78            // TODO: Invoke pmap_change_attr.
79        }
80
81        if (0x47000703u64 & (1 << mode)) != 0 {
82            todo!()
83        }
84
85        // TODO: There is a write to unknown variable here.
86        if (0x47010703u64 & (1 << mode)) != 0 {
87            todo!()
88        }
89
90        // Allocate vision DMEM.
91        let vision = Self::reserve_phys(mi, 0x1000000.try_into().unwrap(), 0x200000);
92
93        if vision == 0 {
94            panic!("not enough memory for vision DMEM");
95        }
96
97        // TODO: Invoke pmap_change_attr.
98        // TODO: There are some write to unknown variables here.
99        let mut unk = dc.unk1;
100
101        if ((0x80C6C0C4u64 + 0x20212022u64) & (1 << mode)) == 0 {
102            loop {
103                let (size, align) = if unk < 0x200000 {
104                    // TODO: Verify if 0x4000 is actually a (virtual) page size.
105                    (unk, 0x4000)
106                } else {
107                    (0x200000, 0x200000)
108                };
109
110                // TODO: Invoke pmap_change_attr.
111                let addr = Self::reserve_phys(mi, size.try_into().unwrap(), align);
112
113                if addr == 0 {
114                    // TODO: Figure out the name of this memory.
115                    panic!("failed to reserve memory");
116                }
117
118                // Check if completed.
119                unk -= size;
120
121                if unk == 0 {
122                    break;
123                }
124            }
125        }
126
127        Self::adjust_pmap(mi, game_end - dc.fmem_max.get(), game_end);
128        Self::adjust_pmap(
129            mi,
130            haddr,
131            haddr + 0xC00000 + if mi.unk != 0 { 0x400000 } else { 0 },
132        );
133
134        mi.end_page = mi.physmap[mi.physmap_last + 1] >> PAGE_SHIFT;
135
136        Arc::new(Self {
137            mode,
138            config: dc,
139            game_end,
140        })
141    }
142
143    pub fn mode(&self) -> usize {
144        self.mode
145    }
146
147    pub fn config(&self) -> &'static DmemConfig {
148        self.config
149    }
150
151    pub fn game_end(&self) -> u64 {
152        self.game_end
153    }
154
155    /// # Reference offsets
156    /// | Version | Offset |
157    /// |---------|--------|
158    /// |PS4 11.00|0x3F64C0|
159    fn reserve_phys(mi: &mut MemoryInfo, size: NonZero<u64>, align: i64) -> u64 {
160        let mut i = mi.physmap_last;
161
162        loop {
163            let start = mi.physmap[i];
164            let end = mi.physmap[i + 1];
165            let addr = (end - size.get()) & ((-align) as u64);
166
167            if addr >= start {
168                let aligned_end = addr + size.get();
169
170                // Check if this take the whole block.
171                if (addr == start) && (aligned_end == end) {
172                    mi.physmap.copy_within((i + 2).., i);
173                    mi.physmap_last -= 2;
174                    return addr;
175                }
176
177                // Check if this create a hole in the block.
178                if (addr != start) && (aligned_end != end) {
179                    mi.physmap.copy_within(i..(mi.physmap_last + 2), i + 2);
180                    mi.physmap[i + 1] = addr;
181                    mi.physmap[i + 2] = aligned_end;
182                    mi.physmap_last += 2;
183                    return addr;
184                }
185
186                // Check if this take the end of the block.
187                if addr != start {
188                    assert_eq!(aligned_end, end);
189                    mi.physmap[i + 1] = addr;
190                    return addr;
191                }
192
193                // Take the start of the block.
194                mi.physmap[i] = aligned_end;
195
196                return addr;
197            }
198
199            // Move to lower map.
200            if i < 2 {
201                break 0;
202            }
203
204            i -= 2;
205        }
206    }
207
208    /// # Reference offsets
209    /// | Version | Offset |
210    /// |---------|--------|
211    /// |PS4 11.00|0x3F65B0|
212    fn adjust_pmap(mi: &mut MemoryInfo, start: u64, end: u64) {
213        // Orbis also check if physmap_last + 2 is less than one. We don't do this because it is
214        // impossible for the value to be zero.
215        let mut idx = 0;
216
217        if start > mi.physmap[0] {
218            // Find the first map with address greater or equal start address.
219            let mut found = true;
220
221            for i in (2..).step_by(2) {
222                idx = i;
223
224                if idx > mi.physmap_last {
225                    found = false;
226                    break;
227                }
228
229                if mi.physmap[idx] >= start {
230                    break;
231                }
232            }
233
234            // TODO: What is this?
235            let mut empty = false;
236
237            if found {
238                empty = mi.physmap[idx] == end;
239
240                if (mi.physmap[idx - 1] == start) && (mi.physmap[idx] == end) {
241                    todo!()
242                }
243            }
244
245            if mi.physmap[idx - 1] == start {
246                todo!()
247            }
248
249            if !empty {
250                if !found {
251                    mi.physmap[idx] = start;
252                    mi.physmap[idx | 1] = end;
253                    mi.physmap_last += 2;
254                    return;
255                }
256
257                todo!()
258            }
259        } else {
260            todo!()
261        }
262
263        mi.physmap[idx] = start;
264    }
265
266    /// # Reference offsets
267    /// | Version | Offset |
268    /// |---------|--------|
269    /// |PS4 11.00|0x3F5B10|
270    fn load_mode(mi: &MemoryInfo) -> usize {
271        // Ths PS4 cache the calculation of this value here but we move it to Dmem struct instead.
272        let c = config();
273        let v = if c.unknown_dmem1() == 0 {
274            if c.dipsw(Dipsw::Unk97) {
275                // TODO: Figure out the name of this constant.
276                3
277            } else if c.dipsw(Dipsw::Unk0) && !c.dipsw(Dipsw::Unk24) {
278                // TODO: Figure out the name of 3 constant.
279                if c.dipsw(Dipsw::Unk16) && c.dipsw(Dipsw::Unk17) && (mi.unk & 3) == 3 {
280                    // TODO: Figure out the name of this constant.
281                    6
282                } else {
283                    todo!()
284                }
285            } else {
286                // TODO: Figure out the name of this constant.
287                4
288            }
289        } else {
290            // TODO: Figure out the name of this constant.
291            5
292        };
293
294        v + usize::try_from(mi.unk).unwrap() * 8
295    }
296}
297
298/// Configurations set for each DMEM mode.
299pub struct DmemConfig {
300    pub name: &'static str,
301    pub game_size: NonZero<u64>,
302    pub fmem_max: NonZero<u64>,
303    pub mini_size: u64,
304    pub mini_shared: bool,
305    pub vsh_size: NonZero<u64>,
306    pub unk1: u64,
307}
308
309// TODO: It is likely to be more than 21 entries on PS4 11.00.
310static DMEM_CONFIGS: [Option<DmemConfig>; 21] = [
311    Some(DmemConfig {
312        name: "BC8 normal",
313        game_size: NonZero::new(0x148000000).unwrap(),
314        fmem_max: NonZero::new(0x40000000).unwrap(),
315        mini_size: 0x30000000,
316        mini_shared: true,
317        vsh_size: NonZero::new(0x17C00000).unwrap(),
318        unk1: 0x13600000,
319    }),
320    Some(DmemConfig {
321        name: "BC8 large",
322        game_size: NonZero::new(0x170000000).unwrap(),
323        fmem_max: NonZero::new(0x40000000).unwrap(),
324        mini_size: 0x30000000,
325        mini_shared: true,
326        vsh_size: NonZero::new(0x16C00000).unwrap(),
327        unk1: 0,
328    }),
329    None,
330    Some(DmemConfig {
331        name: "BC8 kratos",
332        game_size: NonZero::new(0x148000000).unwrap(),
333        fmem_max: NonZero::new(0x40000000).unwrap(),
334        mini_size: 0,
335        mini_shared: false,
336        vsh_size: NonZero::new(0x1CA00000).unwrap(),
337        unk1: 0xA00000,
338    }),
339    Some(DmemConfig {
340        name: "BC8 release",
341        game_size: NonZero::new(0x148000000).unwrap(),
342        fmem_max: NonZero::new(0x40000000).unwrap(),
343        mini_size: 0x30000000,
344        mini_shared: false,
345        vsh_size: NonZero::new(0x1A800000).unwrap(),
346        unk1: 0xA00000,
347    }),
348    Some(DmemConfig {
349        name: "BC8 CS",
350        game_size: NonZero::new(0x124000000).unwrap(),
351        fmem_max: NonZero::new(0x4000000).unwrap(),
352        mini_size: 0x58800000,
353        mini_shared: false,
354        vsh_size: NonZero::new(0x28200000).unwrap(),
355        unk1: 0,
356    }),
357    None,
358    None,
359    Some(DmemConfig {
360        name: "BC16 normal",
361        game_size: NonZero::new(0x148000000).unwrap(),
362        fmem_max: NonZero::new(0x40000000).unwrap(),
363        mini_size: 0x30000000,
364        mini_shared: false,
365        vsh_size: NonZero::new(0x1C200000).unwrap(),
366        unk1: 0x17FB00000,
367    }),
368    Some(DmemConfig {
369        name: "BC16 large",
370        game_size: NonZero::new(0x28C000000).unwrap(),
371        fmem_max: NonZero::new(0x5C000000).unwrap(),
372        mini_size: 0x30000000,
373        mini_shared: false,
374        vsh_size: NonZero::new(0x1B200000).unwrap(),
375        unk1: 0x3CB00000,
376    }),
377    Some(DmemConfig {
378        name: "BC16 mini-app large",
379        game_size: NonZero::new(0x148000000).unwrap(),
380        fmem_max: NonZero::new(0x40000000).unwrap(),
381        mini_size: 0x48000000,
382        mini_shared: false,
383        vsh_size: NonZero::new(0x19600000).unwrap(),
384        unk1: 0x13A700000,
385    }),
386    Some(DmemConfig {
387        name: "BC16 kratos",
388        game_size: NonZero::new(0x148000000).unwrap(),
389        fmem_max: NonZero::new(0x40000000).unwrap(),
390        mini_size: 0,
391        mini_shared: false,
392        vsh_size: NonZero::new(0x1CA00000).unwrap(),
393        unk1: 0x1F3C00000,
394    }),
395    Some(DmemConfig {
396        name: "BC16 release",
397        game_size: NonZero::new(0x148000000).unwrap(),
398        fmem_max: NonZero::new(0x40000000).unwrap(),
399        mini_size: 0x30000000,
400        mini_shared: false,
401        vsh_size: NonZero::new(0x1C200000).unwrap(),
402        unk1: 0x1FF600000,
403    }),
404    Some(DmemConfig {
405        name: "BC16 CS",
406        game_size: NonZero::new(0x324000000).unwrap(),
407        fmem_max: NonZero::new(0x4000000).unwrap(),
408        mini_size: 0x58000000,
409        mini_shared: false,
410        vsh_size: NonZero::new(0x28000000).unwrap(),
411        unk1: 0,
412    }),
413    None,
414    None,
415    Some(DmemConfig {
416        name: "GL8 normal",
417        game_size: NonZero::new(0x170000000).unwrap(),
418        fmem_max: NonZero::new(0x40000000).unwrap(),
419        mini_size: 0x70000000,
420        mini_shared: true,
421        vsh_size: NonZero::new(0x27C00000).unwrap(),
422        unk1: 0,
423    }),
424    None,
425    None,
426    Some(DmemConfig {
427        name: "GL8 kratos",
428        game_size: NonZero::new(0x170000000).unwrap(),
429        fmem_max: NonZero::new(0x40000000).unwrap(),
430        mini_size: 0x70000000,
431        mini_shared: true,
432        vsh_size: NonZero::new(0x27C00000).unwrap(),
433        unk1: 0xA00000,
434    }),
435    Some(DmemConfig {
436        name: "GL8 release",
437        game_size: NonZero::new(0x170000000).unwrap(),
438        fmem_max: NonZero::new(0x40000000).unwrap(),
439        mini_size: 0x70000000,
440        mini_shared: true,
441        vsh_size: NonZero::new(0x28000000).unwrap(),
442        unk1: 0xA00000,
443    }),
444];