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