Skip to main content

atmos/kernel/
mailbox.rs

1const MBOX_BASE: usize = 0x3F00_B880;
2
3const MBOX_READ: *mut u32 = MBOX_BASE as *mut u32;
4const MBOX_STATUS: *mut u32 = (MBOX_BASE + 0x18) as *mut u32;
5const MBOX_WRITE: *mut u32 = (MBOX_BASE + 0x20) as *mut u32;
6
7const MBOX_FULL: u32 = 0x8000_0000;
8const MBOX_EMPTY: u32 = 0x4000_0000;
9const MBOX_RESPONSE: u32 = 0x8000_0000;
10
11pub const MBOX_CH_PROP: u32 = 8;
12
13pub struct Mailbox;
14
15impl Mailbox {
16    pub fn call(buffer: &mut [u32], channel: u32) -> Result<(), &'static str> {
17        let addr = buffer.as_ptr() as u32;
18
19        // アドレスの下位4ビットが0であることを検証 (16バイトアライン)
20        if (addr & 0xF) != 0 {
21            return Err("mailbox buffer not 16-byte aligned");
22        }
23
24        // GPU は ARM の L1/L2 キャッシュを共有しないため、メッセージを DRAM に書き出してから
25        // メールボックスに送信する。応答受信後はキャッシュを無効化して GPU の書き込みを見る。
26        crate::kernel::mmu::clean_dcache_range(addr as usize, buffer.len() * 4);
27
28        // アドレスとチャンネルを合成
29        let data = (addr & !0xF) | (channel & 0xF);
30
31        unsafe {
32            // メールボックスが書き込み可能(フルではない)になるまで待つ
33            while (core::ptr::read_volatile(MBOX_STATUS) & MBOX_FULL) != 0 {}
34            // 送信
35            core::ptr::write_volatile(MBOX_WRITE, data);
36
37            loop {
38                // メールボックスが読み込み可能(空ではない)になるまで待つ
39                while (core::ptr::read_volatile(MBOX_STATUS) & MBOX_EMPTY) != 0 {}
40                // 読み込み
41                let res = core::ptr::read_volatile(MBOX_READ);
42                // 送信したアドレス・チャンネルと一致するか確認
43                if res == data {
44                    // GPU の応答が DRAM に書かれているので、古いキャッシュを捨ててから読む
45                    crate::kernel::mmu::invalidate_dcache_range(addr as usize, buffer.len() * 4);
46                    // GPUの応答コードが成功 (0x8000_0000) か確認
47                    if buffer[1] == MBOX_RESPONSE {
48                        return Ok(());
49                    } else {
50                        return Err("mailbox response code indicates failure");
51                    }
52                }
53            }
54        }
55    }
56}
57
58/// フレームバッファ要求メッセージの表現(C互換レイアウト・16バイトアライン)
59#[repr(C, align(16))]
60pub struct FrameBufferRequest {
61    pub size: u32,
62    pub magic: u32,
63    pub tags: [u32; 30],
64}
65
66pub struct FrameBufferInfo {
67    pub pointer: *mut u32,
68    pub width: u32,
69    pub height: u32,
70    pub pitch: u32,
71    pub virtual_height: u32, // SET_VIRTUAL の応答(ダブルバッファ用に 2*height を期待)
72    pub size: u32,           // ALLOC_BUFFER が返したバッファ総バイト数
73}
74
75impl FrameBufferRequest {
76    pub fn new(width: u32, height: u32) -> Self {
77        Self {
78            size: 128, // (2 + 30) * 4 = 128 bytes
79            magic: 0,  // Request magic
80            tags: [
81                // Set Physical width/height (Value buffer: 8 bytes, Req indicator: 0)
82                0x0004_8003,
83                8,
84                0,
85                width,
86                height,
87                // Set Virtual width/height: 高さを物理の2倍にしてダブルバッファ(ページフリップ)用の
88                // 2ページぶんを確保する。SET_VIRTUAL_OFFSET で VBlank 同期フリップする。
89                0x0004_8004,
90                8,
91                0,
92                width,
93                height * 2,
94                // Set Depth (32bit) (Value buffer: 4 bytes, Req indicator: 0)
95                0x0004_8005,
96                4,
97                0,
98                32,
99                // Set Pixel Order: 1 = RGB (RPi hardware default is BGR, causes R/B swap)
100                0x0004_8006,
101                4,
102                0,
103                1,
104                // Allocate Buffer (Alignment 4096) (Value buffer: 8 bytes, Req indicator: 0)
105                0x0004_0001,
106                8,
107                0,
108                4096,
109                0,
110                // Get Pitch (Value buffer: 4 bytes, Req indicator: 0)
111                0x0004_0008,
112                4,
113                0,
114                0,
115                // End tag
116                0,
117                0,
118                0,
119            ],
120        }
121    }
122
123    pub fn get_info(&self) -> Option<FrameBufferInfo> {
124        // tags layout (each tag: id, buf_size, req_code, values...):
125        // [0-4]  SET_PHYS: id,8,0,w,h
126        // [5-9]  SET_VIRT: id,8,0,w,h
127        // [10-13] SET_DEPTH: id,4,0,32
128        // [14-17] SET_PIXEL_ORDER: id,4,0,1
129        // [18-22] ALLOC_BUFFER: id,8,0,base_addr,size
130        // [23-26] GET_PITCH: id,4,0,pitch
131        let pointer = self.tags[21]; // ALLOC_BUFFER response: base address
132        let size = self.tags[22];
133        let pitch = self.tags[26]; // GET_PITCH response
134        let width = self.tags[3];
135        let height = self.tags[4];
136        let virtual_height = self.tags[9]; // SET_VIRTUAL response height
137
138        if pointer != 0 {
139            let cpu_ptr = (pointer & 0x3FFF_FFFF) as *mut u32;
140            Some(FrameBufferInfo {
141                pointer: cpu_ptr,
142                width,
143                height,
144                pitch,
145                virtual_height,
146                size,
147            })
148        } else {
149            None
150        }
151    }
152}
153
154/// RPi3B+ USB電源をMailbox経由でONにする (DWC2初期化前に必須)
155/// Device ID 3 = USB, State bit0=1(on), bit1=1(wait for settling)
156pub fn power_on_usb() -> Result<(), &'static str> {
157    #[repr(C, align(16))]
158    struct UsbPowerMsg {
159        size: u32,
160        req: u32,
161        tag: u32,
162        buf_size: u32,
163        req_code: u32,
164        device_id: u32,
165        state: u32,
166        end_tag: u32,
167    }
168
169    let mut msg = UsbPowerMsg {
170        size: core::mem::size_of::<UsbPowerMsg>() as u32,
171        req: 0,
172        tag: 0x0002_8001, // SET_POWER_STATE
173        buf_size: 8,
174        req_code: 8,
175        device_id: 3, // USB
176        state: 3,     // bit0=on, bit1=wait
177        end_tag: 0,
178    };
179
180    let buffer = unsafe {
181        core::slice::from_raw_parts_mut(
182            &mut msg as *mut _ as *mut u32,
183            core::mem::size_of::<UsbPowerMsg>() / 4,
184        )
185    };
186    Mailbox::call(buffer, MBOX_CH_PROP)?;
187
188    // state bit0が1(on)になっているか確認
189    if (buffer[6] & 1) == 0 {
190        return Err("usb power state did not turn on");
191    }
192    Ok(())
193}
194
195/// フレームバッファの仮想オフセット(表示開始 y)を設定する(ページフリップ)。
196/// RPi ファームウェアは次の VBlank で反映するため、表示中ページを書き換えなければ
197/// ティアリングが起きない。成功時は反映された y を返す。
198pub fn set_virtual_offset(x: u32, y: u32) -> Result<u32, &'static str> {
199    #[repr(C, align(16))]
200    struct OffsetMsg {
201        size: u32,
202        req: u32,
203        tag: u32,
204        buf_size: u32,
205        req_code: u32,
206        x: u32,
207        y: u32,
208        end_tag: u32,
209    }
210
211    let mut msg = OffsetMsg {
212        size: core::mem::size_of::<OffsetMsg>() as u32,
213        req: 0,
214        tag: 0x0004_8009, // SET_VIRTUAL_OFFSET
215        buf_size: 8,
216        req_code: 8,
217        x,
218        y,
219        end_tag: 0,
220    };
221
222    let buffer = unsafe {
223        core::slice::from_raw_parts_mut(
224            &mut msg as *mut _ as *mut u32,
225            core::mem::size_of::<OffsetMsg>() / 4,
226        )
227    };
228    crate::kernel::mmu::clean_dcache_range(
229        buffer.as_ptr() as usize,
230        core::mem::size_of::<OffsetMsg>(),
231    );
232    Mailbox::call(buffer, MBOX_CH_PROP)?;
233    Ok(msg.y)
234}
235
236pub fn enable_v3d() -> Result<(), &'static str> {
237    #[repr(C, align(16))]
238    struct V3dEnableMsg {
239        size: u32,
240        req: u32,
241
242        // Enable QPU
243        tag_enable: u32,
244        tag_enable_buf_size: u32,
245        tag_enable_req: u32,
246        enable_val: u32,
247
248        // Set Clock Rate (V3D = 5)
249        tag_clock: u32,
250        tag_clock_buf_size: u32,
251        tag_clock_req: u32,
252        clock_id: u32,
253        rate: u32,
254        skip_turbo: u32,
255
256        end_tag: u32,
257    }
258
259    let mut msg = V3dEnableMsg {
260        size: core::mem::size_of::<V3dEnableMsg>() as u32,
261        req: 0,
262
263        tag_enable: 0x0003_0012,
264        tag_enable_buf_size: 4,
265        tag_enable_req: 4,
266        enable_val: 1, // 1 = enable
267
268        tag_clock: 0x0003_8002,
269        tag_clock_buf_size: 12,
270        tag_clock_req: 12,
271        clock_id: 5,       // V3D
272        rate: 250_000_000, // 250 MHz
273        skip_turbo: 0,
274
275        end_tag: 0,
276    };
277
278    let buffer = unsafe {
279        core::slice::from_raw_parts_mut(
280            &mut msg as *mut _ as *mut u32,
281            core::mem::size_of::<V3dEnableMsg>() / 4,
282        )
283    };
284
285    // MMU/Cacheが有効な場合を考慮しフラッシュする
286    crate::kernel::mmu::clean_dcache_range(
287        buffer.as_ptr() as usize,
288        core::mem::size_of::<V3dEnableMsg>(),
289    );
290
291    Mailbox::call(buffer, MBOX_CH_PROP)
292}
293
294pub fn wait_vsync() -> Result<(), &'static str> {
295    #[repr(C, align(16))]
296    struct VsyncMsg {
297        size: u32,
298        req: u32,
299        tag: u32,
300        buf_size: u32,
301        req_code: u32,
302        end_tag: u32,
303    }
304
305    let mut msg = VsyncMsg {
306        size: core::mem::size_of::<VsyncMsg>() as u32,
307        req: 0,
308        tag: 0x0004_0011, // WAIT_VSYNC
309        buf_size: 0,
310        req_code: 0,
311        end_tag: 0,
312    };
313
314    let buffer = unsafe {
315        core::slice::from_raw_parts_mut(
316            &mut msg as *mut _ as *mut u32,
317            core::mem::size_of::<VsyncMsg>() / 4,
318        )
319    };
320
321    let addr = buffer.as_ptr() as u32;
322
323    // GPUはARMのL1/L2キャッシュを共有しないため、メッセージをDRAMに書き出してから送信する
324    crate::kernel::mmu::clean_dcache_range(addr as usize, buffer.len() * 4);
325
326    let data = (addr & !0xF) | (MBOX_CH_PROP & 0xF);
327
328    unsafe {
329        // 1. メールボックスが書き込み可能(フルではない)になるまで待つ (タイムアウト付き)
330        let write_start = crate::kernel::timer::get_system_time_us();
331        while (core::ptr::read_volatile(MBOX_STATUS) & MBOX_FULL) != 0 {
332            if crate::kernel::timer::get_system_time_us().wrapping_sub(write_start) > 50000 {
333                return Err("wait_vsync: write timeout");
334            }
335            core::arch::asm!("dsb sy", "isb", options(nostack));
336        }
337
338        // 送信
339        core::ptr::write_volatile(MBOX_WRITE, data);
340
341        // 2. 応答受信待ち (タイムアウト付き)
342        let read_start = crate::kernel::timer::get_system_time_us();
343        loop {
344            // タイムアウトチェック
345            if crate::kernel::timer::get_system_time_us().wrapping_sub(read_start) > 50000 {
346                return Err("wait_vsync: read timeout");
347            }
348
349            // メールボックスが空ではないか確認
350            if (core::ptr::read_volatile(MBOX_STATUS) & MBOX_EMPTY) == 0 {
351                let res = core::ptr::read_volatile(MBOX_READ);
352                if res == data {
353                    // GPUの応答をキャッシュ無効化して確認
354                    crate::kernel::mmu::invalidate_dcache_range(addr as usize, buffer.len() * 4);
355                    if buffer[1] == MBOX_RESPONSE {
356                        return Ok(());
357                    } else {
358                        return Err("wait_vsync: mailbox response code indicates failure");
359                    }
360                }
361            }
362            core::arch::asm!("dsb sy", "isb", options(nostack));
363        }
364    }
365}