blob: 7fc8d3af1922971f43822dc2fe7d01b5c4cb2c99 [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
146 if (cuart->rx_threshold == 1) {
147 /* bypass ringbuffer and report byte directly */
148 card_uart_notification(cuart, CUART_E_RX_SINGLE, &buf[i]);
149 } else {
150 /* go via ringbuffer and notify only after threshold */
151 ringbuffer_put(&cuart->u.tty.rx_ringbuf, buf[i]);
152 if (ringbuffer_num(&cuart->u.tty.rx_ringbuf) >= cuart->rx_threshold)
153 card_uart_notification(cuart, CUART_E_RX_COMPLETE, NULL);
154 }
155 }
156 }
157 if (what & OSMO_FD_WRITE) {
158 unsigned int to_tx;
159 OSMO_ASSERT(cuart->u.tty.tx_buf_len > cuart->u.tty.tx_index);
160 /* push as many pending transmit bytes as possible */
161 to_tx = cuart->u.tty.tx_buf_len - cuart->u.tty.tx_index;
162 rc = write(ofd->fd, cuart->u.tty.tx_buf + cuart->u.tty.tx_index, to_tx);
163 OSMO_ASSERT(rc > 0);
164 cuart->u.tty.tx_index += rc;
165
166 /* if no more bytes to transmit, disable OSMO_FD_WRITE */
167 if (cuart->u.tty.tx_index >= cuart->u.tty.tx_buf_len) {
168 ofd->when &= ~BSC_FD_WRITE;
Harald Welte18ec3222019-09-28 21:41:59 +0200169#ifndef CREAD_ACTUALLY_WORKS
170 /* don't immediately notify user; first wait for characters to be received */
171#else
Harald Weltea40c8e52019-09-27 19:22:34 +0200172 /* ensure everything is written (tx queue/fifo drained) */
173 tcdrain(cuart->u.tty.ofd.fd);
Harald Weltea40c8e52019-09-27 19:22:34 +0200174 cuart->tx_busy = false;
175 /* notify */
176 card_uart_notification(cuart, CUART_E_TX_COMPLETE, (void *)cuart->u.tty.tx_buf);
Harald Welte18ec3222019-09-28 21:41:59 +0200177#endif
Harald Weltea40c8e52019-09-27 19:22:34 +0200178 }
179 }
180 return 0;
181}
182
183static int tty_uart_open(struct card_uart *cuart, const char *device_name)
184{
185 int rc;
186
187 rc = ringbuffer_init(&cuart->u.tty.rx_ringbuf, cuart->u.tty.rx_buf, sizeof(cuart->u.tty.rx_buf));
188 if (rc < 0)
189 return rc;
190
191 cuart->u.tty.ofd.fd = -1;
192 rc = osmo_serial_init(device_name, B9600);
193 if (rc < 0)
194 return rc;
195
196 osmo_fd_setup(&cuart->u.tty.ofd, rc, BSC_FD_READ, tty_uart_fd_cb, cuart, 0);
197 cuart->u.tty.baudrate = B9600;
198
199 rc = _init_uart(cuart->u.tty.ofd.fd);
200 if (rc < 0) {
201 tty_uart_close(cuart);
202 return rc;
203 }
204
205 _flush(cuart->u.tty.ofd.fd);
206
207 osmo_fd_register(&cuart->u.tty.ofd);
208
209 return 0;
210}
211
212static int tty_uart_close(struct card_uart *cuart)
213{
214 OSMO_ASSERT(cuart->driver == &tty_uart_driver);
215 if (cuart->u.tty.ofd.fd != -1) {
216 osmo_fd_unregister(&cuart->u.tty.ofd);
217 close(cuart->u.tty.ofd.fd);
218 cuart->u.tty.ofd.fd = -1;
219 }
220 return 0;
221}
222
223static int tty_uart_async_tx(struct card_uart *cuart, const uint8_t *data, size_t len, bool rx_after)
224{
225 OSMO_ASSERT(cuart->driver == &tty_uart_driver);
226
227 cuart->u.tty.tx_buf = data;
228 cuart->u.tty.tx_buf_len = len;
Harald Welte18ec3222019-09-28 21:41:59 +0200229 cuart->u.tty.tx_index = 0;
230 cuart->u.tty.rx_count_during_tx = 0;
Harald Weltea40c8e52019-09-27 19:22:34 +0200231 cuart->tx_busy = true;
232 cuart->u.tty.ofd.when |= OSMO_FD_WRITE;
233
234 return 0;
235}
236
237static int tty_uart_async_rx(struct card_uart *cuart, uint8_t *data, size_t len)
238{
239 int rc, i;
240 OSMO_ASSERT(cuart->driver == &tty_uart_driver);
241
242 /* FIXME: actually do this asynchronously */
243 for (i = 0; i < len; i++) {
244 rc = ringbuffer_get(&cuart->u.tty.rx_ringbuf, &data[i]);
245 if (rc < 0)
246 return i;
247 }
248
249 return i;
250}
251
252static int tty_uart_ctrl(struct card_uart *cuart, enum card_uart_ctl ctl, bool enable)
253{
254 struct termios tio;
255 int rc;
256
257 switch (ctl) {
258 case CUART_CTL_RX:
259 rc = tcgetattr(cuart->u.tty.ofd.fd, &tio);
260 if (rc < 0) {
261 perror("tcgetattr()");
262 return -EIO;
263 }
264 /* We do our best here, but lots of [USB] serial drivers seem to ignore
265 * CREAD, see https://bugzilla.kernel.org/show_bug.cgi?id=205033 */
266 if (enable)
267 tio.c_cflag |= CREAD;
268 else
269 tio.c_cflag &= ~CREAD;
270 rc = tcsetattr(cuart->u.tty.ofd.fd, TCSANOW, &tio);
271 if (rc < 0) {
272 perror("tcsetattr()");
273 return -EIO;
274 }
275 break;
276 case CUART_CTL_RST:
277 _set_rts(cuart->u.tty.ofd.fd, enable);
278 if (enable)
279 _flush(cuart->u.tty.ofd.fd);
280 break;
281 case CUART_CTL_POWER:
282 case CUART_CTL_CLOCK:
283 default:
284 return -EINVAL;
285 }
286 return 0;
287}
288
289static const struct card_uart_ops tty_uart_ops = {
290 .open = tty_uart_open,
291 .close = tty_uart_close,
292 .async_tx = tty_uart_async_tx,
293 .async_rx = tty_uart_async_rx,
294 .ctrl = tty_uart_ctrl,
295};
296
297static struct card_uart_driver tty_uart_driver = {
298 .name = "tty",
299 .ops = &tty_uart_ops,
300};
301
302static __attribute__((constructor)) void on_dso_load_cuart_tty(void)
303{
304 card_uart_driver_register(&tty_uart_driver);
305}