1*6fda04e7SJoel Fernandes // SPDX-License-Identifier: GPL-2.0 2*6fda04e7SJoel Fernandes 3*6fda04e7SJoel Fernandes //! VBIOS extraction and parsing. 4*6fda04e7SJoel Fernandes 5*6fda04e7SJoel Fernandes // To be removed when all code is used. 6*6fda04e7SJoel Fernandes #![expect(dead_code)] 7*6fda04e7SJoel Fernandes 8*6fda04e7SJoel Fernandes use crate::driver::Bar0; 9*6fda04e7SJoel Fernandes use core::convert::TryFrom; 10*6fda04e7SJoel Fernandes use kernel::error::Result; 11*6fda04e7SJoel Fernandes use kernel::pci; 12*6fda04e7SJoel Fernandes use kernel::prelude::*; 13*6fda04e7SJoel Fernandes 14*6fda04e7SJoel Fernandes /// The offset of the VBIOS ROM in the BAR0 space. 15*6fda04e7SJoel Fernandes const ROM_OFFSET: usize = 0x300000; 16*6fda04e7SJoel Fernandes /// The maximum length of the VBIOS ROM to scan into. 17*6fda04e7SJoel Fernandes const BIOS_MAX_SCAN_LEN: usize = 0x100000; 18*6fda04e7SJoel Fernandes /// The size to read ahead when parsing initial BIOS image headers. 19*6fda04e7SJoel Fernandes const BIOS_READ_AHEAD_SIZE: usize = 1024; 20*6fda04e7SJoel Fernandes /// The bit in the last image indicator byte for the PCI Data Structure that 21*6fda04e7SJoel Fernandes /// indicates the last image. Bit 0-6 are reserved, bit 7 is last image bit. 22*6fda04e7SJoel Fernandes const LAST_IMAGE_BIT_MASK: u8 = 0x80; 23*6fda04e7SJoel Fernandes 24*6fda04e7SJoel Fernandes // PMU lookup table entry types. Used to locate PMU table entries 25*6fda04e7SJoel Fernandes // in the Fwsec image, corresponding to falcon ucodes. 26*6fda04e7SJoel Fernandes #[expect(dead_code)] 27*6fda04e7SJoel Fernandes const FALCON_UCODE_ENTRY_APPID_FIRMWARE_SEC_LIC: u8 = 0x05; 28*6fda04e7SJoel Fernandes #[expect(dead_code)] 29*6fda04e7SJoel Fernandes const FALCON_UCODE_ENTRY_APPID_FWSEC_DBG: u8 = 0x45; 30*6fda04e7SJoel Fernandes const FALCON_UCODE_ENTRY_APPID_FWSEC_PROD: u8 = 0x85; 31*6fda04e7SJoel Fernandes 32*6fda04e7SJoel Fernandes /// Vbios Reader for constructing the VBIOS data. 33*6fda04e7SJoel Fernandes struct VbiosIterator<'a> { 34*6fda04e7SJoel Fernandes pdev: &'a pci::Device, 35*6fda04e7SJoel Fernandes bar0: &'a Bar0, 36*6fda04e7SJoel Fernandes /// VBIOS data vector: As BIOS images are scanned, they are added to this vector for reference 37*6fda04e7SJoel Fernandes /// or copying into other data structures. It is the entire scanned contents of the VBIOS which 38*6fda04e7SJoel Fernandes /// progressively extends. It is used so that we do not re-read any contents that are already 39*6fda04e7SJoel Fernandes /// read as we use the cumulative length read so far, and re-read any gaps as we extend the 40*6fda04e7SJoel Fernandes /// length. 41*6fda04e7SJoel Fernandes data: KVec<u8>, 42*6fda04e7SJoel Fernandes /// Current offset of the [`Iterator`]. 43*6fda04e7SJoel Fernandes current_offset: usize, 44*6fda04e7SJoel Fernandes /// Indicate whether the last image has been found. 45*6fda04e7SJoel Fernandes last_found: bool, 46*6fda04e7SJoel Fernandes } 47*6fda04e7SJoel Fernandes 48*6fda04e7SJoel Fernandes impl<'a> VbiosIterator<'a> { 49*6fda04e7SJoel Fernandes fn new(pdev: &'a pci::Device, bar0: &'a Bar0) -> Result<Self> { 50*6fda04e7SJoel Fernandes Ok(Self { 51*6fda04e7SJoel Fernandes pdev, 52*6fda04e7SJoel Fernandes bar0, 53*6fda04e7SJoel Fernandes data: KVec::new(), 54*6fda04e7SJoel Fernandes current_offset: 0, 55*6fda04e7SJoel Fernandes last_found: false, 56*6fda04e7SJoel Fernandes }) 57*6fda04e7SJoel Fernandes } 58*6fda04e7SJoel Fernandes 59*6fda04e7SJoel Fernandes /// Read bytes from the ROM at the current end of the data vector. 60*6fda04e7SJoel Fernandes fn read_more(&mut self, len: usize) -> Result { 61*6fda04e7SJoel Fernandes let current_len = self.data.len(); 62*6fda04e7SJoel Fernandes let start = ROM_OFFSET + current_len; 63*6fda04e7SJoel Fernandes 64*6fda04e7SJoel Fernandes // Ensure length is a multiple of 4 for 32-bit reads 65*6fda04e7SJoel Fernandes if len % core::mem::size_of::<u32>() != 0 { 66*6fda04e7SJoel Fernandes dev_err!( 67*6fda04e7SJoel Fernandes self.pdev.as_ref(), 68*6fda04e7SJoel Fernandes "VBIOS read length {} is not a multiple of 4\n", 69*6fda04e7SJoel Fernandes len 70*6fda04e7SJoel Fernandes ); 71*6fda04e7SJoel Fernandes return Err(EINVAL); 72*6fda04e7SJoel Fernandes } 73*6fda04e7SJoel Fernandes 74*6fda04e7SJoel Fernandes self.data.reserve(len, GFP_KERNEL)?; 75*6fda04e7SJoel Fernandes // Read ROM data bytes and push directly to `data`. 76*6fda04e7SJoel Fernandes for addr in (start..start + len).step_by(core::mem::size_of::<u32>()) { 77*6fda04e7SJoel Fernandes // Read 32-bit word from the VBIOS ROM 78*6fda04e7SJoel Fernandes let word = self.bar0.try_read32(addr)?; 79*6fda04e7SJoel Fernandes 80*6fda04e7SJoel Fernandes // Convert the `u32` to a 4 byte array and push each byte. 81*6fda04e7SJoel Fernandes word.to_ne_bytes() 82*6fda04e7SJoel Fernandes .iter() 83*6fda04e7SJoel Fernandes .try_for_each(|&b| self.data.push(b, GFP_KERNEL))?; 84*6fda04e7SJoel Fernandes } 85*6fda04e7SJoel Fernandes 86*6fda04e7SJoel Fernandes Ok(()) 87*6fda04e7SJoel Fernandes } 88*6fda04e7SJoel Fernandes 89*6fda04e7SJoel Fernandes /// Read bytes at a specific offset, filling any gap. 90*6fda04e7SJoel Fernandes fn read_more_at_offset(&mut self, offset: usize, len: usize) -> Result { 91*6fda04e7SJoel Fernandes if offset > BIOS_MAX_SCAN_LEN { 92*6fda04e7SJoel Fernandes dev_err!(self.pdev.as_ref(), "Error: exceeded BIOS scan limit.\n"); 93*6fda04e7SJoel Fernandes return Err(EINVAL); 94*6fda04e7SJoel Fernandes } 95*6fda04e7SJoel Fernandes 96*6fda04e7SJoel Fernandes // If `offset` is beyond current data size, fill the gap first. 97*6fda04e7SJoel Fernandes let current_len = self.data.len(); 98*6fda04e7SJoel Fernandes let gap_bytes = offset.saturating_sub(current_len); 99*6fda04e7SJoel Fernandes 100*6fda04e7SJoel Fernandes // Now read the requested bytes at the offset. 101*6fda04e7SJoel Fernandes self.read_more(gap_bytes + len) 102*6fda04e7SJoel Fernandes } 103*6fda04e7SJoel Fernandes 104*6fda04e7SJoel Fernandes /// Read a BIOS image at a specific offset and create a [`BiosImage`] from it. 105*6fda04e7SJoel Fernandes /// 106*6fda04e7SJoel Fernandes /// `self.data` is extended as needed and a new [`BiosImage`] is returned. 107*6fda04e7SJoel Fernandes /// `context` is a string describing the operation for error reporting. 108*6fda04e7SJoel Fernandes fn read_bios_image_at_offset( 109*6fda04e7SJoel Fernandes &mut self, 110*6fda04e7SJoel Fernandes offset: usize, 111*6fda04e7SJoel Fernandes len: usize, 112*6fda04e7SJoel Fernandes context: &str, 113*6fda04e7SJoel Fernandes ) -> Result<BiosImage> { 114*6fda04e7SJoel Fernandes let data_len = self.data.len(); 115*6fda04e7SJoel Fernandes if offset + len > data_len { 116*6fda04e7SJoel Fernandes self.read_more_at_offset(offset, len).inspect_err(|e| { 117*6fda04e7SJoel Fernandes dev_err!( 118*6fda04e7SJoel Fernandes self.pdev.as_ref(), 119*6fda04e7SJoel Fernandes "Failed to read more at offset {:#x}: {:?}\n", 120*6fda04e7SJoel Fernandes offset, 121*6fda04e7SJoel Fernandes e 122*6fda04e7SJoel Fernandes ) 123*6fda04e7SJoel Fernandes })?; 124*6fda04e7SJoel Fernandes } 125*6fda04e7SJoel Fernandes 126*6fda04e7SJoel Fernandes BiosImage::new(self.pdev, &self.data[offset..offset + len]).inspect_err(|err| { 127*6fda04e7SJoel Fernandes dev_err!( 128*6fda04e7SJoel Fernandes self.pdev.as_ref(), 129*6fda04e7SJoel Fernandes "Failed to {} at offset {:#x}: {:?}\n", 130*6fda04e7SJoel Fernandes context, 131*6fda04e7SJoel Fernandes offset, 132*6fda04e7SJoel Fernandes err 133*6fda04e7SJoel Fernandes ) 134*6fda04e7SJoel Fernandes }) 135*6fda04e7SJoel Fernandes } 136*6fda04e7SJoel Fernandes } 137*6fda04e7SJoel Fernandes 138*6fda04e7SJoel Fernandes impl<'a> Iterator for VbiosIterator<'a> { 139*6fda04e7SJoel Fernandes type Item = Result<BiosImage>; 140*6fda04e7SJoel Fernandes 141*6fda04e7SJoel Fernandes /// Iterate over all VBIOS images until the last image is detected or offset 142*6fda04e7SJoel Fernandes /// exceeds scan limit. 143*6fda04e7SJoel Fernandes fn next(&mut self) -> Option<Self::Item> { 144*6fda04e7SJoel Fernandes if self.last_found { 145*6fda04e7SJoel Fernandes return None; 146*6fda04e7SJoel Fernandes } 147*6fda04e7SJoel Fernandes 148*6fda04e7SJoel Fernandes if self.current_offset > BIOS_MAX_SCAN_LEN { 149*6fda04e7SJoel Fernandes dev_err!( 150*6fda04e7SJoel Fernandes self.pdev.as_ref(), 151*6fda04e7SJoel Fernandes "Error: exceeded BIOS scan limit, stopping scan\n" 152*6fda04e7SJoel Fernandes ); 153*6fda04e7SJoel Fernandes return None; 154*6fda04e7SJoel Fernandes } 155*6fda04e7SJoel Fernandes 156*6fda04e7SJoel Fernandes // Parse image headers first to get image size. 157*6fda04e7SJoel Fernandes let image_size = match self.read_bios_image_at_offset( 158*6fda04e7SJoel Fernandes self.current_offset, 159*6fda04e7SJoel Fernandes BIOS_READ_AHEAD_SIZE, 160*6fda04e7SJoel Fernandes "parse initial BIOS image headers", 161*6fda04e7SJoel Fernandes ) { 162*6fda04e7SJoel Fernandes Ok(image) => image.image_size_bytes(), 163*6fda04e7SJoel Fernandes Err(e) => return Some(Err(e)), 164*6fda04e7SJoel Fernandes }; 165*6fda04e7SJoel Fernandes 166*6fda04e7SJoel Fernandes // Now create a new `BiosImage` with the full image data. 167*6fda04e7SJoel Fernandes let full_image = match self.read_bios_image_at_offset( 168*6fda04e7SJoel Fernandes self.current_offset, 169*6fda04e7SJoel Fernandes image_size, 170*6fda04e7SJoel Fernandes "parse full BIOS image", 171*6fda04e7SJoel Fernandes ) { 172*6fda04e7SJoel Fernandes Ok(image) => image, 173*6fda04e7SJoel Fernandes Err(e) => return Some(Err(e)), 174*6fda04e7SJoel Fernandes }; 175*6fda04e7SJoel Fernandes 176*6fda04e7SJoel Fernandes self.last_found = full_image.is_last(); 177*6fda04e7SJoel Fernandes 178*6fda04e7SJoel Fernandes // Advance to next image (aligned to 512 bytes). 179*6fda04e7SJoel Fernandes self.current_offset += image_size; 180*6fda04e7SJoel Fernandes // TODO: replace with `align_up` once it lands. 181*6fda04e7SJoel Fernandes self.current_offset = self.current_offset.next_multiple_of(512); 182*6fda04e7SJoel Fernandes 183*6fda04e7SJoel Fernandes Some(Ok(full_image)) 184*6fda04e7SJoel Fernandes } 185*6fda04e7SJoel Fernandes } 186*6fda04e7SJoel Fernandes 187*6fda04e7SJoel Fernandes pub(crate) struct Vbios { 188*6fda04e7SJoel Fernandes fwsec_image: FwSecBiosImage, 189*6fda04e7SJoel Fernandes } 190*6fda04e7SJoel Fernandes 191*6fda04e7SJoel Fernandes impl Vbios { 192*6fda04e7SJoel Fernandes /// Probe for VBIOS extraction. 193*6fda04e7SJoel Fernandes /// 194*6fda04e7SJoel Fernandes /// Once the VBIOS object is built, `bar0` is not read for [`Vbios`] purposes anymore. 195*6fda04e7SJoel Fernandes pub(crate) fn new(pdev: &pci::Device, bar0: &Bar0) -> Result<Vbios> { 196*6fda04e7SJoel Fernandes // Images to extract from iteration 197*6fda04e7SJoel Fernandes let mut pci_at_image: Option<PciAtBiosImage> = None; 198*6fda04e7SJoel Fernandes let mut first_fwsec_image: Option<FwSecBiosImage> = None; 199*6fda04e7SJoel Fernandes let mut second_fwsec_image: Option<FwSecBiosImage> = None; 200*6fda04e7SJoel Fernandes 201*6fda04e7SJoel Fernandes // Parse all VBIOS images in the ROM 202*6fda04e7SJoel Fernandes for image_result in VbiosIterator::new(pdev, bar0)? { 203*6fda04e7SJoel Fernandes let full_image = image_result?; 204*6fda04e7SJoel Fernandes 205*6fda04e7SJoel Fernandes dev_dbg!( 206*6fda04e7SJoel Fernandes pdev.as_ref(), 207*6fda04e7SJoel Fernandes "Found BIOS image: size: {:#x}, type: {}, last: {}\n", 208*6fda04e7SJoel Fernandes full_image.image_size_bytes(), 209*6fda04e7SJoel Fernandes full_image.image_type_str(), 210*6fda04e7SJoel Fernandes full_image.is_last() 211*6fda04e7SJoel Fernandes ); 212*6fda04e7SJoel Fernandes 213*6fda04e7SJoel Fernandes // Get references to images we will need after the loop, in order to 214*6fda04e7SJoel Fernandes // setup the falcon data offset. 215*6fda04e7SJoel Fernandes match full_image { 216*6fda04e7SJoel Fernandes BiosImage::PciAt(image) => { 217*6fda04e7SJoel Fernandes pci_at_image = Some(image); 218*6fda04e7SJoel Fernandes } 219*6fda04e7SJoel Fernandes BiosImage::FwSec(image) => { 220*6fda04e7SJoel Fernandes if first_fwsec_image.is_none() { 221*6fda04e7SJoel Fernandes first_fwsec_image = Some(image); 222*6fda04e7SJoel Fernandes } else { 223*6fda04e7SJoel Fernandes second_fwsec_image = Some(image); 224*6fda04e7SJoel Fernandes } 225*6fda04e7SJoel Fernandes } 226*6fda04e7SJoel Fernandes // For now we don't need to handle these 227*6fda04e7SJoel Fernandes BiosImage::Efi(_image) => {} 228*6fda04e7SJoel Fernandes BiosImage::Nbsi(_image) => {} 229*6fda04e7SJoel Fernandes } 230*6fda04e7SJoel Fernandes } 231*6fda04e7SJoel Fernandes 232*6fda04e7SJoel Fernandes // Using all the images, setup the falcon data pointer in Fwsec. 233*6fda04e7SJoel Fernandes // These are temporarily unused images and will be used in later patches. 234*6fda04e7SJoel Fernandes if let (Some(second), Some(_first), Some(_pci_at)) = 235*6fda04e7SJoel Fernandes (second_fwsec_image, first_fwsec_image, pci_at_image) 236*6fda04e7SJoel Fernandes { 237*6fda04e7SJoel Fernandes Ok(Vbios { 238*6fda04e7SJoel Fernandes fwsec_image: second, 239*6fda04e7SJoel Fernandes }) 240*6fda04e7SJoel Fernandes } else { 241*6fda04e7SJoel Fernandes dev_err!( 242*6fda04e7SJoel Fernandes pdev.as_ref(), 243*6fda04e7SJoel Fernandes "Missing required images for falcon data setup, skipping\n" 244*6fda04e7SJoel Fernandes ); 245*6fda04e7SJoel Fernandes Err(EINVAL) 246*6fda04e7SJoel Fernandes } 247*6fda04e7SJoel Fernandes } 248*6fda04e7SJoel Fernandes } 249*6fda04e7SJoel Fernandes 250*6fda04e7SJoel Fernandes /// PCI Data Structure as defined in PCI Firmware Specification 251*6fda04e7SJoel Fernandes #[derive(Debug, Clone)] 252*6fda04e7SJoel Fernandes #[repr(C)] 253*6fda04e7SJoel Fernandes struct PcirStruct { 254*6fda04e7SJoel Fernandes /// PCI Data Structure signature ("PCIR" or "NPDS") 255*6fda04e7SJoel Fernandes signature: [u8; 4], 256*6fda04e7SJoel Fernandes /// PCI Vendor ID (e.g., 0x10DE for NVIDIA) 257*6fda04e7SJoel Fernandes vendor_id: u16, 258*6fda04e7SJoel Fernandes /// PCI Device ID 259*6fda04e7SJoel Fernandes device_id: u16, 260*6fda04e7SJoel Fernandes /// Device List Pointer 261*6fda04e7SJoel Fernandes device_list_ptr: u16, 262*6fda04e7SJoel Fernandes /// PCI Data Structure Length 263*6fda04e7SJoel Fernandes pci_data_struct_len: u16, 264*6fda04e7SJoel Fernandes /// PCI Data Structure Revision 265*6fda04e7SJoel Fernandes pci_data_struct_rev: u8, 266*6fda04e7SJoel Fernandes /// Class code (3 bytes, 0x03 for display controller) 267*6fda04e7SJoel Fernandes class_code: [u8; 3], 268*6fda04e7SJoel Fernandes /// Size of this image in 512-byte blocks 269*6fda04e7SJoel Fernandes image_len: u16, 270*6fda04e7SJoel Fernandes /// Revision Level of the Vendor's ROM 271*6fda04e7SJoel Fernandes vendor_rom_rev: u16, 272*6fda04e7SJoel Fernandes /// ROM image type (0x00 = PC-AT compatible, 0x03 = EFI, 0x70 = NBSI) 273*6fda04e7SJoel Fernandes code_type: u8, 274*6fda04e7SJoel Fernandes /// Last image indicator (0x00 = Not last image, 0x80 = Last image) 275*6fda04e7SJoel Fernandes last_image: u8, 276*6fda04e7SJoel Fernandes /// Maximum Run-time Image Length (units of 512 bytes) 277*6fda04e7SJoel Fernandes max_runtime_image_len: u16, 278*6fda04e7SJoel Fernandes } 279*6fda04e7SJoel Fernandes 280*6fda04e7SJoel Fernandes impl PcirStruct { 281*6fda04e7SJoel Fernandes fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { 282*6fda04e7SJoel Fernandes if data.len() < core::mem::size_of::<PcirStruct>() { 283*6fda04e7SJoel Fernandes dev_err!(pdev.as_ref(), "Not enough data for PcirStruct\n"); 284*6fda04e7SJoel Fernandes return Err(EINVAL); 285*6fda04e7SJoel Fernandes } 286*6fda04e7SJoel Fernandes 287*6fda04e7SJoel Fernandes let mut signature = [0u8; 4]; 288*6fda04e7SJoel Fernandes signature.copy_from_slice(&data[0..4]); 289*6fda04e7SJoel Fernandes 290*6fda04e7SJoel Fernandes // Signature should be "PCIR" (0x52494350) or "NPDS" (0x5344504e). 291*6fda04e7SJoel Fernandes if &signature != b"PCIR" && &signature != b"NPDS" { 292*6fda04e7SJoel Fernandes dev_err!( 293*6fda04e7SJoel Fernandes pdev.as_ref(), 294*6fda04e7SJoel Fernandes "Invalid signature for PcirStruct: {:?}\n", 295*6fda04e7SJoel Fernandes signature 296*6fda04e7SJoel Fernandes ); 297*6fda04e7SJoel Fernandes return Err(EINVAL); 298*6fda04e7SJoel Fernandes } 299*6fda04e7SJoel Fernandes 300*6fda04e7SJoel Fernandes let mut class_code = [0u8; 3]; 301*6fda04e7SJoel Fernandes class_code.copy_from_slice(&data[13..16]); 302*6fda04e7SJoel Fernandes 303*6fda04e7SJoel Fernandes let image_len = u16::from_le_bytes([data[16], data[17]]); 304*6fda04e7SJoel Fernandes if image_len == 0 { 305*6fda04e7SJoel Fernandes dev_err!(pdev.as_ref(), "Invalid image length: 0\n"); 306*6fda04e7SJoel Fernandes return Err(EINVAL); 307*6fda04e7SJoel Fernandes } 308*6fda04e7SJoel Fernandes 309*6fda04e7SJoel Fernandes Ok(PcirStruct { 310*6fda04e7SJoel Fernandes signature, 311*6fda04e7SJoel Fernandes vendor_id: u16::from_le_bytes([data[4], data[5]]), 312*6fda04e7SJoel Fernandes device_id: u16::from_le_bytes([data[6], data[7]]), 313*6fda04e7SJoel Fernandes device_list_ptr: u16::from_le_bytes([data[8], data[9]]), 314*6fda04e7SJoel Fernandes pci_data_struct_len: u16::from_le_bytes([data[10], data[11]]), 315*6fda04e7SJoel Fernandes pci_data_struct_rev: data[12], 316*6fda04e7SJoel Fernandes class_code, 317*6fda04e7SJoel Fernandes image_len, 318*6fda04e7SJoel Fernandes vendor_rom_rev: u16::from_le_bytes([data[18], data[19]]), 319*6fda04e7SJoel Fernandes code_type: data[20], 320*6fda04e7SJoel Fernandes last_image: data[21], 321*6fda04e7SJoel Fernandes max_runtime_image_len: u16::from_le_bytes([data[22], data[23]]), 322*6fda04e7SJoel Fernandes }) 323*6fda04e7SJoel Fernandes } 324*6fda04e7SJoel Fernandes 325*6fda04e7SJoel Fernandes /// Check if this is the last image in the ROM. 326*6fda04e7SJoel Fernandes fn is_last(&self) -> bool { 327*6fda04e7SJoel Fernandes self.last_image & LAST_IMAGE_BIT_MASK != 0 328*6fda04e7SJoel Fernandes } 329*6fda04e7SJoel Fernandes 330*6fda04e7SJoel Fernandes /// Calculate image size in bytes from 512-byte blocks. 331*6fda04e7SJoel Fernandes fn image_size_bytes(&self) -> usize { 332*6fda04e7SJoel Fernandes self.image_len as usize * 512 333*6fda04e7SJoel Fernandes } 334*6fda04e7SJoel Fernandes } 335*6fda04e7SJoel Fernandes 336*6fda04e7SJoel Fernandes /// PCI ROM Expansion Header as defined in PCI Firmware Specification. 337*6fda04e7SJoel Fernandes /// 338*6fda04e7SJoel Fernandes /// This is header is at the beginning of every image in the set of images in the ROM. It contains 339*6fda04e7SJoel Fernandes /// a pointer to the PCI Data Structure which describes the image. For "NBSI" images (NoteBook 340*6fda04e7SJoel Fernandes /// System Information), the ROM header deviates from the standard and contains an offset to the 341*6fda04e7SJoel Fernandes /// NBSI image however we do not yet parse that in this module and keep it for future reference. 342*6fda04e7SJoel Fernandes #[derive(Debug, Clone, Copy)] 343*6fda04e7SJoel Fernandes #[expect(dead_code)] 344*6fda04e7SJoel Fernandes struct PciRomHeader { 345*6fda04e7SJoel Fernandes /// 00h: Signature (0xAA55) 346*6fda04e7SJoel Fernandes signature: u16, 347*6fda04e7SJoel Fernandes /// 02h: Reserved bytes for processor architecture unique data (20 bytes) 348*6fda04e7SJoel Fernandes reserved: [u8; 20], 349*6fda04e7SJoel Fernandes /// 16h: NBSI Data Offset (NBSI-specific, offset from header to NBSI image) 350*6fda04e7SJoel Fernandes nbsi_data_offset: Option<u16>, 351*6fda04e7SJoel Fernandes /// 18h: Pointer to PCI Data Structure (offset from start of ROM image) 352*6fda04e7SJoel Fernandes pci_data_struct_offset: u16, 353*6fda04e7SJoel Fernandes /// 1Ah: Size of block (this is NBSI-specific) 354*6fda04e7SJoel Fernandes size_of_block: Option<u32>, 355*6fda04e7SJoel Fernandes } 356*6fda04e7SJoel Fernandes 357*6fda04e7SJoel Fernandes impl PciRomHeader { 358*6fda04e7SJoel Fernandes fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { 359*6fda04e7SJoel Fernandes if data.len() < 26 { 360*6fda04e7SJoel Fernandes // Need at least 26 bytes to read pciDataStrucPtr and sizeOfBlock. 361*6fda04e7SJoel Fernandes return Err(EINVAL); 362*6fda04e7SJoel Fernandes } 363*6fda04e7SJoel Fernandes 364*6fda04e7SJoel Fernandes let signature = u16::from_le_bytes([data[0], data[1]]); 365*6fda04e7SJoel Fernandes 366*6fda04e7SJoel Fernandes // Check for valid ROM signatures. 367*6fda04e7SJoel Fernandes match signature { 368*6fda04e7SJoel Fernandes 0xAA55 | 0xBB77 | 0x4E56 => {} 369*6fda04e7SJoel Fernandes _ => { 370*6fda04e7SJoel Fernandes dev_err!(pdev.as_ref(), "ROM signature unknown {:#x}\n", signature); 371*6fda04e7SJoel Fernandes return Err(EINVAL); 372*6fda04e7SJoel Fernandes } 373*6fda04e7SJoel Fernandes } 374*6fda04e7SJoel Fernandes 375*6fda04e7SJoel Fernandes // Read the pointer to the PCI Data Structure at offset 0x18. 376*6fda04e7SJoel Fernandes let pci_data_struct_ptr = u16::from_le_bytes([data[24], data[25]]); 377*6fda04e7SJoel Fernandes 378*6fda04e7SJoel Fernandes // Try to read optional fields if enough data. 379*6fda04e7SJoel Fernandes let mut size_of_block = None; 380*6fda04e7SJoel Fernandes let mut nbsi_data_offset = None; 381*6fda04e7SJoel Fernandes 382*6fda04e7SJoel Fernandes if data.len() >= 30 { 383*6fda04e7SJoel Fernandes // Read size_of_block at offset 0x1A. 384*6fda04e7SJoel Fernandes size_of_block = Some( 385*6fda04e7SJoel Fernandes (data[29] as u32) << 24 386*6fda04e7SJoel Fernandes | (data[28] as u32) << 16 387*6fda04e7SJoel Fernandes | (data[27] as u32) << 8 388*6fda04e7SJoel Fernandes | (data[26] as u32), 389*6fda04e7SJoel Fernandes ); 390*6fda04e7SJoel Fernandes } 391*6fda04e7SJoel Fernandes 392*6fda04e7SJoel Fernandes // For NBSI images, try to read the nbsiDataOffset at offset 0x16. 393*6fda04e7SJoel Fernandes if data.len() >= 24 { 394*6fda04e7SJoel Fernandes nbsi_data_offset = Some(u16::from_le_bytes([data[22], data[23]])); 395*6fda04e7SJoel Fernandes } 396*6fda04e7SJoel Fernandes 397*6fda04e7SJoel Fernandes Ok(PciRomHeader { 398*6fda04e7SJoel Fernandes signature, 399*6fda04e7SJoel Fernandes reserved: [0u8; 20], 400*6fda04e7SJoel Fernandes pci_data_struct_offset: pci_data_struct_ptr, 401*6fda04e7SJoel Fernandes size_of_block, 402*6fda04e7SJoel Fernandes nbsi_data_offset, 403*6fda04e7SJoel Fernandes }) 404*6fda04e7SJoel Fernandes } 405*6fda04e7SJoel Fernandes } 406*6fda04e7SJoel Fernandes 407*6fda04e7SJoel Fernandes /// NVIDIA PCI Data Extension Structure. 408*6fda04e7SJoel Fernandes /// 409*6fda04e7SJoel Fernandes /// This is similar to the PCI Data Structure, but is Nvidia-specific and is placed right after the 410*6fda04e7SJoel Fernandes /// PCI Data Structure. It contains some fields that are redundant with the PCI Data Structure, but 411*6fda04e7SJoel Fernandes /// are needed for traversing the BIOS images. It is expected to be present in all BIOS images 412*6fda04e7SJoel Fernandes /// except for NBSI images. 413*6fda04e7SJoel Fernandes #[derive(Debug, Clone)] 414*6fda04e7SJoel Fernandes #[repr(C)] 415*6fda04e7SJoel Fernandes struct NpdeStruct { 416*6fda04e7SJoel Fernandes /// 00h: Signature ("NPDE") 417*6fda04e7SJoel Fernandes signature: [u8; 4], 418*6fda04e7SJoel Fernandes /// 04h: NVIDIA PCI Data Extension Revision 419*6fda04e7SJoel Fernandes npci_data_ext_rev: u16, 420*6fda04e7SJoel Fernandes /// 06h: NVIDIA PCI Data Extension Length 421*6fda04e7SJoel Fernandes npci_data_ext_len: u16, 422*6fda04e7SJoel Fernandes /// 08h: Sub-image Length (in 512-byte units) 423*6fda04e7SJoel Fernandes subimage_len: u16, 424*6fda04e7SJoel Fernandes /// 0Ah: Last image indicator flag 425*6fda04e7SJoel Fernandes last_image: u8, 426*6fda04e7SJoel Fernandes } 427*6fda04e7SJoel Fernandes 428*6fda04e7SJoel Fernandes impl NpdeStruct { 429*6fda04e7SJoel Fernandes fn new(pdev: &pci::Device, data: &[u8]) -> Option<Self> { 430*6fda04e7SJoel Fernandes if data.len() < core::mem::size_of::<Self>() { 431*6fda04e7SJoel Fernandes dev_dbg!(pdev.as_ref(), "Not enough data for NpdeStruct\n"); 432*6fda04e7SJoel Fernandes return None; 433*6fda04e7SJoel Fernandes } 434*6fda04e7SJoel Fernandes 435*6fda04e7SJoel Fernandes let mut signature = [0u8; 4]; 436*6fda04e7SJoel Fernandes signature.copy_from_slice(&data[0..4]); 437*6fda04e7SJoel Fernandes 438*6fda04e7SJoel Fernandes // Signature should be "NPDE" (0x4544504E). 439*6fda04e7SJoel Fernandes if &signature != b"NPDE" { 440*6fda04e7SJoel Fernandes dev_dbg!( 441*6fda04e7SJoel Fernandes pdev.as_ref(), 442*6fda04e7SJoel Fernandes "Invalid signature for NpdeStruct: {:?}\n", 443*6fda04e7SJoel Fernandes signature 444*6fda04e7SJoel Fernandes ); 445*6fda04e7SJoel Fernandes return None; 446*6fda04e7SJoel Fernandes } 447*6fda04e7SJoel Fernandes 448*6fda04e7SJoel Fernandes let subimage_len = u16::from_le_bytes([data[8], data[9]]); 449*6fda04e7SJoel Fernandes if subimage_len == 0 { 450*6fda04e7SJoel Fernandes dev_dbg!(pdev.as_ref(), "Invalid subimage length: 0\n"); 451*6fda04e7SJoel Fernandes return None; 452*6fda04e7SJoel Fernandes } 453*6fda04e7SJoel Fernandes 454*6fda04e7SJoel Fernandes Some(NpdeStruct { 455*6fda04e7SJoel Fernandes signature, 456*6fda04e7SJoel Fernandes npci_data_ext_rev: u16::from_le_bytes([data[4], data[5]]), 457*6fda04e7SJoel Fernandes npci_data_ext_len: u16::from_le_bytes([data[6], data[7]]), 458*6fda04e7SJoel Fernandes subimage_len, 459*6fda04e7SJoel Fernandes last_image: data[10], 460*6fda04e7SJoel Fernandes }) 461*6fda04e7SJoel Fernandes } 462*6fda04e7SJoel Fernandes 463*6fda04e7SJoel Fernandes /// Check if this is the last image in the ROM. 464*6fda04e7SJoel Fernandes fn is_last(&self) -> bool { 465*6fda04e7SJoel Fernandes self.last_image & LAST_IMAGE_BIT_MASK != 0 466*6fda04e7SJoel Fernandes } 467*6fda04e7SJoel Fernandes 468*6fda04e7SJoel Fernandes /// Calculate image size in bytes from 512-byte blocks. 469*6fda04e7SJoel Fernandes fn image_size_bytes(&self) -> usize { 470*6fda04e7SJoel Fernandes self.subimage_len as usize * 512 471*6fda04e7SJoel Fernandes } 472*6fda04e7SJoel Fernandes 473*6fda04e7SJoel Fernandes /// Try to find NPDE in the data, the NPDE is right after the PCIR. 474*6fda04e7SJoel Fernandes fn find_in_data( 475*6fda04e7SJoel Fernandes pdev: &pci::Device, 476*6fda04e7SJoel Fernandes data: &[u8], 477*6fda04e7SJoel Fernandes rom_header: &PciRomHeader, 478*6fda04e7SJoel Fernandes pcir: &PcirStruct, 479*6fda04e7SJoel Fernandes ) -> Option<Self> { 480*6fda04e7SJoel Fernandes // Calculate the offset where NPDE might be located 481*6fda04e7SJoel Fernandes // NPDE should be right after the PCIR structure, aligned to 16 bytes 482*6fda04e7SJoel Fernandes let pcir_offset = rom_header.pci_data_struct_offset as usize; 483*6fda04e7SJoel Fernandes let npde_start = (pcir_offset + pcir.pci_data_struct_len as usize + 0x0F) & !0x0F; 484*6fda04e7SJoel Fernandes 485*6fda04e7SJoel Fernandes // Check if we have enough data 486*6fda04e7SJoel Fernandes if npde_start + core::mem::size_of::<Self>() > data.len() { 487*6fda04e7SJoel Fernandes dev_dbg!(pdev.as_ref(), "Not enough data for NPDE\n"); 488*6fda04e7SJoel Fernandes return None; 489*6fda04e7SJoel Fernandes } 490*6fda04e7SJoel Fernandes 491*6fda04e7SJoel Fernandes // Try to create NPDE from the data 492*6fda04e7SJoel Fernandes NpdeStruct::new(pdev, &data[npde_start..]) 493*6fda04e7SJoel Fernandes } 494*6fda04e7SJoel Fernandes } 495*6fda04e7SJoel Fernandes 496*6fda04e7SJoel Fernandes // Use a macro to implement BiosImage enum and methods. This avoids having to 497*6fda04e7SJoel Fernandes // repeat each enum type when implementing functions like base() in BiosImage. 498*6fda04e7SJoel Fernandes macro_rules! bios_image { 499*6fda04e7SJoel Fernandes ( 500*6fda04e7SJoel Fernandes $($variant:ident: $class:ident),* $(,)? 501*6fda04e7SJoel Fernandes ) => { 502*6fda04e7SJoel Fernandes // BiosImage enum with variants for each image type 503*6fda04e7SJoel Fernandes enum BiosImage { 504*6fda04e7SJoel Fernandes $($variant($class)),* 505*6fda04e7SJoel Fernandes } 506*6fda04e7SJoel Fernandes 507*6fda04e7SJoel Fernandes impl BiosImage { 508*6fda04e7SJoel Fernandes /// Get a reference to the common BIOS image data regardless of type 509*6fda04e7SJoel Fernandes fn base(&self) -> &BiosImageBase { 510*6fda04e7SJoel Fernandes match self { 511*6fda04e7SJoel Fernandes $(Self::$variant(img) => &img.base),* 512*6fda04e7SJoel Fernandes } 513*6fda04e7SJoel Fernandes } 514*6fda04e7SJoel Fernandes 515*6fda04e7SJoel Fernandes /// Returns a string representing the type of BIOS image 516*6fda04e7SJoel Fernandes fn image_type_str(&self) -> &'static str { 517*6fda04e7SJoel Fernandes match self { 518*6fda04e7SJoel Fernandes $(Self::$variant(_) => stringify!($variant)),* 519*6fda04e7SJoel Fernandes } 520*6fda04e7SJoel Fernandes } 521*6fda04e7SJoel Fernandes } 522*6fda04e7SJoel Fernandes } 523*6fda04e7SJoel Fernandes } 524*6fda04e7SJoel Fernandes 525*6fda04e7SJoel Fernandes impl BiosImage { 526*6fda04e7SJoel Fernandes /// Check if this is the last image. 527*6fda04e7SJoel Fernandes fn is_last(&self) -> bool { 528*6fda04e7SJoel Fernandes let base = self.base(); 529*6fda04e7SJoel Fernandes 530*6fda04e7SJoel Fernandes // For NBSI images (type == 0x70), return true as they're 531*6fda04e7SJoel Fernandes // considered the last image 532*6fda04e7SJoel Fernandes if matches!(self, Self::Nbsi(_)) { 533*6fda04e7SJoel Fernandes return true; 534*6fda04e7SJoel Fernandes } 535*6fda04e7SJoel Fernandes 536*6fda04e7SJoel Fernandes // For other image types, check the NPDE first if available 537*6fda04e7SJoel Fernandes if let Some(ref npde) = base.npde { 538*6fda04e7SJoel Fernandes return npde.is_last(); 539*6fda04e7SJoel Fernandes } 540*6fda04e7SJoel Fernandes 541*6fda04e7SJoel Fernandes // Otherwise, fall back to checking the PCIR last_image flag 542*6fda04e7SJoel Fernandes base.pcir.is_last() 543*6fda04e7SJoel Fernandes } 544*6fda04e7SJoel Fernandes 545*6fda04e7SJoel Fernandes /// Get the image size in bytes. 546*6fda04e7SJoel Fernandes fn image_size_bytes(&self) -> usize { 547*6fda04e7SJoel Fernandes let base = self.base(); 548*6fda04e7SJoel Fernandes 549*6fda04e7SJoel Fernandes // Prefer NPDE image size if available 550*6fda04e7SJoel Fernandes if let Some(ref npde) = base.npde { 551*6fda04e7SJoel Fernandes return npde.image_size_bytes(); 552*6fda04e7SJoel Fernandes } 553*6fda04e7SJoel Fernandes 554*6fda04e7SJoel Fernandes // Otherwise, fall back to the PCIR image size 555*6fda04e7SJoel Fernandes base.pcir.image_size_bytes() 556*6fda04e7SJoel Fernandes } 557*6fda04e7SJoel Fernandes 558*6fda04e7SJoel Fernandes /// Create a [`BiosImageBase`] from a byte slice and convert it to a [`BiosImage`] which 559*6fda04e7SJoel Fernandes /// triggers the constructor of the specific BiosImage enum variant. 560*6fda04e7SJoel Fernandes fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { 561*6fda04e7SJoel Fernandes let base = BiosImageBase::new(pdev, data)?; 562*6fda04e7SJoel Fernandes let image = base.into_image().inspect_err(|e| { 563*6fda04e7SJoel Fernandes dev_err!(pdev.as_ref(), "Failed to create BiosImage: {:?}\n", e); 564*6fda04e7SJoel Fernandes })?; 565*6fda04e7SJoel Fernandes 566*6fda04e7SJoel Fernandes Ok(image) 567*6fda04e7SJoel Fernandes } 568*6fda04e7SJoel Fernandes } 569*6fda04e7SJoel Fernandes 570*6fda04e7SJoel Fernandes bios_image! { 571*6fda04e7SJoel Fernandes PciAt: PciAtBiosImage, // PCI-AT compatible BIOS image 572*6fda04e7SJoel Fernandes Efi: EfiBiosImage, // EFI (Extensible Firmware Interface) 573*6fda04e7SJoel Fernandes Nbsi: NbsiBiosImage, // NBSI (Nvidia Bios System Interface) 574*6fda04e7SJoel Fernandes FwSec: FwSecBiosImage, // FWSEC (Firmware Security) 575*6fda04e7SJoel Fernandes } 576*6fda04e7SJoel Fernandes 577*6fda04e7SJoel Fernandes struct PciAtBiosImage { 578*6fda04e7SJoel Fernandes base: BiosImageBase, 579*6fda04e7SJoel Fernandes // PCI-AT-specific fields can be added here in the future. 580*6fda04e7SJoel Fernandes } 581*6fda04e7SJoel Fernandes 582*6fda04e7SJoel Fernandes struct EfiBiosImage { 583*6fda04e7SJoel Fernandes base: BiosImageBase, 584*6fda04e7SJoel Fernandes // EFI-specific fields can be added here in the future. 585*6fda04e7SJoel Fernandes } 586*6fda04e7SJoel Fernandes 587*6fda04e7SJoel Fernandes struct NbsiBiosImage { 588*6fda04e7SJoel Fernandes base: BiosImageBase, 589*6fda04e7SJoel Fernandes // NBSI-specific fields can be added here in the future. 590*6fda04e7SJoel Fernandes } 591*6fda04e7SJoel Fernandes 592*6fda04e7SJoel Fernandes struct FwSecBiosImage { 593*6fda04e7SJoel Fernandes base: BiosImageBase, 594*6fda04e7SJoel Fernandes // FWSEC-specific fields can be added here in the future. 595*6fda04e7SJoel Fernandes } 596*6fda04e7SJoel Fernandes 597*6fda04e7SJoel Fernandes // Convert from BiosImageBase to BiosImage 598*6fda04e7SJoel Fernandes impl TryFrom<BiosImageBase> for BiosImage { 599*6fda04e7SJoel Fernandes type Error = Error; 600*6fda04e7SJoel Fernandes 601*6fda04e7SJoel Fernandes fn try_from(base: BiosImageBase) -> Result<Self> { 602*6fda04e7SJoel Fernandes match base.pcir.code_type { 603*6fda04e7SJoel Fernandes 0x00 => Ok(BiosImage::PciAt(PciAtBiosImage { base })), 604*6fda04e7SJoel Fernandes 0x03 => Ok(BiosImage::Efi(EfiBiosImage { base })), 605*6fda04e7SJoel Fernandes 0x70 => Ok(BiosImage::Nbsi(NbsiBiosImage { base })), 606*6fda04e7SJoel Fernandes 0xE0 => Ok(BiosImage::FwSec(FwSecBiosImage { base })), 607*6fda04e7SJoel Fernandes _ => Err(EINVAL), 608*6fda04e7SJoel Fernandes } 609*6fda04e7SJoel Fernandes } 610*6fda04e7SJoel Fernandes } 611*6fda04e7SJoel Fernandes 612*6fda04e7SJoel Fernandes /// BIOS Image structure containing various headers and reference fields to all BIOS images. 613*6fda04e7SJoel Fernandes /// 614*6fda04e7SJoel Fernandes /// Each BiosImage type has a BiosImageBase type along with other image-specific fields. Note that 615*6fda04e7SJoel Fernandes /// Rust favors composition of types over inheritance. 616*6fda04e7SJoel Fernandes #[derive(Debug)] 617*6fda04e7SJoel Fernandes #[expect(dead_code)] 618*6fda04e7SJoel Fernandes struct BiosImageBase { 619*6fda04e7SJoel Fernandes /// PCI ROM Expansion Header 620*6fda04e7SJoel Fernandes rom_header: PciRomHeader, 621*6fda04e7SJoel Fernandes /// PCI Data Structure 622*6fda04e7SJoel Fernandes pcir: PcirStruct, 623*6fda04e7SJoel Fernandes /// NVIDIA PCI Data Extension (optional) 624*6fda04e7SJoel Fernandes npde: Option<NpdeStruct>, 625*6fda04e7SJoel Fernandes /// Image data (includes ROM header and PCIR) 626*6fda04e7SJoel Fernandes data: KVec<u8>, 627*6fda04e7SJoel Fernandes } 628*6fda04e7SJoel Fernandes 629*6fda04e7SJoel Fernandes impl BiosImageBase { 630*6fda04e7SJoel Fernandes fn into_image(self) -> Result<BiosImage> { 631*6fda04e7SJoel Fernandes BiosImage::try_from(self) 632*6fda04e7SJoel Fernandes } 633*6fda04e7SJoel Fernandes 634*6fda04e7SJoel Fernandes /// Creates a new BiosImageBase from raw byte data. 635*6fda04e7SJoel Fernandes fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { 636*6fda04e7SJoel Fernandes // Ensure we have enough data for the ROM header. 637*6fda04e7SJoel Fernandes if data.len() < 26 { 638*6fda04e7SJoel Fernandes dev_err!(pdev.as_ref(), "Not enough data for ROM header\n"); 639*6fda04e7SJoel Fernandes return Err(EINVAL); 640*6fda04e7SJoel Fernandes } 641*6fda04e7SJoel Fernandes 642*6fda04e7SJoel Fernandes // Parse the ROM header. 643*6fda04e7SJoel Fernandes let rom_header = PciRomHeader::new(pdev, &data[0..26]) 644*6fda04e7SJoel Fernandes .inspect_err(|e| dev_err!(pdev.as_ref(), "Failed to create PciRomHeader: {:?}\n", e))?; 645*6fda04e7SJoel Fernandes 646*6fda04e7SJoel Fernandes // Get the PCI Data Structure using the pointer from the ROM header. 647*6fda04e7SJoel Fernandes let pcir_offset = rom_header.pci_data_struct_offset as usize; 648*6fda04e7SJoel Fernandes let pcir_data = data 649*6fda04e7SJoel Fernandes .get(pcir_offset..pcir_offset + core::mem::size_of::<PcirStruct>()) 650*6fda04e7SJoel Fernandes .ok_or(EINVAL) 651*6fda04e7SJoel Fernandes .inspect_err(|_| { 652*6fda04e7SJoel Fernandes dev_err!( 653*6fda04e7SJoel Fernandes pdev.as_ref(), 654*6fda04e7SJoel Fernandes "PCIR offset {:#x} out of bounds (data length: {})\n", 655*6fda04e7SJoel Fernandes pcir_offset, 656*6fda04e7SJoel Fernandes data.len() 657*6fda04e7SJoel Fernandes ); 658*6fda04e7SJoel Fernandes dev_err!( 659*6fda04e7SJoel Fernandes pdev.as_ref(), 660*6fda04e7SJoel Fernandes "Consider reading more data for construction of BiosImage\n" 661*6fda04e7SJoel Fernandes ); 662*6fda04e7SJoel Fernandes })?; 663*6fda04e7SJoel Fernandes 664*6fda04e7SJoel Fernandes let pcir = PcirStruct::new(pdev, pcir_data) 665*6fda04e7SJoel Fernandes .inspect_err(|e| dev_err!(pdev.as_ref(), "Failed to create PcirStruct: {:?}\n", e))?; 666*6fda04e7SJoel Fernandes 667*6fda04e7SJoel Fernandes // Look for NPDE structure if this is not an NBSI image (type != 0x70). 668*6fda04e7SJoel Fernandes let npde = NpdeStruct::find_in_data(pdev, data, &rom_header, &pcir); 669*6fda04e7SJoel Fernandes 670*6fda04e7SJoel Fernandes // Create a copy of the data. 671*6fda04e7SJoel Fernandes let mut data_copy = KVec::new(); 672*6fda04e7SJoel Fernandes data_copy.extend_from_slice(data, GFP_KERNEL)?; 673*6fda04e7SJoel Fernandes 674*6fda04e7SJoel Fernandes Ok(BiosImageBase { 675*6fda04e7SJoel Fernandes rom_header, 676*6fda04e7SJoel Fernandes pcir, 677*6fda04e7SJoel Fernandes npde, 678*6fda04e7SJoel Fernandes data: data_copy, 679*6fda04e7SJoel Fernandes }) 680*6fda04e7SJoel Fernandes } 681*6fda04e7SJoel Fernandes } 682