1 /*
2 * Copyright (C) 2025 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18 #include "blockio2_protocols.h"
19
20 #include <kernel/thread.h>
21 #include <kernel/vm.h>
22 #include <lib/bio.h>
23 #include <lk/err.h>
24 #include <lk/trace.h>
25 #include <stddef.h>
26 #include <stdio.h>
27 #include <uefi/protocols/block_io2_protocol.h>
28 #include <uefi/types.h>
29
30 #include "defer.h"
31 #include "events.h"
32 #include "io_stack.h"
33 #include "memory_protocols.h"
34 #include "switch_stack.h"
35 #include "thread_utils.h"
36 #include "uefi_platform.h"
37
38 #define LOCAL_TRACE 0
39
40 namespace {
41
42 struct EfiBlockIo2Interface {
43 EfiBlockIo2Protocol protocol;
44 EfiBlockIoMedia media;
45 void* dev;
46 };
47
reset(EfiBlockIo2Protocol * self,bool extended_verification)48 EfiStatus reset(EfiBlockIo2Protocol* self, bool extended_verification) {
49 return UNSUPPORTED;
50 }
51
async_read_callback(void * cookie,struct bdev * dev,ssize_t bytes_read)52 void async_read_callback(void* cookie, struct bdev* dev, ssize_t bytes_read) {
53 // |cookie| might be identity mapped memory, which is in UEFI address space.
54 // We need to switch to the UEFI address space to access it.
55 auto aspace = set_boot_aspace();
56 auto old_aspace = vmm_set_active_aspace(aspace);
57 auto token = reinterpret_cast<EfiBlockIo2Token*>(cookie);
58 if (bytes_read < 0) {
59 token->transaction_status = DEVICE_ERROR;
60 } else {
61 token->transaction_status = SUCCESS;
62 }
63 signal_event(token->event);
64 vmm_set_active_aspace(old_aspace);
65 }
66
67 // Read from dev, after I/O completes, signal token->event and set
68 // token->transaction_status
read_blocks_async(bdev_t * dev,uint64_t lba,EfiBlockIo2Token * token,size_t buffer_size,void * buffer)69 EfiStatus read_blocks_async(bdev_t* dev, uint64_t lba, EfiBlockIo2Token* token,
70 size_t buffer_size, void* buffer) {
71 if (lba >= dev->block_count) {
72 printf("OOB async read %s %llu %u\n", dev->name, lba, dev->block_count);
73 return END_OF_MEDIA;
74 }
75 if (token == nullptr) {
76 printf("Invalid token %p\n", token);
77 return INVALID_PARAMETER;
78 }
79 if (dev->read_async != nullptr) {
80 bio_read_async(dev, buffer, lba * dev->block_size, buffer_size,
81 async_read_callback, token);
82 return SUCCESS;
83 }
84 // First draft of this API will just use a background thread.
85 // More efficient version can be implemented once LK's bio layer
86 // supports async IO
87 auto thread = thread_create_functor(
88 "async_bio",
89 [dev, lba, buffer_size, buffer, token]() {
90 auto aspace = set_boot_aspace();
91 vmm_set_active_aspace(aspace);
92 auto bytes_read =
93 bio_read_block(dev, buffer, lba, buffer_size / dev->block_size);
94 async_read_callback(token, dev, bytes_read);
95 return 0;
96 },
97 get_current_thread()->priority, kIoStackSize);
98 if (thread == nullptr) {
99 printf("Failed to create thread for IO read\n");
100 return DEVICE_ERROR;
101 }
102
103 auto err = thread_detach_and_resume(thread);
104 if (err != NO_ERROR) {
105 printf("Failed to resume thread for IO read %d\n", err);
106 return DEVICE_ERROR;
107 }
108 return SUCCESS;
109 }
110
read_blocks_trampoline(EfiBlockIo2Protocol * self,uint32_t media_id,uint64_t lba,EfiBlockIo2Token * token,size_t buffer_size,void * buffer)111 EfiStatus read_blocks_trampoline(EfiBlockIo2Protocol* self, uint32_t media_id,
112 uint64_t lba, EfiBlockIo2Token* token,
113 size_t buffer_size, void* buffer) {
114 auto interface = reinterpret_cast<EfiBlockIo2Interface*>(self);
115 auto dev = reinterpret_cast<bdev_t*>(interface->dev);
116 void* io_stack = reinterpret_cast<char*>(get_io_stack()) + kIoStackSize;
117 auto ret = call_with_stack(io_stack, read_blocks_async, dev, lba, token,
118 buffer_size, buffer);
119 return static_cast<EfiStatus>(ret);
120 }
121
write_blocks_ex(EfiBlockIo2Protocol * self,uint32_t media_id,uint64_t lba,EfiBlockIo2Token * token,size_t buffer_size,const void * buffer)122 EfiStatus write_blocks_ex(EfiBlockIo2Protocol* self, uint32_t media_id,
123 uint64_t lba, EfiBlockIo2Token* token,
124 size_t buffer_size, const void* buffer) {
125 printf(
126 "Writing blocks from UEFI app is currently not supported to protect the "
127 "device.\n");
128 return UNSUPPORTED;
129 }
130
flush_blocks_ex(EfiBlockIo2Protocol * self,EfiBlockIo2Token * token)131 EfiStatus flush_blocks_ex(EfiBlockIo2Protocol* self, EfiBlockIo2Token* token) {
132 return SUCCESS;
133 }
134
135 } // namespace
136
open_async_block_device(EfiHandle handle,void ** intf)137 EfiStatus open_async_block_device(EfiHandle handle, void** intf) {
138 auto dev = bio_open(reinterpret_cast<const char*>(handle));
139 printf("%s(%s)\n", __FUNCTION__, dev->name);
140 auto interface = reinterpret_cast<EfiBlockIo2Interface*>(
141 uefi_malloc(sizeof(EfiBlockIo2Interface)));
142 auto protocol = &interface->protocol;
143 auto media = &interface->media;
144 protocol->media = media;
145 protocol->reset = reset;
146 protocol->read_blocks_ex = read_blocks_trampoline;
147 protocol->write_blocks_ex = write_blocks_ex;
148 protocol->flush_blocks_ex = flush_blocks_ex;
149 media->block_size = dev->block_size;
150 media->io_align = media->block_size;
151 media->last_block = dev->block_count - 1;
152 interface->dev = dev;
153 *intf = interface;
154
155 return SUCCESS;
156 }