1/**
2 * \file
3 */
4
5/*
6 * Copyright (c) 2009, ETH Zurich.
7 * All rights reserved.
8 *
9 * This file is distributed under the terms in the attached LICENSE file.
10 * If you do not find this file, copies can be found by writing to:
11 * ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
12 */
13
14/* This represents a simple implementation of a 16550 uart controller. It
15 * neglects all timing issues, tranfer rate and error conditions. It declines to
16 * know about FIFOs (which make it to a 16450 controller in fact). In addition
17 * we are almost agnostic to the MCR and MSR register. Everything is
18 * forwarded directly to the terminal. */
19
20#include "vmkitmon.h"
21#include "pc16550d.h"
22#include <stdlib.h>
23#include <barrelfish/terminal.h>
24
25#define FIFO_POS(x)     ((x) & PC16550D_FIFO_MASK)
26
27struct pc16550d *
28pc16550d_new (uint16_t base_port, uint8_t irq, struct lpc *lpc)
29{
30    assert(lpc != NULL);
31
32    struct pc16550d *u = calloc(1, sizeof(struct pc16550d));
33
34    pc16550d_mem_initialize(&u->dev, (mackerel_addr_t)u->regs);
35
36    u->base_port = base_port;
37    u->irq = irq;
38    u->lpc = lpc;
39
40    // initialize the LSR register to have an empty transmit buffer
41    pc16550d_mem_lsr_thre_wrf(&u->dev, 1);
42    pc16550d_mem_lsr_temt_wrf(&u->dev, 1);
43
44    return u;
45}
46
47/**
48 * \brief Check all necessary conditions to raise a certain irq and does so.
49 *
50 * This method must be called at the end of every function returning back to
51 * the guest otherwise some interrupts may get lost.
52 */
53static inline void
54process_interrupt_conditions (struct pc16550d *u)
55{
56    // cycle through the sources for interrupts in the order of their priority
57    // check whethter they are enabled and pending and raise them accordingly
58
59    // data overrun
60    if (pc16550d_mem_ier_rd(&u->dev).elsi &&
61        pc16550d_mem_lsr_rd(&u->dev).oe) {
62
63        pc16550d_mem_iir_iid_wrf(&u->dev, pc16550d_mem_irq_rls);
64    }
65    // receiver data available
66    else if (pc16550d_mem_ier_rd(&u->dev).erbfi &&
67             pc16550d_mem_lsr_rd(&u->dev).dr) {
68        pc16550d_mem_iir_iid_wrf(&u->dev, pc16550d_mem_irq_rda);
69    }
70    // TODO: Here we need the timeout interrupt
71    // transmitter holding register emtpy
72    else if (pc16550d_mem_ier_rd(&u->dev).etbei &&
73             pc16550d_mem_lsr_rd(&u->dev).thre) {
74        pc16550d_mem_iir_iid_wrf(&u->dev, pc16550d_mem_irq_thre);
75    }
76    // no interrupt condition available
77    else {
78        pc16550d_mem_iir_iid_wrf(&u->dev, pc16550d_mem_irq_none);
79    }
80
81    // if there is an intr pending then inform the PIC accordingly
82    if (pc16550d_mem_iir_rd(&u->dev).iid != pc16550d_mem_irq_none) {
83        lpc_pic_assert_irq(u->lpc, u->irq);
84    }
85}
86
87/* this method clears the current pending interrupt
88 * to eventually raise the next interrupt */
89static inline void
90clear_interrupt (struct pc16550d *u)
91{
92    pc16550d_mem_iir_iid_wrf(&u->dev, pc16550d_mem_irq_none);
93}
94
95static inline void
96process_lsr_change (struct pc16550d *u)
97{
98    // for now we only process the interrupts
99}
100
101static inline void
102process_thr_change (struct pc16550d *u)
103{
104    // put out the character
105    char chr = pc16550d_mem_thr_rd_raw(&u->dev);
106    int r = terminal_write(&chr, 1);
107    assert(r == 1);
108
109    // writing the THR reg resets the THRE interrupt pending state
110    if (pc16550d_mem_iir_rd(&u->dev).iid == pc16550d_mem_irq_thre) {
111        clear_interrupt(u);
112    }
113
114    // we always simulate the transmitter register to be empty (an infinitly
115    // fast serial line)
116    pc16550d_mem_lsr_thre_wrf(&u->dev, 1);
117    pc16550d_mem_lsr_temt_wrf(&u->dev, 1);
118
119    process_lsr_change(u);
120}
121
122static inline void
123process_fcr_change (struct pc16550d *u)
124{
125    if (pc16550d_mem_fcr_rd(&u->dev).rfifo_reset) {
126        u->fifo_in_produced = u->fifo_in_consumed = 0;
127        pc16550d_mem_lsr_dr_wrf(&u->dev, 0);
128        pc16550d_mem_lsr_oe_wrf(&u->dev, 0);
129        process_lsr_change(u);
130    }
131}
132
133int
134pc16550d_handle_pio_read (struct pc16550d *u, uint16_t port,
135                          enum opsize size, uint32_t *val)
136{
137    assert(u != NULL);
138    assert(port >= u->base_port);
139
140    port -= u->base_port;
141
142    switch (port) {
143    case 0:
144        if (pc16550d_mem_lcr_rd(&u->dev).dlab) {
145            // DL(L) read
146            switch (size) {
147            case OPSIZE_8:
148                *val = pc16550d_mem_dll_rd_raw(&u->dev);
149                break;
150            default:
151                *val = pc16550d_mem_dl_rd_raw(&u->dev);
152                break;
153            }
154        } else {
155            // RBR read
156            if (FIFO_POS(u->fifo_in_produced) ==
157                FIFO_POS(u->fifo_in_consumed)) {
158                *val = 0;
159            } else {
160                *val = u->fifo_in[FIFO_POS(u->fifo_in_consumed)];
161                u->fifo_in_consumed++;
162            }
163            // reset the data ready bit
164            if (FIFO_POS(u->fifo_in_produced) ==
165                FIFO_POS(u->fifo_in_consumed)) {
166                pc16550d_mem_lsr_dr_wrf(&u->dev, 0);
167                process_lsr_change(u);
168            }
169        }
170        break;
171    case 1:
172        if (pc16550d_mem_lcr_rd(&u->dev).dlab) {
173            // DLM read
174            *val = pc16550d_mem_dlm_rd_raw(&u->dev);
175        } else {
176            // IER READ
177            *val = pc16550d_mem_ier_rd_raw(&u->dev);
178        }
179        break;
180    case 2:
181        // IIR read
182        *val = pc16550d_mem_iir_rd_raw(&u->dev);
183        // reading the IIR reg resets the THRE interrupt pending state
184        // (after the read)
185        if (pc16550d_mem_iir_rd(&u->dev).iid == pc16550d_mem_irq_thre) {
186            clear_interrupt(u);
187        }
188        break;
189    case 3:
190        // LCR read
191        *val = pc16550d_mem_lcr_rd_raw(&u->dev);
192        break;
193    case 4:
194        // MCR read
195        *val = pc16550d_mem_mcr_rd_raw(&u->dev);
196        break;
197    case 5:
198        // LSR read
199        *val = pc16550d_mem_lsr_rd_raw(&u->dev);
200        // reset possible error conditions
201        pc16550d_mem_lsr_oe_wrf(&u->dev, 0);
202        process_lsr_change(u);
203        break;
204    case 6:
205        // MSR read
206        *val = pc16550d_mem_msr_rd_raw(&u->dev);
207        break;
208    case 7:
209        // SCR read
210        *val = pc16550d_mem_scr_rd_raw(&u->dev);
211        break;
212    default:
213        assert(!"pc16550d: read access to unknown port");
214        break;
215    }
216
217    // check whether interrupts shall be raised
218    process_interrupt_conditions(u);
219
220    return HANDLER_ERR_OK;
221}
222
223int
224pc16550d_handle_pio_write (struct pc16550d *u, uint16_t port,
225                           enum opsize size, uint32_t val)
226{
227    assert(u != NULL);
228    assert(port >= u->base_port);
229
230    port -= u->base_port;
231
232    /* all registers which do no processing after they are written and do not
233     * abort the application just ignore their content and store it in case it
234     * is read by the user */
235
236    switch (port) {
237    case 0:
238        if (pc16550d_mem_lcr_rd(&u->dev).dlab) {
239            // DL(L) write
240            switch (size) {
241            case OPSIZE_8:
242                pc16550d_mem_dll_wr_raw(&u->dev, val);
243                break;
244            default:
245                pc16550d_mem_dl_wr_raw(&u->dev, val);
246            }
247            pc16550d_mem_thr_wr_raw(&u->dev, val);
248        } else {
249            // THR write
250            pc16550d_mem_thr_wr_raw(&u->dev, val);
251            process_thr_change(u);
252        }
253        break;
254    case 1:
255        if (pc16550d_mem_lcr_rd(&u->dev).dlab) {
256            // DLM write
257            pc16550d_mem_dlm_wr_raw(&u->dev, val);
258        } else {
259            // IER write
260            pc16550d_mem_ier_wr_raw(&u->dev, val);
261            /* this register only holds info necessary for other operations
262             * therefore we do not need to do any processing */
263        }
264        break;
265    case 2:
266        // FCR write
267        pc16550d_mem_fcr_wr_raw(&u->dev, val);
268        process_fcr_change(u);
269        break;
270    case 3:
271        // LCR write
272        pc16550d_mem_lcr_wr_raw(&u->dev, val);
273        break;
274    case 4:
275        // MCR write
276        pc16550d_mem_mcr_wr_raw(&u->dev, val);
277        break;
278    case 5:
279        // LSR write
280        assert(!"LSR should not be written to");
281        pc16550d_mem_lsr_wr_raw(&u->dev, val);
282        process_lsr_change(u);
283        break;
284    case 6:
285        assert(!"MSR should not be written to");
286        // MSR write
287        pc16550d_mem_msr_wr_raw(&u->dev, val);
288        break;
289    case 7:
290        // SCR write
291        pc16550d_mem_scr_wr_raw(&u->dev, val);
292        break;
293    default:
294        assert(!"pc16550d: write access to unknown port");
295        break;
296    }
297
298    // check whether interrupts shall be raised
299    process_interrupt_conditions(u);
300
301    return HANDLER_ERR_OK;
302}
303
304#if 0
305static void
306input_handler (void *user_data, const char *str, size_t size)
307{
308    assert(user_data != NULL);
309
310    struct pc16550d *u = user_data;
311
312    if (size == 0) {
313        return;
314    }
315
316    // copy the string into our fifo
317    for (int i = 0; i < size; i++) {
318        u->fifo_in[FIFO_POS(u->fifo_in_produced)] = str[i];
319        u->fifo_in_produced++;
320
321        // check for overrun
322        if (FIFO_POS(u->fifo_in_produced) ==
323            FIFO_POS(u->fifo_in_consumed)) {
324            u->fifo_in_produced = u->fifo_in_consumed = 0;
325            pc16550d_mem_lsr_oe_wrf(&u->dev, 1);
326        }
327    }
328
329    // tell the user that data is available
330    pc16550d_mem_lsr_dr_wrf(&u->dev, 1);
331
332    // raise interrupts when necessary
333    process_interrupt_conditions(u);
334}
335#endif
336
337void
338pc16550d_attach_to_console (struct pc16550d *u)
339{
340    assert(u != NULL);
341
342    assert(!"NYI");
343#if 0
344    errval_t err;
345    err = terminal_register_input_handler(input_handler, u);
346    assert(err_is_ok(err));
347#endif
348}
349