/* ph-socket driver for user space mISDN * * (C) 2020 by Andreas Eversberg * 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 . */ #include #include #include #include #include #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); } }