osmo-cc-misdn-endpoint/src/isdn/ph_driver.c

326 lines
8.9 KiB
C

/* ph-socket driver for user space mISDN
*
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* 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 3 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include "../libtimer/timer.h"
#include "../libselect/select.h"
#include "../libph_socket/ph_socket.h"
#include "ph_driver.h"
#define __MISDNL1L2__
#include "../libmisdn/mISDNhw.h"
static inline u_int
get_sapi_tei(u_char *p)
{
u_int sapi, tei;
sapi = *p >> 2;
tei = p[1] >> 1;
return sapi | (tei << 8);
}
/* message from mISDN stack to PH-socket */
static int d_msg_down(struct mISDNchannel *ch, struct sk_buff *skb)
{
struct mISDNdevice *dev = container_of(ch, struct mISDNdevice, D);
struct dchannel *dch = container_of(dev, struct dchannel, dev);
struct ph_socket_driver *drv = dch->hw;
struct mISDNhead *hh = mISDN_HEAD_P(skb);
switch (hh->prim) {
case PH_DATA_REQ:
printk(KERN_DEBUG "PH-DATA-REQ to interface (channel=%d, len=%d)\n", drv->dch->slot, skb->len);
ph_socket_tx_msg(&drv->ph_socket, drv->dch->slot, PH_PRIM_DATA_REQ, skb->data, skb->len);
skb_trim(skb, 0);
printk(KERN_DEBUG "PH-DATA-CNF from interface\n");
queue_ch_frame(ch, PH_DATA_CNF, hh->id, skb);
skb = NULL;
break;
case PH_ACTIVATE_REQ:
printk(KERN_DEBUG "PH-ACTIVATE_REQ to interface\n");
if (drv->enabled)
ph_socket_tx_msg(&drv->ph_socket, drv->dch->slot, PH_PRIM_ACT_REQ, NULL, 0);
else
_queue_data(&drv->dch->dev.D, PH_DEACTIVATE_IND, MISDN_ID_ANY, 0, NULL, GFP_ATOMIC);
break;
case PH_DEACTIVATE_REQ:
printk(KERN_DEBUG "PH-DEACTIVATE_REQ to interface\n");
ph_socket_tx_msg(&drv->ph_socket, drv->dch->slot, PH_PRIM_DACT_REQ, NULL, 0);
break;
}
if (skb)
dev_kfree_skb(skb);
return 0;
}
/* open sub function of d_ctrl */
static int open_dchannel(struct ph_socket_driver __attribute__((unused)) *drv, struct dchannel *dch, struct channel_req *rq)
{
if (dch->debug & DEBUG_HW_OPEN)
printk(KERN_DEBUG "%s: dev(%d) open from %p\n", __func__,
dch->dev.id, __builtin_return_address(0));
if (rq->protocol == ISDN_P_NONE)
return -EINVAL;
if ((dch->dev.D.protocol != ISDN_P_NONE) &&
(dch->dev.D.protocol != rq->protocol)) {
if (dch->debug & DEBUG_HW_OPEN)
printk(KERN_WARNING "%s: change protocol %x to %x\n",
__func__, dch->dev.D.protocol, rq->protocol);
}
if (dch->dev.D.protocol != rq->protocol)
dch->dev.D.protocol = rq->protocol;
if (test_bit(FLG_ACTIVE, &dch->Flags)) {
_queue_data(&dch->dev.D, PH_ACTIVATE_IND, MISDN_ID_ANY,
0, NULL, GFP_KERNEL);
}
rq->ch = &dch->dev.D;
return 0;
}
/* channel sub function of d_ctrl */
static int channel_dctrl(struct dchannel __attribute__((unused)) *dch, struct mISDN_ctrl_req *cq)
{
int ret = 0;
switch (cq->op) {
default:
printk(KERN_WARNING "%s: unknown Op %x\n",
__func__, cq->op);
ret = -EINVAL;
break;
}
return ret;
}
/* control from mISDN stack to this driver */
static int d_ctrl(struct mISDNchannel *ch, u_int cmd, void *arg)
{
struct mISDNdevice *dev = container_of(ch, struct mISDNdevice, D);
struct dchannel *dch = container_of(dev, struct dchannel, dev);
struct ph_socket_driver *drv = dch->hw;
struct channel_req *rq;
int err = 0;
if (dch->debug & DEBUG_HW)
printk(KERN_DEBUG "%s: cmd:%x %p\n",
__func__, cmd, arg);
switch (cmd) {
case OPEN_CHANNEL:
rq = arg;
switch (rq->protocol) {
case ISDN_P_TE_S0:
case ISDN_P_NT_S0:
if (drv->pri) {
err = -EINVAL;
break;
}
err = open_dchannel(drv, dch, rq);
break;
case ISDN_P_TE_E1:
case ISDN_P_NT_E1:
if (!drv->pri) {
err = -EINVAL;
break;
}
err = open_dchannel(drv, dch, rq);
break;
default:
;
//err = open_bchannel(hc, dch, rq);
}
break;
case CLOSE_CHANNEL:
if (dch->debug & DEBUG_HW_OPEN)
printk(KERN_DEBUG "%s: dev(%d) close from %p\n",
__func__, dch->dev.id,
__builtin_return_address(0));
break;
case CONTROL_CHANNEL:
err = channel_dctrl(dch, arg);
break;
default:
if (dch->debug & DEBUG_HW)
printk(KERN_DEBUG "%s: unknown command %x\n",
__func__, cmd);
err = -EINVAL;
}
return err;
}
static void ph_socket_rx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length);
/* init instance of PH-socket driver */
int init_ph_socket_driver(struct ph_socket_driver *drv, void *priv, const char *socket_name, int pri, int nt, uint32_t debug)
{
int rc = 0;
struct dchannel *dch;
memset(drv, 0, sizeof(*drv));
drv->priv = priv;
drv->pri = pri;
drv->nt = nt;
/* allocate dchannel structure */
dch = kzalloc(sizeof(*dch), GFP_KERNEL);
if (!dch) {
rc = -ENOMEM;
goto error;
}
/* populate dchannel structure */
dch->debug = debug;
mISDN_initdchannel(dch, MAX_DFRAME_LEN_L1, NULL);
dch->hw = drv;
if (pri) {
if (nt)
dch->dev.Dprotocols = (1 << ISDN_P_NT_E1);
else
dch->dev.Dprotocols = (1 << ISDN_P_TE_E1);
dch->dev.nrbchan = 30;
} else {
if (nt)
dch->dev.Dprotocols = (1 << ISDN_P_NT_S0);
else
dch->dev.Dprotocols = (1 << ISDN_P_TE_S0);
dch->dev.nrbchan = 2;
}
dch->dev.Bprotocols = (1 << (ISDN_P_B_RAW & ISDN_P_B_MASK)) |
(1 << (ISDN_P_B_HDLC & ISDN_P_B_MASK));
dch->dev.D.send = d_msg_down;
dch->dev.D.ctrl = d_ctrl;
dch->slot = (pri) ? 16 : 3;
dch->dev.nrbchan = 0;
/* register dchannel to mISDN */
rc = mISDN_register_device(&dch->dev, NULL, socket_name);
if (rc) {
goto error;
}
/* link */
drv->dch = dch;
/* socket client, must be initialized here, because driver will receive PH_PRIM_CTRL_IND message during init */
rc = ph_socket_init(&drv->ph_socket, ph_socket_rx_msg, drv, socket_name, 0);
if (rc < 0)
goto error;
return 0;
error:
if (dch) {
kfree(dch);
dch = NULL;
}
exit_ph_socket_driver(drv);
return rc;
}
/* destroy instance of PH-socket driver */
void exit_ph_socket_driver(struct ph_socket_driver *drv)
{
/* unregister dchannel structure and free */
if (drv->dch) {
mISDN_unregister_device(&drv->dch->dev);
mISDN_freedchannel(drv->dch);
kfree(drv->dch);
drv->dch = NULL;
}
/* close socket */
ph_socket_exit(&drv->ph_socket);
}
/* message from PH-socket to mISDN */
static void ph_socket_rx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length)
{
struct ph_socket_driver *drv = (struct ph_socket_driver *)s->priv;
/* stack not complete */
if (!drv || !drv->dch)
return;
switch (prim) {
case PH_PRIM_DATA_IND:
if (drv->dch->slot == channel) {
printk(KERN_DEBUG "PH-DATA-IND from interface (channel %d, len=%d)\n", channel, length);
if (length < 2) {
printk(KERN_ERR "%s: Message too short!\n", __func__);
break;
}
_queue_data(&drv->dch->dev.D, PH_DATA_IND, get_sapi_tei(data), length, data, GFP_ATOMIC);
break;
}
bchannel_ph_sock_receive(drv->priv, channel, prim, data, length);
break;
case PH_PRIM_ACT_IND:
if (drv->dch->slot == channel) {
printk(KERN_DEBUG "PH-ACTIVATE-IND from interface (dchannel)\n");
drv->activated = 1;
_queue_data(&drv->dch->dev.D, PH_ACTIVATE_IND, MISDN_ID_ANY, 0, NULL, GFP_ATOMIC);
break;
}
printk(KERN_DEBUG "PH-ACTIVATE-IND from interface (bchannel)\n");
bchannel_ph_sock_receive(drv->priv, channel, prim, data, length);
break;
case PH_PRIM_DACT_IND:
if (drv->dch->slot == channel) {
printk(KERN_DEBUG "PH-DEACTIVATE-IND from interface (dchannel)\n");
drv->activated = 0;
_queue_data(&drv->dch->dev.D, PH_DEACTIVATE_IND, MISDN_ID_ANY, 0, NULL, GFP_ATOMIC);
break;
}
printk(KERN_DEBUG "PH-DEACTIVATE-IND from interface (bchannel)\n");
bchannel_ph_sock_receive(drv->priv, channel, prim, data, length);
break;
case PH_PRIM_CTRL_IND:
if (length >= 1) {
switch (data[0]) {
case PH_CTRL_UNBLOCK:
printk(KERN_DEBUG "PH-SOCKET Interface available\n");
drv->enabled = 1;
break;
case PH_CTRL_BLOCK:
printk(KERN_DEBUG "PH-SOCKET Interface unavailable\n");
drv->enabled = 0;
if (drv->activated) {
drv->activated = 0;
_queue_data(&drv->dch->dev.D, PH_DEACTIVATE_IND, MISDN_ID_ANY, 0, NULL, GFP_ATOMIC);
}
break;
}
}
break;
case PH_PRIM_DATA_CNF:
break;
default:
printk(KERN_ERR "%s: Rejecting unknown message 0x%02x from PH-socket!\n", __func__, prim);
}
}