summaryrefslogtreecommitdiff
path: root/drivers/gpu/nova-core/gsp/cmdq.rs
blob: 6f946d14868ab27ad2e9d9e7cd4b5f228c5be5c1 (plain)
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
// SPDX-License-Identifier: GPL-2.0

use core::{
    cmp,
    mem,
    sync::atomic::{
        fence,
        Ordering, //
    }, //
};

use kernel::{
    device,
    dma::{
        CoherentAllocation,
        DmaAddress, //
    },
    dma_write,
    io::poll::read_poll_timeout,
    prelude::*,
    sync::aref::ARef,
    time::Delta,
    transmute::{
        AsBytes,
        FromBytes, //
    },
};

use crate::{
    driver::Bar0,
    gsp::{
        fw::{
            GspMsgElement,
            MsgFunction,
            MsgqRxHeader,
            MsgqTxHeader, //
        },
        PteArray,
        GSP_PAGE_SHIFT,
        GSP_PAGE_SIZE, //
    },
    num,
    regs,
    sbuffer::SBufferIter, //
};

/// Trait implemented by types representing a command to send to the GSP.
///
/// The main purpose of this trait is to provide [`Cmdq::send_command`] with the information it
/// needs to send a given command.
///
/// [`CommandToGsp::init`] in particular is responsible for initializing the command directly
/// into the space reserved for it in the command queue buffer.
///
/// Some commands may be followed by a variable-length payload. For these, the
/// [`CommandToGsp::variable_payload_len`] and [`CommandToGsp::init_variable_payload`] need to be
/// defined as well.
pub(crate) trait CommandToGsp {
    /// Function identifying this command to the GSP.
    const FUNCTION: MsgFunction;

    /// Type generated by [`CommandToGsp::init`], to be written into the command queue buffer.
    type Command: FromBytes + AsBytes;

    /// Error type returned by [`CommandToGsp::init`].
    type InitError;

    /// In-place command initializer responsible for filling the command in the command queue
    /// buffer.
    fn init(&self) -> impl Init<Self::Command, Self::InitError>;

    /// Size of the variable-length payload following the command structure generated by
    /// [`CommandToGsp::init`].
    ///
    /// Most commands don't have a variable-length payload, so this is zero by default.
    fn variable_payload_len(&self) -> usize {
        0
    }

    /// Method initializing the variable-length payload.
    ///
    /// The command buffer is circular, which means that we may need to jump back to its beginning
    /// while in the middle of a command. For this reason, the variable-length payload is
    /// initialized using a [`SBufferIter`].
    ///
    /// This method will receive a buffer of the length returned by
    /// [`CommandToGsp::variable_payload_len`], and must write every single byte of it. Leaving
    /// unwritten space will lead to an error.
    ///
    /// Most commands don't have a variable-length payload, so this does nothing by default.
    fn init_variable_payload(
        &self,
        _dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
    ) -> Result {
        Ok(())
    }
}

/// Trait representing messages received from the GSP.
///
/// This trait tells [`Cmdq::receive_msg`] how it can receive a given type of message.
pub(crate) trait MessageFromGsp: Sized {
    /// Function identifying this message from the GSP.
    const FUNCTION: MsgFunction;

    /// Error type returned by [`MessageFromGsp::read`].
    type InitError;

    /// Type containing the raw message to be read from the message queue.
    type Message: FromBytes;

    /// Method reading the message from the message queue and returning it.
    ///
    /// From a `Self::Message` and a [`SBufferIter`], constructs an instance of `Self` and returns
    /// it.
    fn read(
        msg: &Self::Message,
        sbuffer: &mut SBufferIter<core::array::IntoIter<&[u8], 2>>,
    ) -> Result<Self, Self::InitError>;
}

/// Number of GSP pages making the [`Msgq`].
pub(crate) const MSGQ_NUM_PAGES: u32 = 0x3f;

/// Circular buffer of a [`Msgq`].
///
/// This area of memory is to be shared between the driver and the GSP to exchange commands or
/// messages.
#[repr(C, align(0x1000))]
#[derive(Debug)]
struct MsgqData {
    data: [[u8; GSP_PAGE_SIZE]; num::u32_as_usize(MSGQ_NUM_PAGES)],
}

// Annoyingly we are forced to use a literal to specify the alignment of
// `MsgqData`, so check that it corresponds to the actual GSP page size here.
static_assert!(align_of::<MsgqData>() == GSP_PAGE_SIZE);

/// Unidirectional message queue.
///
/// Contains the data for a message queue, that either the driver or GSP writes to.
///
/// Note that while the write pointer of `tx` corresponds to the `msgq` of the same instance, the
/// read pointer of `rx` actually refers to the `Msgq` owned by the other side.
/// This design ensures that only the driver or GSP ever writes to a given instance of this struct.
#[repr(C)]
// There is no struct defined for this in the open-gpu-kernel-source headers.
// Instead it is defined by code in `GspMsgQueuesInit()`.
struct Msgq {
    /// Header for sending messages, including the write pointer.
    tx: MsgqTxHeader,
    /// Header for receiving messages, including the read pointer.
    rx: MsgqRxHeader,
    /// The message queue proper.
    msgq: MsgqData,
}

/// Structure shared between the driver and the GSP and containing the command and message queues.
#[repr(C)]
struct GspMem {
    /// Self-mapping page table entries.
    ptes: PteArray<{ GSP_PAGE_SIZE / size_of::<u64>() }>,
    /// CPU queue: the driver writes commands here, and the GSP reads them. It also contains the
    /// write and read pointers that the CPU updates.
    ///
    /// This member is read-only for the GSP.
    cpuq: Msgq,
    /// GSP queue: the GSP writes messages here, and the driver reads them. It also contains the
    /// write and read pointers that the GSP updates.
    ///
    /// This member is read-only for the driver.
    gspq: Msgq,
}

// SAFETY: These structs don't meet the no-padding requirements of AsBytes but
// that is not a problem because they are not used outside the kernel.
unsafe impl AsBytes for GspMem {}

// SAFETY: These structs don't meet the no-padding requirements of FromBytes but
// that is not a problem because they are not used outside the kernel.
unsafe impl FromBytes for GspMem {}

/// Wrapper around [`GspMem`] to share it with the GPU using a [`CoherentAllocation`].
///
/// This provides the low-level functionality to communicate with the GSP, including allocation of
/// queue space to write messages to and management of read/write pointers.
///
/// This is shared with the GSP, with clear ownership rules regarding the command queues:
///
/// * The driver owns (i.e. can write to) the part of the CPU message queue between the CPU write
///   pointer and the GSP read pointer. This region is returned by [`Self::driver_write_area`].
/// * The driver owns (i.e. can read from) the part of the GSP message queue between the CPU read
///   pointer and the GSP write pointer. This region is returned by [`Self::driver_read_area`].
struct DmaGspMem(CoherentAllocation<GspMem>);

impl DmaGspMem {
    /// Allocate a new instance and map it for `dev`.
    fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
        const MSGQ_SIZE: u32 = num::usize_into_u32::<{ size_of::<Msgq>() }>();
        const RX_HDR_OFF: u32 = num::usize_into_u32::<{ mem::offset_of!(Msgq, rx) }>();

        let gsp_mem =
            CoherentAllocation::<GspMem>::alloc_coherent(dev, 1, GFP_KERNEL | __GFP_ZERO)?;
        dma_write!(gsp_mem[0].ptes = PteArray::new(gsp_mem.dma_handle())?)?;
        dma_write!(gsp_mem[0].cpuq.tx = MsgqTxHeader::new(MSGQ_SIZE, RX_HDR_OFF, MSGQ_NUM_PAGES))?;
        dma_write!(gsp_mem[0].cpuq.rx = MsgqRxHeader::new())?;

        Ok(Self(gsp_mem))
    }

    /// Returns the region of the CPU message queue that the driver is currently allowed to write
    /// to.
    ///
    /// As the message queue is a circular buffer, the region may be discontiguous in memory. In
    /// that case the second slice will have a non-zero length.
    fn driver_write_area(&mut self) -> (&mut [[u8; GSP_PAGE_SIZE]], &mut [[u8; GSP_PAGE_SIZE]]) {
        let tx = self.cpu_write_ptr() as usize;
        let rx = self.gsp_read_ptr() as usize;

        // SAFETY:
        // - The `CoherentAllocation` contains exactly one object.
        // - We will only access the driver-owned part of the shared memory.
        // - Per the safety statement of the function, no concurrent access will be performed.
        let gsp_mem = &mut unsafe { self.0.as_slice_mut(0, 1) }.unwrap()[0];
        // PANIC: per the invariant of `cpu_write_ptr`, `tx` is `<= MSGQ_NUM_PAGES`.
        let (before_tx, after_tx) = gsp_mem.cpuq.msgq.data.split_at_mut(tx);

        if rx <= tx {
            // The area from `tx` up to the end of the ring, and from the beginning of the ring up
            // to `rx`, minus one unit, belongs to the driver.
            if rx == 0 {
                let last = after_tx.len() - 1;
                (&mut after_tx[..last], &mut before_tx[0..0])
            } else {
                (after_tx, &mut before_tx[..rx])
            }
        } else {
            // The area from `tx` to `rx`, minus one unit, belongs to the driver.
            //
            // PANIC: per the invariants of `cpu_write_ptr` and `gsp_read_ptr`, `rx` and `tx` are
            // `<= MSGQ_NUM_PAGES`, and the test above ensured that `rx > tx`.
            (after_tx.split_at_mut(rx - tx).0, &mut before_tx[0..0])
        }
    }

    /// Returns the region of the GSP message queue that the driver is currently allowed to read
    /// from.
    ///
    /// As the message queue is a circular buffer, the region may be discontiguous in memory. In
    /// that case the second slice will have a non-zero length.
    fn driver_read_area(&self) -> (&[[u8; GSP_PAGE_SIZE]], &[[u8; GSP_PAGE_SIZE]]) {
        let tx = self.gsp_write_ptr() as usize;
        let rx = self.cpu_read_ptr() as usize;

        // SAFETY:
        // - The `CoherentAllocation` contains exactly one object.
        // - We will only access the driver-owned part of the shared memory.
        // - Per the safety statement of the function, no concurrent access will be performed.
        let gsp_mem = &unsafe { self.0.as_slice(0, 1) }.unwrap()[0];
        // PANIC: per the invariant of `cpu_read_ptr`, `xx` is `<= MSGQ_NUM_PAGES`.
        let (before_rx, after_rx) = gsp_mem.gspq.msgq.data.split_at(rx);

        match tx.cmp(&rx) {
            cmp::Ordering::Equal => (&after_rx[0..0], &after_rx[0..0]),
            cmp::Ordering::Greater => (&after_rx[..tx], &before_rx[0..0]),
            cmp::Ordering::Less => (after_rx, &before_rx[..tx]),
        }
    }

    /// Allocates a region on the command queue that is large enough to send a command of `size`
    /// bytes.
    ///
    /// This returns a [`GspCommand`] ready to be written to by the caller.
    ///
    /// # Errors
    ///
    /// - `EAGAIN` if the driver area is too small to hold the requested command.
    /// - `EIO` if the command header is not properly aligned.
    fn allocate_command(&mut self, size: usize) -> Result<GspCommand<'_>> {
        // Get the current writable area as an array of bytes.
        let (slice_1, slice_2) = {
            let (slice_1, slice_2) = self.driver_write_area();

            #[allow(clippy::incompatible_msrv)]
            (slice_1.as_flattened_mut(), slice_2.as_flattened_mut())
        };

        // If the GSP is still processing previous messages the shared region
        // may be full in which case we will have to retry once the GSP has
        // processed the existing commands.
        if size_of::<GspMsgElement>() + size > slice_1.len() + slice_2.len() {
            return Err(EAGAIN);
        }

        // Extract area for the `GspMsgElement`.
        let (header, slice_1) = GspMsgElement::from_bytes_mut_prefix(slice_1).ok_or(EIO)?;

        // Create the contents area.
        let (slice_1, slice_2) = if slice_1.len() > size {
            // Contents fits entirely in `slice_1`.
            (&mut slice_1[..size], &mut slice_2[0..0])
        } else {
            // Need all of `slice_1` and some of `slice_2`.
            let slice_2_len = size - slice_1.len();
            (slice_1, &mut slice_2[..slice_2_len])
        };

        Ok(GspCommand {
            header,
            contents: (slice_1, slice_2),
        })
    }

    // Returns the index of the memory page the GSP will write the next message to.
    //
    // # Invariants
    //
    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
    fn gsp_write_ptr(&self) -> u32 {
        let gsp_mem = self.0.start_ptr();

        // SAFETY:
        //  - The 'CoherentAllocation' contains at least one object.
        //  - By the invariants of `CoherentAllocation` the pointer is valid.
        (unsafe { (*gsp_mem).gspq.tx.write_ptr() } % MSGQ_NUM_PAGES)
    }

    // Returns the index of the memory page the GSP will read the next command from.
    //
    // # Invariants
    //
    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
    fn gsp_read_ptr(&self) -> u32 {
        let gsp_mem = self.0.start_ptr();

        // SAFETY:
        //  - The 'CoherentAllocation' contains at least one object.
        //  - By the invariants of `CoherentAllocation` the pointer is valid.
        (unsafe { (*gsp_mem).gspq.rx.read_ptr() } % MSGQ_NUM_PAGES)
    }

    // Returns the index of the memory page the CPU can read the next message from.
    //
    // # Invariants
    //
    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
    fn cpu_read_ptr(&self) -> u32 {
        let gsp_mem = self.0.start_ptr();

        // SAFETY:
        //  - The ['CoherentAllocation'] contains at least one object.
        //  - By the invariants of CoherentAllocation the pointer is valid.
        (unsafe { (*gsp_mem).cpuq.rx.read_ptr() } % MSGQ_NUM_PAGES)
    }

    // Informs the GSP that it can send `elem_count` new pages into the message queue.
    fn advance_cpu_read_ptr(&mut self, elem_count: u32) {
        let rptr = self.cpu_read_ptr().wrapping_add(elem_count) % MSGQ_NUM_PAGES;

        // Ensure read pointer is properly ordered.
        fence(Ordering::SeqCst);

        let gsp_mem = self.0.start_ptr_mut();

        // SAFETY:
        //  - The 'CoherentAllocation' contains at least one object.
        //  - By the invariants of `CoherentAllocation` the pointer is valid.
        unsafe { (*gsp_mem).cpuq.rx.set_read_ptr(rptr) };
    }

    // Returns the index of the memory page the CPU can write the next command to.
    //
    // # Invariants
    //
    // - The returned value is between `0` and `MSGQ_NUM_PAGES`.
    fn cpu_write_ptr(&self) -> u32 {
        let gsp_mem = self.0.start_ptr();

        // SAFETY:
        //  - The 'CoherentAllocation' contains at least one object.
        //  - By the invariants of `CoherentAllocation` the pointer is valid.
        (unsafe { (*gsp_mem).cpuq.tx.write_ptr() } % MSGQ_NUM_PAGES)
    }

    // Informs the GSP that it can process `elem_count` new pages from the command queue.
    fn advance_cpu_write_ptr(&mut self, elem_count: u32) {
        let wptr = self.cpu_write_ptr().wrapping_add(elem_count) & MSGQ_NUM_PAGES;
        let gsp_mem = self.0.start_ptr_mut();

        // SAFETY:
        //  - The 'CoherentAllocation' contains at least one object.
        //  - By the invariants of `CoherentAllocation` the pointer is valid.
        unsafe { (*gsp_mem).cpuq.tx.set_write_ptr(wptr) };

        // Ensure all command data is visible before triggering the GSP read.
        fence(Ordering::SeqCst);
    }
}

/// A command ready to be sent on the command queue.
///
/// This is the type returned by [`DmaGspMem::allocate_command`].
struct GspCommand<'a> {
    // Writable reference to the header of the command.
    header: &'a mut GspMsgElement,
    // Writable slices to the contents of the command. The second slice is zero unless the command
    // loops over the command queue.
    contents: (&'a mut [u8], &'a mut [u8]),
}

/// A message ready to be processed from the message queue.
///
/// This is the type returned by [`Cmdq::wait_for_msg`].
struct GspMessage<'a> {
    // Reference to the header of the message.
    header: &'a GspMsgElement,
    // Slices to the contents of the message. The second slice is zero unless the message loops
    // over the message queue.
    contents: (&'a [u8], &'a [u8]),
}

/// GSP command queue.
///
/// Provides the ability to send commands and receive messages from the GSP using a shared memory
/// area.
pub(crate) struct Cmdq {
    /// Device this command queue belongs to.
    dev: ARef<device::Device>,
    /// Current command sequence number.
    seq: u32,
    /// Memory area shared with the GSP for communicating commands and messages.
    gsp_mem: DmaGspMem,
}

impl Cmdq {
    /// Offset of the data after the PTEs.
    const POST_PTE_OFFSET: usize = core::mem::offset_of!(GspMem, cpuq);

    /// Offset of command queue ring buffer.
    pub(crate) const CMDQ_OFFSET: usize = core::mem::offset_of!(GspMem, cpuq)
        + core::mem::offset_of!(Msgq, msgq)
        - Self::POST_PTE_OFFSET;

    /// Offset of message queue ring buffer.
    pub(crate) const STATQ_OFFSET: usize = core::mem::offset_of!(GspMem, gspq)
        + core::mem::offset_of!(Msgq, msgq)
        - Self::POST_PTE_OFFSET;

    /// Number of page table entries for the GSP shared region.
    pub(crate) const NUM_PTES: usize = size_of::<GspMem>() >> GSP_PAGE_SHIFT;

    /// Creates a new command queue for `dev`.
    pub(crate) fn new(dev: &device::Device<device::Bound>) -> Result<Cmdq> {
        let gsp_mem = DmaGspMem::new(dev)?;

        Ok(Cmdq {
            dev: dev.into(),
            seq: 0,
            gsp_mem,
        })
    }

    /// Computes the checksum for the message pointed to by `it`.
    ///
    /// A message is made of several parts, so `it` is an iterator over byte slices representing
    /// these parts.
    fn calculate_checksum<T: Iterator<Item = u8>>(it: T) -> u32 {
        let sum64 = it
            .enumerate()
            .map(|(idx, byte)| (((idx % 8) * 8) as u32, byte))
            .fold(0, |acc, (rol, byte)| acc ^ u64::from(byte).rotate_left(rol));

        ((sum64 >> 32) as u32) ^ (sum64 as u32)
    }

    /// Notifies the GSP that we have updated the command queue pointers.
    fn notify_gsp(bar: &Bar0) {
        regs::NV_PGSP_QUEUE_HEAD::default()
            .set_address(0)
            .write(bar);
    }

    /// Sends `command` to the GSP.
    ///
    /// # Errors
    ///
    /// - `EAGAIN` if there was not enough space in the command queue to send the command.
    /// - `EIO` if the variable payload requested by the command has not been entirely
    ///   written to by its [`CommandToGsp::init_variable_payload`] method.
    ///
    /// Error codes returned by the command initializers are propagated as-is.
    pub(crate) fn send_command<M>(&mut self, bar: &Bar0, command: M) -> Result
    where
        M: CommandToGsp,
        // This allows all error types, including `Infallible`, to be used for `M::InitError`.
        Error: From<M::InitError>,
    {
        let command_size = size_of::<M::Command>() + command.variable_payload_len();
        let dst = self.gsp_mem.allocate_command(command_size)?;

        // Extract area for the command itself.
        let (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?;

        // Fill the header and command in-place.
        let msg_element = GspMsgElement::init(self.seq, command_size, M::FUNCTION);
        // SAFETY: `msg_header` and `cmd` are valid references, and not touched if the initializer
        // fails.
        unsafe {
            msg_element.__init(core::ptr::from_mut(dst.header))?;
            command.init().__init(core::ptr::from_mut(cmd))?;
        }

        // Fill the variable-length payload.
        if command_size > size_of::<M::Command>() {
            let mut sbuffer =
                SBufferIter::new_writer([&mut payload_1[..], &mut dst.contents.1[..]]);
            command.init_variable_payload(&mut sbuffer)?;

            if !sbuffer.is_empty() {
                return Err(EIO);
            }
        }

        // Compute checksum now that the whole message is ready.
        dst.header
            .set_checksum(Cmdq::calculate_checksum(SBufferIter::new_reader([
                dst.header.as_bytes(),
                dst.contents.0,
                dst.contents.1,
            ])));

        dev_dbg!(
            &self.dev,
            "GSP RPC: send: seq# {}, function={}, length=0x{:x}\n",
            self.seq,
            M::FUNCTION,
            dst.header.length(),
        );

        // All set - update the write pointer and inform the GSP of the new command.
        let elem_count = dst.header.element_count();
        self.seq += 1;
        self.gsp_mem.advance_cpu_write_ptr(elem_count);
        Cmdq::notify_gsp(bar);

        Ok(())
    }

    /// Wait for a message to become available on the message queue.
    ///
    /// This works purely at the transport layer and does not interpret or validate the message
    /// beyond the advertised length in its [`GspMsgElement`].
    ///
    /// This method returns:
    ///
    /// - A reference to the [`GspMsgElement`] of the message,
    /// - Two byte slices with the contents of the message. The second slice is empty unless the
    ///   message loops across the message queue.
    ///
    /// # Errors
    ///
    /// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
    /// - `EIO` if there was some inconsistency (e.g. message shorter than advertised) on the
    ///   message queue.
    ///
    /// Error codes returned by the message constructor are propagated as-is.
    fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
        // Wait for a message to arrive from the GSP.
        let (slice_1, slice_2) = read_poll_timeout(
            || Ok(self.gsp_mem.driver_read_area()),
            |driver_area| !driver_area.0.is_empty(),
            Delta::from_millis(1),
            timeout,
        )
        .map(|(slice_1, slice_2)| {
            #[allow(clippy::incompatible_msrv)]
            (slice_1.as_flattened(), slice_2.as_flattened())
        })?;

        // Extract the `GspMsgElement`.
        let (header, slice_1) = GspMsgElement::from_bytes_prefix(slice_1).ok_or(EIO)?;

        dev_dbg!(
            self.dev,
            "GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
            header.sequence(),
            header.function(),
            header.length(),
        );

        // Check that the driver read area is large enough for the message.
        if slice_1.len() + slice_2.len() < header.length() {
            return Err(EIO);
        }

        // Cut the message slices down to the actual length of the message.
        let (slice_1, slice_2) = if slice_1.len() > header.length() {
            // PANIC: we checked above that `slice_1` is at least as long as `msg_header.length()`.
            (slice_1.split_at(header.length()).0, &slice_2[0..0])
        } else {
            (
                slice_1,
                // PANIC: we checked above that `slice_1.len() + slice_2.len()` is at least as
                // large as `msg_header.length()`.
                slice_2.split_at(header.length() - slice_1.len()).0,
            )
        };

        // Validate checksum.
        if Cmdq::calculate_checksum(SBufferIter::new_reader([
            header.as_bytes(),
            slice_1,
            slice_2,
        ])) != 0
        {
            dev_err!(
                self.dev,
                "GSP RPC: receive: Call {} - bad checksum",
                header.sequence()
            );
            return Err(EIO);
        }

        Ok(GspMessage {
            header,
            contents: (slice_1, slice_2),
        })
    }

    /// Receive a message from the GSP.
    ///
    /// `init` is a closure tasked with processing the message. It receives a reference to the
    /// message in the message queue, and a [`SBufferIter`] pointing to its variable-length
    /// payload, if any.
    ///
    /// The expected message is specified using the `M` generic parameter. If the pending message
    /// is different, `EAGAIN` is returned and the unexpected message is dropped.
    ///
    /// This design is by no means final, but it is simple and will let us go through GSP
    /// initialization.
    ///
    /// # Errors
    ///
    /// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
    /// - `EIO` if there was some inconsistency (e.g. message shorter than advertised) on the
    ///   message queue.
    /// - `EINVAL` if the function of the message was unrecognized.
    pub(crate) fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
    where
        // This allows all error types, including `Infallible`, to be used for `M::InitError`.
        Error: From<M::InitError>,
    {
        let message = self.wait_for_msg(timeout)?;
        let function = message.header.function().map_err(|_| EINVAL)?;

        // Extract the message. Store the result as we want to advance the read pointer even in
        // case of failure.
        let result = if function == M::FUNCTION {
            let (cmd, contents_1) = M::Message::from_bytes_prefix(message.contents.0).ok_or(EIO)?;
            let mut sbuffer = SBufferIter::new_reader([contents_1, message.contents.1]);

            M::read(cmd, &mut sbuffer).map_err(|e| e.into())
        } else {
            Err(ERANGE)
        };

        // Advance the read pointer past this message.
        self.gsp_mem.advance_cpu_read_ptr(u32::try_from(
            message.header.length().div_ceil(GSP_PAGE_SIZE),
        )?);

        result
    }

    /// Returns the DMA handle of the command queue's shared memory region.
    pub(crate) fn dma_handle(&self) -> DmaAddress {
        self.gsp_mem.0.dma_handle()
    }
}