1 /* 2 * Generic pager functionality. 3 * 4 * Copyright (C) 2021, 2022 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA 20 */ 21 22 #include "dataspace_server.h" 23 #include "ipc.h" 24 #include "pager.h" 25 26 #include <string.h> 27 28 29 30 /* Initialise the pager with a page mapper and the given flags controlling 31 access to a file. */ 32 33 Pager::Pager(PageMapper *mapper, map_flags_t flags) 34 : _start(0), _size(0), _mapper(mapper), _flags(flags), 35 _is_masked(false), _start_visible(0), _end_visible(0) 36 { 37 } 38 39 /* Close the pager. */ 40 41 void Pager::close() 42 { 43 /* NOTE: Need to deallocate any allocated regions and flexpages. */ 44 } 45 46 /* Flush data to the file. */ 47 48 long Pager::flush(offset_t populated_size, offset_t *size) 49 { 50 _mapper->flush_all(_start, populated_size); 51 52 *size = _mapper->get_data_size(); 53 return L4_EOK; 54 } 55 56 /* Resize the underlying file. */ 57 58 long Pager::resize(offset_t *size) 59 { 60 _mapper->set_data_size(*size); 61 62 *size = _mapper->get_data_size(); 63 return L4_EOK; 64 } 65 66 /* Expose a region of the file. */ 67 68 long Pager::mmap(offset_t position, offset_t length, 69 offset_t start_visible, offset_t end_visible, 70 offset_t *start_pos, offset_t *end_pos, offset_t *size) 71 { 72 /* Define region characteristics. */ 73 74 _start = trunc(position, PAGE_SIZE); 75 _size = round(position + length, PAGE_SIZE) - _start; 76 77 /* Return the start and end positions plus populated extent. */ 78 79 *start_pos = _start; 80 *end_pos = _start + _size; 81 *size = _mapper->get_data_size(); 82 83 /* Permit masking of mapped regions. */ 84 85 if (start_visible || end_visible) 86 { 87 _start_visible = start_visible; 88 _end_visible = end_visible; 89 _is_masked = (*start_pos != _start_visible) || (*end_pos != _end_visible); 90 } 91 else 92 _is_masked = false; 93 94 return L4_EOK; 95 } 96 97 /* Map a flexpage corresponding to the dataspace 'offset' involving a 'hot_spot' 98 (flexpage offset). */ 99 100 long Pager::map(offset_t offset, address_t hot_spot, map_flags_t flags, 101 l4_snd_fpage_t *region) 102 { 103 offset_t file_offset = _start + offset; 104 offset_t max_offset = _start + _size; 105 106 /* Prevent access beyond that defined by the pager. 107 NOTE: Permitting executable requests here. This needs to be configured 108 when opening the pager or by another means. */ 109 110 if (flags & (~(_flags | L4RE_DS_F_X))) 111 return -L4_EACCESS; 112 113 Flexpage *issued_flexpage = NULL, *obtained_flexpage = NULL; 114 115 /* Obtain any replicated flexpage. 116 NOTE: An additional condition should be introduced to test for the 117 relevance of replicated flexpages. */ 118 119 if (flags & L4RE_DS_F_W) 120 issued_flexpage = _map.find(file_offset); 121 122 /* Without any replicated flexpage, obtain one for the shared, underlying 123 file data. */ 124 125 if (issued_flexpage == NULL) 126 { 127 obtained_flexpage = _mapper->get(file_offset, flags); 128 129 /* Determine if the flexpage should be masked. */ 130 131 issued_flexpage = get_masked_flexpage(obtained_flexpage); 132 133 /* Determine if the flexpage should be replicated. */ 134 135 issued_flexpage = get_replicated_flexpage(issued_flexpage, flags); 136 } 137 138 /* Issue the flexpage via the IPC system. */ 139 140 long err = ipc_prepare_flexpage(issued_flexpage, file_offset, max_offset, 141 hot_spot, flags, region); 142 143 if (!err) 144 err = complete_Dataspace_map(*region); 145 146 /* After the obtained flexpage is issued, it is queued for future reuse. */ 147 148 if (obtained_flexpage != NULL) 149 _mapper->queue(obtained_flexpage); 150 151 if (err) 152 return err; 153 154 return IPC_MESSAGE_SENT; 155 } 156 157 /* Return the total size of the data. */ 158 159 offset_t Pager::get_data_size() 160 { 161 return _mapper->get_data_size(); 162 } 163 164 /* Detect flexpages with masked content, introducing separate flexpages 165 offering masked content from the same region. */ 166 167 Flexpage *Pager::get_masked_flexpage(Flexpage *flexpage) 168 { 169 /* If not masking content, return the original flexpage. */ 170 171 if (!_is_masked) 172 return flexpage; 173 174 /* Determine whether the flexpage involves the limits of the visible 175 region or is beyond such limits. */ 176 177 bool has_start = flexpage->supports_position(_start_visible) && 178 flexpage->base_offset != _start_visible; 179 bool has_end = flexpage->supports_position(_end_visible); 180 bool has_zero = (flexpage->base_offset >= _end_visible) || 181 (flexpage->base_offset + flexpage->size < _start_visible); 182 183 /* Return the original flexpage within the visible limits. */ 184 185 if (!has_start && !has_end && !has_zero) 186 return flexpage; 187 188 /* Allocate and populate a region in one of the preallocated flexpages for 189 masked content. */ 190 191 Flexpage &masked = has_start ? _start_flexpage : 192 has_end ? _end_flexpage : 193 _zero_flexpage; 194 195 allocate_region(flexpage, masked); 196 populate_region(flexpage, masked, has_start, has_end); 197 198 /* Associate the preallocated flexpage with the original flexpage. */ 199 200 flexpage->associate(&masked); 201 202 return &masked; 203 } 204 205 void Pager::allocate_region(Flexpage *flexpage, Flexpage &masked) 206 { 207 offset_t needed = flexpage->region->size(); 208 209 /* Attempt to re-use an existing region. */ 210 211 if (masked.region != NULL) 212 { 213 if (masked.region->size() == needed) 214 return; 215 else 216 _memory.release(masked.region); 217 } 218 219 /* Set the region in the flexpage. */ 220 221 masked.set_region(_memory.region(needed)); 222 masked.reset(flexpage->base_offset); 223 } 224 225 void Pager::populate_region(Flexpage *flexpage, Flexpage &masked, 226 bool has_start, bool has_end) 227 { 228 /* Without any limits involved, provide a zero flexpage. */ 229 230 if (!has_start && !has_end) 231 { 232 memset((void *) masked.region->start, 0, masked.size); 233 return; 234 } 235 236 /* Determine the content limits given start and/or end offsets. */ 237 238 offset_t start_offset = has_start ? _start_visible - masked.base_offset : 0; 239 offset_t end_offset = has_end ? _end_visible - masked.base_offset : masked.size; 240 241 if (has_start) 242 memset((void *) masked.region->start, 0, start_offset); 243 244 memcpy((void *) (masked.region->start + start_offset), 245 (const void *) (flexpage->region->start + start_offset), 246 end_offset - start_offset); 247 248 if (has_end) 249 memset((void *) (masked.region->start + end_offset), 0, masked.size - end_offset); 250 } 251 252 /* Detect flexpages with replicated content, introducing separate flexpages 253 offering replicated content from the same region. */ 254 255 Flexpage *Pager::get_replicated_flexpage(Flexpage *flexpage, map_flags_t flags) 256 { 257 /* NOTE: An additional condition should be introduced to test for the 258 relevance of replicated flexpages. For now, detect copy-on-write 259 situations. */ 260 261 if (_is_masked && (flags & L4RE_DS_F_W)) 262 { 263 Region *region = _memory.region(flexpage->size); 264 Flexpage *replicated = new Flexpage(region); 265 266 replicated->reset(flexpage->base_offset); 267 268 memcpy((void *) region->start, (const void *) flexpage->region->start, flexpage->size); 269 _map.insert(replicated); 270 return replicated; 271 } 272 else 273 return flexpage; 274 } 275 276 // vim: tabstop=4 expandtab shiftwidth=4