source: mainline/kernel/genarch/src/drivers/ns16550/ns16550.c@ b83c5e4

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since b83c5e4 was 98a935e, checked in by jxsvoboda <5887334+jxsvoboda@…>, 4 years ago

Configure NS16550 transmission format settings on initialization on PCs.

Currently, the NS116550 serial line controller is left with its
settings as it was left by the boot firmware and/or bootloader.
On my computer, this was an invalid configuration, and it left me
with a really slow booting system, since each output character
had to go through the full timeout loop in ns16550_sendb().

This patch adds the necessary bit and register descriptions to configure
the baud rate and transmission settings, as well as configuring them on
post-SMP initialization on ia32 and amd64, currently with values matching
the ns8250 userspace character device driver (38400 baud, 8-bit words,
2 stop bits, no parity).

This could perhaps be changed to be adjustable with a kernel command-line
argument, or through the configuration system.

This change does not affect emulators, since those largely ignore these
settings.

  • Property mode set to 100644
File size: 7.0 KB
Line 
1/*
2 * Copyright (c) 2009 Jakub Jermar
3 * Copyright (c) 2018 CZ.NIC, z.s.p.o.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @addtogroup kernel_genarch
31 * @{
32 */
33/**
34 * @file
35 * @brief NS 16550 serial controller driver.
36 */
37
38#include <assert.h>
39#include <genarch/drivers/ns16550/ns16550.h>
40#include <ddi/irq.h>
41#include <ddi/ddi.h>
42#include <arch/asm.h>
43#include <console/chardev.h>
44#include <stdlib.h>
45#include <align.h>
46#include <str.h>
47
48#define LSR_DATA_READY 0x01
49#define LSR_TH_READY 0x20
50
51#define RETRY_CNT 100000
52
53#define NOTHING \
54 do { \
55 } while(0)
56
57#define WHILE_CNT_AND_COND_DO(cnt, expr, statement) \
58 do { \
59 for (volatile unsigned i = 0; i < (cnt); i++) \
60 if ((expr)) \
61 statement; \
62 else \
63 break; \
64 } while (0)
65
66static uint8_t ns16550_reg_read(ns16550_instance_t *inst, ns16550_reg_t reg)
67{
68 return pio_read_8(&inst->ns16550[reg << inst->reg_shift]);
69}
70
71static void ns16550_reg_write(ns16550_instance_t *inst, ns16550_reg_t reg,
72 uint8_t val)
73{
74 pio_write_8(&inst->ns16550[reg << inst->reg_shift], val);
75}
76
77static irq_ownership_t ns16550_claim(irq_t *irq)
78{
79 ns16550_instance_t *instance = irq->instance;
80
81 if (ns16550_reg_read(instance, NS16550_REG_LSR) & LSR_DATA_READY)
82 return IRQ_ACCEPT;
83 else
84 return IRQ_DECLINE;
85}
86
87static void ns16550_irq_handler(irq_t *irq)
88{
89 ns16550_instance_t *instance = irq->instance;
90
91 while (ns16550_reg_read(instance, NS16550_REG_LSR) & LSR_DATA_READY) {
92 uint8_t data = ns16550_reg_read(instance, NS16550_REG_RBR);
93 indev_push_character(instance->input, data);
94 }
95}
96
97/** Clear input buffer. */
98static void ns16550_clear_buffer(ns16550_instance_t *instance)
99{
100 WHILE_CNT_AND_COND_DO(RETRY_CNT,
101 ns16550_reg_read(instance, NS16550_REG_LSR) & LSR_DATA_READY,
102 (void) ns16550_reg_read(instance, NS16550_REG_RBR));
103}
104
105static void ns16550_sendb(ns16550_instance_t *instance, uint8_t byte)
106{
107 WHILE_CNT_AND_COND_DO(RETRY_CNT,
108 !(ns16550_reg_read(instance, NS16550_REG_LSR) & LSR_TH_READY),
109 NOTHING);
110
111 ns16550_reg_write(instance, NS16550_REG_THR, byte);
112}
113
114static void ns16550_putuchar(outdev_t *dev, char32_t ch)
115{
116 ns16550_instance_t *instance = (ns16550_instance_t *) dev->data;
117
118 if ((!instance->parea.mapped) || (console_override)) {
119 if (ch == '\n')
120 ns16550_sendb(instance, '\r');
121
122 if (ascii_check(ch))
123 ns16550_sendb(instance, (uint8_t) ch);
124 else
125 ns16550_sendb(instance, U_SPECIAL);
126 }
127}
128
129static outdev_operations_t ns16550_ops = {
130 .write = ns16550_putuchar,
131 .redraw = NULL
132};
133
134/** Configure ns16550 transmission format.
135 *
136 * @param instance NS 16550 driver instance.
137 * @param baud_rate Transmission speed in bits per second, also known as baud,
138 * maximum value is 115200.
139 * @param lcr_format Line Control Register configuration bits, as defined by
140 * the @c LCR_ macros. These configure the word width,
141 * parity type, and stop bit count.
142 */
143void ns16550_format_set(ns16550_instance_t *instance,
144 unsigned baud_rate, uint8_t lcr_format)
145{
146 uint16_t divisor;
147
148 divisor = (uint16_t)(NS156440_CLOCK / baud_rate);
149 if (divisor == 0)
150 divisor = 1; /* Avoid division by zero. */
151
152 ns16550_reg_write(instance, NS16550_REG_LCR, LCR_DLAB);
153 ns16550_reg_write(instance, NS16550_REG_DLL, divisor & 0xFF);
154 ns16550_reg_write(instance, NS16550_REG_DLH, (divisor >> 8) & 0xFF);
155 ns16550_reg_write(instance, NS16550_REG_LCR, lcr_format & ~LCR_DLAB);
156}
157
158/** Initialize ns16550.
159 *
160 * @param dev Address of the beginning of the device in I/O space.
161 * @param reg_shift Spacing between individual register addresses, in log2.
162 * The individual register location is calculated as
163 * `base + (register offset << reg_shift)`.
164 * @param inr Interrupt number.
165 * @param cir Clear interrupt function.
166 * @param cir_arg First argument to cir.
167 * @param output Where to store pointer to the output device
168 * or NULL if the caller is not interested in
169 * writing to the serial port.
170 *
171 * @return Keyboard instance or NULL on failure.
172 *
173 */
174ns16550_instance_t *ns16550_init(ioport8_t *dev_phys, unsigned reg_shift,
175 inr_t inr, cir_t cir, void *cir_arg, outdev_t **output)
176{
177 size_t size = 6 * (1U << reg_shift);
178 ioport8_t *dev = pio_map((void *) dev_phys, size);
179
180 ns16550_instance_t *instance = malloc(sizeof(ns16550_instance_t));
181 if (instance) {
182 instance->ns16550 = dev;
183 instance->reg_shift = reg_shift;
184 instance->input = NULL;
185 instance->output = NULL;
186
187 if (output) {
188 instance->output = malloc(sizeof(outdev_t));
189 if (!instance->output) {
190 free(instance);
191 pio_unmap((void *) dev_phys, (void *) dev,
192 size);
193 return NULL;
194 }
195
196 outdev_initialize("ns16550", instance->output,
197 &ns16550_ops);
198 instance->output->data = instance;
199 *output = instance->output;
200 }
201
202 irq_initialize(&instance->irq);
203 instance->irq.inr = inr;
204 instance->irq.claim = ns16550_claim;
205 instance->irq.handler = ns16550_irq_handler;
206 instance->irq.instance = instance;
207 instance->irq.cir = cir;
208 instance->irq.cir_arg = cir_arg;
209
210 ddi_parea_init(&instance->parea);
211 instance->parea.pbase = ALIGN_DOWN((uintptr_t) dev_phys,
212 PAGE_SIZE);
213 instance->parea.frames = ALIGN_UP(size, PAGE_SIZE);
214 instance->parea.unpriv = false;
215 instance->parea.mapped = false;
216 ddi_parea_register(&instance->parea);
217 }
218
219 return instance;
220}
221
222void ns16550_wire(ns16550_instance_t *instance, indev_t *input)
223{
224 assert(instance);
225 assert(input);
226
227 instance->input = input;
228 irq_register(&instance->irq);
229
230 ns16550_clear_buffer(instance);
231
232 /* Enable interrupts */
233 ns16550_reg_write(instance, NS16550_REG_IER, IER_ERBFI);
234 ns16550_reg_write(instance, NS16550_REG_MCR, MCR_OUT2);
235}
236
237/** @}
238 */
Note: See TracBrowser for help on using the repository browser.