为树莓派3B+实现HDMI驱动(二)要有光

上帝说要有光,于是便有了光。

本文主要介绍Mailbox驱动、Mailbox通信和framebuffer的初始化。

核心实现部分涉及四个文件,驱动模块mailbox.rs,mailbox通信消息msg.rs,全局framebuffer相关framebuffer.rs以及提供顶层显示的gpu.rs

实现的步骤考虑自底向上,首先完成驱动接口,然后实现消息抽象并借助mailbox向GPU请求FrameBuffer空间。最后在framebuffer基础上实现高层次绘制函数。

1. Mailbox驱动

channel定义:

1
2
3
4
5
6
7
8
9
10
11
12
pub enum MailBoxChannel {
    PowerManagement,
    FrameBuffer,
    VirtualUart,
    Vchiq,
    Leds,
    Buttons,
    Touchscreen,
    Unused,
    PropertyArmToVc,
    PropertyVcToArm,
}

然后是寄存器和顶层定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[repr(C)]
#[allow(non_snake_case)]
struct Registers {
    READ: ReadVolatile<u32>,
    RESERVED: [Reserved<u32>; 3],
    PEEK: ReadVolatile<u32>,
    SENDER: ReadVolatile<u32>,
    STATUS: ReadVolatile<u32>,
    CONFIG: ReadVolatile<u32>,
    WRITE: WriteVolatile<u32>,
}

/// The Raspberry Pi ARM Mailbox.
pub struct Mailbox {
    registers: &'static mut Registers,
}

驱动对外提供两个方法:读和写。

  • 根据channel号,从mailbox寄存器读取有效的buffer地址
  • 根据channe号和buffer地址,写mailbox寄存器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pub fn read(&mut self, chan: MailBoxChannel) -> u32 {
    loop {
        while self.registers.STATUS.read() & MAILBOX_EMPTY > 0 {}
        let data = self.registers.READ.read();
        if data & 0xF == chan as u32 {
            return data & (!0xF);
        }
    }
}

pub fn write(&mut self, data: u32, chan: MailBoxChannel) {
    let val = (data & (!0xF)) | chan as u32;
    while self.registers.STATUS.read() & MAILBOX_FULL > 0 {}
    self.registers.WRITE.write(val);
}

2. 核间通信:消息缓存 + Tags

实现好mailbox的驱动之后,下一步就是在msg.rs实现消息抽象。对外提供的接口是需要调用者提供Tag切片的pub fn send_messages(tags: &mut [Tag]) -> OsResult<()>;

2.1. 消息缓存与Tag格式

为了向GPU请求framebuffer,首先需要能够正确配置核间通信的消息缓存。消息缓存包含了两层抽象如下:

  • 消息缓存
  • tag缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 消息缓存的格式
AAAABBBBxxxxxxxxCCCCyyyyyyyy
└┬─┘└┬─┘└───┬──┘└┬─┘└───┬──┘
                      Padding to specified length
               End Tag (0x00)
           Sequence of Tags
     Buffer request/response code
  Buffer Size

// Tags格式
AAAABBBBCCCCxxxxxxxxyyyyyyyy
└┬─┘└┬─┘└┬─┘└──┬───┘└───┬──┘
                      Padding to align to 32bits
             Value buffer
        Request/Response code
     Value buffer size in bytes
  Tag identifier

下面是请求GPU分配framebuffer使用到的tag定义。

如上图可见,

2.1.1. Allocate buffer

  • Tag: 0x00040001
  • Request:
    • Length: 4
    • Value:
      • u32: alignment in bytes
  • Response:
    • Length: 8
    • Value:
      • u32: frame buffer base address in bytes
      • u32: frame buffer size in bytes

2.1.2. Set physical (display) width/height

physical display size是内存中分配的framebuffer大小,并非发送给显示设备的分辨率。

  • Tag: 0x00048003
  • Request:
    • Length: 8
    • Value:
      • u32: width in pixels
      • u32: height in pixels
  • Response:
    • Length: 8
    • Value:
      • u32: width in pixels
      • u32: height in pixels

2.1.3. Set virtual (buffer) width/height

virtual buffer是GPU发送给显示设备的内存部分。

  • Tag: 0x00048004
  • Request:
    • Length: 8
    • Value:
      • u32: width in pixels
      • u32: height in pixels
  • Response:
    • Length: 8
    • Value:
      • u32: width in pixels
      • u32: height in pixels

The response may not be the same as the request so it must be checked. May be the previous width/height or 0 for unsupported.

2.1.4. Set pixel order

  • Tag: 0x00048006
    • Request:
      • Length: 4
      • Value:
        • u32: state (as above)
    • Response:
      • Length: 4
      • Value:
        • u32: state (as above)

2.1.5. Set depth

  • Tag: 0x00048005
  • Request:
    • Length: 4
    • Value:
      • u32: bits per pixel
  • Response:
    • Length: 4
    • Value:
      • u32: bits per pixel

2.2. 定义格式

由于整个消息缓存包含了两层抽象,且两层抽象的大小都是可变的。比如不同类型tag的大小不同,因此使用什么数据结构表示tag较为麻烦。

我的实现从为了发送消息,接口需要用户提供什么信息这个角度出发。具体而言,接口只需要用户提供要发送的tag,自己负责设置缓存。更进一步,依据接口最小化原则,只需要用户提供 tag的类型/idtag的值

于是,向用户导出下面的结构体:

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
#[allow(dead_code)]
#[derive(Copy, Clone)]
pub enum TagID {
    End = 0x0,
    FBAllocate = 0x00040001,
    FBRelease = 0x00048001,
    FBBlankScreen = 0x00040002,
    FBGetPhysicalDim = 0x00040003,
    FBSetPhysicalDim = 0x00048003,
    FBGetVirtualDim = 0x00040004,
    FBSetVirtualDim = 0x00048004,
    FBGetDepth = 0x00040005,
    FBSetDepth = 0x00048005,
    FBGetPixelOrder = 0x00040006,
    FBSetPixelOrder = 0x00048006,
    FBGetPitch = 0x00040008,
    FBGetVirtualOffset = 0x00040009,
    FBSetVirtualOffset = 0x00048009,
}

pub enum TagValueBuffer {
    FBAlign(u32, u32),
    FBPhysicalDim(u32, u32),
    FBVirtualDim(u32, u32),
    FBDepth(u32),
    FBPitch(u32),
    FBPixelOrder(u32),
    FBVirtualOffset(u32, u32),
}

pub struct Tag {
    pub id: TagID,
    pub value_buffer: TagValueBuffer,
}

基于这两个结构体就能实现send_message函数,主要逻辑就是依据官方的格式把消息缓存配置出来。然后用之前实现的mailbox驱动进行发送。

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
pub fn send_messages(tags: &mut [Tag]) -> OsResult<()> {
    let mut buf_size = 0;
    // tags size
    for tag in tags.iter() {
        // | tag id | value buffer size | req/rsp code | value buffer | padding |
        // 3: id, size, req/rsp code
        buf_size += tag.id.value_buf_len() + 3 * core::mem::size_of::<u32>();
    }
    // | buffer size | req/rsp code | tags | end tag | padding |
    // 3: size, req/rsp, end tag
    buf_size += 3 * core::mem::size_of::<u32>();
    // align buffer size to 16 byte
    buf_size += if buf_size % 16 == 0 { 0 } else { 16 - (buf_size % 16) };
    let ptr = unsafe { ALLOCATOR.alloc(Layout::from_size_align_unchecked(buf_size, 16)) };
    let buf = unsafe { SliceExt::cast_mut::<u32>(core::slice::from_raw_parts_mut(ptr, buf_size)) };
    // buffer size
    buf[0] = buf_size as u32;
    // req_code
    buf[1] = REQUEST_CODE;
    // construct tags
    let mut i = 2;
    for tag in tags.iter() {
        // id
        buf[i] = tag.id as u32;
        i += 1;
        // value buffer size
        buf[i] = tag.id.value_buf_len() as u32;
        i += 1;
        // req/rsp code
        buf[i] = REQUEST_CODE;
        i += 1;
        // value buffer
        match tag.value_buffer {
            TagValueBuffer::FBAlign(align, _) => {
                buf[i] = align;
                i += 1;
                buf[i] = 0;
                i += 1;
            }
            TagValueBuffer::FBPhysicalDim(width, height ) => {
                buf[i] = width;
                i += 1;
                buf[i] = height;
                i += 1;
            }
            TagValueBuffer::FBVirtualDim(width, height) => {
                buf[i] = width;
                i += 1;
                buf[i] = height;
                i += 1; 
            }
            TagValueBuffer::FBDepth(depth) => {
                buf[i] = depth;
                i += 1;
            }
            TagValueBuffer::FBPixelOrder(porder) => {
                buf[i] = porder; 
                i += 1;
            }
            TagValueBuffer::FBPitch(_) => {
                buf[i] = 0;
                i += 1;
            }
            TagValueBuffer::FBVirtualOffset(x, y) => {
                buf[i] = x;
                i += 1;
                buf[i] = y;
                i += 1;
            }
            _ => unreachable!(),
        }
    }
    // end tag
    buf[i] = 0;
    // send msg and check response code
    let mut mailbox = Mailbox::new();
    mailbox.write(buf.as_ptr() as u32, MailBoxChannel::PropertyArmToVc);
    assert_eq!(mailbox.read(MailBoxChannel::PropertyArmToVc), buf.as_ptr() as u32);

    // check response code
    let ret = match buf[1] {
        REQUEST_CODE => Err(OsError::MailboxError),
        RESPONSE_FAIL => Err(OsError::MailboxFailed),
        RESPONSE_SUCCESS => Ok(()),
        _ => unreachable!()
    };

    // overwrite response msg
    let mut i = 2;
    for tag in tags.iter_mut() {
        use TagValueBuffer::*;
        let len = tag.id.value_buf_len();
        i += 3;
        tag.value_buffer = match tag.value_buffer {
            FBAlign(_, _) => { i += 2; FBAlign(buf[i-2], buf[i-1]) }
            FBPhysicalDim(_, _) => { i += 2; FBPhysicalDim(buf[i-2], buf[i-1]) }
            FBVirtualDim(_, _) => { i += 2; FBVirtualDim(buf[i-2], buf[i-1]) }
            FBDepth(_) => { i += 1; FBDepth(buf[i-1]) }
            FBPitch(_) => { i += 1; FBPitch(buf[i-1]) }
            FBPixelOrder(_) => { i += 1; FBPixelOrder(buf[i-1]) }
            FBVirtualOffset(_, _) => { i += 2; FBVirtualOffset(buf[i-2], buf[i-1]) }
        }
    }

    // free msg buffer 
    unsafe { ALLOCATOR.dealloc(ptr, Layout::from_size_align_unchecked(buf_size, 16)); }
    ret
}

3. 请求分配framebuffer

在能够发送消息之后,下一步实现的是framebuffer初始化和绘制基础设施。framebuffer是一个全局结构,因此外部需要套一层Mutex。

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
pub struct GlobalFrameBuffer(Mutex<Option<FrameBuffer>>);

impl GlobalFrameBuffer {
    pub const fn uninitialized() -> GlobalFrameBuffer {
        GlobalFrameBuffer(Mutex::new(None))
    }

    pub fn critical<F, R>(&self, f: F) -> R
    where
        F: FnOnce(&mut FrameBuffer) -> R,
    {
        let mut guart = self.0.lock();
        f(guart.as_mut().expect("framebuffer uninitialized"))
    }

    pub fn initialize(&self) {
        info!("framebuffer: init");
        let fb = FrameBuffer::new();
        match fb {
            Some(buf) => {
                info!("framebuffer: addr: 0x{:x}, size: {}",
                    buf.buffer.as_ptr() as usize, buf.size);
                info!("framebuffer: width: {}, height: {}, vwidth: {}, vheight: {}, voffset_x: {}, voffset_y: {}, ",
                    buf.width, buf.height,
                    buf.vwidth, buf.vheight,
                    buf.voffset_x, buf.voffset_y,
                );
                info!("framebuffer: depth: {}, pitch: {}, porder: {}", buf.depth, buf.pitch, buf.porder);
                *self.0.lock() = Some(buf);
            }
            None => info!("frambuffer: failed")
        }
    }

    pub fn write_pixel(&self, x: u32, y: u32, pixel: Pixel) {
        self.critical(|fb| {
            let pos = (y * fb.pitch + x * fb.depth / 8) as usize;
            fb.buffer[pos] = pixel.blue;
            fb.buffer[pos + 1] = pixel.green;
            fb.buffer[pos + 2] = pixel.red;
        })
    }
}

内部的framebuffer结构体,记录了真正的元数据,以及指向buffer的指针。同时提供了初始化的方法

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
pub struct FrameBuffer {
    pub width: u32,
    pub height: u32,
    pub vwidth: u32,
    pub vheight: u32,
    pub voffset_x: u32,
    pub voffset_y: u32,
    pub depth: u32,
    pub pitch: u32,
    pub porder: u32,
    pub buffer: &'static mut [u8],
    pub size: u32,
}

impl FrameBuffer {
    pub fn new() -> Option<FrameBuffer> {
        let mut tags: Vec<Tag> = Vec::new();
        // 0: set physical dim
        tags.push(Tag {
            id: TagID::FBSetPhysicalDim,
            value_buffer: TagValueBuffer::FBPhysicalDim(WIDTH, HEIGHT)
        });
        // 1: set virtual dim
        tags.push(Tag {
            id: TagID::FBSetVirtualDim,
            value_buffer: TagValueBuffer::FBVirtualDim(WIDTH, HEIGHT)
        });
        // 2: set depth
        tags.push(Tag {
            id: TagID::FBSetDepth,
            value_buffer: TagValueBuffer::FBDepth(24),
        });
        // 3: set virtual offset to 0, 0
        tags.push(Tag {
            id: TagID::FBSetVirtualOffset,
            value_buffer: TagValueBuffer::FBVirtualOffset(0, 0),
        });
        // 4: get pitch
        tags.push(Tag {
            id: TagID::FBGetPitch,
            value_buffer: TagValueBuffer::FBPitch(0),
        });
        // 5: allocate frame buffer
        tags.push(Tag {
            id: TagID::FBAllocate,
            value_buffer: TagValueBuffer::FBAlign(16, 0),
        });
        // 6: set pixel order to RGB
        tags.push(Tag {
            id: TagID::FBSetPixelOrder,
            value_buffer: TagValueBuffer::FBPixelOrder(1),
        });
        match msg::send_messages(&mut tags) {
            Ok(_) => info!("framebuffer: message sent succeed"),
            Err(_) => {
                info!("framebuffer: message sent failed");
                return None;
            }
        }
        let (width, height) = tags[0].value_buffer.as_fb_physical_dim().unwrap();
        let (vwidth, vheight) = tags[1].value_buffer.as_fb_virtual_dim().unwrap();
        let depth = tags[2].value_buffer.as_fb_depth().unwrap();
        let (voffset_x, voffset_y) = tags[3].value_buffer.as_fb_virtual_offset().unwrap();
        let pitch = tags[4].value_buffer.as_fb_pitch().unwrap();
        let (mut buffer, size) = tags[5].value_buffer.as_fb_align().unwrap();
        let porder = tags[6].value_buffer.as_fb_pixel_order().unwrap();
        buffer &= 0x3FFF_FFFF;
        Some(FrameBuffer {
            width,
            height,
            vwidth,
            vheight,
            voffset_x,
            voffset_y,
            depth,
            pitch,
            porder,
            buffer: unsafe { core::slice::from_raw_parts_mut(buffer as *mut u8, size as usize) },
            size,
        })
    }
}

这里配置的virtual buffer和physical buffer使用相同的配置,我使用的3.5寸HDMI显示屏分辨率是480 x 320。共发送7个tag进行framebuffer初始化配置,分别为:

  1. 设置物理宽高
  2. 设置虚拟宽高
  3. 设置pixel深度
  4. 设置虚拟起始偏移0,0
  5. 获取一行字节数
  6. 分配framebuffer
  7. 设置pixel顺序(这个好像目前不起作用)

对于属性设置tag,需要和分配framebuffer的tag存放于单次消息中,不然会被gpu侧忽略。让我盲目debug了半天才发现。

4. 顶层绘图测试

最后是gpu.rs中顶层的绘图测试函数,这里测试非常简单,就是将整个屏幕铺满一种颜色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// Fill the whole screen with a color.
/// Mainly for test purpose.
pub fn draw_screen(color: &str) {
    let color = get_color_pixel(color);
    for x in 0.. WIDTH {
        for y in 0..HEIGHT {
            FRAMEBUFFER.write_pixel(x, y, color);
        }
    }
}

fn get_color_pixel(color: &str) -> Pixel {
    match color.to_lowercase().as_str() {
        "white" => WHITE,
        "black" => BLACK,
        "red" => RED,
        "green" => GREEN,
        "blue" => BLUE,
        _ => WHITE
    }
}

这几个模块实现之后,就可以随意填充屏幕了,如下面这样:

green.jpg]

绿色的,不是很好看。接下来嘛,自然就是基于基本的绘图功能,实现一个较为高级的渲染基础设施:字体渲染。下一篇传送门

5. 参考资料