remarkable-linux/drivers/isdn/gigaset/i4l.c
Tilman Schmidt 81fa7b8257 isdn/gigaset: unify function return values
Various functions in the Gigaset driver were using different
conventions for the meaning of their int return values.
Align them to the usual negative error numbers convention.

Inspired-by: Julia Lawall <julia.lawall@lip6.fr>
Signed-off-by: Tilman Schmidt <tilman@imap.cc>
Signed-off-by: David S. Miller <davem@davemloft.net>
2012-05-07 22:37:56 -04:00

696 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>
#include <linux/export.h>
#define SBUFSIZE 4096 /* sk_buff payload size */
#define TRANSBUFSIZE 768 /* bytes per skb for transparent receive */
#define HW_HDR_LEN 2 /* Header size used to store ack info */
#define MAX_BUF_SIZE (SBUFSIZE - HW_HDR_LEN) /* max data packet from LL */
/* == 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 = gigaset_get_cs_by_id(driverID);
struct bc_state *bcs;
unsigned char *ack_header;
unsigned len;
if (!cs) {
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;
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_CMD,
"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) < 0) {
dev_err(cs->dev, "ISDN_CMD_DIAL: channel not free\n");
return -EBUSY;
}
switch (bcs->proto2) {
case L2_HDLC:
bcs->rx_bufsize = SBUFSIZE;
break;
default: /* assume transparent */
bcs->rx_bufsize = TRANSBUFSIZE;
}
dev_kfree_skb(bcs->rx_skb);
gigaset_new_rx_skb(bcs);
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;
}
gigaset_schedule_event(cs);
break;
case ISDN_CMD_ACCEPTD:
gig_dbg(DEBUG_CMD, "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;
switch (bcs->proto2) {
case L2_HDLC:
bcs->rx_bufsize = SBUFSIZE;
break;
default: /* assume transparent */
bcs->rx_bufsize = TRANSBUFSIZE;
}
dev_kfree_skb(bcs->rx_skb);
gigaset_new_rx_skb(bcs);
if (!gigaset_add_event(cs, &bcs->at_state,
EV_ACCEPT, NULL, 0, NULL))
return -ENOMEM;
gigaset_schedule_event(cs);
break;
case ISDN_CMD_HANGUP:
gig_dbg(DEBUG_CMD, "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;
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 */
gig_dbg(DEBUG_CMD, "ISDN_CMD_SETL3");
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;
default:
gig_dbg(DEBUG_CMD, "unknown command %d from LL",
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]);
kfree(commands);
gigaset_free_channel(bcs);
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;
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]) {
strlcpy(response.parm.setup.phone, at_state->str_var[STR_NMBR],
sizeof response.parm.setup.phone);
} else
response.parm.setup.phone[0] = 0;
if (at_state->str_var[STR_ZCPN]) {
strlcpy(response.parm.setup.eazmsn, at_state->str_var[STR_ZCPN],
sizeof response.parm.setup.eazmsn);
} 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;
} else {
gig_dbg(DEBUG_CMD, "Sending ICALL");
response.command = ISDN_STAT_ICALL;
response.arg = bcs->channel;
}
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_regdev() - register to LL
* @cs: device descriptor structure.
* @isdnid: device name.
*
* Return value: 0 on success, error code < 0 on failure
*/
int gigaset_isdn_regdev(struct cardstate *cs, const char *isdnid)
{
isdn_if *iif;
iif = kmalloc(sizeof *iif, GFP_KERNEL);
if (!iif) {
pr_err("out of memory\n");
return -ENOMEM;
}
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 -EINVAL;
}
iif->owner = THIS_MODULE;
iif->channels = cs->channels;
iif->maxbufsize = MAX_BUF_SIZE;
iif->features = ISDN_FEATURE_L2_TRANS |
ISDN_FEATURE_L2_HDLC |
ISDN_FEATURE_L2_X75I |
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 -EINVAL;
}
cs->iif = iif;
cs->myid = iif->channels; /* Set my device id */
cs->hw_hdr_len = HW_HDR_LEN;
return 0;
}
/**
* gigaset_isdn_unregdev() - unregister device from LL
* @cs: device descriptor structure.
*/
void gigaset_isdn_unregdev(struct cardstate *cs)
{
gig_dbg(DEBUG_CMD, "sending UNLOAD");
gigaset_i4l_cmd(cs, ISDN_STAT_UNLOAD);
kfree(cs->iif);
cs->iif = NULL;
}
/**
* gigaset_isdn_regdrv() - register driver to LL
*/
void gigaset_isdn_regdrv(void)
{
pr_info("ISDN4Linux interface\n");
/* nothing to do */
}
/**
* gigaset_isdn_unregdrv() - unregister driver from LL
*/
void gigaset_isdn_unregdrv(void)
{
/* nothing to do */
}