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 }