summaryrefslogtreecommitdiff
path: root/drivers/gpu/nova-core/firmware/booter.rs
blob: dc071edba3315ff11b9038aebf37b63e57464bd3 (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
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

//! Support for loading and patching the `Booter` firmware. `Booter` is a Heavy Secured firmware
//! running on [`Sec2`], that is used on Turing/Ampere to load the GSP firmware into the GSP falcon
//! (and optionally unload it through a separate firmware image).

use core::marker::PhantomData;

use kernel::{
    device,
    dma::Coherent,
    prelude::*, //
};

use crate::{
    falcon::{
        sec2::Sec2,
        Falcon,
        FalconBromParams,
        FalconDmaLoadTarget,
        FalconDmaLoadable,
        FalconFirmware, //
    },
    firmware::{
        tlv::{
            request_tlv, //
            Tlv,
        },
        FirmwareObject,
        FirmwareSignature,
        Signed,
        Unsigned, //
    },
    gpu::Chipset,
    num::IntoSafeCast,
};

/// Signature for Booter firmware. Their size is encoded into the header and not known a compile
/// time, so we just wrap a byte slices on which we can implement [`FirmwareSignature`].
struct BooterSignature<'a>(&'a [u8]);

impl<'a> AsRef<[u8]> for BooterSignature<'a> {
    fn as_ref(&self) -> &[u8] {
        self.0
    }
}

impl<'a> FirmwareSignature<BooterFirmware> for BooterSignature<'a> {}

/// The `Booter` loader firmware, responsible for loading the GSP.
pub(crate) struct BooterFirmware {
    // Load parameters for Secure `IMEM` falcon memory.
    imem_sec_load_target: FalconDmaLoadTarget,
    // Load parameters for Non-Secure `IMEM` falcon memory,
    // used only on Turing and GA100
    imem_ns_load_target: Option<FalconDmaLoadTarget>,
    // Load parameters for `DMEM` falcon memory.
    dmem_load_target: FalconDmaLoadTarget,
    // BROM falcon parameters.
    brom_params: FalconBromParams,
    // Device-mapped firmware image.
    ucode: FirmwareObject<Self, Signed>,
}

impl FirmwareObject<BooterFirmware, Unsigned> {
    fn new_booter(data: &[u8]) -> Result<Self> {
        let mut ucode = KVVec::new();
        ucode.extend_from_slice(data, GFP_KERNEL)?;

        Ok(Self(ucode, PhantomData))
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum BooterKind {
    Loader,
    Unloader,
}

impl BooterFirmware {
    /// Parses the Booter firmware contained in `fw`, and patches the correct signature so it is
    /// ready to be loaded and run on `falcon`.
    pub(crate) fn new(
        dev: &device::Device<device::Bound>,
        kind: BooterKind,
        chipset: Chipset,
        falcon: &Falcon<'_, <Self as FalconFirmware>::Target>,
    ) -> Result<Self> {
        let fw_name = match kind {
            BooterKind::Loader => "booter_load",
            BooterKind::Unloader => "booter_unload",
        };
        let fw = request_tlv(dev, chipset, fw_name)?;
        let tlv = Tlv::new(fw.data())?;
        dev_dbg!(
            dev,
            "loaded {} firmware v{}\n",
            fw_name,
            tlv.get_string(b"VERS")?
        );

        let os_data_offset = tlv.get_u32(b"DAOF")?;
        let os_data_size = tlv.get_u32(b"DASZ")?;
        let os_code_offset = tlv.get_u32(b"CDOF")?;
        let os_code_size = tlv.get_u32(b"CDSZ")?;
        let patch_loc = tlv.get_u32(b"PLOC")?;
        let fuse_version: usize = tlv.get_u32(b"FUSE")?.into_safe_cast();
        let engine_id = tlv.get_u32(b"ENID")?;
        let ucode_id = tlv.get_u32(b"UCID")?;
        let app0_code_offset = tlv.get_u32(b"A0CO")?;
        let app0_code_size = tlv.get_u32(b"A0CS")?;

        let brom_params = FalconBromParams {
            // `os_data_offset` is an absolute index, but `pkc_data_offset` is from the
            // signature patch location.
            pkc_data_offset: patch_loc.checked_sub(os_data_offset).ok_or(EINVAL)?,
            engine_id_mask: u16::try_from(engine_id).map_err(|_| EINVAL)?,
            ucode_id: u8::try_from(ucode_id).map_err(|_| EINVAL)?,
        };

        let ucode = tlv
            .get_bytes(b"BLOB")
            .and_then(FirmwareObject::<Self, _>::new_booter)?;

        // Obtain the version from the fuse register, and extract the corresponding
        // signature.
        let reg_fuse_version: usize = falcon
            .signature_reg_fuse_version(brom_params.engine_id_mask, brom_params.ucode_id)?
            .into_safe_cast();

        const FUSE_VERSION_USE_LAST_SIG: usize = 0;

        let index = match reg_fuse_version {
            // `0` means the last signature should be used.
            FUSE_VERSION_USE_LAST_SIG => None,
            // Otherwise, hardware fuse version needs to be subtracted to obtain the index.
            _ => Some(fuse_version.checked_sub(reg_fuse_version).ok_or(EINVAL)?),
        };

        // Extract the nth signature.  Booter is always signed.
        let sig_chunk = tlv.get_signature(index)?;

        let signature = BooterSignature(sig_chunk);
        let ucode_signed = ucode.patch_signature(&signature, patch_loc.into_safe_cast())?;

        // There are two versions of Booter, one for Turing/GA100, and another for
        // GA102+.  The extraction of the IMEM sections differs between the two
        // versions.  Unfortunately, the file names are the same, and the headers
        // don't indicate the versions.  The only way to differentiate is by the Chipset.
        let (imem_sec_dst_start, imem_ns_load_target) = if chipset <= Chipset::GA100 {
            (
                app0_code_offset,
                Some(FalconDmaLoadTarget {
                    src_start: 0,
                    dst_start: os_code_offset,
                    len: os_code_size,
                }),
            )
        } else {
            (0, None)
        };

        Ok(Self {
            imem_sec_load_target: FalconDmaLoadTarget {
                src_start: app0_code_offset,
                dst_start: imem_sec_dst_start,
                len: app0_code_size,
            },
            imem_ns_load_target,
            dmem_load_target: FalconDmaLoadTarget {
                src_start: os_data_offset,
                dst_start: 0,
                len: os_data_size,
            },
            brom_params,
            ucode: ucode_signed,
        })
    }

    /// Load and run the booter firmware on SEC2.
    ///
    /// Resets SEC2, loads this firmware image, then boots with the WPR metadata
    /// address passed via the SEC2 mailboxes.
    pub(crate) fn run<T>(
        &self,
        dev: &device::Device<device::Bound>,
        sec2_falcon: &Falcon<'_, Sec2>,
        wpr_meta: &Coherent<T>,
    ) -> Result {
        sec2_falcon.reset()?;
        sec2_falcon.load(self)?;
        let wpr_dma_address = wpr_meta.dma_address();
        let (mbox0, mbox1) = sec2_falcon.boot(
            Some(wpr_dma_address as u32),
            Some((wpr_dma_address >> 32) as u32),
        )?;
        dev_dbg!(dev, "SEC2 MBOX0: {:#x}, MBOX1: {:#x}\n", mbox0, mbox1);

        if mbox0 != 0 {
            dev_err!(dev, "Booter-load failed with error {:#x}\n", mbox0);
            return Err(ENODEV);
        }

        Ok(())
    }
}

impl FalconDmaLoadable for BooterFirmware {
    fn as_slice(&self) -> &[u8] {
        self.ucode.0.as_slice()
    }

    fn imem_sec_load_params(&self) -> FalconDmaLoadTarget {
        self.imem_sec_load_target.clone()
    }

    fn imem_ns_load_params(&self) -> Option<FalconDmaLoadTarget> {
        self.imem_ns_load_target.clone()
    }

    fn dmem_load_params(&self) -> FalconDmaLoadTarget {
        self.dmem_load_target.clone()
    }
}

impl FalconFirmware for BooterFirmware {
    type Target = Sec2;

    fn brom_params(&self) -> FalconBromParams {
        self.brom_params.clone()
    }

    fn boot_addr(&self) -> u32 {
        if let Some(ns_target) = &self.imem_ns_load_target {
            ns_target.dst_start
        } else {
            self.imem_sec_load_target.src_start
        }
    }
}