2020-12-26 13:20:53 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
|
|
|
|
/* Osmocom icE1usb driver
|
|
|
|
* Copyright (C) 2020 by Harald Welte <laforge@osmocom.org>
|
|
|
|
* inspired by osmo-e1d by Sylvain Munaut */
|
|
|
|
|
|
|
|
/* The Osmocom icE1usb is a modern, software-defined open hardware and
|
|
|
|
open source firmware design for a USB E1 interface. You can find more
|
|
|
|
information at https://osmocom.org/projects/e1-t1-adapter/wiki/IcE1usb
|
|
|
|
|
|
|
|
The hardware design can be found at https://git.osmocom.org/osmo-e1-hardware
|
|
|
|
|
|
|
|
The FPGA gateware and associated embedded firmware is hosted in the same
|
|
|
|
git repository. Some parts are in submodules (be sure to use recursive
|
|
|
|
clone)
|
|
|
|
|
|
|
|
This DAHDI driver allows the use of the icE1usb just like any other
|
|
|
|
E1/PRI interface device supported by DAHDI. When using this DAHDI
|
|
|
|
driver, osmo-e1d most not be used. The DAHDI driver is a replacement /
|
|
|
|
alternative for osmo-e1d.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define DEBUG
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/usb.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/pm_runtime.h>
|
|
|
|
|
|
|
|
#include <dahdi/kernel.h>
|
|
|
|
|
|
|
|
#include "ice1usb_proto.h"
|
|
|
|
|
|
|
|
#define VERSION "0.1"
|
|
|
|
|
|
|
|
/* number of isochronous frames per URB */
|
|
|
|
#define ICE1USB_MAX_ISOC_FRAMES 4
|
|
|
|
|
|
|
|
/* number of URBs per endpoint */
|
|
|
|
#define ICE1USB_NUM_URBS 4
|
|
|
|
|
|
|
|
#define ieu_dbg(x, fmt, args ...) \
|
|
|
|
dev_dbg(&((x)->usb_intf->dev), fmt, ## args)
|
|
|
|
#define ieu_info(x, fmt, args ...) \
|
|
|
|
dev_info(&((x)->usb_intf->dev), fmt, ## args)
|
|
|
|
#define ieu_warn(x, fmt, args ...) \
|
|
|
|
dev_warn(&((x)->usb_intf->dev), fmt, ## args)
|
|
|
|
#define ieu_err(x, fmt, args ...) \
|
|
|
|
dev_err(&((x)->usb_intf->dev), fmt, ## args)
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* data structures
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
|
|
enum ice1usb_flags {
|
|
|
|
ICE1USB_ISOC_RUNNING,
|
|
|
|
ICE1USB_IRQ_RUNNING,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ice1usb {
|
|
|
|
/* USB device and interface we operate on */
|
|
|
|
struct usb_device *usb_dev;
|
|
|
|
struct usb_interface *usb_intf;
|
|
|
|
/* altsetting for 'OFF' state */
|
|
|
|
const struct usb_host_interface *alt_off;
|
|
|
|
/* altsetting for 'ON' state */
|
|
|
|
const struct usb_host_interface *alt_on;
|
|
|
|
struct {
|
|
|
|
struct usb_anchor iso_in;
|
|
|
|
struct usb_anchor iso_out;
|
|
|
|
struct usb_anchor iso_fb;
|
|
|
|
struct usb_anchor irq;
|
|
|
|
} anchor;
|
|
|
|
/* USB endpoint numbers */
|
|
|
|
struct {
|
|
|
|
const struct usb_endpoint_descriptor *iso_in;
|
|
|
|
const struct usb_endpoint_descriptor *iso_out;
|
|
|
|
const struct usb_endpoint_descriptor *iso_fb;
|
|
|
|
const struct usb_endpoint_descriptor *irq;
|
|
|
|
} ep;
|
2022-01-01 08:49:01 +00:00
|
|
|
struct {
|
|
|
|
struct ice1usb_tx_config tx;
|
|
|
|
} cfg;
|
2022-01-01 09:22:29 +00:00
|
|
|
/* last received error interrupt */
|
|
|
|
struct ice1usb_irq_err last_err;
|
|
|
|
struct {
|
|
|
|
uint32_t ovfl;
|
|
|
|
uint32_t unfl;
|
|
|
|
} count;
|
2020-12-26 13:20:53 +00:00
|
|
|
/* enum ice1usb_flags */
|
|
|
|
unsigned long flags;
|
|
|
|
/* feedback flow-control */
|
|
|
|
struct {
|
|
|
|
uint32_t r_acc;
|
|
|
|
uint32_t r_sw;
|
|
|
|
} fc;
|
|
|
|
/* DAHDI driver related bits */
|
|
|
|
struct {
|
|
|
|
struct dahdi_device *dev;
|
|
|
|
struct dahdi_span span;
|
|
|
|
struct dahdi_chan *chans[31];
|
|
|
|
/* [next rx byte] offset into chan[i]->readchunk */
|
|
|
|
unsigned int readchunk_idx;
|
|
|
|
/* [next tx byte] offset into chan[i]->writechunk */
|
|
|
|
unsigned int writechunk_idx;
|
|
|
|
} dahdi;
|
|
|
|
/* is the device still present (true) or already absent/unplugged (false) */
|
|
|
|
bool present;
|
|
|
|
/* spinlock protecting concurrent access to fc, {read,write}chunk_idx, ... */
|
|
|
|
spinlock_t lock;
|
|
|
|
/* maximum number of 32-byte E1 frames to send in one ISO OUT packet */
|
|
|
|
unsigned int max_fts;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void ice1usb_free(struct ice1usb *ieu)
|
|
|
|
{
|
|
|
|
usb_put_dev(ieu->usb_dev);
|
|
|
|
kfree(ieu);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void e1u_free_channels(struct ice1usb *ieu)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ieu->dahdi.chans); i++) {
|
|
|
|
kfree(ieu->dahdi.chans[i]);
|
|
|
|
ieu->dahdi.chans[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int e1u_alloc_channels(struct ice1usb *ieu)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ieu->dahdi.chans); i++) {
|
|
|
|
struct dahdi_chan *chan;
|
|
|
|
kfree(ieu->dahdi.chans[i]);
|
|
|
|
chan = ieu->dahdi.chans[i] = kzalloc(sizeof(*chan), GFP_KERNEL);
|
|
|
|
if (!chan) {
|
|
|
|
e1u_free_channels(ieu);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
chan->pvt = ieu;
|
|
|
|
snprintf(chan->name, sizeof(chan->name)-1, "%s/%d",
|
|
|
|
ieu->dahdi.span.name, i+1);
|
|
|
|
chan->chanpos = i+1;
|
|
|
|
chan->sigcap = DAHDI_SIG_CLEAR | DAHDI_SIG_MTP2 | DAHDI_SIG_SF;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-01-01 08:49:01 +00:00
|
|
|
static const char *tx_mode_str(enum ice1usb_tx_mode tx_mode)
|
|
|
|
{
|
|
|
|
switch (tx_mode) {
|
|
|
|
case ICE1USB_TX_MODE_TRANSP:
|
|
|
|
return "TRANSPARENT";
|
|
|
|
case ICE1USB_TX_MODE_TS0:
|
|
|
|
return "TS0";
|
|
|
|
case ICE1USB_TX_MODE_TS0_CRC4:
|
|
|
|
return "TS0_CRC4";
|
|
|
|
case ICE1USB_TX_MODE_TS0_CRC4_E:
|
|
|
|
return "TS0_CRC4_E";
|
|
|
|
default:
|
|
|
|
return "unknown";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *tx_timing_str(enum ice1usb_tx_timing tx_timing)
|
|
|
|
{
|
|
|
|
switch (tx_timing) {
|
|
|
|
case ICE1USB_TX_TIME_SRC_LOCAL:
|
|
|
|
return "LOCAL";
|
|
|
|
case ICE1USB_TX_TIME_SRC_REMOTE:
|
|
|
|
return "REMOTE";
|
|
|
|
default:
|
|
|
|
return "unknown";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *tx_ext_loopback_str(enum ice1usb_tx_ext_loopback ext_lb)
|
|
|
|
{
|
|
|
|
switch (ext_lb) {
|
|
|
|
case ICE1USB_TX_EXT_LOOPBACK_OFF:
|
|
|
|
return "OFF";
|
|
|
|
case ICE1USB_TX_EXT_LOOPBACK_SAME:
|
|
|
|
return "SAME";
|
|
|
|
case ICE1USB_TX_EXT_LOOPBACK_CROSS:
|
|
|
|
return "CROSS";
|
|
|
|
default:
|
|
|
|
return "unknown";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define USB_RT_VEND_IF (USB_TYPE_VENDOR | USB_RECIP_INTERFACE)
|
|
|
|
|
|
|
|
/* synchronous request, may block up to 1s, only called from process context! */
|
|
|
|
static int ice1usb_tx_config(struct ice1usb *ieu)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
ieu_info(ieu, "TX-CONFIG (crc4=%s, timing=%s, ext_loopback=%s, tx_yellow_alarm=%u)\n",
|
|
|
|
tx_mode_str(ieu->cfg.tx.mode), tx_timing_str(ieu->cfg.tx.timing),
|
|
|
|
tx_ext_loopback_str(ieu->cfg.tx.ext_loopback), ieu->cfg.tx.alarm);
|
|
|
|
|
|
|
|
rc = usb_control_msg(ieu->usb_dev, usb_sndctrlpipe(ieu->usb_dev, 0),
|
|
|
|
ICE1USB_INTF_SET_TX_CFG, USB_RT_VEND_IF,
|
|
|
|
0, 0, &ieu->cfg.tx, sizeof(ieu->cfg.tx), 1000);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
if (rc != sizeof(ieu->cfg.tx))
|
|
|
|
return -EIO;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-26 13:20:53 +00:00
|
|
|
/***********************************************************************
|
|
|
|
* ISOCHRONOUS transfers
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
|
|
static inline void __fill_isoc_descriptor(struct urb *urb, unsigned int max_pack_size)
|
|
|
|
{
|
|
|
|
unsigned int i, offset = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < ICE1USB_MAX_ISOC_FRAMES; i++) {
|
|
|
|
urb->iso_frame_desc[i].offset = offset;
|
|
|
|
urb->iso_frame_desc[i].length = max_pack_size;
|
|
|
|
offset += max_pack_size;
|
|
|
|
};
|
|
|
|
urb->number_of_packets = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* allocate + submit an isochronous URB for given EP; anchor it */
|
|
|
|
static int ice1usb_submit_isoc_urb(struct ice1usb *ieu,
|
|
|
|
const struct usb_endpoint_descriptor *ep,
|
|
|
|
struct usb_anchor *anchor,
|
|
|
|
usb_complete_t compl, gfp_t mem_flags)
|
|
|
|
{
|
|
|
|
unsigned int max_pack_size = le16_to_cpu(ep->wMaxPacketSize);
|
|
|
|
unsigned int pipe, size, rc;
|
|
|
|
struct urb *urb;
|
|
|
|
uint8_t *buf;
|
|
|
|
|
|
|
|
urb = usb_alloc_urb(ICE1USB_MAX_ISOC_FRAMES, mem_flags);
|
|
|
|
if (!urb)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
size = max_pack_size * ICE1USB_MAX_ISOC_FRAMES;
|
|
|
|
buf = kmalloc(size, mem_flags);
|
|
|
|
if (!buf) {
|
|
|
|
usb_free_urb(urb);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ep->bEndpointAddress & USB_DIR_IN)
|
|
|
|
pipe = usb_rcvisocpipe(ieu->usb_dev, ep->bEndpointAddress);
|
|
|
|
else
|
|
|
|
pipe = usb_sndisocpipe(ieu->usb_dev, ep->bEndpointAddress);
|
|
|
|
|
|
|
|
usb_fill_int_urb(urb, ieu->usb_dev, pipe, buf, size, compl, ieu,
|
|
|
|
ep->bInterval);
|
|
|
|
urb->transfer_flags = URB_FREE_BUFFER | URB_ISO_ASAP;
|
|
|
|
|
|
|
|
__fill_isoc_descriptor(urb, max_pack_size);
|
|
|
|
|
|
|
|
usb_anchor_urb(urb, anchor);
|
|
|
|
|
|
|
|
rc = usb_submit_urb(urb, mem_flags);
|
|
|
|
if (rc < 0) {
|
|
|
|
/* -EPERM: urb is being killed; -ENODEV: device got disconnected */
|
|
|
|
if (rc != -EPERM && rc != -ENODEV && rc != -ENOENT)
|
|
|
|
ieu_err(ieu, "EP 0x%02x urb %p submission failed (%d)",
|
|
|
|
ep->bEndpointAddress, urb, -rc);
|
|
|
|
usb_unanchor_urb(urb);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* automatically free the URB (and its transfer buffer) once complete */
|
|
|
|
usb_free_urb(urb);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* process one incoming E1 frame (32 bytes; one for each TS) */
|
|
|
|
static void _span_demux_one_frame(struct ice1usb *ieu, const uint8_t *data)
|
|
|
|
{
|
|
|
|
struct dahdi_span *dspan = &ieu->dahdi.span;
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ieu->dahdi.chans); i++) {
|
|
|
|
struct dahdi_chan *chan = ieu->dahdi.chans[i];
|
|
|
|
chan->readchunk[ieu->dahdi.readchunk_idx] = data[1+i];
|
|
|
|
}
|
|
|
|
ieu->dahdi.readchunk_idx++;
|
|
|
|
|
|
|
|
/* DAHDI_CHUNKSIZE is 8, meaning every channel (timeslot) wants data for 8
|
|
|
|
* bytes (PCM samples) every time _dahdi_receive() is called */
|
|
|
|
if (ieu->dahdi.readchunk_idx == DAHDI_CHUNKSIZE) {
|
|
|
|
_dahdi_receive(dspan);
|
|
|
|
ieu->dahdi.readchunk_idx = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* IN endpoint URB completes */
|
|
|
|
static void iso_in_complete(struct urb *urb)
|
|
|
|
{
|
|
|
|
struct ice1usb *ieu = urb->context;
|
|
|
|
unsigned int i;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
//ieu_dbg(ieu, "IN urb %p completion (%d)", urb, urb->status);
|
|
|
|
|
|
|
|
switch (urb->status) {
|
|
|
|
case 0:
|
|
|
|
for (i = 0; i < urb->number_of_packets; i++) {
|
|
|
|
unsigned int offset = urb->iso_frame_desc[i].offset;
|
|
|
|
unsigned int length = urb->iso_frame_desc[i].actual_length;
|
|
|
|
unsigned long flags;
|
|
|
|
unsigned int j;
|
|
|
|
|
|
|
|
if (urb->iso_frame_desc[i].status) {
|
|
|
|
ieu_err(ieu, "IN urb %p frame %u status %d",
|
|
|
|
urb, i, urb->iso_frame_desc[i].status);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* process received data */
|
|
|
|
spin_lock_irqsave(&ieu->lock, flags);
|
|
|
|
for (j = 4; j < length; j += 32)
|
|
|
|
_span_demux_one_frame(ieu, urb->transfer_buffer + offset + j);
|
|
|
|
spin_unlock_irqrestore(&ieu->lock, flags);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case -ENOENT:
|
|
|
|
case -ESHUTDOWN:
|
|
|
|
/* Avoid suspend failed when usb_kill_urb */
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
ieu_err(ieu, "IN urb %p completion (%d)", urb, urb->status);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* re-submit unless stopped */
|
|
|
|
if (!test_bit(ICE1USB_ISOC_RUNNING, &ieu->flags))
|
|
|
|
return;
|
|
|
|
|
|
|
|
usb_anchor_urb(urb, &ieu->anchor.iso_in);
|
|
|
|
usb_mark_last_busy(ieu->usb_dev);
|
|
|
|
|
|
|
|
rc = usb_submit_urb(urb, GFP_ATOMIC);
|
|
|
|
if (rc < 0) {
|
|
|
|
/* -EPERM: urb is being killed; -ENODEV: device got disconnected */
|
|
|
|
if (rc != -EPERM && rc != -ENODEV && rc != -ENOENT)
|
|
|
|
ieu_err(ieu, "IN urb %p submission failed (%d)", urb, -rc);
|
|
|
|
usb_unanchor_urb(urb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Feedback endpoint URB completes */
|
|
|
|
static void iso_fb_complete(struct urb *urb)
|
|
|
|
{
|
|
|
|
struct ice1usb *ieu = urb->context;
|
|
|
|
unsigned int i;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
//ieu_dbg(ieu, "FB urb %p completion (%d)", urb, urb->status);
|
|
|
|
|
|
|
|
switch (urb->status) {
|
|
|
|
case 0:
|
|
|
|
for (i = 0; i < urb->number_of_packets; i++) {
|
|
|
|
unsigned int offset = urb->iso_frame_desc[i].offset;
|
|
|
|
unsigned int length = urb->iso_frame_desc[i].actual_length;
|
|
|
|
const uint8_t *rx = urb->transfer_buffer + offset;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
if (urb->iso_frame_desc[i].status) {
|
|
|
|
ieu_err(ieu, "FB urb %p frame %u status %d",
|
|
|
|
urb, i, urb->iso_frame_desc[i].status);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (urb->iso_frame_desc[i].status || length < 3)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* process received data */
|
|
|
|
spin_lock_irqsave(&ieu->lock, flags);
|
|
|
|
ieu->fc.r_sw = (rx[2] << 16) | (rx[1] << 8) | rx[0];
|
|
|
|
spin_unlock_irqrestore(&ieu->lock, flags);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case -ENOENT:
|
|
|
|
case -ESHUTDOWN:
|
|
|
|
/* Avoid suspend failed when usb_kill_urb */
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
ieu_err(ieu, "FB urb %p completion (%d)", urb, urb->status);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* re-submit unless stopped */
|
|
|
|
if (!test_bit(ICE1USB_ISOC_RUNNING, &ieu->flags))
|
|
|
|
return;
|
|
|
|
|
|
|
|
usb_anchor_urb(urb, &ieu->anchor.iso_fb);
|
|
|
|
usb_mark_last_busy(ieu->usb_dev);
|
|
|
|
|
|
|
|
rc = usb_submit_urb(urb, GFP_ATOMIC);
|
|
|
|
if (rc < 0) {
|
|
|
|
/* -EPERM: urb is being killed; -ENODEV: device got disconnected */
|
|
|
|
if (rc != -EPERM && rc != -ENODEV && rc != -ENOENT)
|
|
|
|
dev_err(&ieu->usb_dev->dev, "FB urb %p submission failed (%d)", urb, -rc);
|
|
|
|
usb_unanchor_urb(urb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* multiplex one E1 frame (32 bytes) and write it to 'out'; caller must hold ieu->lock */
|
|
|
|
static void _span_mux_one_frame(struct ice1usb *ieu, uint8_t *out)
|
|
|
|
{
|
|
|
|
struct dahdi_span *dspan = &ieu->dahdi.span;
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
out[0] = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ieu->dahdi.chans); i++) {
|
|
|
|
struct dahdi_chan *chan = ieu->dahdi.chans[i];
|
|
|
|
out[1+i] = chan->writechunk[ieu->dahdi.writechunk_idx];
|
|
|
|
}
|
|
|
|
ieu->dahdi.writechunk_idx++;
|
|
|
|
|
|
|
|
/* DAHDI_CHUNKSIZE is 8, meaning every channel (timeslot) wants data for 8
|
|
|
|
* bytes (PCM samples) every time _dahdi_receive() is called */
|
|
|
|
if (ieu->dahdi.writechunk_idx == DAHDI_CHUNKSIZE) {
|
|
|
|
_dahdi_transmit(dspan);
|
|
|
|
ieu->dahdi.writechunk_idx = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* OUT endpoint URB completes */
|
|
|
|
static void iso_out_complete(struct urb *urb)
|
|
|
|
{
|
|
|
|
struct ice1usb *ieu = urb->context;
|
|
|
|
unsigned int i, j;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
//ieu_dbg(ieu, "OUT urb %p completion (%d) %d", urb, urb->status, urb->number_of_packets);
|
|
|
|
|
|
|
|
switch (urb->status) {
|
|
|
|
case 0:
|
|
|
|
for (i = 0; i < urb->number_of_packets; i++) {
|
|
|
|
unsigned int offset = urb->iso_frame_desc[i].offset;
|
|
|
|
uint8_t *tx = urb->transfer_buffer + offset;
|
|
|
|
unsigned int fts; // frames to send
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
if (urb->iso_frame_desc[i].status) {
|
|
|
|
ieu_err(ieu, "OUT urb %p frame %u status %d",
|
|
|
|
urb, i, urb->iso_frame_desc[i].status);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
spin_lock_irqsave(&ieu->lock, flags);
|
|
|
|
|
|
|
|
/* flow control */
|
|
|
|
ieu->fc.r_acc += ieu->fc.r_sw;
|
|
|
|
fts = ieu->fc.r_acc >> 10;
|
|
|
|
if (fts < 4)
|
|
|
|
fts = 4;
|
|
|
|
else if (fts > ieu->max_fts)
|
|
|
|
fts = ieu->max_fts;
|
|
|
|
|
|
|
|
ieu->fc.r_acc -= fts << 10;
|
|
|
|
if (ieu->fc.r_acc & 0x80000000)
|
|
|
|
ieu->fc.r_acc = 0;
|
|
|
|
|
|
|
|
for (j = 0; j < fts; j++)
|
|
|
|
_span_mux_one_frame(ieu, tx + 4 + j*32);
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&ieu->lock, flags);
|
|
|
|
|
|
|
|
urb->iso_frame_desc[i].length = 4 + fts * 32;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case -ENOENT:
|
|
|
|
case -ESHUTDOWN:
|
|
|
|
/* Avoid suspend failed when usb_kill_urb */
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
ieu_err(ieu, "OUT urb %p completion (%d)", urb, urb->status);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* re-submit unless stopped */
|
|
|
|
if (!test_bit(ICE1USB_ISOC_RUNNING, &ieu->flags))
|
|
|
|
return;
|
|
|
|
|
|
|
|
usb_anchor_urb(urb, &ieu->anchor.iso_out);
|
|
|
|
usb_mark_last_busy(ieu->usb_dev);
|
|
|
|
|
|
|
|
rc = usb_submit_urb(urb, GFP_ATOMIC);
|
|
|
|
if (rc < 0) {
|
|
|
|
/* -EPERM: urb is being killed; -ENODEV: device got disconnected */
|
|
|
|
if (rc != -EPERM && rc != -ENODEV && rc != -ENOENT)
|
|
|
|
ieu_err(ieu, "OUT urb %p submission failed (%d)", urb, -rc);
|
|
|
|
usb_unanchor_urb(urb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* INTERRUPT transfers
|
|
|
|
***********************************************************************/
|
|
|
|
|
2022-01-01 09:22:29 +00:00
|
|
|
static uint32_t counter_delta(uint16_t old, uint16_t new)
|
|
|
|
{
|
|
|
|
if (new == old)
|
|
|
|
return 0;
|
|
|
|
else if (new > old)
|
|
|
|
return new - old;
|
|
|
|
else
|
|
|
|
return 0xffff - (old - new);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ice1usb_update_counters(struct ice1usb *ieu, const struct ice1usb_irq_err *err)
|
|
|
|
{
|
|
|
|
ieu->dahdi.span.count.fe += counter_delta(ieu->last_err.align, err->align);
|
|
|
|
ieu->dahdi.span.count.crc4 += counter_delta(ieu->last_err.crc, err->crc);
|
|
|
|
/* no DAHDI general counters for overflows / underflows */
|
|
|
|
ieu->count.ovfl += counter_delta(ieu->last_err.ovfl, err->ovfl);
|
|
|
|
ieu->count.unfl += counter_delta(ieu->last_err.unfl, err->unfl);
|
|
|
|
memcpy(&ieu->last_err, err, sizeof(ieu->last_err));
|
|
|
|
}
|
|
|
|
|
2022-01-02 10:27:40 +00:00
|
|
|
#define ALL_RY_ALARMS (DAHDI_ALARM_RED | DAHDI_ALARM_YELLOW | \
|
|
|
|
DAHDI_ALARM_LFA | DAHDI_ALARM_LMFA | ICE1USB_ERR_F_LOS)
|
2021-12-31 19:17:07 +00:00
|
|
|
|
2020-12-26 13:20:53 +00:00
|
|
|
/* interrupt EP completes: Process and resubmit */
|
|
|
|
static void ice1usb_irq_complete(struct urb *urb)
|
|
|
|
{
|
|
|
|
const struct ice1usb_irq *irq;
|
|
|
|
struct ice1usb *ieu = urb->context;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
//ieu_dbg(ieu, "IRQ urb %p completion (%d)", urb, urb->status);
|
|
|
|
|
|
|
|
if (urb->status == 0 && urb->actual_length >= sizeof(*irq)) {
|
|
|
|
const struct ice1usb_irq_err *err;
|
2021-12-31 19:17:07 +00:00
|
|
|
unsigned int alarms = 0;
|
2020-12-26 13:20:53 +00:00
|
|
|
irq = (struct ice1usb_irq *) urb->transfer_buffer;
|
|
|
|
switch (irq->type) {
|
2021-12-31 18:37:32 +00:00
|
|
|
case ICE1USB_IRQ_T_ERRCNT:
|
2020-12-26 13:20:53 +00:00
|
|
|
err = &irq->u.errors;
|
|
|
|
ieu_dbg(ieu, "IRQ: crc=%u, align=%u, ovfl=%u, unfl=%u, flags=%x",
|
|
|
|
le16_to_cpu(err->crc), le16_to_cpu(err->align),
|
|
|
|
le16_to_cpu(err->ovfl), le16_to_cpu(err->unfl), err->flags);
|
2021-12-31 19:17:07 +00:00
|
|
|
/* update alarms. Other drivers have some de-bouncing timers, I guess
|
|
|
|
* we can get away without doing this. */
|
|
|
|
if (err->flags & ICE1USB_ERR_F_LOS)
|
|
|
|
alarms |= DAHDI_ALARM_RED | DAHDI_ALARM_LOS;
|
|
|
|
if (err->flags & ICE1USB_ERR_F_ALIGN_ERR)
|
|
|
|
alarms |= DAHDI_ALARM_RED | DAHDI_ALARM_LFA | DAHDI_ALARM_LMFA;
|
2022-01-02 10:27:40 +00:00
|
|
|
if (err->flags & ICE1USB_ERR_F_RAI)
|
|
|
|
alarms |= DAHDI_ALARM_YELLOW;
|
|
|
|
ieu->dahdi.span.alarms &= ~ALL_RY_ALARMS;
|
2021-12-31 19:17:07 +00:00
|
|
|
ieu->dahdi.span.alarms |= alarms;
|
|
|
|
dahdi_alarm_notify(&ieu->dahdi.span);
|
2022-01-01 09:22:29 +00:00
|
|
|
ice1usb_update_counters(ieu, err);
|
2020-12-26 13:20:53 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (urb->status == -ENOENT) {
|
|
|
|
/* Avoid suspend failed when usb_kill_urb */
|
|
|
|
return;
|
|
|
|
} else
|
|
|
|
ieu_err(ieu, "IRQ urb %p completion (%d)", urb, urb->status);
|
|
|
|
|
|
|
|
/* re-submit unless stopped */
|
|
|
|
if (!test_bit(ICE1USB_IRQ_RUNNING, &ieu->flags))
|
|
|
|
return;
|
|
|
|
|
|
|
|
usb_anchor_urb(urb, &ieu->anchor.irq);
|
|
|
|
usb_mark_last_busy(ieu->usb_dev);
|
|
|
|
|
|
|
|
rc = usb_submit_urb(urb, GFP_ATOMIC);
|
|
|
|
if (rc < 0) {
|
|
|
|
/* -EPERM: urb is being killed; -ENODEV: device got disconnected */
|
|
|
|
if (rc != -EPERM && rc != -ENODEV && rc != -ENOENT)
|
|
|
|
ieu_err(ieu, "IRU urb %p submission failed (%d)", urb, -rc);
|
|
|
|
usb_unanchor_urb(urb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* allocate + submit + anchor an URB for the interrupt endpoint */
|
|
|
|
static int ice1usb_submit_irq_urb(struct ice1usb *ieu, gfp_t mem_flags)
|
|
|
|
{
|
|
|
|
unsigned int pipe, size, rc;
|
|
|
|
struct urb *urb;
|
|
|
|
uint8_t *buf;
|
|
|
|
|
|
|
|
if (!ieu->ep.irq)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
urb = usb_alloc_urb(0, mem_flags);
|
|
|
|
if (!urb)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
size = le16_to_cpu(ieu->ep.irq->wMaxPacketSize);
|
|
|
|
buf = kmalloc(size, mem_flags);
|
|
|
|
if (!buf) {
|
|
|
|
usb_free_urb(urb);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
pipe = usb_rcvintpipe(ieu->usb_dev, ieu->ep.irq->bEndpointAddress);
|
|
|
|
|
|
|
|
usb_fill_int_urb(urb, ieu->usb_dev, pipe, buf, size,
|
|
|
|
ice1usb_irq_complete, ieu, ieu->ep.irq->bInterval);
|
|
|
|
|
|
|
|
urb->transfer_flags |= URB_FREE_BUFFER;
|
|
|
|
|
|
|
|
usb_anchor_urb(urb, &ieu->anchor.irq);
|
|
|
|
|
|
|
|
rc = usb_submit_urb(urb, mem_flags);
|
|
|
|
if (rc < 0) {
|
|
|
|
/* -EPERM: urb is being killed; -ENODEV: device got disconnected */
|
|
|
|
if (rc != -EPERM && rc != -ENODEV && rc != -ENOENT)
|
|
|
|
ieu_err(ieu, "IRQ urb %p submission failed (%d)", urb, -rc);
|
|
|
|
usb_unanchor_urb(urb);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* automatically free the URB (and its transfer buffer) once complete */
|
|
|
|
usb_free_urb(urb);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* DAHDI integration
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
|
|
static int e1u_d_startup(struct file *file, struct dahdi_span *span);
|
2021-12-31 18:24:44 +00:00
|
|
|
static int e1u_d_shutdown(struct dahdi_span *span);
|
2020-12-26 13:20:53 +00:00
|
|
|
|
|
|
|
static int e1u_d_spanconfig(struct file *file, struct dahdi_span *span,
|
|
|
|
struct dahdi_lineconfig *lc)
|
|
|
|
{
|
|
|
|
struct ice1usb *ieu = container_of(span, struct ice1usb, dahdi.span);
|
|
|
|
unsigned int i;
|
2022-01-01 08:49:01 +00:00
|
|
|
int rc;
|
2020-12-26 13:20:53 +00:00
|
|
|
|
|
|
|
ieu_dbg(ieu, "entering %s", __FUNCTION__);
|
|
|
|
|
|
|
|
if (lc->sync < 0)
|
|
|
|
lc->sync = 0;
|
|
|
|
if (lc->sync > 1) {
|
|
|
|
ieu_warn(ieu, "Cannot set clock priority on span %d to %d\n",
|
|
|
|
span->spanno, lc->sync);
|
|
|
|
lc->sync = 0;
|
|
|
|
}
|
|
|
|
|
2022-01-01 08:49:01 +00:00
|
|
|
if (span->lineconfig & DAHDI_CONFIG_CRC4)
|
|
|
|
ieu->cfg.tx.mode = ICE1USB_TX_MODE_TS0_CRC4;
|
|
|
|
else
|
|
|
|
ieu->cfg.tx.mode = ICE1USB_TX_MODE_TS0;
|
|
|
|
|
|
|
|
if (lc->sync > 0)
|
|
|
|
ieu->cfg.tx.timing = ICE1USB_TX_TIME_SRC_LOCAL;
|
|
|
|
else
|
|
|
|
ieu->cfg.tx.timing = ICE1USB_TX_TIME_SRC_REMOTE;
|
|
|
|
|
|
|
|
/* (re-)set to sane defaults */
|
|
|
|
ieu->cfg.tx.ext_loopback = ICE1USB_TX_EXT_LOOPBACK_OFF;
|
|
|
|
ieu->cfg.tx.alarm = 0;
|
2020-12-26 13:20:53 +00:00
|
|
|
|
|
|
|
for (i = 0; i < span->channels; i++) {
|
|
|
|
struct dahdi_chan *const chan = ieu->dahdi.chans[i];
|
|
|
|
chan->sigcap = DAHDI_SIG_CLEAR | DAHDI_SIG_MTP2 | DAHDI_SIG_SF;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we're already running, then go ahead and apply the changes */
|
2021-12-31 18:24:44 +00:00
|
|
|
if (span->flags & DAHDI_FLAG_RUNNING) {
|
|
|
|
/* there should probably be a more elegant way to do this, but
|
|
|
|
* we don't expect people to keep re-configuring their span all
|
|
|
|
* day long, so a brief interruption is deemed acceptable */
|
|
|
|
e1u_d_shutdown(span);
|
2022-01-01 08:49:01 +00:00
|
|
|
rc = ice1usb_tx_config(ieu);
|
2020-12-26 13:20:53 +00:00
|
|
|
e1u_d_startup(file, span);
|
2022-01-01 08:49:01 +00:00
|
|
|
} else
|
|
|
|
rc = ice1usb_tx_config(ieu);
|
2020-12-26 13:20:53 +00:00
|
|
|
|
2022-01-01 08:49:01 +00:00
|
|
|
return rc;
|
2020-12-26 13:20:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int e1u_d_chanconfig(struct file *file, struct dahdi_chan *chan,
|
|
|
|
int sigtype)
|
|
|
|
{
|
|
|
|
struct ice1usb *ieu = chan->pvt;
|
|
|
|
bool already_running;
|
|
|
|
|
|
|
|
already_running = ieu->dahdi.span.flags & DAHDI_FLAG_RUNNING;
|
|
|
|
ieu_info(ieu, "%sconfigured channel %d (%s) sigtype %d\n",
|
|
|
|
already_running ? "Re":"", chan->channo, chan->name, sigtype);
|
|
|
|
|
|
|
|
/* FIXME: we can probably remove this completely? */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ice1usb_set_altif(struct ice1usb *ieu, bool on);
|
|
|
|
|
|
|
|
static int e1u_d_startup(struct file *file, struct dahdi_span *span)
|
|
|
|
{
|
|
|
|
struct ice1usb *ieu = container_of(span, struct ice1usb, dahdi.span);
|
|
|
|
unsigned int i;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
ieu_dbg(ieu, "entering %s", __FUNCTION__);
|
|
|
|
|
|
|
|
/* Ensure we are in the right altsetting */
|
|
|
|
rc = ice1usb_set_altif(ieu, true);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
if (!test_and_set_bit(ICE1USB_ISOC_RUNNING, &ieu->flags)) {
|
|
|
|
for (i = 0; i < ICE1USB_NUM_URBS; i++) {
|
|
|
|
rc = ice1usb_submit_isoc_urb(ieu, ieu->ep.iso_in, &ieu->anchor.iso_in,
|
|
|
|
iso_in_complete, GFP_KERNEL);
|
|
|
|
if (rc) {
|
|
|
|
ieu_err(ieu, "error submitting IN ep URB %u (%d)", i, rc);
|
|
|
|
goto err_isoc;
|
|
|
|
}
|
|
|
|
rc = ice1usb_submit_isoc_urb(ieu, ieu->ep.iso_fb, &ieu->anchor.iso_fb,
|
|
|
|
iso_fb_complete, GFP_KERNEL);
|
|
|
|
if (rc) {
|
|
|
|
ieu_err(ieu, "error submitting FB ep URB %u (%d)", i, rc);
|
|
|
|
goto err_isoc;
|
|
|
|
}
|
|
|
|
rc = ice1usb_submit_isoc_urb(ieu, ieu->ep.iso_out, &ieu->anchor.iso_out,
|
|
|
|
iso_out_complete, GFP_KERNEL);
|
|
|
|
if (rc) {
|
|
|
|
ieu_err(ieu, "error submitting OUT ep URB %u (%d)", i, rc);
|
|
|
|
goto err_isoc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!test_and_set_bit(ICE1USB_IRQ_RUNNING, &ieu->flags)) {
|
|
|
|
for (i = 0; i < ICE1USB_NUM_URBS; i++) {
|
|
|
|
rc = ice1usb_submit_irq_urb(ieu, GFP_KERNEL);
|
|
|
|
if (rc) {
|
|
|
|
ieu_err(ieu, "error submitting IRQ ep %u (%d)", i, rc);
|
|
|
|
goto err_irq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_irq:
|
|
|
|
usb_kill_anchored_urbs(&ieu->anchor.irq);
|
|
|
|
clear_bit(ICE1USB_IRQ_RUNNING, &ieu->flags);
|
|
|
|
err_isoc:
|
|
|
|
usb_kill_anchored_urbs(&ieu->anchor.iso_in);
|
|
|
|
usb_kill_anchored_urbs(&ieu->anchor.iso_out);
|
|
|
|
usb_kill_anchored_urbs(&ieu->anchor.iso_fb);
|
|
|
|
clear_bit(ICE1USB_ISOC_RUNNING, &ieu->flags);
|
|
|
|
ice1usb_set_altif(ieu, false);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int e1u_d_shutdown(struct dahdi_span *span)
|
|
|
|
{
|
|
|
|
struct ice1usb *ieu = container_of(span, struct ice1usb, dahdi.span);
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
ieu_dbg(ieu, "entering %s", __FUNCTION__);
|
|
|
|
|
|
|
|
clear_bit(ICE1USB_ISOC_RUNNING, &ieu->flags);
|
|
|
|
clear_bit(ICE1USB_IRQ_RUNNING, &ieu->flags);
|
|
|
|
|
|
|
|
usb_kill_anchored_urbs(&ieu->anchor.iso_in);
|
|
|
|
usb_kill_anchored_urbs(&ieu->anchor.iso_out);
|
|
|
|
usb_kill_anchored_urbs(&ieu->anchor.iso_fb);
|
|
|
|
usb_kill_anchored_urbs(&ieu->anchor.irq);
|
|
|
|
|
|
|
|
/* guard against devices unplugged (see ice1usb_disconnect) */
|
|
|
|
if (ieu->present) {
|
|
|
|
/* switch to 'off' altsetting */
|
|
|
|
rc = ice1usb_set_altif(ieu, false);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set some maintenance mode according to 'cmd' */
|
|
|
|
static int e1u_d_maint(struct dahdi_span *span, int cmd)
|
|
|
|
{
|
|
|
|
struct ice1usb *ieu = container_of(span, struct ice1usb, dahdi.span);
|
2022-01-01 08:49:01 +00:00
|
|
|
int rc = 0;
|
2020-12-26 13:20:53 +00:00
|
|
|
|
|
|
|
ieu_dbg(ieu, "entering %s(%d)", __FUNCTION__, cmd);
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case DAHDI_MAINT_NONE:
|
|
|
|
ieu_info(ieu, "Clearing all maint modes\n");
|
2022-01-01 08:49:01 +00:00
|
|
|
ieu->cfg.tx.ext_loopback = ICE1USB_TX_EXT_LOOPBACK_OFF;
|
|
|
|
rc = ice1usb_tx_config(ieu);
|
2020-12-26 13:20:53 +00:00
|
|
|
break;
|
2022-01-01 08:49:01 +00:00
|
|
|
case DAHDI_MAINT_NETWORKLINELOOP:
|
|
|
|
ieu_info(ieu, "Turning on network line loopback\n");
|
|
|
|
ieu->cfg.tx.ext_loopback = ICE1USB_TX_EXT_LOOPBACK_SAME;
|
|
|
|
rc = ice1usb_tx_config(ieu);
|
2020-12-26 13:20:53 +00:00
|
|
|
break;
|
|
|
|
/* TODO: DAHDI_MAINT_*_DEFECT */
|
2022-01-01 09:22:29 +00:00
|
|
|
/* TODO: DAHDI_MAINT_ALARM_SIM */
|
|
|
|
case DAHDI_RESET_COUNTERS:
|
|
|
|
memset(&ieu->dahdi.span.count, 0, sizeof(ieu->dahdi.span.count));
|
|
|
|
/* don't reset last_err, as we need it to count the _new_ errors */
|
|
|
|
break;
|
2020-12-26 13:20:53 +00:00
|
|
|
default:
|
|
|
|
ieu_info(ieu, "Unknown E1 maint command: %d\n", cmd);
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
|
2022-01-01 08:49:01 +00:00
|
|
|
return rc;
|
2020-12-26 13:20:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dahdi_span_ops ice1usb_span_ops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.spanconfig = e1u_d_spanconfig,
|
|
|
|
.chanconfig = e1u_d_chanconfig,
|
|
|
|
.startup = e1u_d_startup,
|
|
|
|
.shutdown = e1u_d_shutdown,
|
|
|
|
.maint = e1u_d_maint,
|
|
|
|
};
|
|
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
* kernel USB integration / probing
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
|
|
/* does the given altsetting contain an EP with wMaxPacketSize == 0 ? */
|
|
|
|
static bool has_ep_packetsize_zero(const struct usb_host_interface *alt)
|
|
|
|
{
|
|
|
|
unsigned int j;
|
|
|
|
|
|
|
|
/* compute the sum of all wMaxPacketSize */
|
|
|
|
for (j = 0; j < alt->desc.bNumEndpoints; j++) {
|
|
|
|
const struct usb_endpoint_descriptor *epd = &alt->endpoint[j].desc;
|
|
|
|
if (usb_endpoint_xfer_isoc(epd)) {
|
|
|
|
if (epd->wMaxPacketSize == 0)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* find the 'on' altsetting (all ISOC EP wMaxPacketSize != 0) */
|
|
|
|
static const struct usb_host_interface *find_altsetting_on(struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < intf->num_altsetting; i++) {
|
|
|
|
const struct usb_host_interface *alt = &intf->altsetting[i];
|
2022-01-07 18:14:48 +00:00
|
|
|
if (!has_ep_packetsize_zero(alt) && alt->desc.bNumEndpoints >= 3)
|
2020-12-26 13:20:53 +00:00
|
|
|
return alt;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* find the 'off' altsetting (all ISOC EP wMaxPacketSize == 0) */
|
|
|
|
static const struct usb_host_interface *find_altsetting_off(struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < intf->num_altsetting; i++) {
|
|
|
|
const struct usb_host_interface *alt = &intf->altsetting[i];
|
|
|
|
unsigned int iso_ep_size = 0;
|
|
|
|
unsigned int j;
|
|
|
|
|
|
|
|
/* compute the sum of all wMaxPacketSize */
|
|
|
|
for (j = 0; j < alt->desc.bNumEndpoints; j++) {
|
|
|
|
const struct usb_endpoint_descriptor *epd = &alt->endpoint[j].desc;
|
|
|
|
if (usb_endpoint_xfer_isoc(epd))
|
|
|
|
iso_ep_size += le16_to_cpu(epd->wMaxPacketSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iso_ep_size == 0)
|
|
|
|
return alt;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* find the endpoint numbers within the interface */
|
|
|
|
static int find_endpoints(struct ice1usb *ieu, const struct usb_host_interface *alt)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
int found = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < alt->desc.bNumEndpoints; i++) {
|
|
|
|
const struct usb_endpoint_descriptor *epd = &alt->endpoint[i].desc;
|
|
|
|
switch (usb_endpoint_type(epd)) {
|
|
|
|
case USB_ENDPOINT_XFER_INT:
|
|
|
|
if (!ieu->ep.irq && usb_endpoint_dir_in(epd)) {
|
|
|
|
ieu->ep.irq = epd;
|
|
|
|
found++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
|
|
if (usb_endpoint_dir_in(epd)) {
|
|
|
|
if ((epd->bmAttributes & USB_ENDPOINT_USAGE_MASK) ==
|
|
|
|
USB_ENDPOINT_USAGE_FEEDBACK) {
|
|
|
|
if (!ieu->ep.iso_fb) {
|
|
|
|
ieu->ep.iso_fb = epd;
|
|
|
|
found++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!ieu->ep.iso_in) {
|
|
|
|
ieu->ep.iso_in = epd;
|
|
|
|
found++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!ieu->ep.iso_out) {
|
|
|
|
ieu->ep.iso_out = epd;
|
|
|
|
found++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* select interface altsetting on / off */
|
|
|
|
static int ice1usb_set_altif(struct ice1usb *ieu, bool on)
|
|
|
|
{
|
|
|
|
const struct usb_interface_descriptor *desc;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
ieu_dbg(ieu, "entering %s(%d)", __FUNCTION__, on);
|
|
|
|
|
|
|
|
if (on)
|
|
|
|
desc = &ieu->alt_on->desc;
|
|
|
|
else
|
|
|
|
desc = &ieu->alt_off->desc;
|
|
|
|
|
|
|
|
rc = usb_set_interface(ieu->usb_dev, desc->bInterfaceNumber, desc->bAlternateSetting);
|
|
|
|
if (rc) {
|
|
|
|
ieu_err(ieu, "error setting %s-altif %u (%d)",
|
|
|
|
on ? "ON" : "OFF", desc->bInterfaceNumber, rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ice1usb_probe(struct usb_interface *intf, const struct usb_device_id *prod)
|
|
|
|
{
|
|
|
|
struct usb_device *usb_dev = usb_get_dev(interface_to_usbdev(intf));
|
|
|
|
const struct usb_interface_descriptor *ifdesc = &intf->altsetting->desc;
|
|
|
|
unsigned int max_pack_size;
|
|
|
|
struct dahdi_device *ddev;
|
|
|
|
struct dahdi_span *dspan;
|
|
|
|
struct ice1usb *ieu;
|
|
|
|
int ret = -ENODEV;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
dev_dbg(&intf->dev, "entering %s", __FUNCTION__);
|
|
|
|
|
|
|
|
if (ifdesc->bInterfaceClass != 0xff || ifdesc->bInterfaceSubClass != 0xE1) {
|
|
|
|
dev_dbg(&intf->dev, "Unsupported Interface Class/SubClass %02x/%02x",
|
|
|
|
ifdesc->bInterfaceClass, ifdesc->bInterfaceSubClass);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
ieu = kzalloc(sizeof(*ieu), GFP_KERNEL);
|
|
|
|
if (!ieu) {
|
|
|
|
dev_err(&intf->dev, "Out of memory\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
ieu->usb_dev = usb_dev;
|
|
|
|
ieu->usb_intf = intf;
|
|
|
|
ieu->fc.r_acc = 0;
|
|
|
|
ieu->fc.r_sw = 8192;
|
|
|
|
ieu->present = true;
|
|
|
|
spin_lock_init(&ieu->lock);
|
|
|
|
|
|
|
|
/* locate ON / OFF altsettings */
|
|
|
|
ieu->alt_off = find_altsetting_off(ieu->usb_intf);
|
|
|
|
if (!ieu->alt_off) {
|
|
|
|
dev_err(&intf->dev, "Cannot find OFF altsetting");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
ieu->alt_on = find_altsetting_on(ieu->usb_intf);
|
|
|
|
if (!ieu->alt_on) {
|
|
|
|
dev_err(&intf->dev, "Cannot find ON altsetting");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* locate ON / OFF altsettings */
|
|
|
|
if (find_endpoints(ieu, ieu->alt_on) < 4) {
|
|
|
|
dev_err(&intf->dev, "Cannot find all endpoints");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* compute the maximum number of E1 frames to send in one ISO OUT packet */
|
|
|
|
max_pack_size = le16_to_cpu(ieu->ep.iso_out->wMaxPacketSize);
|
|
|
|
ieu->max_fts = (max_pack_size - 4) / 32;
|
|
|
|
dev_dbg(&intf->dev, "Maximum FTS: %u", ieu->max_fts);
|
|
|
|
|
|
|
|
/* TODO: only one dahdi_device even for multiple USB interfaces? */
|
|
|
|
ddev = ieu->dahdi.dev = dahdi_create_device();
|
|
|
|
if (!ddev)
|
|
|
|
goto error;
|
|
|
|
ddev->manufacturer = usb_dev->manufacturer;
|
|
|
|
ddev->devicetype = usb_dev->product;
|
|
|
|
ddev->hardware_id = usb_dev->serial;
|
|
|
|
//ddev->location = USB_BUS_PATH;
|
|
|
|
|
|
|
|
dspan = &ieu->dahdi.span;
|
|
|
|
snprintf(dspan->name, sizeof(dspan->name), "icE1usb/1/%d",
|
|
|
|
ieu->alt_on->desc.bInterfaceNumber); //TDOO: device != 1
|
|
|
|
snprintf(dspan->desc, sizeof(dspan->desc), "Osmocom icE1USB Card 1 Span %d",
|
|
|
|
ieu->alt_on->desc.bInterfaceNumber); //TODO: device != 1
|
|
|
|
dspan->channels = 31;
|
|
|
|
dspan->spantype = SPANTYPE_DIGITAL_E1;
|
|
|
|
dspan->deflaw = DAHDI_LAW_ALAW;
|
|
|
|
dspan->linecompat = DAHDI_CONFIG_HDB3 | DAHDI_CONFIG_CCS | DAHDI_CONFIG_CRC4;
|
|
|
|
dspan->chans = ieu->dahdi.chans;
|
|
|
|
dspan->flags = 0;
|
|
|
|
dspan->offset = 0;
|
|
|
|
dspan->ops = &ice1usb_span_ops;
|
|
|
|
|
|
|
|
rc = e1u_alloc_channels(ieu);
|
|
|
|
if (rc) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto err_free_ddev;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_add_tail(&dspan->device_node, &ddev->spans);
|
|
|
|
|
|
|
|
rc = dahdi_register_device(ieu->dahdi.dev, &intf->dev);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(&intf->dev, "Failed to reigster with DAHDI\n");
|
|
|
|
goto err_free_chans;
|
|
|
|
}
|
|
|
|
|
|
|
|
init_usb_anchor(&ieu->anchor.iso_in);
|
|
|
|
init_usb_anchor(&ieu->anchor.iso_out);
|
|
|
|
init_usb_anchor(&ieu->anchor.iso_fb);
|
|
|
|
init_usb_anchor(&ieu->anchor.irq);
|
|
|
|
|
|
|
|
usb_set_intfdata(intf, ieu);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_free_chans:
|
|
|
|
e1u_free_channels(ieu);
|
|
|
|
err_free_ddev:
|
|
|
|
dahdi_free_device(ieu->dahdi.dev);
|
|
|
|
error:
|
|
|
|
kfree(ieu);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct usb_driver ice1usb_driver;
|
|
|
|
|
|
|
|
static void ice1usb_disconnect(struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
struct ice1usb *ieu = usb_get_intfdata(intf);
|
|
|
|
|
|
|
|
dev_dbg(&intf->dev, "entering %s", __FUNCTION__);
|
|
|
|
|
|
|
|
if (!ieu)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* avoid any shutdown code from submitting further I/O */
|
|
|
|
ieu->present = false;
|
|
|
|
|
|
|
|
usb_set_intfdata(intf, NULL);
|
|
|
|
|
|
|
|
/* will in turn call e1u_d_shutdown() which stops all transfers */
|
|
|
|
dahdi_unregister_device(ieu->dahdi.dev);
|
|
|
|
|
|
|
|
ice1usb_free(ieu);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
static int ice1usb_suspend(struct usb_interface *intf, pm_message_t message)
|
|
|
|
{
|
|
|
|
struct ice1usb *ieu = usb_get_intfdata(intf);
|
|
|
|
/* FIXME */
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ice1usb_resume(struct usb_interface *intf)
|
|
|
|
{
|
|
|
|
struct ice1usb *ieu = usb_get_intfdata(intf);
|
|
|
|
/* FIXME */
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const struct usb_device_id ice1usb_products[] = {
|
|
|
|
{
|
|
|
|
USB_DEVICE(0x1d50, 0x6145),
|
|
|
|
},
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct usb_driver ice1usb_driver = {
|
|
|
|
.name = "icE1usb",
|
|
|
|
.id_table = ice1usb_products,
|
|
|
|
.probe = ice1usb_probe,
|
|
|
|
.disconnect = ice1usb_disconnect,
|
|
|
|
//.suspend = ice1usb_suspend,
|
|
|
|
//.resume = ice1usb_resume,
|
|
|
|
};
|
|
|
|
|
|
|
|
module_usb_driver(ice1usb_driver);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Harald Welte <laforge@osmocom.org>");
|
|
|
|
MODULE_DESCRIPTION("Osmocom icE1usb USB E1 interface");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_VERSION(VERSION);
|