1/* 2 * Copyright 2014, General Dynamics C4 Systems 3 * 4 * SPDX-License-Identifier: GPL-2.0-only 5 */ 6 7#include <kernel/thread.h> 8#include <api/failures.h> 9#include <api/syscall.h> 10#include <machine/io.h> 11#include <arch/object/ioport.h> 12#include <arch/api/invocation.h> 13#include <plat/machine/io.h> 14 15static inline void apply_pattern(word_t_may_alias *w, word_t pattern, bool_t set) 16{ 17 if (set) { 18 *w |= pattern; 19 } else { 20 *w &= ~pattern; 21 } 22} 23 24static inline word_t make_pattern(int start, int end) 25{ 26 // number of bits we want to have set 27 int num_bits = end - start; 28 // shift down to cut off the bits we don't want, then shift up to put the 29 // bits into position 30 return (~(word_t)0) >> (CONFIG_WORD_SIZE - num_bits) << start; 31} 32 33static exception_t ensurePortOperationAllowed(cap_t cap, uint32_t start_port, uint32_t size) 34{ 35 uint32_t first_allowed = cap_io_port_cap_get_capIOPortFirstPort(cap); 36 uint32_t last_allowed = cap_io_port_cap_get_capIOPortLastPort(cap); 37 uint32_t end_port = start_port + size - 1; 38 assert(first_allowed <= last_allowed); 39 assert(start_port <= end_port); 40 41 if ((start_port < first_allowed) || (end_port > last_allowed)) { 42 userError("IOPort: Ports %d--%d fall outside permitted range %d--%d.", 43 (int)start_port, (int)end_port, 44 (int)first_allowed, (int)last_allowed); 45 current_syscall_error.type = seL4_IllegalOperation; 46 return EXCEPTION_SYSCALL_ERROR; 47 } 48 49 return EXCEPTION_NONE; 50} 51 52void freeIOPortRange(uint16_t first_port, uint16_t last_port) 53{ 54 setIOPortMask(x86KSAllocatedIOPorts, first_port, last_port, false); 55} 56 57static bool_t isIOPortRangeFree(uint16_t first_port, uint16_t last_port) 58{ 59 int low_word = first_port >> wordRadix; 60 int high_word = last_port >> wordRadix; 61 int low_index = first_port & MASK(wordRadix); 62 int high_index = last_port & MASK(wordRadix); 63 64 // check if we are operating on a partial word 65 if (low_word == high_word) { 66 if ((x86KSAllocatedIOPorts[low_word] & make_pattern(low_index, high_index + 1)) != 0) { 67 return false; 68 } 69 return true; 70 } 71 // check the starting word 72 if ((x86KSAllocatedIOPorts[low_word] & make_pattern(low_index, CONFIG_WORD_SIZE)) != 0) { 73 return false; 74 } 75 low_word++; 76 // check the rest of the whole words 77 while (low_word < high_word) { 78 if (x86KSAllocatedIOPorts[low_word] != 0) { 79 return false; 80 } 81 low_word++; 82 } 83 // check any trailing bits 84 if ((x86KSAllocatedIOPorts[low_word] & make_pattern(0, high_index + 1)) != 0) { 85 return false; 86 } 87 return true; 88} 89 90static exception_t invokeX86PortControl(uint16_t first_port, uint16_t last_port, cte_t *ioportSlot, cte_t *controlSlot) 91{ 92 setIOPortMask(x86KSAllocatedIOPorts, first_port, last_port, true); 93 cteInsert(cap_io_port_cap_new(first_port, last_port 94#ifdef CONFIG_VTX 95 , VPID_INVALID 96#endif 97 ), 98 controlSlot, ioportSlot); 99 100 return EXCEPTION_NONE; 101} 102 103exception_t decodeX86PortControlInvocation( 104 word_t invLabel, 105 word_t length, 106 cptr_t cptr, 107 cte_t *slot, 108 cap_t cap, 109 extra_caps_t excaps, 110 word_t *buffer 111) 112{ 113 uint16_t first_port; 114 uint16_t last_port; 115 word_t index, depth; 116 cap_t cnodeCap; 117 cte_t *destSlot; 118 lookupSlot_ret_t lu_ret; 119 exception_t status; 120 121 if (invLabel != X86IOPortControlIssue) { 122 userError("IOPortControl: Unknown operation."); 123 current_syscall_error.type = seL4_IllegalOperation; 124 return EXCEPTION_SYSCALL_ERROR; 125 } 126 127 if (length < 4 || excaps.excaprefs[0] == NULL) { 128 userError("IOPortControl: Truncated message."); 129 current_syscall_error.type = seL4_TruncatedMessage; 130 return EXCEPTION_SYSCALL_ERROR; 131 } 132 133 first_port = getSyscallArg(0, buffer) & 0xffff; 134 last_port = getSyscallArg(1, buffer) & 0xffff; 135 index = getSyscallArg(2, buffer); 136 depth = getSyscallArg(3, buffer); 137 138 cnodeCap = excaps.excaprefs[0]->cap; 139 140 if (last_port < first_port) { 141 userError("IOPortControl: Last port must be > first port."); 142 current_syscall_error.type = seL4_InvalidArgument; 143 current_syscall_error.invalidArgumentNumber = 1; 144 return EXCEPTION_SYSCALL_ERROR; 145 } 146 147 if (!isIOPortRangeFree(first_port, last_port)) { 148 userError("IOPortControl: Some ports in range already in use."); 149 current_syscall_error.type = seL4_RevokeFirst; 150 return EXCEPTION_SYSCALL_ERROR; 151 } 152 153 lu_ret = lookupTargetSlot(cnodeCap, index, depth); 154 if (lu_ret.status != EXCEPTION_NONE) { 155 userError("Target slot for new IO Port cap invalid: cap %lu.", getExtraCPtr(buffer, 0)); 156 return lu_ret.status; 157 } 158 destSlot = lu_ret.slot; 159 160 status = ensureEmptySlot(destSlot); 161 if (status != EXCEPTION_NONE) { 162 userError("Target slot for new IO Port cap not empty: cap %lu.", getExtraCPtr(buffer, 0)); 163 return status; 164 } 165 166 setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart); 167 return invokeX86PortControl(first_port, last_port, destSlot, slot); 168} 169 170static exception_t invokeX86PortIn(word_t invLabel, uint16_t port, bool_t call) 171{ 172 uint32_t res; 173 word_t len; 174 175 switch (invLabel) { 176 case X86IOPortIn8: 177 res = in8(port); 178 break; 179 case X86IOPortIn16: 180 res = in16(port); 181 break; 182 case X86IOPortIn32: 183 res = in32(port); 184 break; 185 } 186 187 if (call) { 188 setRegister(NODE_STATE(ksCurThread), badgeRegister, 0); 189 190 if (n_msgRegisters < 1) { 191 word_t *ipcBuffer; 192 ipcBuffer = lookupIPCBuffer(true, NODE_STATE(ksCurThread)); 193 if (ipcBuffer != NULL) { 194 ipcBuffer[1] = res; 195 len = 1; 196 } else { 197 len = 0; 198 } 199 } else { 200 setRegister(NODE_STATE(ksCurThread), msgRegisters[0], res); 201 len = 1; 202 } 203 204 setRegister(NODE_STATE(ksCurThread), msgInfoRegister, 205 wordFromMessageInfo(seL4_MessageInfo_new(0, 0, 0, len))); 206 } 207 // Prevent handleInvocation from attempting to complete the 'call' with an empty 208 // message (via replyFromKernel_success_empty) by forcing the thread state to 209 // be running. This prevents our stored message we just created from being 210 // overwritten. 211 setThreadState(NODE_STATE(ksCurThread), ThreadState_Running); 212 213 return EXCEPTION_NONE; 214} 215 216static exception_t invokeX86PortOut(word_t invLabel, uint16_t port, uint32_t data) 217{ 218 switch (invLabel) { 219 case X86IOPortOut8: 220 out8(port, data); 221 break; 222 case X86IOPortOut16: 223 out16(port, data); 224 break; 225 case X86IOPortOut32: 226 out32(port, data); 227 break; 228 } 229 230 return EXCEPTION_NONE; 231} 232 233exception_t decodeX86PortInvocation( 234 word_t invLabel, 235 word_t length, 236 cptr_t cptr, 237 cte_t *slot, 238 cap_t cap, 239 extra_caps_t excaps, 240 bool_t call, 241 word_t *buffer 242) 243{ 244 exception_t ret; 245 246 if (invLabel == X86IOPortIn8 || invLabel == X86IOPortIn16 || invLabel == X86IOPortIn32) { 247 if (length < 1) { 248 userError("IOPort: Truncated message."); 249 current_syscall_error.type = seL4_TruncatedMessage; 250 return EXCEPTION_SYSCALL_ERROR; 251 } 252 /* Get the port the user is trying to read from. */ 253 uint16_t port = getSyscallArg(0, buffer) & 0xffff; 254 switch (invLabel) { 255 case X86IOPortIn8: 256 ret = ensurePortOperationAllowed(cap, port, 1); 257 break; 258 case X86IOPortIn16: 259 ret = ensurePortOperationAllowed(cap, port, 2); 260 break; 261 case X86IOPortIn32: 262 ret = ensurePortOperationAllowed(cap, port, 4); 263 break; 264 } 265 if (ret != EXCEPTION_NONE) { 266 return ret; 267 } 268 setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart); 269 return invokeX86PortIn(invLabel, port, call); 270 } else if (invLabel == X86IOPortOut8 || invLabel == X86IOPortOut16 || invLabel == X86IOPortOut32) { 271 /* Ensure the incoming message is long enough for the write. */ 272 if (length < 2) { 273 userError("IOPort Out: Truncated message."); 274 current_syscall_error.type = seL4_TruncatedMessage; 275 return EXCEPTION_SYSCALL_ERROR; 276 } 277 /* Get the port the user is trying to write to. */ 278 uint16_t port = getSyscallArg(0, buffer) & 0xffff; 279 seL4_Word raw_data = getSyscallArg(1, buffer); 280 /* We construct the value for data from raw_data based on the actual size of the port 281 operation. This ensures that there is no 'random' user data left over in the value 282 passed to invokeX86PortOut. Whilst invokeX86PortOut will ignore any extra data and 283 cast down to the correct word size removing the extra here is currently relied upon 284 for verification */ 285 uint32_t data; 286 287 switch (invLabel) { 288 case X86IOPortOut8: 289 ret = ensurePortOperationAllowed(cap, port, 1); 290 data = raw_data & 0xff; 291 break; 292 case X86IOPortOut16: 293 ret = ensurePortOperationAllowed(cap, port, 2); 294 data = raw_data & 0xffff; 295 break; 296 case X86IOPortOut32: 297 ret = ensurePortOperationAllowed(cap, port, 4); 298 data = raw_data & 0xffffffff; 299 break; 300 } 301 if (ret != EXCEPTION_NONE) { 302 return ret; 303 } 304 setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart); 305 return invokeX86PortOut(invLabel, port, data); 306 } else { 307 userError("IOPort: Unknown operation."); 308 current_syscall_error.type = seL4_IllegalOperation; 309 return EXCEPTION_SYSCALL_ERROR; 310 } 311} 312 313void setIOPortMask(void *ioport_bitmap, uint16_t low, uint16_t high, bool_t set) 314{ 315 //get an aliasing pointer 316 word_t_may_alias *bitmap = ioport_bitmap; 317 318 int low_word = low >> wordRadix; 319 int high_word = high >> wordRadix; 320 int low_index = low & MASK(wordRadix); 321 int high_index = high & MASK(wordRadix); 322 323 // see if we are just manipulating bits inside a single word. handling this 324 // specially makes reasoning easier 325 if (low_word == high_word) { 326 apply_pattern(bitmap + low_word, make_pattern(low_index, high_index + 1), set); 327 } else { 328 // operate on the potentially partial first word 329 apply_pattern(bitmap + low_word, make_pattern(low_index, CONFIG_WORD_SIZE), set); 330 low_word++; 331 // iterate over the whole words 332 while (low_word < high_word) { 333 apply_pattern(bitmap + low_word, ~(word_t)0, set); 334 low_word++; 335 } 336 // apply to any remaining bits 337 apply_pattern(bitmap + low_word, make_pattern(0, high_index + 1), set); 338 } 339} 340