/* * trunkdev.c * * (C) 2022 by Harald Welte * * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include "e1d.h" #include "log.h" /*********************************************************************** * low-level trunkdev routines ***********************************************************************/ static int trunkdev_specify(int fd, const char *name) { struct dahdi_trunkdev_open td_o = { 0 }; OSMO_STRLCPY_ARRAY(td_o.name, name); return ioctl(fd, DAHDI_TRUNKDEV_OPEN, &td_o); } /*********************************************************************** * osmo-e1d interface ***********************************************************************/ /* default dahdi chunk size: 8 chunks (in this case E1 frames) per read/write */ #define DAHDI_CHUNKSIZE 8 #define BYTES_PER_FRAME 32 /* one E1 line (DAHDI span) inside the trunkdev */ struct e1_trunkdev_line_data { unsigned int basechan; /* so far, only 0 supported */ unsigned int numchans; /* so far, onlt 32 supported */ }; /* one DAHDI trunkdev */ struct e1_trunkdev_intf_data { /* file descriptor to the character device /dev/dahdi/trunkdev */ struct osmo_fd ofd; }; /* file-descriptor call-back. Triggered by DAHDI via poll(), whenever * there is new E1 frame data available to read from trunkdev. The flow * control in transmit side is simple: We write as * many frames as we are reading */ static int dahdi_trunkdev_fd_cb(struct osmo_fd *ofd, unsigned int what) { struct e1_intf *e1i = ofd->data; struct e1_line *e1l = e1_intf_find_line(e1i, 0); uint8_t buf[DAHDI_CHUNKSIZE*BYTES_PER_FRAME]; int rc, len; OSMO_ASSERT(what & OSMO_FD_READ); len = read(ofd->fd, buf, sizeof(buf)); if (len <= 0) { LOGPIF(e1i, DTRUNKDEV, LOGL_ERROR, "Error %d during trunkdev read: %s\n", len, strerror(errno)); return len; } else if (len < (int) sizeof(buf)) { /* for some not yet known reason this happens quite often, typically 244 of 256 bytes, * followed by the remaining 32 bytes in the next read. No data is lost, it just * costs a lot of extra syscalls / context switches */ LOGPIF(e1i, DTRUNKDEV, LOGL_DEBUG, "Short read during trunkdev read: %d < %zu\n", len, sizeof(buf)); } if (len % BYTES_PER_FRAME) { LOGPIF(e1i, DTRUNKDEV, LOGL_ERROR, "Odd number of bytes during read: %d\n", len); return -EIO; } if (!e1l) { /* no line: discard input; transmit all-ff (BLUE) */ memset(buf, 0xff, len); } else { /* DAHDI trunkdev currently only supports one span/line per trunk */ rc = e1_line_demux_in(e1l, buf, len, -1); #if 0 if (rc < 0) { LOGPLI(e1l, DTRUNKDEV, LOGL_ERROR, "Error %d during e1_line_demux_in()\n", rc); return rc; } #endif /* only pull as many frames out of our muxer as we have just read from the trunk */ len = e1_line_mux_out(e1l, buf, len/BYTES_PER_FRAME); if (len < 0) { LOGPLI(e1l, DTRUNKDEV, LOGL_ERROR, "Error %d during mux_out\n", len); return len; } } rc = write(ofd->fd, buf, len); if (rc <= 0) { LOGPIF(e1i, DTRUNKDEV, LOGL_ERROR, "Error %d during trunkdev write: %s\n", rc, strerror(errno)); return rc; } else if (rc < len) { LOGPIF(e1i, DTRUNKDEV, LOGL_ERROR, "Short write during trunkdev write: %d < %d\n", rc, len); } return 0; } int e1_dahdi_trunkdev_open(struct e1_intf *e1i) { struct dahdi_trunkdev_create _cr; struct e1_trunkdev_intf_data *tid; struct e1_line *e1l; int rc, fd; /* various sanity checks */ if (e1i->drv != E1_DRIVER_DAHDI_TRUNKDEV) { LOGPIF(e1i, DTRUNKDEV, LOGL_ERROR, "Cannot open non-trunkdev trunk as trunkdev\n"); return -EINVAL; } if (!e1i->dahdi_trunkdev.name) { LOGPIF(e1i, DTRUNKDEV, LOGL_ERROR, "Cannot open trunkdev without name\n"); return -EINVAL; } if (strlen(e1i->dahdi_trunkdev.name) > sizeof(_cr.name)-1) { LOGPIF(e1i, DTRUNKDEV, LOGL_ERROR, "Cannot open trunkdev with excessively long name\n"); return -EINVAL; } if (e1i->drv_data) { LOGPIF(e1i, DTRUNKDEV, LOGL_NOTICE, "Cannot open trunkdev that's already open\n"); return -EBUSY; } /* open the trunkdev */ fd = open("/dev/dahdi/trunkdev", O_RDWR); if (fd < 0) { LOGPIF(e1i, DTRUNKDEV, LOGL_ERROR, "Cannot open /dev/dahdi/trunkdev: %s\n", strerror(errno)); return -errno; } /* try to select the trunk by name */ rc = trunkdev_specify(fd, e1i->dahdi_trunkdev.name); if (rc < 0) { LOGPIF(e1i, DTRUNKDEV, LOGL_ERROR, "Unable to specify trunkdev '%s': %s\n", e1i->dahdi_trunkdev.name, strerror(errno)); /* TODO: auto- create on demand? */ close(fd); return -errno; } LOGPIF(e1i, DTRUNKDEV, LOGL_NOTICE, "Successfully opened trunkdev '%s'\n", e1i->dahdi_trunkdev.name); tid = talloc_zero(e1i->e1d->ctx, struct e1_trunkdev_intf_data); OSMO_ASSERT(tid); osmo_fd_setup(&tid->ofd, fd, OSMO_FD_READ, dahdi_trunkdev_fd_cb, e1i, e1i->id); osmo_fd_register(&tid->ofd); e1i->drv_data = tid; /* ensure line0 exists */ if (!e1_intf_find_line(e1i, 0)) { e1l = e1_line_new(e1i, 0, NULL); e1l->mode = E1_LINE_MODE_E1OIP; } /* activate line */ llist_for_each_entry(e1l, &e1i->lines, list) e1_line_active(e1l); return 0; } int e1_dahdi_trunkdev_close(struct e1_intf *e1i) { struct e1_trunkdev_intf_data *tid = e1i->drv_data; int rc; if (e1i->drv != E1_DRIVER_DAHDI_TRUNKDEV) { LOGPIF(e1i, DTRUNKDEV, LOGL_ERROR, "Cannot close non-trunkdev trunk as trunkdev\n"); return -EINVAL; } if (!tid) { LOGPIF(e1i, DTRUNKDEV, LOGL_DEBUG, "No need to close trunkdev; was not open\n"); return 0; } osmo_fd_unregister(&tid->ofd); /* we're not deleting the dahdi trunkdev as that might upset the applications using * the channel-side of it */ rc = close(tid->ofd.fd); if (rc < 0) LOGPIF(e1i, DTRUNKDEV, LOGL_ERROR, "Error closing trunkdev: %s\n", strerror(errno)); talloc_free(tid); e1i->drv_data = tid = NULL; LOGPIF(e1i, DTRUNKDEV, LOGL_NOTICE, "Closed trunkdev '%s'\n", e1i->dahdi_trunkdev.name); return 0; }