├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── read.rs └── util │ └── mod.rs ├── rust-toolchain.toml └── src ├── io.rs ├── lib.rs ├── main.rs ├── mapper.rs └── mmio.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | *.rom 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "intel-spi" 3 | version = "0.1.7" 4 | authors = ["Jeremy Soller "] 5 | edition = "2021" 6 | description = "Library for accessing Intel PCH SPI" 7 | license = "MIT" 8 | 9 | [dependencies] 10 | bitflags = "2.4.0" 11 | coreboot-fs = "0.1.1" 12 | libc = "0.2" 13 | redox_intelflash = "0.1.3" 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 System76 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # intel-spi 2 | Library for accessing Intel PCH SPI 3 | -------------------------------------------------------------------------------- /examples/read.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | extern crate libc; 4 | extern crate intel_spi; 5 | 6 | use intel_spi::Spi; 7 | use std::fs; 8 | 9 | mod util; 10 | 11 | fn main() { 12 | let mut spi = unsafe { util::get_spi() }; 13 | 14 | eprintln!("SPI HSFSTS_CTL: {:?}", spi.regs.hsfsts_ctl()); 15 | 16 | let len = spi.len().unwrap(); 17 | eprintln!("SPI ROM: {} KB", len / 1024); 18 | 19 | let mut data = Vec::with_capacity(len); 20 | while data.len() < len { 21 | let mut buf = [0; 65536]; 22 | let read = spi.read(data.len(), &mut buf).unwrap(); 23 | data.extend_from_slice(&buf[..read]); 24 | eprint!("\rSPI READ: {} KB", data.len() / 1024); 25 | } 26 | 27 | eprintln!(); 28 | 29 | fs::write("read.rom", &data).unwrap(); 30 | } 31 | -------------------------------------------------------------------------------- /examples/util/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use intel_spi::{Mapper, SpiDev, PhysicalAddress, VirtualAddress}; 4 | 5 | use std::{fs, ptr}; 6 | 7 | pub struct LinuxMapper; 8 | 9 | impl Mapper for LinuxMapper { 10 | unsafe fn map_aligned(&mut self, address: PhysicalAddress, size: usize) -> Result { 11 | let fd = libc::open( 12 | b"/dev/mem\0".as_ptr() as *const libc::c_char, 13 | libc::O_RDWR 14 | ); 15 | if fd < 0 { 16 | return Err("failed to open /dev/mem") 17 | } 18 | 19 | let ptr = libc::mmap( 20 | ptr::null_mut(), 21 | size, 22 | libc::PROT_READ | libc::PROT_WRITE, 23 | libc::MAP_SHARED, 24 | fd, 25 | address.0 as libc::off_t 26 | ); 27 | 28 | libc::close(fd); 29 | 30 | if ptr == libc::MAP_FAILED { 31 | return Err("failed to map /dev/mem"); 32 | } 33 | 34 | Ok(VirtualAddress(ptr as usize)) 35 | } 36 | 37 | unsafe fn unmap_aligned(&mut self, address: VirtualAddress, size: usize) -> Result<(), &'static str> { 38 | if libc::munmap(address.0 as *mut libc::c_void, size) == 0 { 39 | Ok(()) 40 | } else { 41 | Err("failed to unmap /dev/mem") 42 | } 43 | } 44 | 45 | fn page_size(&self) -> usize { 46 | //TODO: get dynamically 47 | 4096 48 | } 49 | } 50 | 51 | pub unsafe fn get_spi() -> SpiDev<'static, LinuxMapper> { 52 | static mut LINUX_MAPPER: LinuxMapper = LinuxMapper; 53 | let mcfg = fs::read("/sys/firmware/acpi/tables/MCFG").expect("failed to read MCFG"); 54 | SpiDev::new(&mcfg, &mut LINUX_MAPPER).expect("failed to get SPI device") 55 | } 56 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # SPDX-FileCopyrightText: NONE 3 | 4 | [toolchain] 5 | channel = "1.85.0" 6 | components = ["clippy", "rustfmt"] 7 | targets = ["x86_64-unknown-uefi"] 8 | profile = "minimal" 9 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #![allow(dead_code)] 4 | 5 | use core::cmp::PartialEq; 6 | use core::ops::{BitAnd, BitOr, Not}; 7 | 8 | pub trait Io { 9 | type Value: Copy + PartialEq + BitAnd + BitOr + Not; 10 | 11 | fn read(&self) -> Self::Value; 12 | fn write(&mut self, value: Self::Value); 13 | 14 | #[inline(always)] 15 | fn readf(&self, flags: Self::Value) -> bool { 16 | (self.read() & flags) as Self::Value == flags 17 | } 18 | 19 | #[inline(always)] 20 | fn writef(&mut self, flags: Self::Value, value: bool) { 21 | let tmp: Self::Value = match value { 22 | true => self.read() | flags, 23 | false => self.read() & !flags, 24 | }; 25 | self.write(tmp); 26 | } 27 | } 28 | 29 | pub struct ReadOnly { 30 | inner: I 31 | } 32 | 33 | impl ReadOnly { 34 | pub /* const */ fn new(inner: I) -> Self { 35 | Self { inner } 36 | } 37 | 38 | #[inline(always)] 39 | pub fn read(&self) -> I::Value { 40 | self.inner.read() 41 | } 42 | 43 | #[inline(always)] 44 | pub fn readf(&self, flags: I::Value) -> bool { 45 | self.inner.readf(flags) 46 | } 47 | } 48 | 49 | pub struct WriteOnly { 50 | inner: I 51 | } 52 | 53 | impl WriteOnly { 54 | pub /* const */ fn new(inner: I) -> Self { 55 | Self { inner } 56 | } 57 | 58 | #[inline(always)] 59 | pub fn write(&mut self, value: I::Value) { 60 | self.inner.write(value) 61 | } 62 | 63 | #[inline(always)] 64 | pub fn writef(&mut self, flags: I::Value, value: bool) { 65 | self.inner.writef(flags, value) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #![no_std] 4 | 5 | #[macro_use] 6 | extern crate bitflags; 7 | 8 | use core::{cmp, mem, slice}; 9 | 10 | pub use self::io::Io; 11 | mod io; 12 | 13 | pub use self::mapper::{PhysicalAddress, VirtualAddress, Mapper}; 14 | mod mapper; 15 | 16 | pub use self::mmio::Mmio; 17 | mod mmio; 18 | 19 | pub static PCI_IDS: &[(u16, u16)] = &[ 20 | (0x8086, 0x02A4), // Comet Lake 21 | (0x8086, 0x06A4), // Comet Lake-H 22 | (0x8086, 0x43A4), // Tiger Lake-H 23 | (0x8086, 0x51A4), // Alder Lake-P 24 | (0x8086, 0x7723), // Arrow Lake-HU 25 | (0x8086, 0x7A24), // Alder Lake-S 26 | (0x8086, 0x7E23), // Meteor Lake-HU 27 | (0x8086, 0x9DA4), // Cannon Lake 28 | (0x8086, 0xA0A4), // Tiger Lake 29 | (0x8086, 0xA324), // Cannon Lake-H 30 | (0x8086, 0xA324), // Cannon Lake-H 31 | ]; 32 | 33 | #[derive(Debug)] 34 | pub enum SpiError { 35 | /// Access Error Log (H_AEL) is set 36 | Access, 37 | /// Flash Cycle Error (FCERR) is set 38 | Cycle, 39 | /// Register contains unexpected data 40 | Register, 41 | } 42 | 43 | #[allow(clippy::len_without_is_empty)] 44 | pub trait Spi { 45 | fn len(&mut self) -> Result; 46 | 47 | fn read(&mut self, address: usize, buf: &mut [u8]) -> Result; 48 | 49 | fn erase(&mut self, address: usize) -> Result<(), SpiError>; 50 | 51 | fn write(&mut self, address: usize, buf: &[u8]) -> Result; 52 | } 53 | 54 | pub struct SpiDev<'m, M: Mapper> { 55 | mapper: &'m mut M, 56 | pub regs: &'m mut SpiRegs, 57 | } 58 | 59 | impl<'m, M: Mapper> SpiDev<'m, M> { 60 | #[allow(clippy::missing_safety_doc)] 61 | pub unsafe fn new(mcfg: &[u8], mapper: &'m mut M) -> Result { 62 | let pcie_base = 63 | (mcfg[0x2c] as usize) | 64 | (mcfg[0x2d] as usize) << 8 | 65 | (mcfg[0x2e] as usize) << 16 | 66 | (mcfg[0x2f] as usize) << 24 | 67 | (mcfg[0x30] as usize) << 32 | 68 | (mcfg[0x31] as usize) << 40 | 69 | (mcfg[0x32] as usize) << 48 | 70 | (mcfg[0x33] as usize) << 56; 71 | 72 | let mut phys_opt = None; 73 | { 74 | let (pcie_bus, pcie_dev, pcie_func) = (0x00, 0x1F, 0x05); 75 | let pcie_size = 4096; 76 | 77 | let pcie_phys = PhysicalAddress( 78 | pcie_base | 79 | (pcie_bus << 20) | 80 | (pcie_dev << 15) | 81 | (pcie_func << 12) 82 | ); 83 | let pcie_virt = mapper.map(pcie_phys, pcie_size)?; 84 | { 85 | let pcie_space = slice::from_raw_parts_mut(pcie_virt.0 as *mut u8, pcie_size); 86 | 87 | let vendor_id = 88 | (pcie_space[0x00] as u16) | 89 | (pcie_space[0x01] as u16) << 8; 90 | let product_id = 91 | (pcie_space[0x02] as u16) | 92 | (pcie_space[0x03] as u16) << 8; 93 | for known_id in PCI_IDS.iter() { 94 | if known_id.0 == vendor_id && known_id.1 == product_id { 95 | let bar0 = 96 | (pcie_space[0x10] as u32) | 97 | (pcie_space[0x11] as u32) << 8 | 98 | (pcie_space[0x12] as u32) << 16 | 99 | (pcie_space[0x13] as u32) << 24; 100 | phys_opt = Some(PhysicalAddress(bar0 as usize)); 101 | break; 102 | } 103 | } 104 | } 105 | mapper.unmap(pcie_virt, pcie_size)?; 106 | } 107 | 108 | let phys = match phys_opt { 109 | Some(some) => some, 110 | None => return Err("no supported SPI device found"), 111 | }; 112 | let virt = mapper.map(phys, mem::size_of::())?; 113 | let regs = &mut *(virt.0 as *mut SpiRegs); 114 | 115 | Ok(Self { 116 | mapper, 117 | regs, 118 | }) 119 | } 120 | } 121 | 122 | impl<'m, M: Mapper> Spi for SpiDev<'m, M> { 123 | fn len(&mut self) -> Result { 124 | self.regs.len() 125 | } 126 | 127 | fn read(&mut self, address: usize, buf: &mut [u8]) -> Result { 128 | self.regs.read(address, buf) 129 | } 130 | 131 | fn erase(&mut self, address: usize) -> Result<(), SpiError> { 132 | self.regs.erase(address) 133 | } 134 | 135 | fn write(&mut self, address: usize, buf: &[u8]) -> Result { 136 | self.regs.write(address, buf) 137 | } 138 | } 139 | 140 | impl<'m, M: Mapper> Drop for SpiDev<'m, M> { 141 | fn drop(&mut self) { 142 | let virt = VirtualAddress(self.regs as *mut SpiRegs as usize); 143 | let _ = unsafe { self.mapper.unmap(virt, mem::size_of::()) }; 144 | } 145 | } 146 | 147 | bitflags! { 148 | #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] 149 | pub struct HsfStsCtl: u32 { 150 | /// Flash Cycle Done 151 | const FDONE = 1 << 0; 152 | /// Flash Cycle Error 153 | const FCERR = 1 << 1; 154 | /// Access Error Log 155 | const H_AEL = 1 << 2; 156 | 157 | // Reserved 3:4 158 | 159 | /// SPI Cycle In Progress 160 | const H_SCIP = 1 << 5; 161 | 162 | // Reserved 6:10 163 | 164 | /// Write Status Disable 165 | const WRSDIS = 1 << 11; 166 | /// PRR3 PRR4 Lock-Down 167 | const PRR34_LOCKDN = 1 << 12; 168 | /// Flash Descriptor Override Pin-Strap Status 169 | const FDOPSS = 1 << 13; 170 | /// Flash Descriptor Valid 171 | const FDV = 1 << 14; 172 | /// Flash Configuration Lock-Down 173 | const FLOCKDN = 1 << 15; 174 | //// Flash Cycle Go 175 | const FGO = 1 << 16; 176 | 177 | /// Flash Cycle 178 | const FCYCLE = 0b1111 << 17; 179 | const FCYCLE_0 = 1 << 17; 180 | const FCYCLE_1 = 1 << 18; 181 | const FCYCLE_2 = 1 << 19; 182 | const FCYCLE_3 = 1 << 20; 183 | 184 | /// Write Enable Type 185 | const WET = 1 << 21; 186 | 187 | // Reserved 22:23 188 | 189 | /// Flash Data Byte Count - Programmed with count minus one 190 | const FDBC = 0b111111 << 24; 191 | const FDBC_0 = 1 << 24; 192 | const FDBC_1 = 1 << 25; 193 | const FDBC_2 = 1 << 26; 194 | const FDBC_3 = 1 << 27; 195 | const FDBC_4 = 1 << 28; 196 | const FDBC_5 = 1 << 29; 197 | 198 | // Reserved 30 199 | 200 | /// Flash SPI SMI# Enable 201 | const FSMIE = 1 << 31; 202 | } 203 | } 204 | 205 | #[allow(dead_code)] 206 | impl HsfStsCtl { 207 | fn sanitize(&mut self) { 208 | // FDONE, FCERR, and H_AEL are cleared if one is written, so they are left untouched 209 | 210 | // H_SCIP, FDOPSS, and FDV are RO; WRSDIS, PRR34_LOCKDN, and FLOCKDN are probably locked, 211 | // so they are left untouched 212 | 213 | // Clear FGO 214 | self.remove(Self::FGO); 215 | 216 | // Clear FCYCLE 217 | self.remove(Self::FCYCLE); 218 | 219 | // Clear WET 220 | self.remove(Self::WET); 221 | 222 | // Clear FDBC 223 | self.remove(Self::FDBC); 224 | 225 | // Clear FSMIE 226 | self.remove(Self::FSMIE); 227 | } 228 | 229 | fn cycle(&self) -> HsfStsCtlCycle { 230 | unsafe { mem::transmute((*self & Self::FCYCLE).bits()) } 231 | } 232 | 233 | fn set_cycle(&mut self, value: HsfStsCtlCycle) { 234 | *self = (*self & !Self::FCYCLE) | ( 235 | Self::from_bits_truncate(value as u32) 236 | ); 237 | } 238 | 239 | fn count(&self) -> u8 { 240 | (((*self & Self::FDBC).bits() >> 24) + 1) as u8 241 | } 242 | 243 | fn set_count(&mut self, value: u8) { 244 | *self = (*self & !Self::FDBC) | ( 245 | Self::from_bits_truncate( 246 | (cmp::max(value, 64).saturating_sub(1) as u32) << 24 247 | ) 248 | ); 249 | } 250 | } 251 | 252 | #[repr(u32)] 253 | pub enum HsfStsCtlCycle { 254 | /// Read number of bytes in FDBC plus one 255 | Read = 0x0 << 17, 256 | /// Reserved - treated as 0x0 read 257 | Rsvd = 0x1 << 17, 258 | /// Write number of bytes in FDBC plus one 259 | Write = 0x2 << 17, 260 | /// Erase 4096 bytes 261 | BlockErase = 0x3 << 17, 262 | /// Erase 65536 bytes 263 | SectorErase = 0x4 << 17, 264 | /// Read SFDP 265 | ReadSfdp = 0x5 << 17, 266 | /// Read JEDEC ID 267 | ReadJedec = 0x6 << 17, 268 | /// Write status 269 | WriteStatus = 0x7 << 17, 270 | /// Read status 271 | ReadStatus = 0x8 << 17, 272 | /// RPMC Op1 273 | RpmcOp1 = 0x9 << 17, 274 | /// Rpmc Op2 275 | RpmcOp2 = 0xA << 17, 276 | } 277 | 278 | #[repr(u32)] 279 | pub enum FdoSection { 280 | Map = 0b000 << 12, 281 | Component = 0b001 << 12, 282 | Region = 0b010 << 12, 283 | Master = 0b011 << 12 284 | } 285 | 286 | #[allow(dead_code)] 287 | #[repr(C)] 288 | pub struct SpiRegs { 289 | /// BIOS Flash Primary Region 290 | bfpreg: Mmio, 291 | /// Hardware Sequencing Flash Status and Control 292 | hsfsts_ctl: Mmio, 293 | /// Flash Address 294 | faddr: Mmio, 295 | /// Discrete Lock Bits 296 | dlock: Mmio, 297 | /// Flash Data 298 | fdata: [Mmio; 16], 299 | /// Flash Region Access Permissions 300 | fracc: Mmio, 301 | /// Flash Regions 302 | freg: [Mmio; 6], 303 | _reserved1: [Mmio; 6], 304 | /// Flash Protected Ranges 305 | fpr: [Mmio; 5], 306 | /// Global Protected Range 307 | gpr: Mmio, 308 | _reserved2: [Mmio; 5], 309 | /// Secondary Flash Region Access Permissions 310 | sfracc: Mmio, 311 | /// Flash Descriptor Observability Control 312 | fdoc: Mmio, 313 | /// Flash Descriptor Observability Data 314 | fdod: Mmio, 315 | _reserved3: Mmio, 316 | // Additional Flash Control 317 | afc: Mmio, 318 | /// Vendor Specific Capabilities for Component 0 319 | vscc0: Mmio, 320 | /// Vendor Specific Capabilities for Component 1 321 | vscc1: Mmio, 322 | /// Parameter Table Index 323 | ptinx: Mmio, 324 | /// Parameter Table Data 325 | ptdata: Mmio, 326 | /// SPI Bus Requester Status 327 | sbrs: Mmio, 328 | } 329 | 330 | impl SpiRegs { 331 | pub fn hsfsts_ctl(&self) -> HsfStsCtl { 332 | HsfStsCtl::from_bits_truncate(self.hsfsts_ctl.read()) 333 | } 334 | 335 | pub fn set_hsfsts_ctl(&mut self, value: HsfStsCtl) { 336 | self.hsfsts_ctl.write(value.bits()); 337 | } 338 | 339 | pub fn fdo(&mut self, section: FdoSection, index: u16) -> u32 { 340 | self.fdoc.write( 341 | (section as u32) | 342 | (((index & 0b1111111111) as u32) << 2) 343 | ); 344 | self.fdod.read() 345 | } 346 | } 347 | 348 | impl Spi for SpiRegs { 349 | fn len(&mut self) -> Result { 350 | let kib = 1024; 351 | let mib = 1024 * kib; 352 | 353 | let component = self.fdo(FdoSection::Component, 0); 354 | Ok(match component & 0b111 { 355 | 0b000 => 512 * kib, 356 | 0b001 => mib, 357 | 0b010 => 2 * mib, 358 | 0b011 => 4 * mib, 359 | 0b100 => 8 * mib, 360 | 0b101 => 16 * mib, 361 | 0b110 => 32 * mib, 362 | 0b111 => 64 * mib, 363 | _ => return Err(SpiError::Register) 364 | }) 365 | } 366 | 367 | fn read(&mut self, address: usize, buf: &mut [u8]) -> Result { 368 | let mut count = 0; 369 | for chunk in buf.chunks_mut(64) { 370 | let mut hsfsts_ctl; 371 | 372 | // Wait for other transactions 373 | loop { 374 | hsfsts_ctl = self.hsfsts_ctl(); 375 | if ! hsfsts_ctl.contains(HsfStsCtl::H_SCIP) { 376 | break; 377 | } 378 | } 379 | 380 | hsfsts_ctl.sanitize(); 381 | self.set_hsfsts_ctl(hsfsts_ctl); 382 | 383 | hsfsts_ctl.set_cycle(HsfStsCtlCycle::Read); 384 | hsfsts_ctl.set_count(chunk.len() as u8); 385 | hsfsts_ctl.insert(HsfStsCtl::FGO); 386 | 387 | // Start command 388 | self.faddr.write((address + count) as u32); 389 | self.set_hsfsts_ctl(hsfsts_ctl); 390 | 391 | // Wait for command to finish 392 | loop { 393 | hsfsts_ctl = self.hsfsts_ctl(); 394 | 395 | if hsfsts_ctl.contains(HsfStsCtl::FCERR) { 396 | hsfsts_ctl.sanitize(); 397 | self.set_hsfsts_ctl(hsfsts_ctl); 398 | 399 | return Err(SpiError::Cycle); 400 | } 401 | 402 | if hsfsts_ctl.contains(HsfStsCtl::FDONE) { 403 | break; 404 | } 405 | } 406 | 407 | for (i, dword) in chunk.chunks_mut(4).enumerate() { 408 | let data = self.fdata[i].read(); 409 | for (j, byte) in dword.iter_mut().enumerate() { 410 | *byte = (data >> (j * 8)) as u8; 411 | } 412 | } 413 | 414 | hsfsts_ctl.sanitize(); 415 | self.set_hsfsts_ctl(hsfsts_ctl); 416 | 417 | count += chunk.len() 418 | } 419 | Ok(count) 420 | } 421 | 422 | fn erase(&mut self, address: usize) -> Result<(), SpiError> { 423 | let mut hsfsts_ctl; 424 | 425 | // Wait for other transactions 426 | loop { 427 | hsfsts_ctl = self.hsfsts_ctl(); 428 | if ! hsfsts_ctl.contains(HsfStsCtl::H_SCIP) { 429 | break; 430 | } 431 | } 432 | 433 | hsfsts_ctl.sanitize(); 434 | self.set_hsfsts_ctl(hsfsts_ctl); 435 | 436 | hsfsts_ctl.set_cycle(HsfStsCtlCycle::BlockErase); 437 | hsfsts_ctl.insert(HsfStsCtl::FGO); 438 | 439 | // Start command 440 | self.faddr.write(address as u32); 441 | self.set_hsfsts_ctl(hsfsts_ctl); 442 | 443 | // Wait for command to finish 444 | loop { 445 | hsfsts_ctl = self.hsfsts_ctl(); 446 | 447 | if hsfsts_ctl.contains(HsfStsCtl::FCERR) { 448 | hsfsts_ctl.sanitize(); 449 | self.set_hsfsts_ctl(hsfsts_ctl); 450 | 451 | return Err(SpiError::Cycle); 452 | } 453 | 454 | if hsfsts_ctl.contains(HsfStsCtl::FDONE) { 455 | break; 456 | } 457 | } 458 | 459 | hsfsts_ctl.sanitize(); 460 | self.set_hsfsts_ctl(hsfsts_ctl); 461 | 462 | Ok(()) 463 | } 464 | 465 | fn write(&mut self, address: usize, buf: &[u8]) -> Result { 466 | let mut count = 0; 467 | for chunk in buf.chunks(64) { 468 | let mut hsfsts_ctl; 469 | 470 | // Wait for other transactions 471 | loop { 472 | hsfsts_ctl = self.hsfsts_ctl(); 473 | if ! hsfsts_ctl.contains(HsfStsCtl::H_SCIP) { 474 | break; 475 | } 476 | } 477 | 478 | hsfsts_ctl.sanitize(); 479 | self.set_hsfsts_ctl(hsfsts_ctl); 480 | 481 | hsfsts_ctl.set_cycle(HsfStsCtlCycle::Write); 482 | hsfsts_ctl.set_count(chunk.len() as u8); 483 | hsfsts_ctl.insert(HsfStsCtl::FGO); 484 | 485 | // Fill data 486 | for (i, dword) in chunk.chunks(4).enumerate() { 487 | let mut data = 0; 488 | for (j, byte) in dword.iter().enumerate() { 489 | data |= (*byte as u32) << (j * 8); 490 | } 491 | self.fdata[i].write(data); 492 | } 493 | 494 | // Start command 495 | self.faddr.write((address + count) as u32); 496 | self.set_hsfsts_ctl(hsfsts_ctl); 497 | 498 | // Wait for command to finish 499 | loop { 500 | hsfsts_ctl = self.hsfsts_ctl(); 501 | 502 | if hsfsts_ctl.contains(HsfStsCtl::FCERR) { 503 | hsfsts_ctl.sanitize(); 504 | self.set_hsfsts_ctl(hsfsts_ctl); 505 | 506 | return Err(SpiError::Cycle); 507 | } 508 | 509 | if hsfsts_ctl.contains(HsfStsCtl::FDONE) { 510 | break; 511 | } 512 | } 513 | 514 | hsfsts_ctl.sanitize(); 515 | self.set_hsfsts_ctl(hsfsts_ctl); 516 | 517 | count += chunk.len() 518 | } 519 | Ok(count) 520 | } 521 | } 522 | 523 | #[cfg(test)] 524 | mod tests { 525 | use super::SpiRegs; 526 | 527 | #[test] 528 | fn offsets() { 529 | unsafe { 530 | let spi: &SpiRegs = &*(0 as *const SpiRegs); 531 | 532 | assert_eq!(&spi.bfpreg as *const _ as usize, 0x00); 533 | 534 | assert_eq!(&spi.freg as *const _ as usize, 0x54); 535 | assert_eq!(&spi.fpr as *const _ as usize, 0x84); 536 | 537 | assert_eq!(&spi.gpr as *const _ as usize, 0x98); 538 | assert_eq!(&spi.sfracc as *const _ as usize, 0xb0); 539 | 540 | assert_eq!(&spi.fdod as *const _ as usize, 0xb8); 541 | assert_eq!(&spi.afc as *const _ as usize, 0xc0); 542 | 543 | assert_eq!(&spi.sbrs as *const _ as usize, 0xd4); 544 | } 545 | } 546 | } 547 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | extern crate coreboot_fs; 4 | extern crate libc; 5 | extern crate intel_spi; 6 | 7 | use coreboot_fs::Rom; 8 | use intel_spi::Spi; 9 | use std::collections::BTreeMap; 10 | use std::{env, fs, process}; 11 | 12 | #[path = "../examples/util/mod.rs"] 13 | mod util; 14 | 15 | fn copy_region(region: intelflash::RegionKind, old_data: &[u8], new_data: &mut [u8]) -> Result { 16 | let old_opt = intelflash::Rom::new(old_data)?.get_region_base_limit(region)?; 17 | let new_opt = intelflash::Rom::new(new_data)?.get_region_base_limit(region)?; 18 | 19 | if old_opt.is_none() && new_opt.is_none() { 20 | // Neither ROM has this region, so ignore it 21 | return Ok(false); 22 | } 23 | 24 | let old = match old_opt { 25 | Some((base, limit)) => if base < limit && limit < old_data.len() { 26 | &old_data[base..limit + 1] 27 | } else { 28 | return Err(format!("old region {:#X}:{:#X} is invalid", base, limit)); 29 | }, 30 | None => return Err("missing old region".to_string()), 31 | }; 32 | 33 | let new = match new_opt { 34 | Some((base, limit)) => if base < limit && limit < new_data.len() { 35 | &mut new_data[base..limit + 1] 36 | } else { 37 | return Err(format!("new region {:#X}:{:#X} is invalid", base, limit)); 38 | }, 39 | None => return Err("missing new region".to_string()), 40 | }; 41 | 42 | if old.len() != new.len() { 43 | return Err(format!("old region size {} does not match new region size {}", old.len(), new.len())); 44 | } 45 | 46 | new.copy_from_slice(old); 47 | Ok(true) 48 | } 49 | 50 | fn main() { 51 | let path = match env::args().nth(1) { 52 | Some(some) => some, 53 | None => { 54 | eprintln!("intel-spi [rom file]"); 55 | process::exit(1); 56 | } 57 | }; 58 | 59 | let mut spi = unsafe { util::get_spi() }; 60 | 61 | eprintln!("SPI HSFSTS_CTL: {:?}", spi.regs.hsfsts_ctl()); 62 | 63 | // Read new data 64 | let mut new; 65 | { 66 | let loading = "Loading"; 67 | eprint!("SPI FILE: {}", loading); 68 | new = fs::read(path).unwrap(); 69 | for _c in loading.chars() { 70 | eprint!("\x08 \x08"); 71 | } 72 | eprintln!("{} MB", new.len() / (1024 * 1024)); 73 | } 74 | 75 | // Grab new FMAP areas area, if they exist 76 | let mut new_areas = BTreeMap::new(); 77 | { 78 | let rom = Rom::new(&new); 79 | if let Some(fmap) = rom.fmap() { 80 | let name: String = fmap.name.iter().take_while(|&&b| b != 0).map(|&b| b as char).collect(); 81 | 82 | eprintln!(" {}", name); 83 | 84 | for i in 0..fmap.nareas { 85 | let area = fmap.area(i); 86 | 87 | let name: String = area.name.iter().take_while(|&&b| b != 0).map(|&b| b as char).collect(); 88 | 89 | eprintln!(" {}: {}", i, name); 90 | 91 | new_areas.insert(name, *area); 92 | } 93 | } 94 | } 95 | 96 | // Check ROM size 97 | let len = spi.len().unwrap(); 98 | eprintln!("SPI ROM: {} MB", len / (1024 * 1024)); 99 | assert!(len == new.len(), "firmware.rom size invalid"); 100 | 101 | // Read current data 102 | let mut data; 103 | { 104 | data = Vec::with_capacity(len); 105 | let mut print_mb = !0; // Invalid number to force first print 106 | while data.len() < len { 107 | let mut buf = [0; 4096]; 108 | let read = spi.read(data.len(), &mut buf).unwrap(); 109 | data.extend_from_slice(&buf[..read]); 110 | 111 | // Print output once per megabyte 112 | let mb = data.len() / (1024 * 1024); 113 | if mb != print_mb { 114 | eprint!("\rSPI READ: {} MB", mb); 115 | print_mb = mb; 116 | } 117 | } 118 | eprintln!(); 119 | } 120 | 121 | // Copy GBE region, if it exists 122 | match copy_region(intelflash::RegionKind::Ethernet, &data, &mut new) { 123 | Ok(true) => eprintln!("Ethernet: copied region from old firmware to new firmare"), 124 | Ok(false) => (), 125 | Err(err) => panic!("Ethernet: failed to copy: {}", err), 126 | } 127 | 128 | // Grab old FMAP areas, if they exist 129 | let mut areas = BTreeMap::new(); 130 | { 131 | let rom = Rom::new(&data); 132 | if let Some(fmap) = rom.fmap() { 133 | let name: String = fmap.name.iter().take_while(|&&b| b != 0).map(|&b| b as char).collect(); 134 | 135 | eprintln!(" {}", name); 136 | 137 | for i in 0..fmap.nareas { 138 | let area = fmap.area(i); 139 | 140 | let name: String = area.name.iter().take_while(|&&b| b != 0).map(|&b| b as char).collect(); 141 | 142 | eprintln!(" {}: {}", i, name); 143 | 144 | areas.insert(name, *area); 145 | } 146 | } 147 | } 148 | 149 | // Copy old areas to new areas 150 | let area_names: &[String] = &[ 151 | //Warning: Copying these regions can be dangerous 152 | // "RW_MRC_CACHE".to_string(), 153 | // "SMMSTORE".to_string(), 154 | ]; 155 | for area_name in area_names { 156 | if let Some(new_area) = new_areas.get(area_name) { 157 | let new_offset = new_area.offset as usize; 158 | let new_size = new_area.size as usize; 159 | eprintln!( 160 | "{}: found in new firmware: offset {:#X}, size {} KB", 161 | area_name, 162 | new_offset, 163 | new_size / 1024 164 | ); 165 | let new_slice = new.get_mut( 166 | new_offset .. new_offset + new_size 167 | ).unwrap(); 168 | 169 | if let Some(area) = areas.get(area_name) { 170 | let offset = area.offset as usize; 171 | let size = area.size as usize; 172 | eprintln!( 173 | "{}: found in old firmware: offset {:#X}, size {} KB", 174 | area_name, 175 | new_offset, 176 | new_size / 1024 177 | ); 178 | let slice = data.get( 179 | offset .. offset + size 180 | ).unwrap(); 181 | 182 | if slice.len() == new_slice.len() { 183 | new_slice.copy_from_slice(slice); 184 | 185 | eprintln!( 186 | "{}: copied from old firmware to new firmware", 187 | area_name 188 | ); 189 | } else { 190 | eprintln!( 191 | "{}: old firmware size {} does not match new firmware size {}, not copying", 192 | area_name, 193 | slice.len(), 194 | new_slice.len() 195 | ); 196 | } 197 | } else { 198 | eprintln!( 199 | "{}: found in new firmware, but not found in old firmware", 200 | area_name 201 | ); 202 | } 203 | } else if areas.get(area_name).is_some() { 204 | eprintln!( 205 | "{}: found in old firmware, but not found in new firmware", 206 | area_name 207 | ); 208 | } 209 | } 210 | 211 | // Erase and write 212 | { 213 | let erase_byte = 0xFF; 214 | let erase_size = 4096; 215 | let mut i = 0; 216 | let mut print_mb = !0; // Invalid number to force first print 217 | for (chunk, new_chunk) in data.chunks(erase_size).zip(new.chunks(erase_size)) { 218 | // Data matches, meaning sector can be skipped 219 | let mut matching = true; 220 | // Data is erased, meaning sector can be erased instead of written 221 | let mut erased = true; 222 | for (&byte, &new_byte) in chunk.iter().zip(new_chunk.iter()) { 223 | if new_byte != byte { 224 | matching = false; 225 | } 226 | if new_byte != erase_byte { 227 | erased = false; 228 | } 229 | } 230 | 231 | if ! matching { 232 | spi.erase(i).unwrap(); 233 | if ! erased { 234 | spi.write(i, new_chunk).unwrap(); 235 | } 236 | } 237 | 238 | i += chunk.len(); 239 | 240 | // Print output once per megabyte 241 | let mb = i / (1024 * 1024); 242 | if mb != print_mb { 243 | eprint!("\rSPI WRITE: {} MB", mb); 244 | print_mb = mb; 245 | } 246 | } 247 | eprintln!(); 248 | } 249 | 250 | // Verify 251 | { 252 | data.clear(); 253 | let mut print_mb = !0; // Invalid number to force first print 254 | while data.len() < len { 255 | let mut address = data.len(); 256 | 257 | let mut buf = [0; 4096]; 258 | let read = spi.read(address, &mut buf).unwrap(); 259 | data.extend_from_slice(&buf[..read]); 260 | 261 | while address < data.len() { 262 | assert!(data[address] == new[address], 263 | "\nverification failed as {:#x}: {:#x} != {:#x}", 264 | address, 265 | data[address], 266 | new[address] 267 | ); 268 | 269 | address += 1; 270 | } 271 | 272 | let mb = data.len() / (1024 * 1024); 273 | if mb != print_mb { 274 | eprint!("\rSPI VERIFY: {} MB", mb); 275 | print_mb = mb; 276 | } 277 | } 278 | eprintln!(); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/mapper.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::missing_safety_doc)] 2 | 3 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] 4 | pub struct PhysicalAddress(pub usize); 5 | 6 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] 7 | pub struct VirtualAddress(pub usize); 8 | 9 | pub trait Mapper { 10 | unsafe fn map_aligned(&mut self, address: PhysicalAddress, size: usize) -> Result; 11 | unsafe fn unmap_aligned(&mut self, address: VirtualAddress, size: usize) -> Result<(), &'static str>; 12 | fn page_size(&self) -> usize; 13 | 14 | unsafe fn map(&mut self, address: PhysicalAddress, size: usize) -> Result { 15 | let page_size = self.page_size(); 16 | let page = address.0/page_size; 17 | let aligned_address = PhysicalAddress(page * page_size); 18 | let offset = address.0 - aligned_address.0; 19 | let pages = (offset + size + page_size - 1) / page_size; 20 | let aligned_size = pages * page_size; 21 | let virtual_address = self.map_aligned(aligned_address, aligned_size)?; 22 | Ok(VirtualAddress(virtual_address.0 + offset)) 23 | } 24 | 25 | unsafe fn unmap(&mut self, address: VirtualAddress, size: usize) -> Result<(), &'static str> { 26 | let page_size = self.page_size(); 27 | let page = address.0/page_size; 28 | let aligned_address = VirtualAddress(page * page_size); 29 | let offset = address.0 - aligned_address.0; 30 | let pages = (offset + size + page_size - 1) / page_size; 31 | let aligned_size = pages * page_size; 32 | self.unmap_aligned(aligned_address, aligned_size) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/mmio.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use core::ops::{BitAnd, BitOr, Not}; 4 | use core::ptr; 5 | 6 | use super::io::Io; 7 | 8 | #[repr(transparent)] 9 | pub struct Mmio { 10 | value: T 11 | } 12 | 13 | impl Io for Mmio where T: Copy + PartialEq + BitAnd + BitOr + Not { 14 | type Value = T; 15 | 16 | fn read(&self) -> T { 17 | unsafe { ptr::read_volatile(&self.value) } 18 | } 19 | 20 | fn write(&mut self, value: T) { 21 | unsafe { ptr::write_volatile(&mut self.value, value) }; 22 | } 23 | } 24 | --------------------------------------------------------------------------------