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