summaryrefslogtreecommitdiffstats
path: root/vendor/r-efi/examples/gop-query.rs
blob: b4ab4c370b9ed09ba599c1d975215dc5bc07ec39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Example: Graphics Query
//
// This is a slightly more complex UEFI application than `hello-world`. It
// locates the graphics-output-protocol, queries its current mode and prints
// the current resolution to the UEFI console.
//
// This example should make everyone aware that UEFI programing in Rust really
// asks for helper layers. While the C/FFI/Spec API can be used directly, it
// is quite cumbersome. Especially the error handling is overly difficult.
//
// Nevertheless, this example shows how to find UEFI protocol and invoke
// their member functions.
//
// Like all the other r-efi examples, it is a standalone example. That is, no
// UTF-16 helpers are pulled in, nor any allocators or panic frameworks. For
// real world scenarios, you really should choose such helpers.

#![no_main]
#![no_std]

use r_efi::efi;

#[panic_handler]
fn panic_handler(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

fn fail(_r: efi::Status) -> ! {
    panic!();
}

// A simple `itoa()`-ish function that takes a u32 and turns it into a UTF-16
// string. It always prints exactly 10 characters, so leading zeroes are used
// for small numbers.
fn utoa(mut u: u32, a: &mut [u16]) {
    for i in 0..10 {
        a[9 - i] = 0x0030u16 + ((u % 10) as u16);
        u = u / 10;
    }
}

// A simple helper that takes two integers and prints them to the UEFI console
// with a short prefix. It uses a UTF-16 buffer and fills in the numbers before
// printing the entire buffer.
fn print_xy(st: *mut efi::SystemTable, x: u32, y: u32) {
    let mut s = [
        0x0058u16, 0x0059u16, 0x003au16, //            "XY:"
        0x0020u16, //                                  " "
        0x0020u16, 0x0020u16, 0x0020u16, 0x0020u16, // "    "
        0x0020u16, 0x0020u16, 0x0020u16, 0x0020u16, // "    "
        0x0020u16, 0x0020u16, //                       "    "
        0x0078u16, //                                  "x"
        0x0020u16, 0x0020u16, 0x0020u16, 0x0020u16, // "    "
        0x0020u16, 0x0020u16, 0x0020u16, 0x0020u16, // "    "
        0x0020u16, 0x0020u16, //                       "    "
        0x000au16, //                                  "\n"
        0x0000u16, //                                  NUL
    ];

    utoa(x, &mut s[4..14]);
    utoa(y, &mut s[15..25]);

    unsafe {
        let r = ((*(*st).con_out).output_string)((*st).con_out, s.as_ptr() as *mut efi::Char16);
        if r.is_error() {
            fail(r);
        }
    }
}

// This function locates singleton UEFI protocols. Those protocols do not
// require to register listener handles, but are globally available to all
// UEFI applications. It takes a GUID of the protocol to locate and returns
// the protocol pointer on success.
fn locate_singleton(
    st: *mut efi::SystemTable,
    guid: *const efi::Guid,
) -> Result<*mut core::ffi::c_void, efi::Status> {
    let mut interface: *mut core::ffi::c_void = core::ptr::null_mut();
    let mut handles: *mut efi::Handle = core::ptr::null_mut();
    let mut n_handles: usize = 0;
    let mut r: efi::Status;

    // Use `locate_handle_buffer()` to find all handles that support the
    // specified protocol.
    unsafe {
        if (*st).hdr.revision < efi::SYSTEM_TABLE_REVISION_1_10 {
            // We use `LocateHandleBuffer`, which was introduced in 1.10.
            return Err(efi::Status::UNSUPPORTED);
        }

        let r = ((*(*st).boot_services).locate_handle_buffer)(
            efi::BY_PROTOCOL,
            guid as *mut _,
            core::ptr::null_mut(),
            &mut n_handles,
            &mut handles,
        );
        match r {
            efi::Status::SUCCESS => {}
            efi::Status::NOT_FOUND => return Err(r),
            efi::Status::OUT_OF_RESOURCES => return Err(r),
            _ => panic!(),
        };
    }

    // Now that we have all handles with the specified protocol, query it for
    // the protocol interface. We loop here, even though every item should
    // succeed. Lets be on the safe side.
    // Secondly, we use `handle_protocol()` here, but really should be using
    // `open_protocol()`. But for singleton protocols, this does not matter,
    // so lets use the simple path for now.
    unsafe {
        r = efi::Status::NOT_FOUND;
        for i in 0..n_handles {
            r = ((*(*st).boot_services).handle_protocol)(
                *handles.offset(core::convert::TryFrom::<usize>::try_from(i).unwrap()),
                guid as *mut _,
                &mut interface,
            );
            match r {
                efi::Status::SUCCESS => break,
                efi::Status::UNSUPPORTED => continue,
                _ => panic!(),
            };
        }
    }

    // Free the allocated buffer memory of `handles`. This was allocated on the
    // pool by `locate_handle_buffer()`.
    unsafe {
        let r = ((*(*st).boot_services).free_pool)(handles as *mut core::ffi::c_void);
        assert!(!r.is_error());
    }

    // In case we found nothing, return `NOT_FOUND`, otherwise return the
    // interface identifier.
    match r {
        efi::Status::SUCCESS => Ok(interface),
        _ => Err(efi::Status::NOT_FOUND),
    }
}

// A simple helper that queries the current mode of the GraphicsOutputProtocol
// and returns the x and y dimensions on success.
fn query_gop(
    gop: *mut efi::protocols::graphics_output::Protocol,
) -> Result<(u32, u32), efi::Status> {
    let mut info: *mut efi::protocols::graphics_output::ModeInformation = core::ptr::null_mut();
    let mut z_info: usize = 0;

    unsafe {
        // We could just look at `gop->mode->info`, but lets query the mode
        // instead to show how to query other modes than the active one.
        let r = ((*gop).query_mode)(gop, (*(*gop).mode).mode, &mut z_info, &mut info);
        match r {
            efi::Status::SUCCESS => {}
            efi::Status::DEVICE_ERROR => return Err(r),
            _ => panic!(),
        };
        if z_info < core::mem::size_of_val(&*info) {
            return Err(efi::Status::UNSUPPORTED);
        }

        Ok(((*info).horizontal_resolution, (*info).vertical_resolution))
    }
}

// This is the UEFI application entrypoint. We use it to locate the GOP
// pointer, query the current mode, and then print it to the system console.
#[export_name = "efi_main"]
pub extern "C" fn main(_h: efi::Handle, st: *mut efi::SystemTable) -> efi::Status {
    let r = locate_singleton(st, &efi::protocols::graphics_output::PROTOCOL_GUID);
    let gop = match r {
        Ok(v) => v,
        Err(r) => fail(r),
    };

    let r = query_gop(gop as _);
    let v = match r {
        Ok(v) => v,
        Err(r) => fail(r),
    };

    print_xy(st, v.0, v.1);

    efi::Status::SUCCESS
}