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
use crate::{
    Error, {BoardType, ProcessorId},
};
use std::{ffi::CString, os::raw::c_int};

impl BoardType {
    /// Make a new [`BoardType`] from a [`c_int`].
    fn new(n: c_int) -> Result<Self, c_int> {
        match u32::from_ne_bytes(n.to_ne_bytes()) {
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_MODELA => Ok(Self::ModelA),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_MODELB => Ok(Self::ModelB),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_MODELAPLUS => Ok(Self::ModelAPlus),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_MODELBPLUS => Ok(Self::ModelBPlus),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_PI2MODELB => Ok(Self::Pi2ModelB),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_ALPHA => Ok(Self::Alpha),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_CM => Ok(Self::Cm),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_CM2 => Ok(Self::Cm2),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_PI3MODELB => Ok(Self::Pi3ModelB),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_PI0 => Ok(Self::Pi0),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_CM3 => Ok(Self::Cm3),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_CUSTOM => Ok(Self::Custom),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_PI0W => Ok(Self::Pi0W),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_PI3MODELBPLUS => {
                Ok(Self::Pi3ModelBPlus)
            }
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_PI3MODELAPLUS => {
                Ok(Self::Pi3ModelAPlus)
            }
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_FPGA => Ok(Self::Fpga),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_CM3PLUS => Ok(Self::Cm3Plus),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_PI4MODELB => Ok(Self::Pi4ModelB),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_PI400 => Ok(Self::Pi400),
            raspberry_pi_sys::libbcm_host::BCM_HOST_BOARD_TYPE_CM4 => Ok(Self::Cm4),
            _ => Err(n),
        }
    }
}

impl ProcessorId {
    /// Try to make a new [`ProcessorId`] from a [`c_int`]
    fn new(n: c_int) -> Result<Self, c_int> {
        match u32::from_ne_bytes(n.to_ne_bytes()) {
            raspberry_pi_sys::libbcm_host::BCM_HOST_PROCESSOR_BCM2835 => Ok(Self::Bcm2835),
            raspberry_pi_sys::libbcm_host::BCM_HOST_PROCESSOR_BCM2836 => Ok(Self::Bcm2836),
            raspberry_pi_sys::libbcm_host::BCM_HOST_PROCESSOR_BCM2837 => Ok(Self::Bcm2837),
            raspberry_pi_sys::libbcm_host::BCM_HOST_PROCESSOR_BCM2838 => Ok(Self::Bcm2711),
            _ => Err(n),
        }
    }
}

/// An interface to RaspberryPi libraries
pub struct RaspberryPi {
    bcm_host: raspberry_pi_sys::libbcm_host::libbcm_host,
    bcm_host_initialized: bool,

    vc_gencmd_initialized: bool,
    vcos_initialized: bool,
}

impl RaspberryPi {
    /// Initialize a new [`RaspberryPi`].
    ///
    /// # Safety
    /// 1. There may only be 1 instance of this struct in the program at any given time.
    /// 2. The libraries `libbcm_host.so` in the search path must be the correct libraries with the correct function definitions.
    /// 3. No other calls may be made to the VideoCore may be made in this process by any means while this struct is active.
    /// This includes linking and using FFMpeg in-process.
    pub unsafe fn new() -> Result<Self, Error> {
        let bcm_host =
            raspberry_pi_sys::libbcm_host::libbcm_host::new("libbcm_host.so").map_err(|error| {
                Error::LibraryLoad {
                    name: "libbcm_host.so",
                    error,
                }
            })?;

        Ok(Self {
            bcm_host,
            bcm_host_initialized: false,

            vc_gencmd_initialized: false,
            vcos_initialized: false,
        })
    }

    // TODO: Call on load?
    /// Init bcm_host.
    ///
    /// This must be called before any other functions.
    ///
    /// Right now, this is safe to call multiple times, even while already initialized.
    pub fn bcm_host_init(&mut self) {
        unsafe { self.bcm_host.bcm_host_init() };
        self.bcm_host_initialized = true;
    }

    /// Deinit bcm_host.
    ///
    /// The impl is currently a no-op, but this may change in the future.
    ///
    /// # Safety
    /// The user must be done using the GPU.
    pub unsafe fn bcm_host_deinit(&mut self) -> Result<(), Error> {
        if !self.bcm_host_initialized {
            return Err(Error::BcmHostNotInitialized);
        }
        self.bcm_host.bcm_host_deinit();
        self.bcm_host_initialized = false;

        Ok(())
    }

    /// Get the size of the graphics display.
    pub fn graphics_get_display_size(&mut self, display_number: u16) -> Result<(u32, u32), Error> {
        if !self.bcm_host_initialized {
            return Err(Error::BcmHostNotInitialized);
        }

        let mut width = 0;
        let mut height = 0;
        let error_code = unsafe {
            self.bcm_host
                .graphics_get_display_size(display_number, &mut width, &mut height)
        };

        if error_code < 0 {
            return Err(Error::GraphicsGetDisplaySize(error_code));
        }

        Ok((width, height))
    }

    /// Get the model type
    pub fn get_model_type(&mut self) -> Result<BoardType, Error> {
        BoardType::new(unsafe { self.bcm_host.bcm_host_get_model_type() })
            .map_err(Error::UnknownBoardType)
    }

    /// Return `true` if this is a pi 4, or in the same family.
    pub fn is_model_pi4(&mut self) -> bool {
        unsafe { self.bcm_host.bcm_host_is_model_pi4() == 1 }
    }

    /// Return `true` if fkms is active.
    pub fn is_fkms_active(&mut self) -> bool {
        unsafe { self.bcm_host.bcm_host_is_fkms_active() == 1 }
    }

    /// Return `true` if kms is active.
    pub fn is_kms_active(&mut self) -> bool {
        unsafe { self.bcm_host.bcm_host_is_kms_active() == 1 }
    }

    /// Get the processor id.
    pub fn get_processor_id(&mut self) -> Result<ProcessorId, Error> {
        ProcessorId::new(unsafe { self.bcm_host.bcm_host_get_processor_id() })
            .map_err(Error::UnknownProcessorId)
    }

    /*
    /// Initialise the general command service for use.
    pub fn vc_vchi_gencmd_init(&mut self) {
        unsafe { self.bcm_host.vc_vchi_gencmd_init() }

        self.vc_gencmd_initialized = true;
    }
    */

    /// Stop the service from being used.
    pub fn vc_gencmd_stop(&mut self) -> Result<(), Error> {
        if !self.vc_gencmd_initialized {
            return Ok(());
        }

        unsafe { self.bcm_host.vc_gencmd_stop() };
        self.vc_gencmd_initialized = false;

        Ok(())
    }

    /// Send command to general command serivce
    pub fn vc_gencmd_send<F>(&mut self, format: F) -> Result<(), Error>
    where
        F: Into<Vec<u8>>,
    {
        if !self.bcm_host_initialized {
            return Err(Error::BcmHostNotInitialized);
        }

        let format = CString::new(format)?;
        let error_code = unsafe { (self.bcm_host.vc_gencmd_send)(format.as_ptr()) };

        if error_code != 0 {
            return Err(Error::VcGenCmd(error_code));
        }

        Ok(())
    }

    /// get resonse from general command serivce
    pub fn vc_gencmd_read_response(&mut self) -> Result<CString, Error> {
        if !self.bcm_host_initialized {
            return Err(Error::BcmHostNotInitialized);
        }

        let capacity: usize = raspberry_pi_sys::libbcm_host::GENCMDSERVICE_MSGFIFO_SIZE
            .try_into()
            .expect("`GENCMDSERVICE_MSGFIFO_SIZE` is larger than a `usize`");

        let mut buffer = Vec::with_capacity(capacity);

        unsafe {
            let error_code = self.bcm_host.vc_gencmd_read_response(
                buffer.as_mut_ptr(),
                capacity
                    .try_into()
                    .expect("`GENCMDSERVICE_MSGFIFO_SIZE` is larger than a `u32`"),
            );

            if error_code != 0 {
                return Err(Error::VcGenCmd(error_code));
            }

            *buffer.as_mut_ptr().add(capacity - 1) = 0;
            let len = libc::strlen(buffer.as_ptr());
            buffer.set_len(len);
        }

        Ok(CString::new(buffer).expect("there should be only one nul"))
    }
    /*
    vc_gencmd_string_property: unsafe extern "C" fn(text: *mut c_char, property: *const c_char, value: *mut *mut c_char, length: *mut c_int) -> c_int
    vc_gencmd_number_property: unsafe extern "C" fn(text: *mut c_char, property: *const c_char, number: *mut c_int) -> c_int
    vc_gencmd_until: unsafe extern "C" fn(cmd: *mut c_char, property: *const c_char, value: *mut c_char, error_string: *const c_char, timeout: c_int) -> c_int
        */

    /// vcos initialization. Call this function before using other vcos functions.
    /// Calls can be nested within the same process; they are reference counted so
    /// that only a call from uninitialized state has any effect.
    /// # Note
    /// On platforms/toolchains that support it, gcc's constructor attribute or
    /// similar is used to invoke this function before main() or equivalent.
    ///
    /// # Returns
    /// Status of initialisation.
    pub fn vcos_init(&mut self) -> Result<(), Error> {
        let error_code = unsafe { self.bcm_host.vcos_init() };

        if error_code != 0 {
            return Err(Error::VCos(error_code));
        }

        Ok(())
    }

    /// vcos deinitialization. Call this function when vcos is no longer required,
    /// in order to free resources.
    /// Calls can be nested within the same process; they are reference counted so
    /// that only a call that decrements the reference count to 0 has any effect.
    ///
    /// # Note
    /// On platforms/toolchains that support it, gcc's destructor attribute or
    /// similar is used to invoke this function after exit() or equivalent.
    ///
    /// # Safety
    /// This function should not be called if vcos is not initialized.
    /// Furthermore, you should not use this while still using vcos interfaces.
    pub unsafe fn vcos_deinit(&mut self) {
        if !self.vcos_initialized {
            return;
        }

        self.bcm_host.vcos_deinit()
    }
}