mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-26 19:36:41 +00:00
4dd8230acd
The CAPI interface incorrectly assumed that CAPI messages would always start at the beginning of the data buffer: fix by treating DATA_B3 messages as the link layer header to their payload data. This fix changes the way acknowledgement information is propagated through the hardware specific modules and thereby impacts the ISDN4Linux variant of the driver, too. Also some assumptions about methods not being called from interrupt context turned out to be unwarranted; fix by using dev_kfree_skb_any() wherever non-interrupt context isn't guaranteed. Impact: bugfix Signed-off-by: Tilman Schmidt <tilman@imap.cc> Signed-off-by: David S. Miller <davem@davemloft.net>
710 lines
18 KiB
C
710 lines
18 KiB
C
/*
|
|
* Stuff used by all variants of the driver
|
|
*
|
|
* Copyright (c) 2001 by Stefan Eilers,
|
|
* Hansjoerg Lipp <hjlipp@web.de>,
|
|
* Tilman Schmidt <tilman@imap.cc>.
|
|
*
|
|
* =====================================================================
|
|
* 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.
|
|
* =====================================================================
|
|
*/
|
|
|
|
#include "gigaset.h"
|
|
#include <linux/isdnif.h>
|
|
|
|
#define HW_HDR_LEN 2 /* Header size used to store ack info */
|
|
|
|
/* == Handling of I4L IO =====================================================*/
|
|
|
|
/* writebuf_from_LL
|
|
* called by LL to transmit data on an open channel
|
|
* inserts the buffer data into the send queue and starts the transmission
|
|
* Note that this operation must not sleep!
|
|
* When the buffer is processed completely, gigaset_skb_sent() should be called.
|
|
* parameters:
|
|
* driverID driver ID as assigned by LL
|
|
* channel channel number
|
|
* ack if != 0 LL wants to be notified on completion via
|
|
* statcallb(ISDN_STAT_BSENT)
|
|
* skb skb containing data to send
|
|
* return value:
|
|
* number of accepted bytes
|
|
* 0 if temporarily unable to accept data (out of buffer space)
|
|
* <0 on error (eg. -EINVAL)
|
|
*/
|
|
static int writebuf_from_LL(int driverID, int channel, int ack,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct cardstate *cs;
|
|
struct bc_state *bcs;
|
|
unsigned char *ack_header;
|
|
unsigned len;
|
|
|
|
if (!(cs = gigaset_get_cs_by_id(driverID))) {
|
|
pr_err("%s: invalid driver ID (%d)\n", __func__, driverID);
|
|
return -ENODEV;
|
|
}
|
|
if (channel < 0 || channel >= cs->channels) {
|
|
dev_err(cs->dev, "%s: invalid channel ID (%d)\n",
|
|
__func__, channel);
|
|
return -ENODEV;
|
|
}
|
|
bcs = &cs->bcs[channel];
|
|
|
|
/* can only handle linear sk_buffs */
|
|
if (skb_linearize(skb) < 0) {
|
|
dev_err(cs->dev, "%s: skb_linearize failed\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
len = skb->len;
|
|
|
|
gig_dbg(DEBUG_LLDATA,
|
|
"Receiving data from LL (id: %d, ch: %d, ack: %d, sz: %d)",
|
|
driverID, channel, ack, len);
|
|
|
|
if (!len) {
|
|
if (ack)
|
|
dev_notice(cs->dev, "%s: not ACKing empty packet\n",
|
|
__func__);
|
|
return 0;
|
|
}
|
|
if (len > MAX_BUF_SIZE) {
|
|
dev_err(cs->dev, "%s: packet too large (%d bytes)\n",
|
|
__func__, len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* set up acknowledgement header */
|
|
if (skb_headroom(skb) < HW_HDR_LEN) {
|
|
/* should never happen */
|
|
dev_err(cs->dev, "%s: insufficient skb headroom\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
skb_set_mac_header(skb, -HW_HDR_LEN);
|
|
skb->mac_len = HW_HDR_LEN;
|
|
ack_header = skb_mac_header(skb);
|
|
if (ack) {
|
|
ack_header[0] = len & 0xff;
|
|
ack_header[1] = len >> 8;
|
|
} else {
|
|
ack_header[0] = ack_header[1] = 0;
|
|
}
|
|
gig_dbg(DEBUG_MCMD, "skb: len=%u, ack=%d: %02x %02x",
|
|
len, ack, ack_header[0], ack_header[1]);
|
|
|
|
/* pass to device-specific module */
|
|
return cs->ops->send_skb(bcs, skb);
|
|
}
|
|
|
|
/**
|
|
* gigaset_skb_sent() - acknowledge sending an skb
|
|
* @bcs: B channel descriptor structure.
|
|
* @skb: sent data.
|
|
*
|
|
* Called by hardware module {bas,ser,usb}_gigaset when the data in a
|
|
* skb has been successfully sent, for signalling completion to the LL.
|
|
*/
|
|
void gigaset_skb_sent(struct bc_state *bcs, struct sk_buff *skb)
|
|
{
|
|
isdn_if *iif = bcs->cs->iif;
|
|
unsigned char *ack_header = skb_mac_header(skb);
|
|
unsigned len;
|
|
isdn_ctrl response;
|
|
|
|
++bcs->trans_up;
|
|
|
|
if (skb->len)
|
|
dev_warn(bcs->cs->dev, "%s: skb->len==%d\n",
|
|
__func__, skb->len);
|
|
|
|
len = ack_header[0] + ((unsigned) ack_header[1] << 8);
|
|
if (len) {
|
|
gig_dbg(DEBUG_MCMD, "ACKing to LL (id: %d, ch: %d, sz: %u)",
|
|
bcs->cs->myid, bcs->channel, len);
|
|
|
|
response.driver = bcs->cs->myid;
|
|
response.command = ISDN_STAT_BSENT;
|
|
response.arg = bcs->channel;
|
|
response.parm.length = len;
|
|
iif->statcallb(&response);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(gigaset_skb_sent);
|
|
|
|
/**
|
|
* gigaset_skb_rcvd() - pass received skb to LL
|
|
* @bcs: B channel descriptor structure.
|
|
* @skb: received data.
|
|
*
|
|
* Called by hardware module {bas,ser,usb}_gigaset when user data has
|
|
* been successfully received, for passing to the LL.
|
|
* Warning: skb must not be accessed anymore!
|
|
*/
|
|
void gigaset_skb_rcvd(struct bc_state *bcs, struct sk_buff *skb)
|
|
{
|
|
isdn_if *iif = bcs->cs->iif;
|
|
|
|
iif->rcvcallb_skb(bcs->cs->myid, bcs->channel, skb);
|
|
bcs->trans_down++;
|
|
}
|
|
EXPORT_SYMBOL_GPL(gigaset_skb_rcvd);
|
|
|
|
/**
|
|
* gigaset_isdn_rcv_err() - signal receive error
|
|
* @bcs: B channel descriptor structure.
|
|
*
|
|
* Called by hardware module {bas,ser,usb}_gigaset when a receive error
|
|
* has occurred, for signalling to the LL.
|
|
*/
|
|
void gigaset_isdn_rcv_err(struct bc_state *bcs)
|
|
{
|
|
isdn_if *iif = bcs->cs->iif;
|
|
isdn_ctrl response;
|
|
|
|
/* if currently ignoring packets, just count down */
|
|
if (bcs->ignore) {
|
|
bcs->ignore--;
|
|
return;
|
|
}
|
|
|
|
/* update statistics */
|
|
bcs->corrupted++;
|
|
|
|
/* error -> LL */
|
|
gig_dbg(DEBUG_CMD, "sending L1ERR");
|
|
response.driver = bcs->cs->myid;
|
|
response.command = ISDN_STAT_L1ERR;
|
|
response.arg = bcs->channel;
|
|
response.parm.errcode = ISDN_STAT_L1ERR_RECV;
|
|
iif->statcallb(&response);
|
|
}
|
|
EXPORT_SYMBOL_GPL(gigaset_isdn_rcv_err);
|
|
|
|
/* This function will be called by LL to send commands
|
|
* NOTE: LL ignores the returned value, for commands other than ISDN_CMD_IOCTL,
|
|
* so don't put too much effort into it.
|
|
*/
|
|
static int command_from_LL(isdn_ctrl *cntrl)
|
|
{
|
|
struct cardstate *cs;
|
|
struct bc_state *bcs;
|
|
int retval = 0;
|
|
char **commands;
|
|
int ch;
|
|
int i;
|
|
size_t l;
|
|
|
|
gigaset_debugdrivers();
|
|
|
|
gig_dbg(DEBUG_CMD, "driver: %d, command: %d, arg: 0x%lx",
|
|
cntrl->driver, cntrl->command, cntrl->arg);
|
|
|
|
cs = gigaset_get_cs_by_id(cntrl->driver);
|
|
if (cs == NULL) {
|
|
pr_err("%s: invalid driver ID (%d)\n", __func__, cntrl->driver);
|
|
return -ENODEV;
|
|
}
|
|
ch = cntrl->arg & 0xff;
|
|
|
|
switch (cntrl->command) {
|
|
case ISDN_CMD_IOCTL:
|
|
dev_warn(cs->dev, "ISDN_CMD_IOCTL not supported\n");
|
|
return -EINVAL;
|
|
|
|
case ISDN_CMD_DIAL:
|
|
gig_dbg(DEBUG_ANY,
|
|
"ISDN_CMD_DIAL (phone: %s, msn: %s, si1: %d, si2: %d)",
|
|
cntrl->parm.setup.phone, cntrl->parm.setup.eazmsn,
|
|
cntrl->parm.setup.si1, cntrl->parm.setup.si2);
|
|
|
|
if (ch >= cs->channels) {
|
|
dev_err(cs->dev,
|
|
"ISDN_CMD_DIAL: invalid channel (%d)\n", ch);
|
|
return -EINVAL;
|
|
}
|
|
bcs = cs->bcs + ch;
|
|
if (!gigaset_get_channel(bcs)) {
|
|
dev_err(cs->dev, "ISDN_CMD_DIAL: channel not free\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
commands = kzalloc(AT_NUM*(sizeof *commands), GFP_ATOMIC);
|
|
if (!commands) {
|
|
gigaset_free_channel(bcs);
|
|
dev_err(cs->dev, "ISDN_CMD_DIAL: out of memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
l = 3 + strlen(cntrl->parm.setup.phone);
|
|
commands[AT_DIAL] = kmalloc(l, GFP_ATOMIC);
|
|
if (!commands[AT_DIAL])
|
|
goto oom;
|
|
if (cntrl->parm.setup.phone[0] == '*' &&
|
|
cntrl->parm.setup.phone[1] == '*') {
|
|
/* internal call: translate ** prefix to CTP value */
|
|
commands[AT_TYPE] = kstrdup("^SCTP=0\r", GFP_ATOMIC);
|
|
if (!commands[AT_TYPE])
|
|
goto oom;
|
|
snprintf(commands[AT_DIAL], l,
|
|
"D%s\r", cntrl->parm.setup.phone+2);
|
|
} else {
|
|
commands[AT_TYPE] = kstrdup("^SCTP=1\r", GFP_ATOMIC);
|
|
if (!commands[AT_TYPE])
|
|
goto oom;
|
|
snprintf(commands[AT_DIAL], l,
|
|
"D%s\r", cntrl->parm.setup.phone);
|
|
}
|
|
|
|
l = strlen(cntrl->parm.setup.eazmsn);
|
|
if (l) {
|
|
l += 8;
|
|
commands[AT_MSN] = kmalloc(l, GFP_ATOMIC);
|
|
if (!commands[AT_MSN])
|
|
goto oom;
|
|
snprintf(commands[AT_MSN], l, "^SMSN=%s\r",
|
|
cntrl->parm.setup.eazmsn);
|
|
}
|
|
|
|
switch (cntrl->parm.setup.si1) {
|
|
case 1: /* audio */
|
|
/* BC = 9090A3: 3.1 kHz audio, A-law */
|
|
commands[AT_BC] = kstrdup("^SBC=9090A3\r", GFP_ATOMIC);
|
|
if (!commands[AT_BC])
|
|
goto oom;
|
|
break;
|
|
case 7: /* data */
|
|
default: /* hope the app knows what it is doing */
|
|
/* BC = 8890: unrestricted digital information */
|
|
commands[AT_BC] = kstrdup("^SBC=8890\r", GFP_ATOMIC);
|
|
if (!commands[AT_BC])
|
|
goto oom;
|
|
}
|
|
/* ToDo: other si1 values, inspect si2, set HLC/LLC */
|
|
|
|
commands[AT_PROTO] = kmalloc(9, GFP_ATOMIC);
|
|
if (!commands[AT_PROTO])
|
|
goto oom;
|
|
snprintf(commands[AT_PROTO], 9, "^SBPR=%u\r", bcs->proto2);
|
|
|
|
commands[AT_ISO] = kmalloc(9, GFP_ATOMIC);
|
|
if (!commands[AT_ISO])
|
|
goto oom;
|
|
snprintf(commands[AT_ISO], 9, "^SISO=%u\r",
|
|
(unsigned) bcs->channel + 1);
|
|
|
|
if (!gigaset_add_event(cs, &bcs->at_state, EV_DIAL, commands,
|
|
bcs->at_state.seq_index, NULL)) {
|
|
for (i = 0; i < AT_NUM; ++i)
|
|
kfree(commands[i]);
|
|
kfree(commands);
|
|
gigaset_free_channel(bcs);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
gig_dbg(DEBUG_CMD, "scheduling DIAL");
|
|
gigaset_schedule_event(cs);
|
|
break;
|
|
case ISDN_CMD_ACCEPTD:
|
|
if (ch >= cs->channels) {
|
|
dev_err(cs->dev,
|
|
"ISDN_CMD_ACCEPTD: invalid channel (%d)\n", ch);
|
|
return -EINVAL;
|
|
}
|
|
bcs = cs->bcs + ch;
|
|
if (!gigaset_add_event(cs, &bcs->at_state,
|
|
EV_ACCEPT, NULL, 0, NULL))
|
|
return -ENOMEM;
|
|
|
|
gig_dbg(DEBUG_CMD, "scheduling ACCEPT");
|
|
gigaset_schedule_event(cs);
|
|
|
|
break;
|
|
case ISDN_CMD_ACCEPTB:
|
|
break;
|
|
case ISDN_CMD_HANGUP:
|
|
if (ch >= cs->channels) {
|
|
dev_err(cs->dev,
|
|
"ISDN_CMD_HANGUP: invalid channel (%d)\n", ch);
|
|
return -EINVAL;
|
|
}
|
|
bcs = cs->bcs + ch;
|
|
if (!gigaset_add_event(cs, &bcs->at_state,
|
|
EV_HUP, NULL, 0, NULL))
|
|
return -ENOMEM;
|
|
|
|
gig_dbg(DEBUG_CMD, "scheduling HUP");
|
|
gigaset_schedule_event(cs);
|
|
|
|
break;
|
|
case ISDN_CMD_CLREAZ: /* Do not signal incoming signals */
|
|
dev_info(cs->dev, "ignoring ISDN_CMD_CLREAZ\n");
|
|
break;
|
|
case ISDN_CMD_SETEAZ: /* Signal incoming calls for given MSN */
|
|
dev_info(cs->dev, "ignoring ISDN_CMD_SETEAZ (%s)\n",
|
|
cntrl->parm.num);
|
|
break;
|
|
case ISDN_CMD_SETL2: /* Set L2 to given protocol */
|
|
if (ch >= cs->channels) {
|
|
dev_err(cs->dev,
|
|
"ISDN_CMD_SETL2: invalid channel (%d)\n", ch);
|
|
return -EINVAL;
|
|
}
|
|
bcs = cs->bcs + ch;
|
|
if (bcs->chstate & CHS_D_UP) {
|
|
dev_err(cs->dev,
|
|
"ISDN_CMD_SETL2: channel active (%d)\n", ch);
|
|
return -EINVAL;
|
|
}
|
|
switch (cntrl->arg >> 8) {
|
|
case ISDN_PROTO_L2_HDLC:
|
|
gig_dbg(DEBUG_CMD, "ISDN_CMD_SETL2: setting L2_HDLC");
|
|
bcs->proto2 = L2_HDLC;
|
|
break;
|
|
case ISDN_PROTO_L2_TRANS:
|
|
gig_dbg(DEBUG_CMD, "ISDN_CMD_SETL2: setting L2_VOICE");
|
|
bcs->proto2 = L2_VOICE;
|
|
break;
|
|
default:
|
|
dev_err(cs->dev,
|
|
"ISDN_CMD_SETL2: unsupported protocol (%lu)\n",
|
|
cntrl->arg >> 8);
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
case ISDN_CMD_SETL3: /* Set L3 to given protocol */
|
|
if (ch >= cs->channels) {
|
|
dev_err(cs->dev,
|
|
"ISDN_CMD_SETL3: invalid channel (%d)\n", ch);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cntrl->arg >> 8 != ISDN_PROTO_L3_TRANS) {
|
|
dev_err(cs->dev,
|
|
"ISDN_CMD_SETL3: unsupported protocol (%lu)\n",
|
|
cntrl->arg >> 8);
|
|
return -EINVAL;
|
|
}
|
|
|
|
break;
|
|
case ISDN_CMD_PROCEED:
|
|
gig_dbg(DEBUG_ANY, "ISDN_CMD_PROCEED"); //FIXME
|
|
break;
|
|
case ISDN_CMD_ALERT:
|
|
gig_dbg(DEBUG_ANY, "ISDN_CMD_ALERT"); //FIXME
|
|
if (cntrl->arg >= cs->channels) {
|
|
dev_err(cs->dev,
|
|
"ISDN_CMD_ALERT: invalid channel (%d)\n",
|
|
(int) cntrl->arg);
|
|
return -EINVAL;
|
|
}
|
|
//bcs = cs->bcs + cntrl->arg;
|
|
//bcs->proto2 = -1;
|
|
// FIXME
|
|
break;
|
|
case ISDN_CMD_REDIR:
|
|
gig_dbg(DEBUG_ANY, "ISDN_CMD_REDIR"); //FIXME
|
|
break;
|
|
case ISDN_CMD_PROT_IO:
|
|
gig_dbg(DEBUG_ANY, "ISDN_CMD_PROT_IO");
|
|
break;
|
|
case ISDN_CMD_FAXCMD:
|
|
gig_dbg(DEBUG_ANY, "ISDN_CMD_FAXCMD");
|
|
break;
|
|
case ISDN_CMD_GETL2:
|
|
gig_dbg(DEBUG_ANY, "ISDN_CMD_GETL2");
|
|
break;
|
|
case ISDN_CMD_GETL3:
|
|
gig_dbg(DEBUG_ANY, "ISDN_CMD_GETL3");
|
|
break;
|
|
case ISDN_CMD_GETEAZ:
|
|
gig_dbg(DEBUG_ANY, "ISDN_CMD_GETEAZ");
|
|
break;
|
|
case ISDN_CMD_SETSIL:
|
|
gig_dbg(DEBUG_ANY, "ISDN_CMD_SETSIL");
|
|
break;
|
|
case ISDN_CMD_GETSIL:
|
|
gig_dbg(DEBUG_ANY, "ISDN_CMD_GETSIL");
|
|
break;
|
|
default:
|
|
dev_err(cs->dev, "unknown command %d from LL\n",
|
|
cntrl->command);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return retval;
|
|
|
|
oom:
|
|
dev_err(bcs->cs->dev, "out of memory\n");
|
|
for (i = 0; i < AT_NUM; ++i)
|
|
kfree(commands[i]);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void gigaset_i4l_cmd(struct cardstate *cs, int cmd)
|
|
{
|
|
isdn_if *iif = cs->iif;
|
|
isdn_ctrl command;
|
|
|
|
command.driver = cs->myid;
|
|
command.command = cmd;
|
|
command.arg = 0;
|
|
iif->statcallb(&command);
|
|
}
|
|
|
|
static void gigaset_i4l_channel_cmd(struct bc_state *bcs, int cmd)
|
|
{
|
|
isdn_if *iif = bcs->cs->iif;
|
|
isdn_ctrl command;
|
|
|
|
command.driver = bcs->cs->myid;
|
|
command.command = cmd;
|
|
command.arg = bcs->channel;
|
|
iif->statcallb(&command);
|
|
}
|
|
|
|
/**
|
|
* gigaset_isdn_icall() - signal incoming call
|
|
* @at_state: connection state structure.
|
|
*
|
|
* Called by main module to notify the LL that an incoming call has been
|
|
* received. @at_state contains the parameters of the call.
|
|
*
|
|
* Return value: call disposition (ICALL_*)
|
|
*/
|
|
int gigaset_isdn_icall(struct at_state_t *at_state)
|
|
{
|
|
struct cardstate *cs = at_state->cs;
|
|
struct bc_state *bcs = at_state->bcs;
|
|
isdn_if *iif = cs->iif;
|
|
isdn_ctrl response;
|
|
int retval;
|
|
|
|
/* fill ICALL structure */
|
|
response.parm.setup.si1 = 0; /* default: unknown */
|
|
response.parm.setup.si2 = 0;
|
|
response.parm.setup.screen = 0; //FIXME how to set these?
|
|
response.parm.setup.plan = 0;
|
|
if (!at_state->str_var[STR_ZBC]) {
|
|
/* no BC (internal call): assume speech, A-law */
|
|
response.parm.setup.si1 = 1;
|
|
} else if (!strcmp(at_state->str_var[STR_ZBC], "8890")) {
|
|
/* unrestricted digital information */
|
|
response.parm.setup.si1 = 7;
|
|
} else if (!strcmp(at_state->str_var[STR_ZBC], "8090A3")) {
|
|
/* speech, A-law */
|
|
response.parm.setup.si1 = 1;
|
|
} else if (!strcmp(at_state->str_var[STR_ZBC], "9090A3")) {
|
|
/* 3,1 kHz audio, A-law */
|
|
response.parm.setup.si1 = 1;
|
|
response.parm.setup.si2 = 2;
|
|
} else {
|
|
dev_warn(cs->dev, "RING ignored - unsupported BC %s\n",
|
|
at_state->str_var[STR_ZBC]);
|
|
return ICALL_IGNORE;
|
|
}
|
|
if (at_state->str_var[STR_NMBR]) {
|
|
strncpy(response.parm.setup.phone, at_state->str_var[STR_NMBR],
|
|
sizeof response.parm.setup.phone - 1);
|
|
response.parm.setup.phone[sizeof response.parm.setup.phone - 1] = 0;
|
|
} else
|
|
response.parm.setup.phone[0] = 0;
|
|
if (at_state->str_var[STR_ZCPN]) {
|
|
strncpy(response.parm.setup.eazmsn, at_state->str_var[STR_ZCPN],
|
|
sizeof response.parm.setup.eazmsn - 1);
|
|
response.parm.setup.eazmsn[sizeof response.parm.setup.eazmsn - 1] = 0;
|
|
} else
|
|
response.parm.setup.eazmsn[0] = 0;
|
|
|
|
if (!bcs) {
|
|
dev_notice(cs->dev, "no channel for incoming call\n");
|
|
response.command = ISDN_STAT_ICALLW;
|
|
response.arg = 0; //FIXME
|
|
} else {
|
|
gig_dbg(DEBUG_CMD, "Sending ICALL");
|
|
response.command = ISDN_STAT_ICALL;
|
|
response.arg = bcs->channel; //FIXME
|
|
}
|
|
response.driver = cs->myid;
|
|
retval = iif->statcallb(&response);
|
|
gig_dbg(DEBUG_CMD, "Response: %d", retval);
|
|
switch (retval) {
|
|
case 0: /* no takers */
|
|
return ICALL_IGNORE;
|
|
case 1: /* alerting */
|
|
bcs->chstate |= CHS_NOTIFY_LL;
|
|
return ICALL_ACCEPT;
|
|
case 2: /* reject */
|
|
return ICALL_REJECT;
|
|
case 3: /* incomplete */
|
|
dev_warn(cs->dev,
|
|
"LL requested unsupported feature: Incomplete Number\n");
|
|
return ICALL_IGNORE;
|
|
case 4: /* proceeding */
|
|
/* Gigaset will send ALERTING anyway.
|
|
* There doesn't seem to be a way to avoid this.
|
|
*/
|
|
return ICALL_ACCEPT;
|
|
case 5: /* deflect */
|
|
dev_warn(cs->dev,
|
|
"LL requested unsupported feature: Call Deflection\n");
|
|
return ICALL_IGNORE;
|
|
default:
|
|
dev_err(cs->dev, "LL error %d on ICALL\n", retval);
|
|
return ICALL_IGNORE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gigaset_isdn_connD() - signal D channel connect
|
|
* @bcs: B channel descriptor structure.
|
|
*
|
|
* Called by main module to notify the LL that the D channel connection has
|
|
* been established.
|
|
*/
|
|
void gigaset_isdn_connD(struct bc_state *bcs)
|
|
{
|
|
gig_dbg(DEBUG_CMD, "sending DCONN");
|
|
gigaset_i4l_channel_cmd(bcs, ISDN_STAT_DCONN);
|
|
}
|
|
|
|
/**
|
|
* gigaset_isdn_hupD() - signal D channel hangup
|
|
* @bcs: B channel descriptor structure.
|
|
*
|
|
* Called by main module to notify the LL that the D channel connection has
|
|
* been shut down.
|
|
*/
|
|
void gigaset_isdn_hupD(struct bc_state *bcs)
|
|
{
|
|
gig_dbg(DEBUG_CMD, "sending DHUP");
|
|
gigaset_i4l_channel_cmd(bcs, ISDN_STAT_DHUP);
|
|
}
|
|
|
|
/**
|
|
* gigaset_isdn_connB() - signal B channel connect
|
|
* @bcs: B channel descriptor structure.
|
|
*
|
|
* Called by main module to notify the LL that the B channel connection has
|
|
* been established.
|
|
*/
|
|
void gigaset_isdn_connB(struct bc_state *bcs)
|
|
{
|
|
gig_dbg(DEBUG_CMD, "sending BCONN");
|
|
gigaset_i4l_channel_cmd(bcs, ISDN_STAT_BCONN);
|
|
}
|
|
|
|
/**
|
|
* gigaset_isdn_hupB() - signal B channel hangup
|
|
* @bcs: B channel descriptor structure.
|
|
*
|
|
* Called by main module to notify the LL that the B channel connection has
|
|
* been shut down.
|
|
*/
|
|
void gigaset_isdn_hupB(struct bc_state *bcs)
|
|
{
|
|
gig_dbg(DEBUG_CMD, "sending BHUP");
|
|
gigaset_i4l_channel_cmd(bcs, ISDN_STAT_BHUP);
|
|
}
|
|
|
|
/**
|
|
* gigaset_isdn_start() - signal device availability
|
|
* @cs: device descriptor structure.
|
|
*
|
|
* Called by main module to notify the LL that the device is available for
|
|
* use.
|
|
*/
|
|
void gigaset_isdn_start(struct cardstate *cs)
|
|
{
|
|
gig_dbg(DEBUG_CMD, "sending RUN");
|
|
gigaset_i4l_cmd(cs, ISDN_STAT_RUN);
|
|
}
|
|
|
|
/**
|
|
* gigaset_isdn_stop() - signal device unavailability
|
|
* @cs: device descriptor structure.
|
|
*
|
|
* Called by main module to notify the LL that the device is no longer
|
|
* available for use.
|
|
*/
|
|
void gigaset_isdn_stop(struct cardstate *cs)
|
|
{
|
|
gig_dbg(DEBUG_CMD, "sending STOP");
|
|
gigaset_i4l_cmd(cs, ISDN_STAT_STOP);
|
|
}
|
|
|
|
/**
|
|
* gigaset_isdn_register() - register to LL
|
|
* @cs: device descriptor structure.
|
|
* @isdnid: device name.
|
|
*
|
|
* Called by main module to register the device with the LL.
|
|
*
|
|
* Return value: 1 for success, 0 for failure
|
|
*/
|
|
int gigaset_isdn_register(struct cardstate *cs, const char *isdnid)
|
|
{
|
|
isdn_if *iif;
|
|
|
|
pr_info("ISDN4Linux interface\n");
|
|
|
|
iif = kmalloc(sizeof *iif, GFP_KERNEL);
|
|
if (!iif) {
|
|
pr_err("out of memory\n");
|
|
return 0;
|
|
}
|
|
|
|
if (snprintf(iif->id, sizeof iif->id, "%s_%u", isdnid, cs->minor_index)
|
|
>= sizeof iif->id) {
|
|
pr_err("ID too long: %s\n", isdnid);
|
|
kfree(iif);
|
|
return 0;
|
|
}
|
|
|
|
iif->owner = THIS_MODULE;
|
|
iif->channels = cs->channels;
|
|
iif->maxbufsize = MAX_BUF_SIZE;
|
|
iif->features = ISDN_FEATURE_L2_TRANS |
|
|
ISDN_FEATURE_L2_HDLC |
|
|
#ifdef GIG_X75
|
|
ISDN_FEATURE_L2_X75I |
|
|
#endif
|
|
ISDN_FEATURE_L3_TRANS |
|
|
ISDN_FEATURE_P_EURO;
|
|
iif->hl_hdrlen = HW_HDR_LEN; /* Area for storing ack */
|
|
iif->command = command_from_LL;
|
|
iif->writebuf_skb = writebuf_from_LL;
|
|
iif->writecmd = NULL; /* Don't support isdnctrl */
|
|
iif->readstat = NULL; /* Don't support isdnctrl */
|
|
iif->rcvcallb_skb = NULL; /* Will be set by LL */
|
|
iif->statcallb = NULL; /* Will be set by LL */
|
|
|
|
if (!register_isdn(iif)) {
|
|
pr_err("register_isdn failed\n");
|
|
kfree(iif);
|
|
return 0;
|
|
}
|
|
|
|
cs->iif = iif;
|
|
cs->myid = iif->channels; /* Set my device id */
|
|
cs->hw_hdr_len = HW_HDR_LEN;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* gigaset_isdn_unregister() - unregister from LL
|
|
* @cs: device descriptor structure.
|
|
*
|
|
* Called by main module to unregister the device from the LL.
|
|
*/
|
|
void gigaset_isdn_unregister(struct cardstate *cs)
|
|
{
|
|
gig_dbg(DEBUG_CMD, "sending UNLOAD");
|
|
gigaset_i4l_cmd(cs, ISDN_STAT_UNLOAD);
|
|
kfree(cs->iif);
|
|
cs->iif = NULL;
|
|
}
|