* drivers/serial/uart_gsmmux.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
* Included Files
****************************************************************************/
#include <debug.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/poll.h>
#include <nuttx/atomic.h>
#include <nuttx/crc8.h>
#include <nuttx/init.h>
#include <nuttx/fs/fs.h>
#include <nuttx/kmalloc.h>
#include <nuttx/list.h>
#include <nuttx/serial/gsmmux.h>
#include <nuttx/serial/serial.h>
* Pre-processor Definitions
****************************************************************************/
#define GSMMUX_EA 0x01
#define GSMMUX_CMD 0x02
#define GSMMUX_RSP 0x00
#define GSMMUX_PF 0x10
#define GSMMUX_FLAG 0xf9
#define GSMMUX_ADV_ESC 0x7d
#define GSMMUX_ADV_ESC_MASK 0x20
#define GSMMUX_SABM 0x2f
#define GSMMUX_UA 0x63
#define GSMMUX_DM 0x0f
#define GSMMUX_DISC 0x43
#define GSMMUX_UIH 0xef
#define GSMMUX_UI 0x03
#define GSMMUX_ADDR(cmd, dlci) (GSMMUX_EA | (cmd) | (dlci) << 2)
#define GSMMUX_LEN0(len) (GSMMUX_EA | (len) << 1)
#define GSMMUX_CMD_PN (GSMMUX_EA | 0x80)
#define GSMMUX_CMD_PSC (GSMMUX_EA | 0x40)
#define GSMMUX_CMD_CLD (GSMMUX_EA | 0xc0)
#define GSMMUX_CMD_TEST (GSMMUX_EA | 0x20)
#define GSMMUX_CMD_FCON (GSMMUX_EA | 0xa0)
#define GSMMUX_CMD_FCOFF (GSMMUX_EA | 0x60)
#define GSMMUX_CMD_MSC (GSMMUX_EA | 0xe0)
#define GSMMUX_CMD_NSC (GSMMUX_EA | 0x10)
#define GSMMUX_CMD_RPN (GSMMUX_EA | 0x90)
#define GSMMUX_CMD_RLS (GSMMUX_EA | 0x50)
#define GSMMUX_CMD_SNC (GSMMUX_EA | 0xd0)
#define GSMMUX_MIN_MTU 8
#define GSMMUX_MIN_MRU 8
#define GSMMUX_STATE_INIT 0
#define GSMMUX_STATE_ADDRESS 1
#define GSMMUX_STATE_CONTROL 2
#define GSMMUX_STATE_LEN0 3
#define GSMMUX_STATE_LEN1 4
#define GSMMUX_STATE_DATA 5
#define GSMMUX_STATE_FCS 6
#define GSMMUX_STATE_END 7
#define GSMMUX_DUMP_ERROR 0x01
#define GSMMUX_DUMP_WARNING 0x02
#define GSMMUX_DUMP_INFO 0x04
#define GSMMUX_DUMP_DATA 0x08
#define GSMMUX_DUMP_PAYLOAD 0x10
#define gsmmux_err(fmt, ...) \
if (CONFIG_UART_GSMMUX_DEBUG & GSMMUX_DUMP_ERROR) \
{ \
syslog(LOG_ERR, fmt, ##__VA_ARGS__); \
}
#define gsmmux_warn(fmt, ...) \
if (CONFIG_UART_GSMMUX_DEBUG & GSMMUX_DUMP_WARNING) \
{ \
syslog(LOG_WARNING, fmt, ##__VA_ARGS__); \
}
#define gsmmux_info(fmt, ...) \
if (CONFIG_UART_GSMMUX_DEBUG & GSMMUX_DUMP_INFO) \
{ \
syslog(LOG_INFO, fmt, ##__VA_ARGS__); \
}
#define gsmmux_dump_payload(msg, buf, len) \
if (CONFIG_UART_GSMMUX_DEBUG & GSMMUX_DUMP_PAYLOAD) \
{ \
lib_dumpbuffer(msg, (FAR uint8_t *)(buf), len); \
}
#define gsmmux_dump_data(msg, buf, len) \
if (CONFIG_UART_GSMMUX_DEBUG & GSMMUX_DUMP_DATA) \
{ \
lib_dumpbuffer(msg, (FAR uint8_t *)(buf), len); \
}
* Private Types
****************************************************************************/
struct gsmmux_channel_s
{
struct uart_dev_s uart;
FAR struct gsmmux_s *gsmmux;
struct list_node node;
};
struct gsmmux_s
{
FAR struct uart_dev_s *uart;
struct list_node head;
FAR const char *devname;
struct file file;
struct pollfd fds;
atomic_t refcnt;
FAR uint8_t *rxbuf;
size_t offset;
size_t len;
size_t recv;
uint8_t dlci;
uint8_t ctrl;
uint8_t fcs;
uint8_t state;
* Are currently not supported
*/
unsigned int encapsulation;
unsigned int initiator;
size_t mtu;
size_t mru;
size_t nchannel;
struct gsmmux_channel_s channels[0];
};
* Private Function Prototypes
****************************************************************************/
static int gsmmux_tty_setup(FAR struct uart_dev_s *dev);
static void gsmmux_tty_shutdown(FAR struct uart_dev_s *dev);
static int gsmmux_tty_attach(FAR struct uart_dev_s *dev);
static void gsmmux_tty_detach(FAR struct uart_dev_s *dev);
static int gsmmux_tty_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
static void gsmmux_tty_rxint(FAR struct uart_dev_s *dev, bool enable);
static bool gsmmux_tty_rxavailable(FAR struct uart_dev_s *dev);
static void gsmmux_tty_dmasend(FAR struct uart_dev_s *dev);
static void gsmmux_tty_dmareceive(FAR struct uart_dev_s *dev);
static void gsmmux_tty_dmarxfree(FAR struct uart_dev_s *dev);
static void gsmmux_tty_dmatxavail(FAR struct uart_dev_s *dev);
static void gsmmux_tty_txint(FAR struct uart_dev_s *dev, bool enable);
static bool gsmmux_tty_txready(FAR struct uart_dev_s *dev);
static bool gsmmux_tty_txempty(FAR struct uart_dev_s *dev);
static ssize_t gsmmux_tty_recvbuf(FAR struct uart_dev_s *dev,
FAR void *buf, size_t len);
static ssize_t gsmmux_tty_sendbuf(FAR struct uart_dev_s *dev,
FAR const void *buf, size_t len);
static size_t gsmmux_read_frame(FAR struct gsmmux_s *gsmmux,
FAR const void *buf, size_t len);
* Private Data
****************************************************************************/
static const struct uart_ops_s g_gsmmux_ops =
{
gsmmux_tty_setup,
gsmmux_tty_shutdown,
gsmmux_tty_attach,
gsmmux_tty_detach,
gsmmux_tty_ioctl,
NULL,
gsmmux_tty_rxint,
gsmmux_tty_rxavailable,
#ifdef CONFIG_SERIAL_IFLOWCONTROL
NULL,
#endif
gsmmux_tty_dmasend,
gsmmux_tty_dmareceive,
gsmmux_tty_dmarxfree,
gsmmux_tty_dmatxavail,
NULL,
gsmmux_tty_txint,
gsmmux_tty_txready,
gsmmux_tty_txempty,
NULL,
gsmmux_tty_recvbuf,
gsmmux_tty_sendbuf,
};
* Private Functions
****************************************************************************/
* Name: gsmmux_frame_fcs
****************************************************************************/
static inline_function uint8_t
gsmmux_frame_fcs(FAR const void *buffer, size_t len)
{
return crc8rohcpart(buffer, len, 0);
}
* Name: gsmmux_frame_fcs_add
****************************************************************************/
static inline_function uint8_t gsmmux_frame_fcs_add(uint8_t fcs, uint8_t ch)
{
return crc8rohcpart(&ch, 1, fcs);
}
* Name: gsmmux_uart_pollin
*
* Description:
* Physical layer data sending and receiving.
*
****************************************************************************/
static void gsmmux_uart_pollin(FAR struct gsmmux_s *gsmmux)
{
FAR struct uart_dev_s *dev = gsmmux->uart;
FAR struct uart_buffer_s *rxbuf = &dev->recv;
irqstate_t flags;
size_t tail;
size_t head;
size_t recv;
size_t len;
flags = uart_spinlock(dev, true);
tail = rxbuf->tail;
head = rxbuf->head;
if (tail <= head)
{
len = head - tail;
gsmmux_dump_data("<---uart", rxbuf->buffer + tail, len);
recv = gsmmux_read_frame(gsmmux, rxbuf->buffer + tail, len);
rxbuf->tail += recv;
}
else
{
len = rxbuf->size - tail;
gsmmux_dump_data("<---uart1", rxbuf->buffer + tail, len);
recv = gsmmux_read_frame(gsmmux, rxbuf->buffer + tail, len);
if (recv >= len)
{
gsmmux_dump_data("<---uart2", rxbuf->buffer, head);
recv = gsmmux_read_frame(gsmmux, rxbuf->buffer, head);
rxbuf->tail = recv;
}
else
{
rxbuf->tail += recv;
}
}
uart_dmarxfree(dev);
uart_spinunlock(dev, true, flags);
}
* Name: gsmmux_uart_pollout
****************************************************************************/
static void gsmmux_uart_pollout(FAR struct gsmmux_s *gsmmux)
{
struct list_node head = LIST_INITIAL_VALUE(head);
FAR struct gsmmux_channel_s *channel;
irqstate_t flags;
flags = uart_spinlock(gsmmux->uart, false);
list_move(&gsmmux->head, &head);
uart_spinunlock(gsmmux->uart, false, flags);
while ((channel = list_remove_head_type(&head,
struct gsmmux_channel_s, node)))
{
flags = uart_spinlock(&channel->uart, true);
uart_xmitchars_dma(&channel->uart);
uart_spinunlock(&channel->uart, true, flags);
}
}
* Name: gsmmux_uart_poll
****************************************************************************/
static void gsmmux_uart_poll(FAR struct pollfd *fds)
{
FAR struct gsmmux_s *gsmmux = fds->arg;
if (fds->revents & POLLIN)
{
fds->revents &= ~POLLIN;
gsmmux_uart_pollin(gsmmux);
}
else if (fds->revents & POLLOUT)
{
fds->revents &= ~POLLOUT;
gsmmux_uart_pollout(gsmmux);
}
}
* Name: gsmmux_uart_open
****************************************************************************/
static int gsmmux_uart_open(FAR struct gsmmux_s *gsmmux)
{
struct termios term;
int ret;
gsmmux_info("gsmmux_uart_open: %s, refcnt: %" PRId32 "\n",
gsmmux->devname, atomic_read(&gsmmux->refcnt));
if (atomic_add(&gsmmux->refcnt, 1) > 0)
{
return OK;
}
ret = file_open(&gsmmux->file, gsmmux->devname, O_RDWR | O_NONBLOCK);
if (ret < 0)
{
atomic_sub(&gsmmux->refcnt, 1);
gsmmux_err("Failed to open %s: %d\n", gsmmux->devname, ret);
return ret;
}
file_ioctl(&gsmmux->file, TCGETS, &term);
cfmakeraw(&term);
file_ioctl(&gsmmux->file, TCSETS, &term);
gsmmux->uart = gsmmux->file.f_inode->i_private;
gsmmux->fds.arg = gsmmux;
gsmmux->fds.events = POLLIN | POLLOUT;
gsmmux->fds.cb = gsmmux_uart_poll;
file_poll(&gsmmux->file, &gsmmux->fds, true);
return OK;
}
* Name: gsmmux_uart_close
****************************************************************************/
static void gsmmux_uart_close(FAR struct gsmmux_s *gsmmux)
{
gsmmux_info("gsmmux_uart_close: %s, refcnt: %" PRId32 "\n",
gsmmux->devname, atomic_read(&gsmmux->refcnt));
if (atomic_sub(&gsmmux->refcnt, 1) > 1)
{
return;
}
file_poll(&gsmmux->file, &gsmmux->fds, false);
file_close(&gsmmux->file);
}
* Name: gsmmux_uart_rxavailable
*
* Description:
* Calculate the available space for reading from the RX buffer.
*
****************************************************************************/
static size_t gsmmux_uart_rxavailable(FAR struct uart_buffer_s *rxbuf)
{
return rxbuf->head >= rxbuf->tail ?
rxbuf->head - rxbuf->tail :
rxbuf->size - rxbuf->tail + rxbuf->head;
}
* Name: gsmmux_uart_txavailable
*
* Description:
* Calculate the available space for writing to the TX buffer.
*
****************************************************************************/
static size_t gsmmux_uart_txavailable(FAR struct uart_buffer_s *txbuf)
{
return txbuf->head >= txbuf->tail ?
txbuf->size - txbuf->head + txbuf->tail - 1 :
txbuf->tail - txbuf->head - 1;
}
* Name: gsmmux_uart_read
*
* Description:
* Update the RX buffer.
*
****************************************************************************/
static ssize_t gsmmux_uart_read(FAR struct gsmmux_s *gsmmux)
{
FAR struct uart_dev_s *dev = gsmmux->uart;
FAR struct uart_buffer_s *rxbuf = &dev->recv;
irqstate_t flags;
size_t recv = 0;
ssize_t ret = 0;
flags = uart_spinlock(dev, true);
while (uart_rxavailable(dev))
{
if (dev->ops->recvbuf)
{
FAR char *buf = &rxbuf->buffer[rxbuf->head];
size_t len;
if (rxbuf->tail > rxbuf->head)
{
len = rxbuf->tail - rxbuf->head - 1;
}
else if (rxbuf->tail)
{
len = rxbuf->size - rxbuf->head;
}
else
{
len = rxbuf->size - rxbuf->head - 1;
}
ret = uart_recvbuf(dev, buf, len);
if (ret <= 0)
{
break;
}
recv += ret;
rxbuf->head += ret;
if (rxbuf->head >= rxbuf->size)
{
rxbuf->head = 0;
}
}
else
{
unsigned int status;
ret = uart_receive(dev, &status);
if (ret < 0)
{
break;
}
recv += 1;
rxbuf->buffer[rxbuf->head] = ret;
if (++rxbuf->head >= rxbuf->size)
{
rxbuf->head = 0;
}
}
}
uart_spinunlock(dev, true, flags);
if (recv > 0)
{
gsmmux_uart_pollin(gsmmux);
ret = recv;
}
return ret;
}
static ssize_t gsmmux_uart_write(FAR struct gsmmux_s *gsmmux,
FAR const struct iovec *iov,
size_t iovcnt)
{
FAR struct uart_buffer_s *txbuf = &gsmmux->uart->xmit;
irqstate_t flags;
size_t available;
size_t written = 0;
size_t len = 0;
size_t i;
for (i = 0; i < iovcnt; i++)
{
len += iov[i].iov_len;
gsmmux_dump_data("uart--->", iov[i].iov_base, iov[i].iov_len);
}
* and each transmission must be atomic.
*/
flags = uart_spinlock(gsmmux->uart, true);
if (up_interrupt_context() || sched_idletask() || OSINIT_IS_PANIC())
{
size_t j;
for (i = 0; i < iovcnt; i++)
{
if (gsmmux->uart->ops->sendbuf)
{
written += uart_sendbuf(gsmmux->uart, iov[i].iov_base,
iov[i].iov_len);
}
else
{
for (j = 0; j < iov[i].iov_len; j++)
{
int ch = ((FAR const char *)iov[i].iov_base)[j];
uart_send(gsmmux->uart, ch);
written++;
}
}
}
uart_spinunlock(gsmmux->uart, true, flags);
return written;
}
available = gsmmux_uart_txavailable(txbuf);
* wait until there is enough space
*/
if (available < len)
{
uart_spinunlock(gsmmux->uart, true, flags);
gsmmux_warn("buffer not enough, available: %zu, buflen: %zu\n",
available, len);
return -EAGAIN;
}
uart_disabletxint(gsmmux->uart);
written = len;
for (i = 0; i < iovcnt; i++)
{
len = txbuf->size - txbuf->head;
if (iov[i].iov_len > len)
{
memcpy(txbuf->buffer + txbuf->head, iov[i].iov_base, len);
memcpy(txbuf->buffer, iov[i].iov_base + len, iov[i].iov_len - len);
txbuf->head = iov[i].iov_len - len;
}
else
{
memcpy(txbuf->buffer + txbuf->head, iov[i].iov_base,
iov[i].iov_len);
txbuf->head += iov[i].iov_len;
}
}
uart_dmatxavail(gsmmux->uart);
uart_enabletxint(gsmmux->uart);
uart_spinunlock(gsmmux->uart, true, flags);
return written;
}
static ssize_t gsmmux_send_packet(FAR struct gsmmux_s *gsmmux,
int dlci, int cmd, int type,
FAR const void *buf, size_t len)
{
struct iovec iov[3];
uint8_t head[6];
uint8_t end[2];
size_t headlen;
ssize_t ret;
head[0] = GSMMUX_FLAG;
head[1] = GSMMUX_ADDR(cmd, dlci);
head[2] = type | GSMMUX_PF;
if (len <= 127)
{
head[3] = GSMMUX_LEN0(len);
headlen = 4;
}
else
{
head[3] = len << 1;
head[4] = len >> 7;
headlen = 5;
}
iov[0].iov_base = head;
iov[0].iov_len = headlen;
iov[1].iov_base = (FAR uint8_t *)buf;
iov[1].iov_len = len;
end[0] = gsmmux_frame_fcs(head + 1, headlen - 1);
end[1] = GSMMUX_FLAG;
iov[2].iov_base = end;
iov[2].iov_len = sizeof(end);
ret = gsmmux_uart_write(gsmmux, iov, 3);
return ret < 0 ? ret : len;
}
static ssize_t gsmmux_send_data(FAR struct gsmmux_channel_s *channel,
FAR const void *buf, size_t len)
{
FAR struct gsmmux_s *gsmmux = channel->gsmmux;
size_t written = 0;
ssize_t ret = 0;
* starts from 1
*/
int dlci = channel - gsmmux->channels + 1;
while (len > 0)
{
ret = MIN(gsmmux->mtu, len);
gsmmux_dump_payload("UIH --->:", buf, ret);
ret = gsmmux_send_packet(gsmmux, dlci, GSMMUX_CMD, GSMMUX_UIH,
buf, ret);
if (ret < 0)
{
irqstate_t flags = uart_spinlock(gsmmux->uart, false);
if (!list_in_list(&channel->node))
{
list_add_tail(&gsmmux->head, &channel->node);
}
uart_spinunlock(gsmmux->uart, false, flags);
break;
}
written += ret;
buf += ret;
len -= ret;
}
return written ? written : ret;
}
static size_t gsmmux_send_command(FAR struct gsmmux_s *gsmmux, int dlci,
int type, FAR const uint8_t *buf,
size_t len)
{
if (CONFIG_UART_GSMMUX_DEBUG & GSMMUX_DUMP_PAYLOAD)
{
FAR const char *cmd = type == GSMMUX_SABM ? "SABM" :
type == GSMMUX_DISC ? "DISC" : "Unknown";
gsmmux_info("CMD --->: (%s) %02x\n", cmd, type);
}
return gsmmux_send_packet(gsmmux, dlci, GSMMUX_CMD, type, buf, len);
}
static size_t gsmmux_send_response(FAR struct gsmmux_s *gsmmux, int dlci,
int type, FAR const uint8_t *buffer,
size_t buflen)
{
if (CONFIG_UART_GSMMUX_DEBUG & GSMMUX_DUMP_PAYLOAD)
{
FAR const char *resp = type == GSMMUX_UA ? "UA" :
type == GSMMUX_DM ? "DM" : "Unknown";
gsmmux_info("RSP --->: (%s) %02x\n", resp, type);
}
return gsmmux_send_packet(gsmmux, dlci, GSMMUX_RSP, type, buffer, buflen);
}
static size_t gsmmux_handle_data(FAR struct gsmmux_channel_s *channel)
{
FAR struct gsmmux_s *gsmmux = channel->gsmmux;
size_t ret;
irqstate_t flags = uart_spinlock(gsmmux->uart, true);
gsmmux->recv = 0;
uart_recvchars_dma(&channel->uart);
ret = gsmmux->recv;
uart_spinunlock(gsmmux->uart, true, flags);
return ret;
}
static void gsmmux_handle_dlci0(FAR struct gsmmux_s *gsmmux,
FAR const uint8_t *buf, size_t len)
{
uint8_t cmd = buf[0] & 0xfd;
switch (cmd)
{
case GSMMUX_CMD_MSC:
* successful reception
*/
gsmmux_send_response(gsmmux, 0, GSMMUX_UIH, buf, len);
break;
}
}
static size_t gsmmux_handle_frame(FAR struct gsmmux_s *gsmmux, uint8_t dlci,
uint8_t ctrl, FAR const void *buf,
size_t len)
{
FAR struct gsmmux_channel_s *channel;
* starts from 1
*/
channel = dlci ? &gsmmux->channels[dlci - 1] : NULL;
switch (ctrl)
{
case GSMMUX_UI:
case GSMMUX_UIH:
case GSMMUX_UI | GSMMUX_PF:
case GSMMUX_UIH | GSMMUX_PF:
if (dlci == 0)
{
gsmmux_dump_payload("<--- ctrl cmd:", buf, len);
gsmmux_handle_dlci0(gsmmux, buf, len);
}
else
{
gsmmux_dump_payload("<--- UIH:", buf, len);
return gsmmux_handle_data(channel);
}
break;
case GSMMUX_SABM:
case GSMMUX_SABM | GSMMUX_PF:
gsmmux_dump_payload("<--- SABM:", buf, len);
gsmmux_send_command(gsmmux, dlci, GSMMUX_UA, NULL, 0);
break;
case GSMMUX_DISC:
case GSMMUX_DISC | GSMMUX_PF:
gsmmux_dump_payload("<--- DISC:", buf, len);
gsmmux_send_command(gsmmux, dlci, GSMMUX_DM, NULL, 0);
break;
case GSMMUX_UA:
case GSMMUX_UA | GSMMUX_PF:
gsmmux_dump_payload("<--- UA:", buf, len);
break;
case GSMMUX_DM:
case GSMMUX_DM | GSMMUX_PF:
gsmmux_dump_payload("<--- DM:", buf, len);
break;
default:
gsmmux_warn("unknown frame type: %02x\n", ctrl);
break;
}
return len;
}
static size_t gsmmux_read_frame(FAR struct gsmmux_s *gsmmux,
FAR const void *buf, size_t len)
{
size_t i;
if (gsmmux->len && gsmmux->state == GSMMUX_STATE_END)
{
size_t recv = gsmmux_handle_frame(gsmmux, gsmmux->dlci,
gsmmux->ctrl,
gsmmux->rxbuf,
gsmmux->len);
gsmmux->len -= recv;
gsmmux->offset -= recv;
if (gsmmux->len != 0)
{
memmove(gsmmux->rxbuf, gsmmux->rxbuf + recv, gsmmux->len);
return 0;
}
else
{
gsmmux->state = GSMMUX_STATE_INIT;
}
}
*
* | Flag | Address | Control | Length | Data | FCS | Flag |
*
*/
for (i = 0; i < len; i++)
{
uint8_t ch = ((FAR const char *)buf)[i];
uint8_t dlci;
switch (gsmmux->state)
{
case GSMMUX_STATE_INIT:
if (ch == GSMMUX_FLAG)
{
gsmmux->state = GSMMUX_STATE_ADDRESS;
gsmmux->fcs = 0;
gsmmux->offset = 0;
gsmmux->len = 0;
}
break;
case GSMMUX_STATE_ADDRESS:
dlci = ch >> 2;
if (ch == GSMMUX_FLAG)
{
gsmmux_warn("addr error, skip %02x\n", ch);
}
else if (dlci > gsmmux->nchannel)
{
gsmmux_warn("addr error: %d, skip %02x\n", dlci, ch);
gsmmux->state = GSMMUX_STATE_INIT;
}
else
{
gsmmux->dlci = dlci;
gsmmux->fcs = gsmmux_frame_fcs_add(gsmmux->fcs, ch);
gsmmux->state = GSMMUX_STATE_CONTROL;
}
break;
case GSMMUX_STATE_CONTROL:
gsmmux->fcs = gsmmux_frame_fcs_add(gsmmux->fcs, ch);
gsmmux->ctrl = ch;
gsmmux->state = GSMMUX_STATE_LEN0;
break;
case GSMMUX_STATE_LEN0:
gsmmux->len = ch >> 1;
if (gsmmux->len > gsmmux->mru)
{
gsmmux_warn("len0 error, skip %02x\n", ch);
gsmmux->state = GSMMUX_STATE_INIT;
}
gsmmux->fcs = gsmmux_frame_fcs_add(gsmmux->fcs, ch);
if (ch & GSMMUX_EA)
{
gsmmux->state = gsmmux->len ? GSMMUX_STATE_DATA :
GSMMUX_STATE_FCS;
}
else
{
gsmmux->state = GSMMUX_STATE_LEN1;
}
break;
case GSMMUX_STATE_LEN1:
gsmmux->fcs = gsmmux_frame_fcs_add(gsmmux->fcs, ch);
gsmmux->len = gsmmux->len | (ch << 7);
if (gsmmux->len > gsmmux->mru)
{
gsmmux_warn("len1 error, skip %02x\n", ch);
gsmmux->state = GSMMUX_STATE_INIT;
}
else
{
gsmmux->state = GSMMUX_STATE_DATA;
}
break;
case GSMMUX_STATE_DATA:
gsmmux->rxbuf[gsmmux->offset++] = ch;
if (gsmmux->offset == gsmmux->len)
{
gsmmux->state = GSMMUX_STATE_FCS;
}
break;
case GSMMUX_STATE_FCS:
if (gsmmux->fcs == ch)
{
gsmmux->state = GSMMUX_STATE_END;
}
else
{
gsmmux_warn("fcs error, skip %02x, expected %02x\n",
ch, gsmmux->fcs);
gsmmux->state = GSMMUX_STATE_INIT;
}
break;
case GSMMUX_STATE_END:
if (ch == GSMMUX_FLAG)
{
DEBUGASSERT(gsmmux->offset == gsmmux->len);
size_t recv = gsmmux_handle_frame(gsmmux, gsmmux->dlci,
gsmmux->ctrl,
gsmmux->rxbuf,
gsmmux->len);
gsmmux->len -= recv;
gsmmux->offset -= recv;
if (gsmmux->len != 0)
{
memmove(gsmmux->rxbuf, gsmmux->rxbuf + recv, gsmmux->len);
return i;
}
else
{
gsmmux->state = GSMMUX_STATE_INIT;
}
}
else
{
gsmmux_warn("end error, skip %02x\n", ch);
gsmmux->state = GSMMUX_STATE_INIT;
}
break;
default:
gsmmux_warn("unknown %d, skip %02x\n", gsmmux->state, ch);
gsmmux->state = GSMMUX_STATE_INIT;
break;
}
}
return len;
}
static int gsmmux_tty_setup(FAR struct uart_dev_s *dev)
{
return OK;
}
static void gsmmux_tty_shutdown(FAR struct uart_dev_s *dev)
{
}
static int gsmmux_tty_attach(FAR struct uart_dev_s *dev)
{
FAR struct gsmmux_channel_s *channel = dev->priv;
FAR struct gsmmux_s *gsmmux = channel->gsmmux;
return gsmmux_uart_open(gsmmux);
}
static void gsmmux_tty_detach(FAR struct uart_dev_s *dev)
{
FAR struct gsmmux_channel_s *channel = dev->priv;
FAR struct gsmmux_s *gsmmux = channel->gsmmux;
gsmmux_uart_close(gsmmux);
}
static int gsmmux_tty_ioctl(FAR struct file *filep, int cmd,
unsigned long arg)
{
* otherwise, the physical serial port configuration will
* be modified when the pseudo-device is set up with ioctl.
* For example, if you register a /dev/console pseudo-node and
* execute tcsetattr(infd, TCSANOW, &cfg);
* the physical serial port will be changed to console.
*/
return -ENOTTY;
}
static ssize_t gsmmux_tty_recvbuf(FAR struct uart_dev_s *dev,
FAR void *buf, size_t len)
{
FAR struct uart_buffer_s *rxbuf = &dev->recv;
FAR uint8_t *dest = (FAR uint8_t *)buf;
irqstate_t flags;
size_t copy;
if (len == 0 || !gsmmux_tty_rxavailable(dev))
{
return 0;
}
flags = uart_spinlock(dev, true);
len = MIN(len, gsmmux_uart_rxavailable(rxbuf));
copy = MIN(len, rxbuf->size - rxbuf->tail);
memcpy(dest, rxbuf->buffer + rxbuf->tail, copy);
if (copy < len)
{
memcpy(dest + copy, rxbuf->buffer, len - copy);
}
rxbuf->tail += len;
if (rxbuf->tail >= rxbuf->size)
{
rxbuf->tail -= rxbuf->size;
}
uart_spinunlock(dev, true, flags);
return len;
}
static ssize_t gsmmux_tty_sendbuf(FAR struct uart_dev_s *dev,
FAR const void *buf, size_t len)
{
FAR struct gsmmux_channel_s *channel = dev->priv;
return gsmmux_send_data(channel, buf, len);
}
static void gsmmux_tty_rxint(FAR struct uart_dev_s *dev, bool enable)
{
}
static bool gsmmux_tty_rxavailable(FAR struct uart_dev_s *dev)
{
FAR struct gsmmux_channel_s *channel = dev->priv;
FAR struct gsmmux_s *gsmmux = channel->gsmmux;
irqstate_t flags;
size_t ret;
gsmmux_uart_read(gsmmux);
flags = uart_spinlock(dev, true);
ret = gsmmux_uart_rxavailable(&dev->recv) > 0;
uart_spinunlock(dev, true, flags);
return ret;
}
static void gsmmux_tty_dmasend(FAR struct uart_dev_s *dev)
{
FAR struct gsmmux_channel_s *channel = dev->priv;
ssize_t ret;
ret = gsmmux_send_data(channel, dev->dmatx.buffer,
dev->dmatx.length);
dev->dmatx.nbytes = ret > 0 ? ret : 0;
uart_xmitchars_done(dev);
}
static void gsmmux_tty_dmareceive(FAR struct uart_dev_s *dev)
{
FAR struct gsmmux_channel_s *channel = dev->priv;
FAR struct gsmmux_s *gsmmux = channel->gsmmux;
FAR struct uart_dmaxfer_s *rxbuf = &channel->uart.dmarx;
FAR const uint8_t *buf = gsmmux->rxbuf;
size_t len = gsmmux->len;
if (len <= rxbuf->length)
{
memcpy(rxbuf->buffer, buf, len);
}
else
{
len = MIN(len, rxbuf->length + rxbuf->nlength);
memcpy(rxbuf->buffer, buf, rxbuf->length);
memcpy(rxbuf->nbuffer, buf + rxbuf->length, len - rxbuf->length);
}
rxbuf->nbytes = len;
gsmmux->recv = len;
uart_recvchars_done(&channel->uart);
}
static void gsmmux_tty_dmarxfree(FAR struct uart_dev_s *dev)
{
FAR struct gsmmux_channel_s *channel = dev->priv;
FAR struct gsmmux_s *gsmmux = channel->gsmmux;
gsmmux_uart_read(gsmmux);
}
static void gsmmux_tty_dmatxavail(FAR struct uart_dev_s *dev)
{
uart_xmitchars_dma(dev);
}
static void gsmmux_tty_txint(FAR struct uart_dev_s *dev, bool enable)
{
}
static bool gsmmux_tty_txready(FAR struct uart_dev_s *dev)
{
FAR struct gsmmux_channel_s *channel = dev->priv;
FAR struct gsmmux_s *gsmmux = channel->gsmmux;
return uart_txready(gsmmux->uart);
}
static bool gsmmux_tty_txempty(FAR struct uart_dev_s *dev)
{
FAR struct gsmmux_channel_s *channel = dev->priv;
FAR struct gsmmux_s *gsmmux = channel->gsmmux;
return uart_txempty(gsmmux->uart);
}
static int gsmmux_tty_register(FAR struct gsmmux_s *gsmmux,
FAR const char *path,
FAR struct gsmmux_channel_s *channel)
{
FAR char *buffer = kmm_malloc(CONFIG_UART_GSMMUX_TXBUF_SIZE +
CONFIG_UART_GSMMUX_RXBUF_SIZE);
int ret;
if (buffer == NULL)
{
return -ENOMEM;
}
channel->gsmmux = gsmmux;
channel->uart.ops = &g_gsmmux_ops;
channel->uart.priv = channel;
channel->uart.xmit.size = CONFIG_UART_GSMMUX_TXBUF_SIZE;
channel->uart.xmit.buffer = buffer;
channel->uart.recv.size = CONFIG_UART_GSMMUX_RXBUF_SIZE;
channel->uart.recv.buffer = buffer + CONFIG_UART_GSMMUX_TXBUF_SIZE;
if (strcmp(path, "/dev/console") == 0)
{
channel->uart.isconsole = true;
ret = uart_register("/dev/ttyGSM0", &channel->uart);
if (ret < 0)
{
goto errout;
}
}
ret = uart_register(path, &channel->uart);
if (ret < 0)
{
gsmmux_err("Failed to register %s\n", path);
goto errout;
}
return OK;
errout:
kmm_free(buffer);
return ret;
}
static void gsmmux_tty_unregister(FAR struct gsmmux_channel_s *channel,
FAR const char *path)
{
if (strcmp(path, "/dev/console") == 0)
{
unregister_driver("/dev/ttyGSM0");
}
unregister_driver(path);
kmm_free(channel->uart.xmit.buffer);
}
* Public Functions
****************************************************************************/
* Name: gsmmux_register
*
* Description:
* Register a gsmmux device with multiple tty channels.
*
* Note:
* This API is called by the application, initializes the serial port
* to gsmmux mode according to config,
* and creates the device specified by devlist.
*
****************************************************************************/
FAR struct gsmmux_s *
gsmmux_register(FAR const char *devname,
FAR const struct gsm_config *config,
FAR const char *devlist[], size_t devcount)
{
FAR struct gsmmux_s *gsmmux;
size_t i;
int ret;
if (devcount == 0 || devname == NULL ||
config->mru < GSMMUX_MIN_MRU ||
config->mtu < GSMMUX_MIN_MTU ||
config->encapsulation != GSMMUX_BASIC_MODE)
{
gsmmux_err("Invalid parameter\n");
return NULL;
}
gsmmux = kmm_zalloc(sizeof(struct gsmmux_s) + config->mru +
devcount * sizeof(struct gsmmux_channel_s));
if (gsmmux == NULL)
{
gsmmux_err("Failed to allocate memory\n");
return NULL;
}
gsmmux->devname = devname;
gsmmux->nchannel = devcount;
gsmmux->mru = config->mru;
gsmmux->mtu = config->mtu;
gsmmux->initiator = config->initiator;
gsmmux->encapsulation = config->encapsulation;
list_initialize(&gsmmux->head);
gsmmux->rxbuf = (FAR uint8_t *)&gsmmux->channels[devcount];
for (i = 0; i < devcount; i++)
{
ret = gsmmux_tty_register(gsmmux, devlist[i], &gsmmux->channels[i]);
if (ret < 0)
{
gsmmux_err("Failed to register %s\n", devlist[i]);
goto errout;
}
}
return gsmmux;
errout:
while (i-- > 0)
{
gsmmux_tty_unregister(&gsmmux->channels[i], devlist[i]);
}
kmm_free(gsmmux);
return NULL;
}
* Name: gsmmux_initialize
*
* Description:
* Initialize the gsmmux device.
*
****************************************************************************/
#ifdef CONFIG_UART_GSMMUX_TTYGSM
int gsmmux_initialize(void)
{
FAR struct gsmmux_s *gsmmux;
const struct gsm_config config =
{
.encapsulation = GSMMUX_BASIC_MODE,
.initiator = GSMMUX_SLAVE,
.mru = CONFIG_UART_GSMMUX_BUFFER_SIZE,
.mtu = CONFIG_UART_GSMMUX_BUFFER_SIZE,
};
FAR const char *devlist[] =
{
#ifdef CONFIG_UART_GSMMUX_CONSOLE
"/dev/console",
#endif
#ifdef CONFIG_UART_GSMMUX_TTYGSM1
"/dev/ttyGSM1",
#endif
#ifdef CONFIG_UART_GSMMUX_TTYGSM2
"/dev/ttyGSM2",
#endif
#ifdef CONFIG_UART_GSMMUX_TTYGSM3
"/dev/ttyGSM3",
#endif
};
gsmmux = gsmmux_register(CONFIG_UART_GSMMUX_DEVNAME, &config,
devlist, nitems(devlist));
if (gsmmux == NULL)
{
gsmmux_err("Failed to register gsmmux\n");
return -EINVAL;
}
return OK;
}
#endif