'cuart' Card-UART abstraction + driver for simple serial reader
Change-Id: Ic7e324d99f78b3bfb98fc667d9a1b7fa363f092d
diff --git a/ccid_host/cuart_driver_tty.c b/ccid_host/cuart_driver_tty.c
new file mode 100644
index 0000000..6bd2b52
--- /dev/null
+++ b/ccid_host/cuart_driver_tty.c
@@ -0,0 +1,287 @@
+/* Card (ICC) UART driver for simple serial readers attached to tty.
+ * This allows you to use the CCID core in Linux userspace against a serial reader */
+
+#include <unistd.h>
+#include <termios.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/serial.h>
+#include <osmocom/core/utils.h>
+
+#include "cuart.h"
+#include "utils_ringbuffer.h"
+
+/***********************************************************************
+ * low-level helper routines
+ ***********************************************************************/
+
+static int _init_uart(int fd)
+{
+ struct termios tio;
+ int rc;
+
+ rc = tcgetattr(fd, &tio);
+ if (rc < 0) {
+ perror("tcgetattr()");
+ return -EIO;
+ }
+
+ tio.c_iflag = 0;
+ tio.c_oflag = 0;
+ tio.c_lflag = 0;
+ tio.c_cflag = CREAD | CLOCAL | CSTOPB | PARENB | CS8 | B9600;
+
+ rc = tcsetattr(fd, TCSANOW, &tio);
+ if (rc < 0) {
+ perror("tcsetattr()");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void _set_dtr(int fd, bool dtr)
+{
+ int status, rc;
+
+ rc = ioctl(fd, TIOCMGET, &status);
+ OSMO_ASSERT(rc == 0);
+ if (dtr) /* set DTR */
+ status |= TIOCM_DTR;
+ else
+ status &= ~TIOCM_DTR;
+ rc = ioctl(fd, TIOCMSET, &status);
+ OSMO_ASSERT(rc == 0);
+}
+
+static void _set_rts(int fd, bool rts)
+{
+ int status, rc;
+
+ rc = ioctl(fd, TIOCMGET, &status);
+ OSMO_ASSERT(rc == 0);
+ if (rts) /* set RTS */
+ status |= TIOCM_RTS;
+ else
+ status &= ~TIOCM_RTS;
+ rc = ioctl(fd, TIOCMSET, &status);
+ OSMO_ASSERT(rc == 0);
+}
+
+static int read_timeout(int fd, uint8_t *out, size_t len, unsigned long timeout_ms)
+{
+ struct timeval tv = { .tv_sec = timeout_ms / 1000, .tv_usec = (timeout_ms % 1000) * 1000 };
+ fd_set rd_set;
+ int rc;
+
+ FD_ZERO(&rd_set);
+ FD_SET(fd, &rd_set);
+ rc = select(fd+1, &rd_set, NULL, NULL, &tv);
+ if (rc == 0)
+ return -ETIMEDOUT;
+ else if (rc < 0)
+ return rc;
+
+ return read(fd, out, len);
+}
+
+static int _flush(int fd)
+{
+#if 0
+ uint8_t buf[1];
+ int rc;
+
+ while (1) {
+ rc = read_timeout(fd, buf, sizeof(buf), 10);
+ if (rc == -ETIMEDOUT)
+ return 0;
+ else if (rc < 0)
+ return rc;
+ }
+#else
+ return tcflush(fd, TCIFLUSH);
+#endif
+}
+
+/***********************************************************************
+ * Interface with card_uart (cuart) core
+ ***********************************************************************/
+
+/* forward-declaration */
+static struct card_uart_driver tty_uart_driver;
+static int tty_uart_close(struct card_uart *cuart);
+
+static int tty_uart_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct card_uart *cuart = (struct card_uart *) ofd->data;
+ uint8_t buf[256];
+ int rc;
+
+ if (what & OSMO_FD_READ) {
+ int i;
+ /* read any pending bytes and feed them into ring buffer */
+ rc = read(ofd->fd, buf, sizeof(buf));
+ OSMO_ASSERT(rc > 0);
+ for (i = 0; i < rc; i++) {
+ /* work-around for https://bugzilla.kernel.org/show_bug.cgi?id=205033 */
+ if (!cuart->rx_enabled)
+ continue;
+
+ if (cuart->rx_threshold == 1) {
+ /* bypass ringbuffer and report byte directly */
+ card_uart_notification(cuart, CUART_E_RX_SINGLE, &buf[i]);
+ } else {
+ /* go via ringbuffer and notify only after threshold */
+ ringbuffer_put(&cuart->u.tty.rx_ringbuf, buf[i]);
+ if (ringbuffer_num(&cuart->u.tty.rx_ringbuf) >= cuart->rx_threshold)
+ card_uart_notification(cuart, CUART_E_RX_COMPLETE, NULL);
+ }
+ }
+ }
+ if (what & OSMO_FD_WRITE) {
+ unsigned int to_tx;
+ OSMO_ASSERT(cuart->u.tty.tx_buf_len > cuart->u.tty.tx_index);
+ /* push as many pending transmit bytes as possible */
+ to_tx = cuart->u.tty.tx_buf_len - cuart->u.tty.tx_index;
+ rc = write(ofd->fd, cuart->u.tty.tx_buf + cuart->u.tty.tx_index, to_tx);
+ OSMO_ASSERT(rc > 0);
+ cuart->u.tty.tx_index += rc;
+
+ /* if no more bytes to transmit, disable OSMO_FD_WRITE */
+ if (cuart->u.tty.tx_index >= cuart->u.tty.tx_buf_len) {
+ ofd->when &= ~BSC_FD_WRITE;
+ /* ensure everything is written (tx queue/fifo drained) */
+ tcdrain(cuart->u.tty.ofd.fd);
+ osmo_select_main(true);
+ cuart->tx_busy = false;
+ /* notify */
+ card_uart_notification(cuart, CUART_E_TX_COMPLETE, (void *)cuart->u.tty.tx_buf);
+ }
+ }
+ return 0;
+}
+
+static int tty_uart_open(struct card_uart *cuart, const char *device_name)
+{
+ int rc;
+
+ rc = ringbuffer_init(&cuart->u.tty.rx_ringbuf, cuart->u.tty.rx_buf, sizeof(cuart->u.tty.rx_buf));
+ if (rc < 0)
+ return rc;
+
+ cuart->u.tty.ofd.fd = -1;
+ rc = osmo_serial_init(device_name, B9600);
+ if (rc < 0)
+ return rc;
+
+ osmo_fd_setup(&cuart->u.tty.ofd, rc, BSC_FD_READ, tty_uart_fd_cb, cuart, 0);
+ cuart->u.tty.baudrate = B9600;
+
+ rc = _init_uart(cuart->u.tty.ofd.fd);
+ if (rc < 0) {
+ tty_uart_close(cuart);
+ return rc;
+ }
+
+ _flush(cuart->u.tty.ofd.fd);
+
+ osmo_fd_register(&cuart->u.tty.ofd);
+
+ return 0;
+}
+
+static int tty_uart_close(struct card_uart *cuart)
+{
+ OSMO_ASSERT(cuart->driver == &tty_uart_driver);
+ if (cuart->u.tty.ofd.fd != -1) {
+ osmo_fd_unregister(&cuart->u.tty.ofd);
+ close(cuart->u.tty.ofd.fd);
+ cuart->u.tty.ofd.fd = -1;
+ }
+ return 0;
+}
+
+static int tty_uart_async_tx(struct card_uart *cuart, const uint8_t *data, size_t len, bool rx_after)
+{
+ OSMO_ASSERT(cuart->driver == &tty_uart_driver);
+
+ cuart->u.tty.tx_buf = data;
+ cuart->u.tty.tx_buf_len = len;
+ cuart->u.tty.tx_buf_len = len;
+ cuart->tx_busy = true;
+ cuart->u.tty.ofd.when |= OSMO_FD_WRITE;
+
+ return 0;
+}
+
+static int tty_uart_async_rx(struct card_uart *cuart, uint8_t *data, size_t len)
+{
+ int rc, i;
+ OSMO_ASSERT(cuart->driver == &tty_uart_driver);
+
+ /* FIXME: actually do this asynchronously */
+ for (i = 0; i < len; i++) {
+ rc = ringbuffer_get(&cuart->u.tty.rx_ringbuf, &data[i]);
+ if (rc < 0)
+ return i;
+ }
+
+ return i;
+}
+
+static int tty_uart_ctrl(struct card_uart *cuart, enum card_uart_ctl ctl, bool enable)
+{
+ struct termios tio;
+ int rc;
+
+ switch (ctl) {
+ case CUART_CTL_RX:
+ rc = tcgetattr(cuart->u.tty.ofd.fd, &tio);
+ if (rc < 0) {
+ perror("tcgetattr()");
+ return -EIO;
+ }
+ /* We do our best here, but lots of [USB] serial drivers seem to ignore
+ * CREAD, see https://bugzilla.kernel.org/show_bug.cgi?id=205033 */
+ if (enable)
+ tio.c_cflag |= CREAD;
+ else
+ tio.c_cflag &= ~CREAD;
+ rc = tcsetattr(cuart->u.tty.ofd.fd, TCSANOW, &tio);
+ if (rc < 0) {
+ perror("tcsetattr()");
+ return -EIO;
+ }
+ break;
+ case CUART_CTL_RST:
+ _set_rts(cuart->u.tty.ofd.fd, enable);
+ if (enable)
+ _flush(cuart->u.tty.ofd.fd);
+ break;
+ case CUART_CTL_POWER:
+ case CUART_CTL_CLOCK:
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct card_uart_ops tty_uart_ops = {
+ .open = tty_uart_open,
+ .close = tty_uart_close,
+ .async_tx = tty_uart_async_tx,
+ .async_rx = tty_uart_async_rx,
+ .ctrl = tty_uart_ctrl,
+};
+
+static struct card_uart_driver tty_uart_driver = {
+ .name = "tty",
+ .ops = &tty_uart_ops,
+};
+
+static __attribute__((constructor)) void on_dso_load_cuart_tty(void)
+{
+ card_uart_driver_register(&tty_uart_driver);
+}