blob: 6bd2b52d24cc8911aaf9f6af943b4658df3fcbf6 [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++) {
128 /* work-around for https://bugzilla.kernel.org/show_bug.cgi?id=205033 */
129 if (!cuart->rx_enabled)
130 continue;
131
132 if (cuart->rx_threshold == 1) {
133 /* bypass ringbuffer and report byte directly */
134 card_uart_notification(cuart, CUART_E_RX_SINGLE, &buf[i]);
135 } else {
136 /* go via ringbuffer and notify only after threshold */
137 ringbuffer_put(&cuart->u.tty.rx_ringbuf, buf[i]);
138 if (ringbuffer_num(&cuart->u.tty.rx_ringbuf) >= cuart->rx_threshold)
139 card_uart_notification(cuart, CUART_E_RX_COMPLETE, NULL);
140 }
141 }
142 }
143 if (what & OSMO_FD_WRITE) {
144 unsigned int to_tx;
145 OSMO_ASSERT(cuart->u.tty.tx_buf_len > cuart->u.tty.tx_index);
146 /* push as many pending transmit bytes as possible */
147 to_tx = cuart->u.tty.tx_buf_len - cuart->u.tty.tx_index;
148 rc = write(ofd->fd, cuart->u.tty.tx_buf + cuart->u.tty.tx_index, to_tx);
149 OSMO_ASSERT(rc > 0);
150 cuart->u.tty.tx_index += rc;
151
152 /* if no more bytes to transmit, disable OSMO_FD_WRITE */
153 if (cuart->u.tty.tx_index >= cuart->u.tty.tx_buf_len) {
154 ofd->when &= ~BSC_FD_WRITE;
155 /* ensure everything is written (tx queue/fifo drained) */
156 tcdrain(cuart->u.tty.ofd.fd);
157 osmo_select_main(true);
158 cuart->tx_busy = false;
159 /* notify */
160 card_uart_notification(cuart, CUART_E_TX_COMPLETE, (void *)cuart->u.tty.tx_buf);
161 }
162 }
163 return 0;
164}
165
166static int tty_uart_open(struct card_uart *cuart, const char *device_name)
167{
168 int rc;
169
170 rc = ringbuffer_init(&cuart->u.tty.rx_ringbuf, cuart->u.tty.rx_buf, sizeof(cuart->u.tty.rx_buf));
171 if (rc < 0)
172 return rc;
173
174 cuart->u.tty.ofd.fd = -1;
175 rc = osmo_serial_init(device_name, B9600);
176 if (rc < 0)
177 return rc;
178
179 osmo_fd_setup(&cuart->u.tty.ofd, rc, BSC_FD_READ, tty_uart_fd_cb, cuart, 0);
180 cuart->u.tty.baudrate = B9600;
181
182 rc = _init_uart(cuart->u.tty.ofd.fd);
183 if (rc < 0) {
184 tty_uart_close(cuart);
185 return rc;
186 }
187
188 _flush(cuart->u.tty.ofd.fd);
189
190 osmo_fd_register(&cuart->u.tty.ofd);
191
192 return 0;
193}
194
195static int tty_uart_close(struct card_uart *cuart)
196{
197 OSMO_ASSERT(cuart->driver == &tty_uart_driver);
198 if (cuart->u.tty.ofd.fd != -1) {
199 osmo_fd_unregister(&cuart->u.tty.ofd);
200 close(cuart->u.tty.ofd.fd);
201 cuart->u.tty.ofd.fd = -1;
202 }
203 return 0;
204}
205
206static int tty_uart_async_tx(struct card_uart *cuart, const uint8_t *data, size_t len, bool rx_after)
207{
208 OSMO_ASSERT(cuart->driver == &tty_uart_driver);
209
210 cuart->u.tty.tx_buf = data;
211 cuart->u.tty.tx_buf_len = len;
212 cuart->u.tty.tx_buf_len = len;
213 cuart->tx_busy = true;
214 cuart->u.tty.ofd.when |= OSMO_FD_WRITE;
215
216 return 0;
217}
218
219static int tty_uart_async_rx(struct card_uart *cuart, uint8_t *data, size_t len)
220{
221 int rc, i;
222 OSMO_ASSERT(cuart->driver == &tty_uart_driver);
223
224 /* FIXME: actually do this asynchronously */
225 for (i = 0; i < len; i++) {
226 rc = ringbuffer_get(&cuart->u.tty.rx_ringbuf, &data[i]);
227 if (rc < 0)
228 return i;
229 }
230
231 return i;
232}
233
234static int tty_uart_ctrl(struct card_uart *cuart, enum card_uart_ctl ctl, bool enable)
235{
236 struct termios tio;
237 int rc;
238
239 switch (ctl) {
240 case CUART_CTL_RX:
241 rc = tcgetattr(cuart->u.tty.ofd.fd, &tio);
242 if (rc < 0) {
243 perror("tcgetattr()");
244 return -EIO;
245 }
246 /* We do our best here, but lots of [USB] serial drivers seem to ignore
247 * CREAD, see https://bugzilla.kernel.org/show_bug.cgi?id=205033 */
248 if (enable)
249 tio.c_cflag |= CREAD;
250 else
251 tio.c_cflag &= ~CREAD;
252 rc = tcsetattr(cuart->u.tty.ofd.fd, TCSANOW, &tio);
253 if (rc < 0) {
254 perror("tcsetattr()");
255 return -EIO;
256 }
257 break;
258 case CUART_CTL_RST:
259 _set_rts(cuart->u.tty.ofd.fd, enable);
260 if (enable)
261 _flush(cuart->u.tty.ofd.fd);
262 break;
263 case CUART_CTL_POWER:
264 case CUART_CTL_CLOCK:
265 default:
266 return -EINVAL;
267 }
268 return 0;
269}
270
271static const struct card_uart_ops tty_uart_ops = {
272 .open = tty_uart_open,
273 .close = tty_uart_close,
274 .async_tx = tty_uart_async_tx,
275 .async_rx = tty_uart_async_rx,
276 .ctrl = tty_uart_ctrl,
277};
278
279static struct card_uart_driver tty_uart_driver = {
280 .name = "tty",
281 .ops = &tty_uart_ops,
282};
283
284static __attribute__((constructor)) void on_dso_load_cuart_tty(void)
285{
286 card_uart_driver_register(&tty_uart_driver);
287}