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 
apply_pattern(word_t_may_alias * w,word_t pattern,bool_t set)15 static 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 
make_pattern(int start,int end)24 static 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 
ensurePortOperationAllowed(cap_t cap,uint32_t start_port,uint32_t size)33 static 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 
freeIOPortRange(uint16_t first_port,uint16_t last_port)52 void freeIOPortRange(uint16_t first_port, uint16_t last_port)
53 {
54     setIOPortMask(x86KSAllocatedIOPorts, first_port, last_port, false);
55 }
56 
isIOPortRangeFree(uint16_t first_port,uint16_t last_port)57 static 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 
invokeX86PortControl(uint16_t first_port,uint16_t last_port,cte_t * ioportSlot,cte_t * controlSlot)90 static 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 
decodeX86PortControlInvocation(word_t invLabel,word_t length,cptr_t cptr,cte_t * slot,cap_t cap,word_t * buffer)103 exception_t decodeX86PortControlInvocation(
104     word_t invLabel,
105     word_t length,
106     cptr_t cptr,
107     cte_t *slot,
108     cap_t cap,
109     word_t *buffer
110 )
111 {
112     uint16_t first_port;
113     uint16_t last_port;
114     word_t index, depth;
115     cap_t cnodeCap;
116     cte_t *destSlot;
117     lookupSlot_ret_t lu_ret;
118     exception_t status;
119 
120     if (invLabel != X86IOPortControlIssue) {
121         userError("IOPortControl: Unknown operation.");
122         current_syscall_error.type = seL4_IllegalOperation;
123         return EXCEPTION_SYSCALL_ERROR;
124     }
125 
126     if (length < 4 || current_extra_caps.excaprefs[0] == NULL) {
127         userError("IOPortControl: Truncated message.");
128         current_syscall_error.type = seL4_TruncatedMessage;
129         return EXCEPTION_SYSCALL_ERROR;
130     }
131 
132     first_port = getSyscallArg(0, buffer) & 0xffff;
133     last_port = getSyscallArg(1, buffer) & 0xffff;
134     index = getSyscallArg(2, buffer);
135     depth = getSyscallArg(3, buffer);
136 
137     cnodeCap = current_extra_caps.excaprefs[0]->cap;
138 
139     if (last_port < first_port) {
140         userError("IOPortControl: Last port must be > first port.");
141         current_syscall_error.type = seL4_InvalidArgument;
142         current_syscall_error.invalidArgumentNumber = 1;
143         return EXCEPTION_SYSCALL_ERROR;
144     }
145 
146     if (!isIOPortRangeFree(first_port, last_port)) {
147         userError("IOPortControl: Some ports in range already in use.");
148         current_syscall_error.type = seL4_RevokeFirst;
149         return EXCEPTION_SYSCALL_ERROR;
150     }
151 
152     lu_ret = lookupTargetSlot(cnodeCap, index, depth);
153     if (lu_ret.status != EXCEPTION_NONE) {
154         userError("Target slot for new IO Port cap invalid: cap %lu.", getExtraCPtr(buffer, 0));
155         return lu_ret.status;
156     }
157     destSlot = lu_ret.slot;
158 
159     status = ensureEmptySlot(destSlot);
160     if (status != EXCEPTION_NONE) {
161         userError("Target slot for new IO Port cap not empty: cap %lu.", getExtraCPtr(buffer, 0));
162         return status;
163     }
164 
165     setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart);
166     return invokeX86PortControl(first_port, last_port, destSlot, slot);
167 }
168 
invokeX86PortIn(word_t invLabel,uint16_t port,bool_t call)169 static exception_t invokeX86PortIn(word_t invLabel, uint16_t port, bool_t call)
170 {
171     uint32_t res;
172     word_t len;
173 
174     switch (invLabel) {
175     case X86IOPortIn8:
176         res = in8(port);
177         break;
178     case X86IOPortIn16:
179         res = in16(port);
180         break;
181     case X86IOPortIn32:
182         res = in32(port);
183         break;
184     }
185 
186     if (call) {
187         setRegister(NODE_STATE(ksCurThread), badgeRegister, 0);
188 
189         if (n_msgRegisters < 1) {
190             word_t *ipcBuffer;
191             ipcBuffer = lookupIPCBuffer(true, NODE_STATE(ksCurThread));
192             if (ipcBuffer != NULL) {
193                 ipcBuffer[1] = res;
194                 len = 1;
195             } else {
196                 len = 0;
197             }
198         } else {
199             setRegister(NODE_STATE(ksCurThread), msgRegisters[0], res);
200             len = 1;
201         }
202 
203         setRegister(NODE_STATE(ksCurThread), msgInfoRegister,
204                     wordFromMessageInfo(seL4_MessageInfo_new(0, 0, 0, len)));
205     }
206     // Prevent handleInvocation from attempting to complete the 'call' with an empty
207     // message (via replyFromKernel_success_empty) by forcing the thread state to
208     // be running. This prevents our stored message we just created from being
209     // overwritten.
210     setThreadState(NODE_STATE(ksCurThread), ThreadState_Running);
211 
212     return EXCEPTION_NONE;
213 }
214 
invokeX86PortOut(word_t invLabel,uint16_t port,uint32_t data)215 static exception_t invokeX86PortOut(word_t invLabel, uint16_t port, uint32_t data)
216 {
217     switch (invLabel) {
218     case X86IOPortOut8:
219         out8(port, data);
220         break;
221     case X86IOPortOut16:
222         out16(port, data);
223         break;
224     case X86IOPortOut32:
225         out32(port, data);
226         break;
227     }
228 
229     return EXCEPTION_NONE;
230 }
231 
decodeX86PortInvocation(word_t invLabel,word_t length,cptr_t cptr,cte_t * slot,cap_t cap,bool_t call,word_t * buffer)232 exception_t decodeX86PortInvocation(
233     word_t invLabel,
234     word_t length,
235     cptr_t cptr,
236     cte_t *slot,
237     cap_t cap,
238     bool_t call,
239     word_t *buffer
240 )
241 {
242     exception_t ret;
243 
244     if (invLabel == X86IOPortIn8 || invLabel == X86IOPortIn16 || invLabel == X86IOPortIn32) {
245         if (length < 1) {
246             userError("IOPort: Truncated message.");
247             current_syscall_error.type = seL4_TruncatedMessage;
248             return EXCEPTION_SYSCALL_ERROR;
249         }
250         /* Get the port the user is trying to read from. */
251         uint16_t port = getSyscallArg(0, buffer) & 0xffff;
252         switch (invLabel) {
253         case X86IOPortIn8:
254             ret = ensurePortOperationAllowed(cap, port, 1);
255             break;
256         case X86IOPortIn16:
257             ret = ensurePortOperationAllowed(cap, port, 2);
258             break;
259         case X86IOPortIn32:
260             ret = ensurePortOperationAllowed(cap, port, 4);
261             break;
262         }
263         if (ret != EXCEPTION_NONE) {
264             return ret;
265         }
266         setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart);
267         return invokeX86PortIn(invLabel, port, call);
268     } else if (invLabel == X86IOPortOut8 || invLabel == X86IOPortOut16 || invLabel == X86IOPortOut32) {
269         /* Ensure the incoming message is long enough for the write. */
270         if (length < 2) {
271             userError("IOPort Out: Truncated message.");
272             current_syscall_error.type = seL4_TruncatedMessage;
273             return EXCEPTION_SYSCALL_ERROR;
274         }
275         /* Get the port the user is trying to write to. */
276         uint16_t port = getSyscallArg(0, buffer) & 0xffff;
277         seL4_Word raw_data = getSyscallArg(1, buffer);
278         /* We construct the value for data from raw_data based on the actual size of the port
279            operation. This ensures that there is no 'random' user data left over in the value
280            passed to invokeX86PortOut. Whilst invokeX86PortOut will ignore any extra data and
281            cast down to the correct word size removing the extra here is currently relied upon
282            for verification */
283         uint32_t data;
284 
285         switch (invLabel) {
286         case X86IOPortOut8:
287             ret = ensurePortOperationAllowed(cap, port, 1);
288             data = raw_data & 0xff;
289             break;
290         case X86IOPortOut16:
291             ret = ensurePortOperationAllowed(cap, port, 2);
292             data = raw_data & 0xffff;
293             break;
294         case X86IOPortOut32:
295             ret = ensurePortOperationAllowed(cap, port, 4);
296             data = raw_data & 0xffffffff;
297             break;
298         }
299         if (ret != EXCEPTION_NONE) {
300             return ret;
301         }
302         setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart);
303         return invokeX86PortOut(invLabel, port, data);
304     } else {
305         userError("IOPort: Unknown operation.");
306         current_syscall_error.type = seL4_IllegalOperation;
307         return EXCEPTION_SYSCALL_ERROR;
308     }
309 }
310 
setIOPortMask(void * ioport_bitmap,uint16_t low,uint16_t high,bool_t set)311 void setIOPortMask(void *ioport_bitmap, uint16_t low, uint16_t high, bool_t set)
312 {
313     //get an aliasing pointer
314     word_t_may_alias *bitmap = ioport_bitmap;
315 
316     int low_word = low >> wordRadix;
317     int high_word = high >> wordRadix;
318     int low_index = low & MASK(wordRadix);
319     int high_index = high & MASK(wordRadix);
320 
321     // see if we are just manipulating bits inside a single word. handling this
322     // specially makes reasoning easier
323     if (low_word == high_word) {
324         apply_pattern(bitmap + low_word, make_pattern(low_index, high_index + 1), set);
325     } else {
326         // operate on the potentially partial first word
327         apply_pattern(bitmap + low_word, make_pattern(low_index, CONFIG_WORD_SIZE), set);
328         low_word++;
329         // iterate over the whole words
330         while (low_word < high_word) {
331             apply_pattern(bitmap + low_word, ~(word_t)0, set);
332             low_word++;
333         }
334         // apply to any remaining bits
335         apply_pattern(bitmap + low_word, make_pattern(0, high_index + 1), set);
336     }
337 }
338