xref: /kvmtool/hw/serial.c (revision 4e49b05b434a565c3cfde98bd1ec09817115551d)
1 #include "kvm/8250-serial.h"
2 
3 #include "kvm/ioport.h"
4 #include "kvm/util.h"
5 #include "kvm/kvm.h"
6 
7 #include <linux/serial_reg.h>
8 
9 #include <stdbool.h>
10 #include <poll.h>
11 
12 struct serial8250_device {
13 	uint16_t		iobase;
14 	uint8_t			irq;
15 
16 	uint8_t			thr;
17 	uint8_t			dll;
18 	uint8_t			dlm;
19 	uint8_t			iir;
20 	uint8_t			ier;
21 	uint8_t			fcr;
22 	uint8_t			lcr;
23 	uint8_t			mcr;
24 	uint8_t			lsr;
25 	uint8_t			scr;
26 };
27 
28 static struct serial8250_device device = {
29 	.iobase			= 0x3f8,	/* ttyS0 */
30 	.irq			= 4,
31 
32 	.iir			= UART_IIR_NO_INT,
33 	.lsr			= UART_LSR_THRE,
34 };
35 
36 static int read_char(int fd)
37 {
38 	int c;
39 
40 	if (read(fd, &c, 1) < 0)
41 		return -1;
42 
43 	return c;
44 }
45 
46 static bool is_readable(int fd)
47 {
48 	struct pollfd pollfd = (struct pollfd) {
49 		.fd	= fd,
50 		.events	= POLLIN,
51 	};
52 
53 	return poll(&pollfd, 1, 0) > 0;
54 }
55 
56 void serial8250__interrupt(struct kvm *self)
57 {
58 	if (!(device.lsr & UART_LSR_DR) && is_readable(fileno(stdin))) {
59 		int c;
60 
61 		c			= read_char(fileno(stdin));
62 		if (c >= 0) {
63 			device.thr		= c;
64 			device.lsr		|= UART_LSR_DR;
65 		}
66 	}
67 
68 	if (device.ier & UART_IER_THRI || device.lsr & UART_LSR_DR) {
69 		device.iir		&= ~UART_IIR_NO_INT;
70 		kvm__irq_line(self, device.irq, 1);
71 	}
72 }
73 
74 static bool serial8250_out(struct kvm *self, uint16_t port, void *data, int size, uint32_t count)
75 {
76 	uint16_t offset = port - device.iobase;
77 
78 	if (device.lcr & UART_LCR_DLAB) {
79 		switch (offset) {
80 		case UART_DLL:
81 			device.dll		= ioport__read8(data);
82 			break;
83 		case UART_DLM:
84 			device.dlm		= ioport__read8(data);
85 			break;
86 		case UART_FCR:
87 			device.fcr		= ioport__read8(data);
88 			break;
89 		case UART_LCR:
90 			device.lcr		= ioport__read8(data);
91 			break;
92 		default:
93 			return false;
94 		}
95 	} else {
96 		switch (offset) {
97 		case UART_TX: {
98 			char *p = data;
99 			int i;
100 
101 			while (count--) {
102 				for (i = 0; i < size; i++)
103 					fprintf(stdout, "%c", *p++);
104 			}
105 			fflush(stdout);
106 
107 			device.iir		|= UART_IIR_NO_INT;
108 			kvm__irq_line(self, device.irq, 0);
109 
110 			break;
111 		}
112 		case UART_IER:
113 			device.ier		= ioport__read8(data);
114 			break;
115 		case UART_FCR:
116 			device.fcr		= ioport__read8(data);
117 			break;
118 		case UART_LCR:
119 			device.lcr		= ioport__read8(data);
120 			break;
121 		case UART_MCR:
122 			device.mcr		= ioport__read8(data);
123 			break;
124 		case UART_SCR:
125 			device.scr		= ioport__read8(data);
126 			break;
127 		default:
128 			return false;
129 		}
130 	}
131 
132 	return true;
133 }
134 
135 static bool serial8250_in(struct kvm *self, uint16_t port, void *data, int size, uint32_t count)
136 {
137 	uint16_t offset = port - device.iobase;
138 
139 	if (device.lcr & UART_LCR_DLAB)
140 		return false;
141 
142 	switch (offset) {
143 	case UART_TX:
144 		if (device.lsr & UART_LSR_DR) {
145 			device.lsr		&= ~UART_LSR_DR;
146 			ioport__write8(data, device.thr);
147 
148 			device.iir		|= UART_IIR_NO_INT;
149 			kvm__irq_line(self, device.irq, 0);
150 		}
151 		break;
152 	case UART_IER:
153 		ioport__write8(data, device.ier);
154 		break;
155 	case UART_IIR:
156 		ioport__write8(data, device.iir);
157 		break;
158 	case UART_LCR:
159 		ioport__write8(data, device.lcr);
160 		break;
161 	case UART_MCR:
162 		ioport__write8(data, device.mcr);
163 		break;
164 	case UART_LSR:
165 		ioport__write8(data, device.lsr);
166 		break;
167 	case UART_MSR:
168 		ioport__write8(data, UART_MSR_CTS);
169 		break;
170 	case UART_SCR:
171 		ioport__write8(data, device.scr);
172 		break;
173 	default:
174 		return false;
175 	}
176 
177 	return true;
178 }
179 
180 static struct ioport_operations serial8250_ops = {
181 	.io_in		= serial8250_in,
182 	.io_out		= serial8250_out,
183 };
184 
185 void serial8250__init(void)
186 {
187 	ioport__register(device.iobase, &serial8250_ops, 8);
188 }
189