2022-03-27 14:30:19 +00:00
|
|
|
/* 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 "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:
|
2022-05-01 08:09:05 +00:00
|
|
|
printk(KERN_DEBUG "PH-ACTIVATE_REQ to interface\n");
|
2022-03-27 14:30:19 +00:00
|
|
|
ph_socket_tx_msg(&drv->ph_socket, drv->dch->slot, PH_PRIM_ACT_REQ, NULL, 0);
|
|
|
|
break;
|
|
|
|
case PH_DEACTIVATE_REQ:
|
2022-05-01 08:09:05 +00:00
|
|
|
printk(KERN_DEBUG "PH-DEACTIVATE_REQ to interface\n");
|
2022-03-27 14:30:19 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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 ret = 0;
|
|
|
|
struct dchannel *dch;
|
|
|
|
|
|
|
|
|
|
|
|
memset(drv, 0, sizeof(*drv));
|
|
|
|
drv->priv = priv;
|
|
|
|
drv->pri = pri;
|
|
|
|
drv->nt = nt;
|
|
|
|
|
|
|
|
/* socket client */
|
|
|
|
ph_socket_init(&drv->ph_socket, drv, socket_name, 0);
|
|
|
|
|
|
|
|
/* allocate dchannel structure */
|
|
|
|
dch = kzalloc(sizeof(*dch), GFP_KERNEL);
|
|
|
|
if (!dch) {
|
|
|
|
ret = -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 */
|
|
|
|
ret = mISDN_register_device(&dch->dev, NULL, "ph-socket");
|
|
|
|
if (ret) {
|
|
|
|
kfree(dch);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* link */
|
|
|
|
drv->dch = dch;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
exit_ph_socket_driver(drv);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
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_ENABLE:
|
|
|
|
printk(KERN_DEBUG "PH-SOCKET Interface available\n");
|
|
|
|
drv->enabled = 1;
|
|
|
|
break;
|
|
|
|
case PH_CTRL_DISABLE:
|
|
|
|
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;
|
|
|
|
default:
|
|
|
|
printk(KERN_ERR "%s: Rejecting unknown message 0x%02x from PH-socket!\n", __func__, prim);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|