1 // SPDX-License-Identifier: GPL-2.0 or MIT 2 3 //! Firmware loading and management for Mali CSF GPU. 4 //! 5 //! This module handles loading the Mali GPU firmware binary, parsing it into sections, 6 //! and mapping those sections into the MCU's virtual address space. Each firmware section 7 //! has specific properties (read/write/execute permissions, cache modes) and must be loaded 8 //! at specific virtual addresses expected by the MCU. 9 //! 10 //! See [`Firmware`] for the main firmware management interface and [`Section`] for 11 //! individual firmware sections. 12 //! 13 //! [`Firmware`]: crate::fw::Firmware 14 //! [`Section`]: crate::fw::Section 15 16 use kernel::{ 17 device::{ 18 Bound, 19 Device, // 20 }, 21 drm::{ 22 gem::BaseObject, // 23 }, 24 io::{ 25 poll, 26 Io, // 27 }, 28 num::Bounded, 29 prelude::*, 30 register, 31 str::CString, 32 sync::{ 33 Arc, 34 ArcBorrow, // 35 }, 36 time, // 37 }; 38 39 use crate::{ 40 driver::{ 41 IoMem, 42 TyrDrmDevice, // 43 }, 44 fw::parser::{ 45 FwParser, 46 ParsedSection, // 47 }, 48 gem, 49 gem::{ 50 KernelBo, 51 KernelBoVaAlloc, // 52 }, 53 gpu::GpuInfo, 54 55 mmu::Mmu, 56 regs::{ 57 gpu_control::{ 58 McuControlMode, 59 McuStatus, 60 GPU_ID, 61 MCU_CONTROL, 62 MCU_STATUS, // 63 }, // 64 job_control::{ 65 JOB_IRQ_CLEAR, 66 JOB_IRQ_RAWSTAT, // 67 }, // 68 }, 69 vm::Vm, // 70 }; 71 72 mod parser; 73 74 pub(super) const CSF_MCU_SHARED_REGION_START: u32 = 0x04000000; 75 76 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 77 #[repr(u8)] 78 pub(super) enum CacheMode { 79 None = 0, 80 Cached = 1, 81 UncachedCoherent = 2, 82 CachedCoherent = 3, 83 } 84 85 impl From<Bounded<u32, 2>> for CacheMode { 86 fn from(value: Bounded<u32, 2>) -> Self { 87 match value.get() { 88 0 => Self::None, 89 1 => Self::Cached, 90 2 => Self::UncachedCoherent, 91 3 => Self::CachedCoherent, 92 _ => unreachable!(), 93 } 94 } 95 } 96 97 impl From<CacheMode> for Bounded<u32, 2> { 98 fn from(value: CacheMode) -> Self { 99 Bounded::try_new(value as u32).unwrap() 100 } 101 } 102 103 register! { 104 #[allow(non_upper_case_globals)] 105 pub(super) SectionFlags(u32) @ 0x0 { 106 0:0 read => bool; 107 1:1 write => bool; 108 2:2 exec => bool; 109 4:3 cache_mode => CacheMode; 110 5:5 prot => bool; 111 30:30 shared => bool; 112 31:31 zero => bool; 113 } 114 } 115 116 impl SectionFlags { 117 const VALID_MASK: u32 = Self::READ_MASK 118 | Self::WRITE_MASK 119 | Self::EXEC_MASK 120 | Self::CACHE_MODE_MASK 121 | Self::PROT_MASK 122 | Self::SHARED_MASK 123 | Self::ZERO_MASK; 124 125 fn try_from_fw(value: u32) -> Result<Self> { 126 if value & !Self::VALID_MASK != 0 { 127 Err(EINVAL) 128 } else { 129 Ok(Self::from_raw(value)) 130 } 131 } 132 } 133 134 /// A parsed section of the firmware binary. 135 struct Section<'drm> { 136 // Raw firmware section data for reset purposes 137 #[expect(dead_code)] 138 data: KVec<u8>, 139 140 // Keep the BO backing this firmware section so that both the 141 // GPU mapping and CPU mapping remain valid until the Section is dropped. 142 #[expect(dead_code)] 143 mem: gem::KernelBo<'drm>, 144 } 145 146 /// Loaded firmware with sections mapped into MCU VM. 147 pub(crate) struct Firmware<'drm> { 148 /// Iomem need to access registers. 149 iomem: Arc<IoMem<'drm>>, 150 151 /// MCU VM. 152 vm: Arc<Vm<'drm>>, 153 154 /// List of firmware sections. 155 #[expect(dead_code)] 156 sections: KVec<Section<'drm>>, 157 } 158 159 impl<'drm> Drop for Firmware<'drm> { 160 fn drop(&mut self) { 161 // Stop the MCU before releasing its firmware mappings and memory. 162 let _ = self.stop(); 163 164 // AS slots retain a VM ref, we need to kill the circular ref manually. 165 self.vm.kill(); 166 } 167 } 168 169 impl<'drm> Firmware<'drm> { 170 fn init_section_mem(dev: &Device, mem: &mut KernelBo<'drm>, data: &KVec<u8>) -> Result { 171 if data.is_empty() { 172 return Ok(()); 173 } 174 175 let vmap = mem.bo().vmap::<0>()?; 176 let size = mem.bo().size(); 177 178 if data.len() > size { 179 dev_err!(dev, "fw section {} bigger than BO {}", data.len(), size); 180 return Err(EINVAL); 181 } 182 183 for (i, &byte) in data.iter().enumerate() { 184 vmap.try_write8(byte, i)?; 185 } 186 187 Ok(()) 188 } 189 190 fn request(ddev: &TyrDrmDevice, gpu_info: &GpuInfo) -> Result<kernel::firmware::Firmware> { 191 let gpu_id = GPU_ID::from_raw(gpu_info.gpu_id); 192 193 let path = CString::try_from_fmt(fmt!( 194 "arm/mali/arch{}.{}/mali_csffw.bin", 195 gpu_id.arch_major().get(), 196 gpu_id.arch_minor().get() 197 ))?; 198 199 kernel::firmware::Firmware::request(&path, ddev.as_ref().as_ref()) 200 } 201 202 fn load( 203 dev: &Device, 204 ddev: &TyrDrmDevice, 205 gpu_info: &GpuInfo, 206 ) -> Result<(kernel::firmware::Firmware, KVec<ParsedSection>)> { 207 let fw = Self::request(ddev, gpu_info)?; 208 let mut parser = FwParser::new(dev, fw.data()); 209 210 let parsed_sections = parser.parse()?; 211 212 Ok((fw, parsed_sections)) 213 } 214 215 /// Load firmware and map sections into MCU VM. 216 pub(crate) fn new( 217 dev: &'drm Device<Bound>, 218 iomem: Arc<IoMem<'drm>>, 219 ddev: &TyrDrmDevice, 220 mmu: ArcBorrow<'_, Mmu<'drm>>, 221 gpu_info: &GpuInfo, 222 ) -> Result<Firmware<'drm>> { 223 let vm = Vm::new(dev, ddev, mmu, gpu_info)?; 224 vm.activate()?; 225 226 let result = (|| { 227 let (fw, parsed_sections) = Self::load(dev, ddev, gpu_info)?; 228 let mut sections = KVec::new(); 229 for parsed in parsed_sections { 230 let size = u64::from(parsed.va.end.checked_sub(parsed.va.start).ok_or(EINVAL)?); 231 232 let va = u64::from(parsed.va.start); 233 234 let mut mem = KernelBo::new( 235 ddev, 236 vm.clone(), 237 size, 238 KernelBoVaAlloc::Explicit(va), 239 parsed.vm_map_flags, 240 )?; 241 242 let section_start = parsed.data_range.start as usize; 243 let section_end = parsed.data_range.end as usize; 244 let mut data = KVec::new(); 245 246 // Ensure that the firmware slice is not out of bounds. 247 let fw_data = fw.data(); 248 let bytes = fw_data.get(section_start..section_end).ok_or(EINVAL)?; 249 data.extend_from_slice(bytes, GFP_KERNEL)?; 250 251 Self::init_section_mem(dev, &mut mem, &data)?; 252 253 sections.push(Section { data, mem }, GFP_KERNEL)?; 254 } 255 256 Ok(Firmware { 257 iomem, 258 vm: vm.clone(), 259 sections, 260 }) 261 })(); 262 263 if result.is_err() { 264 vm.kill(); 265 } 266 267 result 268 } 269 270 pub(crate) fn boot(&self) -> Result { 271 let io = &self.iomem; 272 273 // Discard any stale global interrupt. 274 io.write_reg(JOB_IRQ_CLEAR::zeroed().with_glb(true)); 275 276 io.write_reg(MCU_CONTROL::zeroed().with_req(McuControlMode::Auto)); 277 278 if let Err(e) = poll::read_poll_timeout( 279 || Ok((io.read(MCU_STATUS), io.read(JOB_IRQ_RAWSTAT))), 280 |(mcu_status, irq_rawstat)| { 281 mcu_status.value() == McuStatus::Enabled && irq_rawstat.glb() 282 }, 283 time::Delta::from_millis(1), 284 time::Delta::from_millis(100), 285 ) { 286 let status = io.read(MCU_STATUS); 287 dev_err!( 288 self.vm.dev(), 289 "MCU failed to boot, status: {:?}", 290 status.value() 291 ); 292 return Err(e); 293 } 294 295 io.write_reg(JOB_IRQ_CLEAR::zeroed().with_glb(true)); 296 297 Ok(()) 298 } 299 300 fn stop(&self) -> Result { 301 let io = &self.iomem; 302 io.write_reg(MCU_CONTROL::zeroed().with_req(McuControlMode::Disable)); 303 304 if let Err(e) = poll::read_poll_timeout( 305 || Ok(io.read(MCU_STATUS)), 306 |status| status.value() == McuStatus::Disabled, 307 time::Delta::from_micros(10), 308 time::Delta::from_millis(100), 309 ) { 310 let status = io.read(MCU_STATUS); 311 dev_err!( 312 self.vm.dev(), 313 "MCU failed to stop, status: {:?}", 314 status.value() 315 ); 316 return Err(e); 317 } 318 319 Ok(()) 320 } 321 } 322