blob: 5f635b58bc050ea35fd91b16d5024f3eeaabea7a [file] [log] [blame]
Harald Weltea40c8e52019-09-27 19:22:34 +02001/* Card (ICC) UART driver for simple serial readers attached to tty.
2 * This allows you to use the CCID core in Linux userspace against a serial reader */
3
4#include <unistd.h>
5#include <termios.h>
6#include <errno.h>
7#include <sys/ioctl.h>
8
9#include <osmocom/core/select.h>
10#include <osmocom/core/serial.h>
11#include <osmocom/core/utils.h>
12
13#include "cuart.h"
14#include "utils_ringbuffer.h"
15
16/***********************************************************************
17 * low-level helper routines
18 ***********************************************************************/
19
20static int _init_uart(int fd)
21{
22 struct termios tio;
23 int rc;
24
25 rc = tcgetattr(fd, &tio);
26 if (rc < 0) {
27 perror("tcgetattr()");
28 return -EIO;
29 }
30
31 tio.c_iflag = 0;
32 tio.c_oflag = 0;
33 tio.c_lflag = 0;
34 tio.c_cflag = CREAD | CLOCAL | CSTOPB | PARENB | CS8 | B9600;
35
36 rc = tcsetattr(fd, TCSANOW, &tio);
37 if (rc < 0) {
38 perror("tcsetattr()");
39 return -EIO;
40 }
41
42 return 0;
43}
44
45static void _set_dtr(int fd, bool dtr)
46{
47 int status, rc;
48
49 rc = ioctl(fd, TIOCMGET, &status);
50 OSMO_ASSERT(rc == 0);
51 if (dtr) /* set DTR */
52 status |= TIOCM_DTR;
53 else
54 status &= ~TIOCM_DTR;
55 rc = ioctl(fd, TIOCMSET, &status);
56 OSMO_ASSERT(rc == 0);
57}
58
59static void _set_rts(int fd, bool rts)
60{
61 int status, rc;
62
63 rc = ioctl(fd, TIOCMGET, &status);
64 OSMO_ASSERT(rc == 0);
65 if (rts) /* set RTS */
66 status |= TIOCM_RTS;
67 else
68 status &= ~TIOCM_RTS;
69 rc = ioctl(fd, TIOCMSET, &status);
70 OSMO_ASSERT(rc == 0);
71}
72
73static int read_timeout(int fd, uint8_t *out, size_t len, unsigned long timeout_ms)
74{
75 struct timeval tv = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 };
76 fd_set rd_set;
77 int rc;
78
79 FD_ZERO(&rd_set);
80 FD_SET(fd, &rd_set);
81 rc = select(fd+1, &rd_set, NULL, NULL, &tv);
82 if (rc == 0)
83 return -ETIMEDOUT;
84 else if (rc < 0)
85 return rc;
86
87 return read(fd, out, len);
88}
89
90static int _flush(int fd)
91{
92#if 0
93 uint8_t buf[1];
94 int rc;
95
96 while (1) {
97 rc = read_timeout(fd, buf, sizeof(buf), 10);
98 if (rc == -ETIMEDOUT)
99 return 0;
100 else if (rc < 0)
101 return rc;
102 }
103#else
104 return tcflush(fd, TCIFLUSH);
105#endif
106}
107
108/***********************************************************************
109 * Interface with card_uart (cuart) core
110 ***********************************************************************/
111
112/* forward-declaration */
113static struct card_uart_driver tty_uart_driver;
114static int tty_uart_close(struct card_uart *cuart);
115
116static int tty_uart_fd_cb(struct osmo_fd *ofd, unsigned int what)
117{
118 struct card_uart *cuart = (struct card_uart *) ofd->data;
119 uint8_t buf[256];
120 int rc;
121
122 if (what & OSMO_FD_READ) {
123 int i;
124 /* read any pending bytes and feed them into ring buffer */
125 rc = read(ofd->fd, buf, sizeof(buf));
126 OSMO_ASSERT(rc > 0);
127 for (i = 0; i < rc; i++) {
Harald Welte18ec3222019-09-28 21:41:59 +0200128#ifndef CREAD_ACTUALLY_WORKS
Harald Weltea40c8e52019-09-27 19:22:34 +0200129 /* work-around for https://bugzilla.kernel.org/show_bug.cgi?id=205033 */
Harald Welte18ec3222019-09-28 21:41:59 +0200130 if (cuart->tx_busy) {
131 if (cuart->u.tty.rx_count_during_tx < cuart->u.tty.tx_buf_len) {
132 /* FIXME: compare! */
133 cuart->u.tty.rx_count_during_tx += 1;
134 if (cuart->u.tty.rx_count_during_tx == cuart->u.tty.tx_buf_len) {
135 cuart->tx_busy = false;
136 card_uart_notification(cuart, CUART_E_TX_COMPLETE,
137 (void *)cuart->u.tty.tx_buf);
138 }
139 continue;
140 }
141 }
142#endif
Harald Weltea40c8e52019-09-27 19:22:34 +0200143 if (!cuart->rx_enabled)
144 continue;
145
Harald Welte7b64fc02019-10-09 20:49:35 +0200146 card_uart_wtime_restart(cuart);
147
Harald Weltea40c8e52019-09-27 19:22:34 +0200148 if (cuart->rx_threshold == 1) {
149 /* bypass ringbuffer and report byte directly */
150 card_uart_notification(cuart, CUART_E_RX_SINGLE, &buf[i]);
151 } else {
152 /* go via ringbuffer and notify only after threshold */
153 ringbuffer_put(&cuart->u.tty.rx_ringbuf, buf[i]);
154 if (ringbuffer_num(&cuart->u.tty.rx_ringbuf) >= cuart->rx_threshold)
155 card_uart_notification(cuart, CUART_E_RX_COMPLETE, NULL);
156 }
157 }
158 }
159 if (what & OSMO_FD_WRITE) {
160 unsigned int to_tx;
161 OSMO_ASSERT(cuart->u.tty.tx_buf_len > cuart->u.tty.tx_index);
162 /* push as many pending transmit bytes as possible */
163 to_tx = cuart->u.tty.tx_buf_len - cuart->u.tty.tx_index;
164 rc = write(ofd->fd, cuart->u.tty.tx_buf + cuart->u.tty.tx_index, to_tx);
165 OSMO_ASSERT(rc > 0);
166 cuart->u.tty.tx_index += rc;
167
168 /* if no more bytes to transmit, disable OSMO_FD_WRITE */
169 if (cuart->u.tty.tx_index >= cuart->u.tty.tx_buf_len) {
170 ofd->when &= ~BSC_FD_WRITE;
Harald Welte18ec3222019-09-28 21:41:59 +0200171#ifndef CREAD_ACTUALLY_WORKS
172 /* don't immediately notify user; first wait for characters to be received */
173#else
Harald Weltea40c8e52019-09-27 19:22:34 +0200174 /* ensure everything is written (tx queue/fifo drained) */
175 tcdrain(cuart->u.tty.ofd.fd);
Harald Weltea40c8e52019-09-27 19:22:34 +0200176 cuart->tx_busy = false;
177 /* notify */
178 card_uart_notification(cuart, CUART_E_TX_COMPLETE, (void *)cuart->u.tty.tx_buf);
Harald Welte18ec3222019-09-28 21:41:59 +0200179#endif
Harald Weltea40c8e52019-09-27 19:22:34 +0200180 }
181 }
182 return 0;
183}
184
185static int tty_uart_open(struct card_uart *cuart, const char *device_name)
186{
187 int rc;
188
189 rc = ringbuffer_init(&cuart->u.tty.rx_ringbuf, cuart->u.tty.rx_buf, sizeof(cuart->u.tty.rx_buf));
190 if (rc < 0)
191 return rc;
192
193 cuart->u.tty.ofd.fd = -1;
194 rc = osmo_serial_init(device_name, B9600);
195 if (rc < 0)
196 return rc;
197
198 osmo_fd_setup(&cuart->u.tty.ofd, rc, BSC_FD_READ, tty_uart_fd_cb, cuart, 0);
199 cuart->u.tty.baudrate = B9600;
200
201 rc = _init_uart(cuart->u.tty.ofd.fd);
202 if (rc < 0) {
203 tty_uart_close(cuart);
204 return rc;
205 }
206
207 _flush(cuart->u.tty.ofd.fd);
208
209 osmo_fd_register(&cuart->u.tty.ofd);
210
211 return 0;
212}
213
214static int tty_uart_close(struct card_uart *cuart)
215{
216 OSMO_ASSERT(cuart->driver == &tty_uart_driver);
217 if (cuart->u.tty.ofd.fd != -1) {
218 osmo_fd_unregister(&cuart->u.tty.ofd);
219 close(cuart->u.tty.ofd.fd);
220 cuart->u.tty.ofd.fd = -1;
221 }
222 return 0;
223}
224
Harald Welte02dd9112019-10-01 08:55:29 +0200225static int tty_uart_async_tx(struct card_uart *cuart, const uint8_t *data, size_t len)
Harald Weltea40c8e52019-09-27 19:22:34 +0200226{
227 OSMO_ASSERT(cuart->driver == &tty_uart_driver);
228
229 cuart->u.tty.tx_buf = data;
230 cuart->u.tty.tx_buf_len = len;
Harald Welte18ec3222019-09-28 21:41:59 +0200231 cuart->u.tty.tx_index = 0;
232 cuart->u.tty.rx_count_during_tx = 0;
Harald Weltea40c8e52019-09-27 19:22:34 +0200233 cuart->tx_busy = true;
234 cuart->u.tty.ofd.when |= OSMO_FD_WRITE;
235
236 return 0;
237}
238
239static int tty_uart_async_rx(struct card_uart *cuart, uint8_t *data, size_t len)
240{
241 int rc, i;
242 OSMO_ASSERT(cuart->driver == &tty_uart_driver);
243
244 /* FIXME: actually do this asynchronously */
245 for (i = 0; i < len; i++) {
246 rc = ringbuffer_get(&cuart->u.tty.rx_ringbuf, &data[i]);
247 if (rc < 0)
248 return i;
249 }
250
251 return i;
252}
253
Harald Welte9700d392019-10-09 20:17:28 +0200254static int tty_uart_ctrl(struct card_uart *cuart, enum card_uart_ctl ctl, int arg)
Harald Weltea40c8e52019-09-27 19:22:34 +0200255{
256 struct termios tio;
257 int rc;
258
259 switch (ctl) {
260 case CUART_CTL_RX:
261 rc = tcgetattr(cuart->u.tty.ofd.fd, &tio);
262 if (rc < 0) {
263 perror("tcgetattr()");
264 return -EIO;
265 }
266 /* We do our best here, but lots of [USB] serial drivers seem to ignore
267 * CREAD, see https://bugzilla.kernel.org/show_bug.cgi?id=205033 */
Harald Welte7b64fc02019-10-09 20:49:35 +0200268 if (arg) {
Harald Weltea40c8e52019-09-27 19:22:34 +0200269 tio.c_cflag |= CREAD;
Harald Welte7b64fc02019-10-09 20:49:35 +0200270 card_uart_wtime_restart(cuart);
271 } else
Harald Weltea40c8e52019-09-27 19:22:34 +0200272 tio.c_cflag &= ~CREAD;
273 rc = tcsetattr(cuart->u.tty.ofd.fd, TCSANOW, &tio);
274 if (rc < 0) {
275 perror("tcsetattr()");
276 return -EIO;
277 }
278 break;
279 case CUART_CTL_RST:
Harald Welte9700d392019-10-09 20:17:28 +0200280 _set_rts(cuart->u.tty.ofd.fd, arg ? true : false);
281 if (arg)
Harald Weltea40c8e52019-09-27 19:22:34 +0200282 _flush(cuart->u.tty.ofd.fd);
283 break;
Harald Welte7b64fc02019-10-09 20:49:35 +0200284 case CUART_CTL_WTIME:
285 /* no driver-specific handling of this */
286 break;
Harald Weltea40c8e52019-09-27 19:22:34 +0200287 case CUART_CTL_POWER:
288 case CUART_CTL_CLOCK:
289 default:
290 return -EINVAL;
291 }
292 return 0;
293}
294
295static const struct card_uart_ops tty_uart_ops = {
296 .open = tty_uart_open,
297 .close = tty_uart_close,
298 .async_tx = tty_uart_async_tx,
299 .async_rx = tty_uart_async_rx,
300 .ctrl = tty_uart_ctrl,
301};
302
303static struct card_uart_driver tty_uart_driver = {
304 .name = "tty",
305 .ops = &tty_uart_ops,
306};
307
308static __attribute__((constructor)) void on_dso_load_cuart_tty(void)
309{
310 card_uart_driver_register(&tty_uart_driver);
311}