diff --git a/src/base.rs b/src/base.rs index 6acbde7..d469790 100644 --- a/src/base.rs +++ b/src/base.rs @@ -5,7 +5,7 @@ use void::CVoid; /// Type for EFI_HANDLE. #[derive(Copy, Clone, Debug)] #[repr(C)] -pub struct Handle(*mut CVoid); +pub struct Handle(pub(crate) *mut CVoid); impl default::Default for Handle { fn default() -> Handle { Handle(ptr::null_mut()) } @@ -178,6 +178,15 @@ pub enum MemoryType { PalCode = 13, } +/// Type for EFI_ALLOCATE_TYPE +#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)] +#[repr(C)] +pub enum AllocateType { + AnyPages = 0, + MaxAddress = 1, + Address = 2, +} + /// UEFI Time structure. #[derive(Copy, Clone, Debug, Default)] #[repr(C)] diff --git a/src/bootservices.rs b/src/bootservices.rs index bcf5642..505b0f6 100644 --- a/src/bootservices.rs +++ b/src/bootservices.rs @@ -2,13 +2,24 @@ use core::ptr; use core::mem; use void::{NotYetDef, CVoid}; -use base::{Event, Handle, Handles, MemoryType, Status}; +use base::{AllocateType, Event, Handle, Handles, MemoryType, Status}; use event::{EventType, EventNotify, TimerDelay}; use task::TPL; use protocol::{DevicePathProtocol, Protocol, get_current_image}; use guid; use table; +bitflags! { + pub struct OpenProtocolAttributes: u32 { + const BY_HANDLE_PROTOCOL = 0x01; + const GET_PROTOCOL = 0x02; + const TEST_PROTOCOL = 0x04; + const BY_CHILD_CONTROLLER = 0x08; + const BY_DRIVER = 0x10; + const EXCLUSIVE = 0x20; + } +} + #[repr(C)] pub enum LocateSearchType { AllHandles = 0, @@ -22,8 +33,8 @@ pub struct BootServices { header: table::TableHeader, raise_tpl: *const NotYetDef, restore_tpl: *const NotYetDef, - allocate_pages: *const NotYetDef, - free_pages: *const NotYetDef, + allocate_pages: unsafe extern "win64" fn(allocate_type: AllocateType, pool_type: MemoryType, pages: usize, memory: *mut *mut CVoid) -> Status, + free_pages: unsafe extern "win64" fn(memory: *mut CVoid, pages: usize) -> Status, get_memory_map: *const NotYetDef, allocate_pool: unsafe extern "win64" fn(pool_type: MemoryType, size: usize, out: *mut *mut u8) -> Status, free_pool: unsafe extern "win64" fn(*mut CVoid), @@ -41,7 +52,7 @@ pub struct BootServices { __reserved: *const NotYetDef, register_protocol_notify: *const NotYetDef, locate_handle: *const NotYetDef, - locate_device_path: *const NotYetDef, + locate_device_path: unsafe extern "win64" fn(protocol: &guid::Guid, device_path: *mut *const DevicePathProtocol, device: &mut Handle) -> Status, install_configuration_table: *const NotYetDef, load_image: unsafe extern "win64" fn(boot_policy: u8, parent_image_handle: Handle, device_path: *const DevicePathProtocol, source_buffer: *const CVoid, source_size: usize, image_handle: *mut Handle) -> Status, start_image: unsafe extern "win64" fn(image_handle: Handle, exit_data_size: *mut usize, exit_data: *mut *const u16) -> Status, @@ -53,7 +64,7 @@ pub struct BootServices { set_watchdog_timer: unsafe extern "win64" fn(timeout: usize, code: u64, data_size: usize, data: *const u16) -> Status, connect_controller: *const NotYetDef, disconnect_controller: *const NotYetDef, - open_protocol: *const NotYetDef, + open_protocol: unsafe extern "win64" fn(handle: Handle, protocol: &guid::Guid, interface: &mut *const CVoid, agent_handle: Handle, controller_handle: Handle, attributes: u32) -> Status, close_protocol: unsafe extern "win64" fn(handle: Handle, protocol: &guid::Guid, agent_handle: Handle, controller_handle: Handle) -> Status, open_protocol_information: *const NotYetDef, protocols_per_handle: *const NotYetDef, @@ -61,13 +72,30 @@ pub struct BootServices { locate_protocol: unsafe extern "win64" fn(protocol: &guid::Guid, registration: *const CVoid, interface: &mut *mut CVoid) -> Status, install_multiple_protocol_interfaces: *const NotYetDef, uninstall_multiple_protocol_interfaces: *const NotYetDef, - calculate_crc32: *const NotYetDef, + calculate_crc32: unsafe extern "win64" fn(data: *const CVoid, data_size: usize, crc32: &mut u32) -> Status, copy_mem: unsafe extern "win64" fn(*mut CVoid, *mut CVoid, usize), set_mem: unsafe extern "win64" fn(*mut CVoid, usize, u8), create_event_ex: *const NotYetDef, } impl BootServices { + pub fn allocate_pages(&self, pages: usize) -> Result<*mut CVoid, Status> { + let mut ptr: *mut CVoid = ptr::null_mut(); + + let result = unsafe { (self.allocate_pages)(AllocateType::AnyPages, get_current_image().image_data_type, pages, &mut ptr) }; + if result != Status::Success { + return Err(result); + } + + Ok(ptr) + } + + pub fn free_pages(&self, p: *const T, pages: usize) { + unsafe { + (self.free_pages)(p as *mut CVoid, pages); + } + } + /// Allocate `size` bytes of memory using type `T`. pub fn allocate_pool(&self, size: usize) -> Result<*mut T, Status> { let mut ptr: *mut u8 = 0 as *mut u8; @@ -131,10 +159,41 @@ impl BootServices { } } - let r = unsafe { mem::transmute::<*mut CVoid, &'static T>(ptr) }; + let r = unsafe { &*(ptr as *const T) }; + Ok(r) + } + + pub fn handle_protocol_mut(&self, handle: Handle) -> Result<&'static mut T, Status> { + let mut ptr : *mut CVoid = 0 as *mut CVoid; + let guid = T::guid(); + + + unsafe { + let status = (self.handle_protocol)(handle, guid, &mut ptr); + if status != Status::Success { + return Err(status); + } + } + + let r = unsafe { &mut *(ptr as *mut T) }; Ok(r) } + /// Queries a handle to determine if it supports a specified protocol. If the protocol is + /// supported by the handle, it opens the protocol on behalf of the calling agent. + pub fn open_protocol(&self, handle: Handle, agent_handle: Handle, controller_handle: Handle, attributes: OpenProtocolAttributes) -> Result<&'static T, Status> { + let mut ptr: *const CVoid = ptr::null(); + let guid = T::guid(); + + unsafe { + let status = (self.open_protocol)(handle, guid, &mut ptr, agent_handle, controller_handle, attributes.bits()); + match status { + Status::Success => Ok(mem::transmute(ptr.as_ref().unwrap())), + e => Err(e) + } + } + } + // TODO: for the love of types, fix me pub fn close_protocol(&self, handle: Handle, agent_handle: Handle, controller_handle: Handle) -> Status { let guid = T::guid(); @@ -159,6 +218,21 @@ impl BootServices { return Ok(Handles::new(handles as *mut Handle, nhandles)); } + /// Retrieves the closest device on `device_path` to `device_path` that supports the given + /// protocol. + pub fn locate_device_path(&self, device_path: &DevicePathProtocol) -> Result<(Handle, Option<&DevicePathProtocol>), Status> { + let mut out: Handle = Handle::default(); + let mut device_path_out = device_path as *const DevicePathProtocol; + let guid = T::guid(); + + let res = unsafe { (self.locate_device_path)(&guid, &mut device_path_out, &mut out) }; + if res != Status::Success { + return Err(res); + } + + unsafe { Ok((out, device_path_out.as_ref())) } + } + /// Load an image by device path and return its handle. pub fn load_image(&self, boot_policy: bool, parent_image_handle: Handle, device_path: *const DevicePathProtocol) -> Result { self.load_image_buffer(boot_policy, parent_image_handle, device_path, 0 as *const CVoid, 0) @@ -230,6 +304,24 @@ impl BootServices { } } + /// Calculate the CRC-32 checksum of some data. + pub fn calculate_crc32(&self, data: &T) -> Result { + self.calculate_crc32_sized(data, mem::size_of::()) + } + + /// Calculate the CRC-32 checksum of some data, using the provided length. + pub fn calculate_crc32_sized(&self, data: *const T, size: usize) -> Result { + let mut crc32 = 0; + + let result = + unsafe { (self.calculate_crc32)(data as *const CVoid, size, &mut crc32) }; + if result != Status::Success { + return Err(result); + } + + Ok(crc32) + } + /// Copy memory, similar to memcpy. pub fn copy_mem(&self, dest: *mut u8, src: *const u8, n: usize) -> *mut u8 { unsafe { diff --git a/src/guid.rs b/src/guid.rs index 0d38b01..79489c6 100644 --- a/src/guid.rs +++ b/src/guid.rs @@ -1,7 +1,7 @@ use core::fmt; /// Type for EFI_GUID. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(C)] pub struct Guid(pub u32, pub u16, pub u16, pub [u8; 8]); diff --git a/src/lib.rs b/src/lib.rs index 0247c9b..d412e35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,7 +103,7 @@ pub use guid::*; pub use systemtable::*; -pub use bootservices::BootServices; +pub use bootservices::*; pub use runtimeservices::{ResetType, RuntimeServices}; diff --git a/src/protocol/block.rs b/src/protocol/block.rs new file mode 100644 index 0000000..d5bc022 --- /dev/null +++ b/src/protocol/block.rs @@ -0,0 +1,216 @@ +// Copyright 2017 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied +// See the License for the specific language governing permissions and +// limitations under the License. + + +use core::slice; + +use base::Status; +use guid::Guid; +use protocol::Protocol; +use void::CVoid; + +#[repr(C)] +#[derive(Debug)] +pub struct BlockIOMedia { + media_id: u32, + removable: u8, + present: u8, + logical_partition: u8, + read_only: u8, + write_caching: u8, + block_size: u32, + pub io_align: u32, + pub last_block: u64, + lowest_aligned_lba: u64, + logical_blocks_per_physical_block: u32, + optimal_transfer_length_granularity: u32, +} + +pub static EFI_BLOCK_IO_PROTOCOL_GUID: Guid = Guid( + 0x964E_5B21, + 0x6459, + 0x11D2, + [0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B], +); + +/// Bindings to the EFI Block I/O protocol. This protocol provides synchronous access to block +/// devices, and allows block-by-block access. +#[repr(C)] +pub struct BlockIOProtocol { + revision: u64, + pub media: *const BlockIOMedia, + reset: unsafe extern "win64" fn(this: *const BlockIOProtocol, extended_verification: u8) + -> Status, + read_blocks: unsafe extern "win64" fn( + this: *const BlockIOProtocol, + media_id: u32, + lba: u64, + buffer_size: usize, + buffer: *mut CVoid, + ) -> Status, + write_blocks: unsafe extern "win64" fn( + this: *const BlockIOProtocol, + media_id: u32, + lba: u64, + buffer_size: usize, + buffer: *const CVoid, + ) -> Status, + flush_blocks: unsafe extern "win64" fn(this: *const BlockIOProtocol) -> Status, +} + +impl Protocol for BlockIOProtocol { + fn guid() -> &'static Guid { + &EFI_BLOCK_IO_PROTOCOL_GUID + } +} + +impl BlockIOProtocol { + /// Indicates whether or not the device is removable. + pub fn is_removable(&self) -> bool { + unsafe { (*self.media).removable == 1 } + } + + /// Indicates whether or not the device is present. + pub fn is_present(&self) -> bool { + unsafe { (*self.media).present == 1 } + } + + /// Indicates whether or not the device is a logical partition. + pub fn is_logical_partition(&self) -> bool { + unsafe { (*self.media).logical_partition == 1 } + } + + /// Indicates whether or not the device is read only. + pub fn is_read_only(&self) -> bool { + unsafe { (*self.media).read_only == 1 } + } + + /// Indicates whether or not the device performs write caching. + pub fn write_caching(&self) -> bool { + unsafe { (*self.media).write_caching == 1 } + } + + /// Indicates whether or not the device has alignment requirements for buffers. + pub fn must_align(&self) -> bool { + unsafe { (*self.media).io_align > 1 } + } + + pub fn required_pages_block(&self, blocks: usize) -> usize { + let block_size = unsafe { (*self.media).block_size } as usize; + let mut bytes = block_size * blocks; + + if bytes % block_size != 0 { + bytes = block_size * ((bytes / block_size) + 1); + } + + self.required_pages(bytes) + } + pub fn required_pages(&self, read_size: usize) -> usize { + let mut num_pages = read_size / 4096; + if read_size % 4096 != 0 { + num_pages += 1; + } + + num_pages + } + + /// Reset the device. + pub fn reset(&self, extended_verification: bool) -> Result<(), Status> { + match unsafe { (self.reset)(self, extended_verification as u8) } { + Status::Success => Ok(()), + e => Err(e), + } + } + + /// Read `num_bytes` bytes from the disk starting at block `start`. The returned slice includes + /// memory allocated with `allocate_pool` or `allocate_pages`, and it is the caller's + /// responsibility to free it with the appropriate function based on `must_align()`. + pub fn read_bytes(&self, start: u64, num_bytes: usize) -> Result<&mut [u8], Status> { + let bs = ::get_system_table().boot_services(); + let mut read_size = num_bytes; + let buffer: Result<*mut u8, Status>; + + // Reads can only be performed in multiples of the block size, so round up to the nearest + // block. + let block_size = unsafe { (*self.media).block_size } as usize; + if num_bytes % block_size != 0 { + read_size = block_size * ((num_bytes / block_size) + 1); + } + + // The read buffer must be aligned to the value of `media.io_align`. UEFI doesn't provide + // any sort of memalign, so in order to be safe, if we need to be aligned on any boundary + // greater than 1, use `allocate_pages` to obtain a 4K-aligned address instead of + // `allocate_pool`. This isn't an ideal solution, but it does work in lieu of implementing + // memalign and keeping track of the original allocation. + if self.must_align() { + buffer = bs.allocate_pages(self.required_pages(read_size)).map(|buf| buf as *mut u8); + } else { + buffer = bs.allocate_pool::(read_size); + } + + buffer.and_then(|buffer| unsafe { + match (self.read_blocks)( + self, + (*self.media).media_id, + start, + num_bytes, + buffer as *mut CVoid, + ) { + Status::Success => Ok(slice::from_raw_parts_mut(buffer, num_bytes)), + e => { + if self.must_align() { + bs.free_pages(buffer, self.required_pages(read_size)); + } else { + bs.free_pool(buffer); + } + Err(e) + } + } + }) + } + + /// Read `num_blocks` blocks from the disk starting at block `start`. The returned slice + /// includes memory allocated with `allocate_pool`, and it is the caller's responsibility to + /// free it. + pub fn read_blocks(&self, start: u64, num_blocks: usize) -> Result<&mut [u8], Status> { + let block_size = unsafe { (*self.media).block_size }; + let read_size_bytes = num_blocks * block_size as usize; + self.read_bytes(start, read_size_bytes) + } + + /// Write `buffer` to the disk starting at block `start`. `buffer.len()` must be a multiple of + /// the disks's block size, or else this call will fail. + pub fn write_bytes(&self, start: u64, buffer: &[u8]) -> Result<(), Status> { + match unsafe { + (self.write_blocks)( + self, + (*self.media).media_id, + start, + buffer.len(), + buffer.as_ptr() as *const CVoid, + ) + } { + Status::Success => Ok(()), + e => Err(e), + } + } + + /// Flush any pending writes to this disk. + pub fn flush_blocks(&self) -> Result<(), Status> { + match unsafe { (self.flush_blocks)(self) } { + Status::Success => Ok(()), + e => Err(e), + } + } +} diff --git a/src/protocol/disk.rs b/src/protocol/disk.rs new file mode 100644 index 0000000..173a764 --- /dev/null +++ b/src/protocol/disk.rs @@ -0,0 +1,66 @@ +// Copyright 2017 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::slice; + +use base::Status; +use guid::Guid; +use protocol::Protocol; +use void::{CVoid, NotYetDef}; + +pub static EFI_DISK_IO_PROTOCOL_GUID: Guid = Guid( + 0xCE34_5171, + 0xBA0B, + 0x11D2, + [0x8E, 0x4F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B], +); + +/// Bindings to the EFI Disk I/O protocol. This protocol is a synchronous abstraction on top of the +/// Block I/O protocol, and allows accessing arbitrary offsets/lengths instead of the block-based +/// accesses the Block I/O protocol provides. +#[repr(C)] +pub struct DiskIOProtocol { + revision: u64, + read_disk: unsafe extern "win64" fn(this: *const DiskIOProtocol, + media_id: u32, + offset: u64, + buffer_size: usize, + buffer: *mut CVoid) + -> Status, + write_disk: *const NotYetDef, +} + +impl Protocol for DiskIOProtocol { + fn guid() -> &'static Guid { + &EFI_DISK_IO_PROTOCOL_GUID + } +} + +impl DiskIOProtocol { + /// Read data from the disk at the given offset and size. `media_id` should be derived from the + /// Block I/O protocol (see specifically the `BlockIOMedia` struct). The returned slice + /// includes memory allocated with `allocate_pool`, and it is the caller's responsibility to + /// free it. + pub fn read_disk(&self, media_id: u32, offset: u64, size: usize) -> Result<&[u8], Status> { + ::get_system_table() + .boot_services() + .allocate_pool::(size) + .and_then(|buffer| unsafe { + match (self.read_disk)(self, media_id, offset, size, buffer as *mut CVoid) { + Status::Success => Ok(slice::from_raw_parts(buffer, size)), + e => Err(e), + } + }) + } +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 8ed7f20..c6971b3 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,11 +1,15 @@ use base::{Handle, MemoryType, Status}; use guid::Guid; -use void::NotYetDef; +use void::{CVoid, NotYetDef}; +mod block; mod device_path; +mod disk; mod serial; +pub use self::block::*; pub use self::device_path::*; +pub use self::disk::*; pub use self::serial::*; pub trait Protocol { @@ -16,6 +20,7 @@ pub trait Protocol { pub static EFI_LOADED_IMAGE_PROTOCOL_GUID: Guid = Guid(0x5B1B31A1, 0x9562, 0x11d2, [0x8E,0x3F,0x00,0xA0,0xC9,0x69,0x72,0x3B]); static mut THIS_LOADED_IMAGE: *const LoadedImageProtocol = 0 as *const LoadedImageProtocol; +static mut THIS_LOADED_IMAGE_HANDLE: Handle = Handle(0 as *mut CVoid); #[derive(Debug)] #[repr(C)] @@ -26,8 +31,8 @@ pub struct LoadedImageProtocol { pub device_handle: Handle, pub file_path: *const DevicePathProtocol, __reserved: *const NotYetDef, - load_options_size: u32, - load_options: *const NotYetDef, + pub load_options_size: u32, + pub load_options: *const CVoid, pub image_base: usize, pub image_size: u64, image_code_type: MemoryType, @@ -49,6 +54,7 @@ pub fn set_current_image(handle: Handle) -> Result<&'static LoadedImageProtocol, let loaded_image_proto: Result<&'static LoadedImageProtocol, Status> = st.boot_services().handle_protocol(handle); if let Ok(image) = loaded_image_proto { unsafe { + THIS_LOADED_IMAGE_HANDLE = handle; THIS_LOADED_IMAGE = image; } } @@ -62,3 +68,9 @@ pub fn get_current_image() -> &'static LoadedImageProtocol { } } +pub fn get_current_image_handle() -> Handle { + unsafe { + THIS_LOADED_IMAGE_HANDLE + } +} +