| Commit | Line | Data |
|---|---|---|
| b06ebda0 | 1 | /*- |
| 22ff886e AH |
2 | * (MPSAFE) |
| 3 | * | |
| b06ebda0 MD |
4 | * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin@yahoo.com> |
| 5 | * All rights reserved. | |
| 6 | * | |
| 22ff886e AH |
7 | * ng_h4.c |
| 8 | * | |
| b06ebda0 MD |
9 | * Redistribution and use in source and binary forms, with or without |
| 10 | * modification, are permitted provided that the following conditions | |
| 11 | * are met: | |
| 12 | * 1. Redistributions of source code must retain the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer. | |
| 14 | * 2. Redistributions in binary form must reproduce the above copyright | |
| 15 | * notice, this list of conditions and the following disclaimer in the | |
| 16 | * documentation and/or other materials provided with the distribution. | |
| 17 | * | |
| 18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
| 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
| 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
| 24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
| 25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
| 26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
| 27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
| 28 | * SUCH DAMAGE. | |
| 29 | * | |
| 30 | * $Id: ng_h4.c,v 1.10 2005/10/31 17:57:43 max Exp $ | |
| 31 | * $FreeBSD: src/sys/netgraph/bluetooth/drivers/h4/ng_h4.c,v 1.17 2007/08/13 17:19:28 emax Exp $ | |
| 5a975a3d MD |
32 | * $DragonFly: src/sys/netgraph7/bluetooth/drivers/h4/ng_h4.c,v 1.2 2008/06/26 23:05:40 dillon Exp $ |
| 33 | * $DragonFly: src/sys/netgraph7/bluetooth/drivers/h4/ng_h4.c,v 1.2 2008/06/26 23:05:40 dillon Exp $ | |
| b06ebda0 MD |
34 | * |
| 35 | * Based on: | |
| 36 | * --------- | |
| 37 | * | |
| 38 | * FreeBSD: src/sys/netgraph/ng_tty.c | |
| 39 | * Author: Archie Cobbs <archie@freebsd.org> | |
| 40 | * | |
| 41 | */ | |
| 42 | ||
| 43 | #include <sys/param.h> | |
| 44 | #include <sys/systm.h> | |
| 45 | #include <sys/kernel.h> | |
| 46 | #include <sys/conf.h> | |
| 47 | #include <sys/endian.h> | |
| 48 | #include <sys/errno.h> | |
| 49 | #include <sys/fcntl.h> | |
| b06ebda0 MD |
50 | #include <sys/malloc.h> |
| 51 | #include <sys/mbuf.h> | |
| 52 | #include <sys/priv.h> | |
| 53 | #include <sys/socket.h> | |
| 54 | #include <sys/tty.h> | |
| 55 | #include <sys/ttycom.h> | |
| 56 | #include <net/if.h> | |
| 57 | #include <net/if_var.h> | |
| 5a975a3d MD |
58 | #include "ng_message.h" |
| 59 | #include "netgraph.h" | |
| 60 | #include "ng_parse.h" | |
| 61 | #include "bluetooth/include/ng_bluetooth.h" | |
| 62 | #include "bluetooth/include/ng_hci.h" | |
| 63 | #include "bluetooth/include/ng_h4.h" | |
| 64 | #include "bluetooth/drivers/h4/ng_h4_var.h" | |
| 65 | #include "bluetooth/drivers/h4/ng_h4_prse.h" | |
| b06ebda0 MD |
66 | |
| 67 | /***************************************************************************** | |
| 68 | ***************************************************************************** | |
| 69 | ** This node implements a Bluetooth HCI UART transport layer as per chapter | |
| 70 | ** H4 of the Bluetooth Specification Book v1.1. It is a terminal line | |
| 71 | ** discipline that is also a netgraph node. Installing this line discipline | |
| 72 | ** on a terminal device instantiates a new netgraph node of this type, which | |
| 73 | ** allows access to the device via the "hook" hook of the node. | |
| 74 | ** | |
| 75 | ** Once the line discipline is installed, you can find out the name of the | |
| 76 | ** corresponding netgraph node via a NGIOCGINFO ioctl(). | |
| 77 | ***************************************************************************** | |
| 78 | *****************************************************************************/ | |
| 79 | ||
| 80 | /* MALLOC define */ | |
| 81 | #ifndef NG_SEPARATE_MALLOC | |
| 82 | MALLOC_DEFINE(M_NETGRAPH_H4, "netgraph_h4", "Netgraph Bluetooth H4 node"); | |
| 83 | #else | |
| 84 | #define M_NETGRAPH_H4 M_NETGRAPH | |
| 85 | #endif /* NG_SEPARATE_MALLOC */ | |
| 86 | ||
| 87 | /* Line discipline methods */ | |
| 88 | static int ng_h4_open (struct cdev *, struct tty *); | |
| 89 | static int ng_h4_close (struct tty *, int); | |
| 90 | static int ng_h4_read (struct tty *, struct uio *, int); | |
| 91 | static int ng_h4_write (struct tty *, struct uio *, int); | |
| 92 | static int ng_h4_input (int, struct tty *); | |
| 93 | static int ng_h4_start (struct tty *); | |
| 94 | static int ng_h4_ioctl (struct tty *, u_long, caddr_t, | |
| 95 | int, struct thread *); | |
| 96 | ||
| 97 | /* Line discipline descriptor */ | |
| 98 | static struct linesw ng_h4_disc = { | |
| 99 | ng_h4_open, /* open */ | |
| 100 | ng_h4_close, /* close */ | |
| 101 | ng_h4_read, /* read */ | |
| 102 | ng_h4_write, /* write */ | |
| 103 | ng_h4_ioctl, /* ioctl */ | |
| 104 | ng_h4_input, /* input */ | |
| 105 | ng_h4_start, /* start */ | |
| 106 | ttymodem /* modem */ | |
| 107 | }; | |
| 108 | ||
| 109 | /* Netgraph methods */ | |
| 110 | static ng_constructor_t ng_h4_constructor; | |
| 111 | static ng_rcvmsg_t ng_h4_rcvmsg; | |
| 112 | static ng_shutdown_t ng_h4_shutdown; | |
| 113 | static ng_newhook_t ng_h4_newhook; | |
| 114 | static ng_connect_t ng_h4_connect; | |
| 115 | static ng_rcvdata_t ng_h4_rcvdata; | |
| 116 | static ng_disconnect_t ng_h4_disconnect; | |
| 117 | ||
| 118 | /* Other stuff */ | |
| 119 | static void ng_h4_process_timeout (node_p, hook_p, void *, int); | |
| 120 | static int ng_h4_mod_event (module_t, int, void *); | |
| 121 | ||
| 122 | /* Netgraph node type descriptor */ | |
| 123 | static struct ng_type typestruct = { | |
| 124 | .version = NG_ABI_VERSION, | |
| 125 | .name = NG_H4_NODE_TYPE, | |
| 126 | .mod_event = ng_h4_mod_event, | |
| 127 | .constructor = ng_h4_constructor, | |
| 128 | .rcvmsg = ng_h4_rcvmsg, | |
| 129 | .shutdown = ng_h4_shutdown, | |
| 130 | .newhook = ng_h4_newhook, | |
| 131 | .connect = ng_h4_connect, | |
| 132 | .rcvdata = ng_h4_rcvdata, | |
| 133 | .disconnect = ng_h4_disconnect, | |
| 134 | .cmdlist = ng_h4_cmdlist | |
| 135 | }; | |
| 136 | NETGRAPH_INIT(h4, &typestruct); | |
| 137 | MODULE_VERSION(ng_h4, NG_BLUETOOTH_VERSION); | |
| 138 | ||
| 139 | static int ng_h4_node = 0; | |
| 140 | ||
| 141 | /***************************************************************************** | |
| 142 | ***************************************************************************** | |
| 143 | ** Line discipline methods | |
| 144 | ***************************************************************************** | |
| 145 | *****************************************************************************/ | |
| 146 | ||
| 147 | /* | |
| 148 | * Set our line discipline on the tty. | |
| 149 | */ | |
| 150 | ||
| 151 | static int | |
| 152 | ng_h4_open(struct cdev *dev, struct tty *tp) | |
| 153 | { | |
| 154 | struct thread *td = curthread; | |
| 155 | char name[NG_NODESIZ]; | |
| 156 | ng_h4_info_p sc = NULL; | |
| 157 | int error; | |
| 158 | ||
| 159 | /* Super-user only */ | |
| 160 | error = priv_check(td, PRIV_NETGRAPH_TTY); /* XXX */ | |
| 161 | if (error != 0) | |
| 162 | return (error); | |
| 163 | ||
| 164 | /* Initialize private struct */ | |
| fc025606 | 165 | sc = kmalloc(sizeof(*sc), M_NETGRAPH_H4, M_WAITOK | M_NULLOK | M_ZERO); |
| b06ebda0 MD |
166 | if (sc == NULL) |
| 167 | return (ENOMEM); | |
| 168 | ||
| 22ff886e | 169 | lwkt_gettoken(&tty_token); |
| b06ebda0 MD |
170 | sc->tp = tp; |
| 171 | sc->debug = NG_H4_WARN_LEVEL; | |
| 172 | ||
| 173 | sc->state = NG_H4_W4_PKT_IND; | |
| 174 | sc->want = 1; | |
| 175 | sc->got = 0; | |
| 176 | ||
| 177 | mtx_init(&sc->outq.ifq_mtx, "ng_h4 node+queue", NULL, MTX_DEF); | |
| 178 | IFQ_SET_MAXLEN(&sc->outq, NG_H4_DEFAULTQLEN); | |
| 179 | ng_callout_init(&sc->timo); | |
| 180 | ||
| 181 | NG_H4_LOCK(sc); | |
| 182 | ||
| 183 | /* Setup netgraph node */ | |
| 184 | error = ng_make_node_common(&typestruct, &sc->node); | |
| 185 | if (error != 0) { | |
| 186 | NG_H4_UNLOCK(sc); | |
| 187 | ||
| 188 | printf("%s: Unable to create new node!\n", __func__); | |
| 189 | ||
| 190 | mtx_destroy(&sc->outq.ifq_mtx); | |
| 191 | bzero(sc, sizeof(*sc)); | |
| fc025606 | 192 | kfree(sc, M_NETGRAPH_H4); |
| b06ebda0 | 193 | |
| 22ff886e | 194 | lwkt_reltoken(&tty_token); |
| b06ebda0 MD |
195 | return (error); |
| 196 | } | |
| 197 | ||
| 198 | /* Assign node its name */ | |
| 199 | snprintf(name, sizeof(name), "%s%d", typestruct.name, ng_h4_node ++); | |
| 200 | ||
| 201 | error = ng_name_node(sc->node, name); | |
| 202 | if (error != 0) { | |
| 203 | NG_H4_UNLOCK(sc); | |
| 204 | ||
| 205 | printf("%s: %s - node name exists?\n", __func__, name); | |
| 206 | ||
| 207 | NG_NODE_UNREF(sc->node); | |
| 208 | mtx_destroy(&sc->outq.ifq_mtx); | |
| 209 | bzero(sc, sizeof(*sc)); | |
| fc025606 | 210 | kfree(sc, M_NETGRAPH_H4); |
| b06ebda0 | 211 | |
| 22ff886e | 212 | lwkt_reltoken(&tty_token); |
| b06ebda0 MD |
213 | return (error); |
| 214 | } | |
| 215 | ||
| 216 | /* Set back pointers */ | |
| 217 | NG_NODE_SET_PRIVATE(sc->node, sc); | |
| 218 | tp->t_lsc = (caddr_t) sc; | |
| 219 | ||
| 220 | /* The node has to be a WRITER because data can change node status */ | |
| 221 | NG_NODE_FORCE_WRITER(sc->node); | |
| 222 | ||
| 223 | /* | |
| 224 | * Pre-allocate cblocks to the an appropriate amount. | |
| 225 | * I'm not sure what is appropriate. | |
| 226 | */ | |
| 227 | ||
| 228 | ttyflush(tp, FREAD | FWRITE); | |
| 229 | clist_alloc_cblocks(&tp->t_canq, 0, 0); | |
| 230 | clist_alloc_cblocks(&tp->t_rawq, 0, 0); | |
| 231 | clist_alloc_cblocks(&tp->t_outq, | |
| 232 | MLEN + NG_H4_HIWATER, MLEN + NG_H4_HIWATER); | |
| 233 | ||
| 234 | NG_H4_UNLOCK(sc); | |
| 235 | ||
| 22ff886e | 236 | lwkt_reltoken(&tty_token); |
| b06ebda0 MD |
237 | return (error); |
| 238 | } /* ng_h4_open */ | |
| 239 | ||
| 240 | /* | |
| 241 | * Line specific close routine, called from device close routine | |
| 242 | * and from ttioctl. This causes the node to be destroyed as well. | |
| 243 | */ | |
| 244 | ||
| 245 | static int | |
| 246 | ng_h4_close(struct tty *tp, int flag) | |
| 247 | { | |
| 248 | ng_h4_info_p sc = (ng_h4_info_p) tp->t_lsc; | |
| 249 | ||
| 22ff886e | 250 | lwkt_gettoken(&tty_token); |
| b06ebda0 MD |
251 | ttyflush(tp, FREAD | FWRITE); |
| 252 | clist_free_cblocks(&tp->t_outq); | |
| 253 | ||
| 254 | if (sc != NULL) { | |
| 255 | NG_H4_LOCK(sc); | |
| 256 | ||
| 257 | if (callout_pending(&sc->timo)) | |
| 258 | ng_uncallout(&sc->timo, sc->node); | |
| 259 | ||
| 260 | tp->t_lsc = NULL; | |
| 261 | sc->dying = 1; | |
| 262 | ||
| 263 | NG_H4_UNLOCK(sc); | |
| 264 | ||
| 265 | ng_rmnode_self(sc->node); | |
| 266 | } | |
| 267 | ||
| 22ff886e | 268 | lwkt_reltoken(&tty_token); |
| b06ebda0 MD |
269 | return (0); |
| 270 | } /* ng_h4_close */ | |
| 271 | ||
| 272 | /* | |
| 273 | * Once the device has been turned into a node, we don't allow reading. | |
| 274 | */ | |
| 275 | ||
| 276 | static int | |
| 277 | ng_h4_read(struct tty *tp, struct uio *uio, int flag) | |
| 278 | { | |
| 279 | return (EIO); | |
| 280 | } /* ng_h4_read */ | |
| 281 | ||
| 282 | /* | |
| 283 | * Once the device has been turned into a node, we don't allow writing. | |
| 284 | */ | |
| 285 | ||
| 286 | static int | |
| 287 | ng_h4_write(struct tty *tp, struct uio *uio, int flag) | |
| 288 | { | |
| 289 | return (EIO); | |
| 290 | } /* ng_h4_write */ | |
| 291 | ||
| 292 | /* | |
| 293 | * We implement the NGIOCGINFO ioctl() defined in ng_message.h. | |
| 294 | */ | |
| 295 | ||
| 296 | static int | |
| 297 | ng_h4_ioctl(struct tty *tp, u_long cmd, caddr_t data, int flag, | |
| 298 | struct thread *td) | |
| 299 | { | |
| 300 | ng_h4_info_p sc = (ng_h4_info_p) tp->t_lsc; | |
| 301 | int error = 0; | |
| 302 | ||
| 303 | if (sc == NULL) | |
| 304 | return (ENXIO); | |
| 305 | ||
| 22ff886e | 306 | lwkt_gettoken(&tty_token); |
| b06ebda0 MD |
307 | NG_H4_LOCK(sc); |
| 308 | ||
| 309 | switch (cmd) { | |
| 310 | case NGIOCGINFO: | |
| 311 | #undef NI | |
| 312 | #define NI(x) ((struct nodeinfo *)(x)) | |
| 313 | ||
| 314 | bzero(data, sizeof(*NI(data))); | |
| 315 | ||
| 316 | if (NG_NODE_HAS_NAME(sc->node)) | |
| 317 | strncpy(NI(data)->name, NG_NODE_NAME(sc->node), | |
| 318 | sizeof(NI(data)->name) - 1); | |
| 319 | ||
| 320 | strncpy(NI(data)->type, sc->node->nd_type->name, | |
| 321 | sizeof(NI(data)->type) - 1); | |
| 322 | ||
| 323 | NI(data)->id = (u_int32_t) ng_node2ID(sc->node); | |
| 324 | NI(data)->hooks = NG_NODE_NUMHOOKS(sc->node); | |
| 325 | break; | |
| 326 | ||
| 327 | default: | |
| 328 | error = ENOIOCTL; | |
| 329 | break; | |
| 330 | } | |
| 331 | ||
| 332 | NG_H4_UNLOCK(sc); | |
| 333 | ||
| 22ff886e | 334 | lwkt_reltoken(&tty_token); |
| b06ebda0 MD |
335 | return (error); |
| 336 | } /* ng_h4_ioctl */ | |
| 337 | ||
| 338 | /* | |
| 339 | * Receive data coming from the device. We get one character at a time, which | |
| 340 | * is kindof silly. | |
| 341 | */ | |
| 342 | ||
| 343 | static int | |
| 344 | ng_h4_input(int c, struct tty *tp) | |
| 345 | { | |
| 346 | ng_h4_info_p sc = (ng_h4_info_p) tp->t_lsc; | |
| 347 | ||
| 22ff886e | 348 | lwkt_gettoken(&tty_token); |
| b06ebda0 | 349 | if (sc == NULL || tp != sc->tp || |
| 22ff886e AH |
350 | sc->node == NULL || NG_NODE_NOT_VALID(sc->node)) { |
| 351 | lwkt_reltoken(&tty_token); | |
| b06ebda0 | 352 | return (0); |
| 22ff886e | 353 | } |
| b06ebda0 MD |
354 | |
| 355 | NG_H4_LOCK(sc); | |
| 356 | ||
| 357 | /* Check for error conditions */ | |
| 358 | if ((tp->t_state & TS_CONNECTED) == 0) { | |
| 359 | NG_H4_INFO("%s: %s - no carrier\n", __func__, | |
| 360 | NG_NODE_NAME(sc->node)); | |
| 361 | ||
| 362 | sc->state = NG_H4_W4_PKT_IND; | |
| 363 | sc->want = 1; | |
| 364 | sc->got = 0; | |
| 365 | ||
| 366 | NG_H4_UNLOCK(sc); | |
| 367 | ||
| 22ff886e | 368 | lwkt_reltoken(&tty_token); |
| b06ebda0 MD |
369 | return (0); /* XXX Loss of synchronization here! */ |
| 370 | } | |
| 371 | ||
| 372 | /* Check for framing error or overrun on this char */ | |
| 373 | if (c & TTY_ERRORMASK) { | |
| 374 | NG_H4_ERR("%s: %s - line error %#x, c=%#x\n", __func__, | |
| 375 | NG_NODE_NAME(sc->node), c & TTY_ERRORMASK, | |
| 376 | c & TTY_CHARMASK); | |
| 377 | ||
| 378 | NG_H4_STAT_IERROR(sc->stat); | |
| 379 | ||
| 380 | sc->state = NG_H4_W4_PKT_IND; | |
| 381 | sc->want = 1; | |
| 382 | sc->got = 0; | |
| 383 | ||
| 384 | NG_H4_UNLOCK(sc); | |
| 385 | ||
| 22ff886e | 386 | lwkt_reltoken(&tty_token); |
| b06ebda0 MD |
387 | return (0); /* XXX Loss of synchronization here! */ |
| 388 | } | |
| 389 | ||
| 390 | NG_H4_STAT_BYTES_RECV(sc->stat, 1); | |
| 391 | ||
| 392 | /* Append char to mbuf */ | |
| 393 | if (sc->got >= sizeof(sc->ibuf)) { | |
| 394 | NG_H4_ALERT("%s: %s - input buffer overflow, c=%#x, got=%d\n", | |
| 395 | __func__, NG_NODE_NAME(sc->node), c & TTY_CHARMASK, | |
| 396 | sc->got); | |
| 397 | ||
| 398 | NG_H4_STAT_IERROR(sc->stat); | |
| 399 | ||
| 400 | sc->state = NG_H4_W4_PKT_IND; | |
| 401 | sc->want = 1; | |
| 402 | sc->got = 0; | |
| 403 | ||
| 404 | NG_H4_UNLOCK(sc); | |
| 405 | ||
| 22ff886e | 406 | lwkt_reltoken(&tty_token); |
| b06ebda0 MD |
407 | return (0); /* XXX Loss of synchronization here! */ |
| 408 | } | |
| 409 | ||
| 410 | sc->ibuf[sc->got ++] = (c & TTY_CHARMASK); | |
| 411 | ||
| 412 | NG_H4_INFO("%s: %s - got char %#x, want=%d, got=%d\n", __func__, | |
| 413 | NG_NODE_NAME(sc->node), c, sc->want, sc->got); | |
| 414 | ||
| 415 | if (sc->got < sc->want) { | |
| 416 | NG_H4_UNLOCK(sc); | |
| 417 | ||
| 22ff886e | 418 | lwkt_reltoken(&tty_token); |
| b06ebda0 MD |
419 | return (0); /* Wait for more */ |
| 420 | } | |
| 421 | ||
| 422 | switch (sc->state) { | |
| 423 | /* Got packet indicator */ | |
| 424 | case NG_H4_W4_PKT_IND: | |
| 425 | NG_H4_INFO("%s: %s - got packet indicator %#x\n", __func__, | |
| 426 | NG_NODE_NAME(sc->node), sc->ibuf[0]); | |
| 427 | ||
| 428 | sc->state = NG_H4_W4_PKT_HDR; | |
| 429 | ||
| 430 | /* | |
| 431 | * Since packet indicator included in the packet header | |
| 432 | * just set sc->want to sizeof(packet header). | |
| 433 | */ | |
| 434 | ||
| 435 | switch (sc->ibuf[0]) { | |
| 436 | case NG_HCI_ACL_DATA_PKT: | |
| 437 | sc->want = sizeof(ng_hci_acldata_pkt_t); | |
| 438 | break; | |
| 439 | ||
| 440 | case NG_HCI_SCO_DATA_PKT: | |
| 441 | sc->want = sizeof(ng_hci_scodata_pkt_t); | |
| 442 | break; | |
| 443 | ||
| 444 | case NG_HCI_EVENT_PKT: | |
| 445 | sc->want = sizeof(ng_hci_event_pkt_t); | |
| 446 | break; | |
| 447 | ||
| 448 | default: | |
| 449 | NG_H4_WARN("%s: %s - ignoring unknown packet " \ | |
| 450 | "type=%#x\n", __func__, NG_NODE_NAME(sc->node), | |
| 451 | sc->ibuf[0]); | |
| 452 | ||
| 453 | NG_H4_STAT_IERROR(sc->stat); | |
| 454 | ||
| 455 | sc->state = NG_H4_W4_PKT_IND; | |
| 456 | sc->want = 1; | |
| 457 | sc->got = 0; | |
| 458 | break; | |
| 459 | } | |
| 460 | break; | |
| 461 | ||
| 462 | /* Got packet header */ | |
| 463 | case NG_H4_W4_PKT_HDR: | |
| 464 | sc->state = NG_H4_W4_PKT_DATA; | |
| 465 | ||
| 466 | switch (sc->ibuf[0]) { | |
| 467 | case NG_HCI_ACL_DATA_PKT: | |
| 468 | c = le16toh(((ng_hci_acldata_pkt_t *) | |
| 469 | (sc->ibuf))->length); | |
| 470 | break; | |
| 471 | ||
| 472 | case NG_HCI_SCO_DATA_PKT: | |
| 473 | c = ((ng_hci_scodata_pkt_t *)(sc->ibuf))->length; | |
| 474 | break; | |
| 475 | ||
| 476 | case NG_HCI_EVENT_PKT: | |
| 477 | c = ((ng_hci_event_pkt_t *)(sc->ibuf))->length; | |
| 478 | break; | |
| 479 | ||
| 480 | default: | |
| 481 | KASSERT((0), ("Invalid packet type=%#x\n", | |
| 482 | sc->ibuf[0])); | |
| 483 | break; | |
| 484 | } | |
| 485 | ||
| 486 | NG_H4_INFO("%s: %s - got packet header, packet type=%#x, " \ | |
| 487 | "packet size=%d, payload size=%d\n", __func__, | |
| 488 | NG_NODE_NAME(sc->node), sc->ibuf[0], sc->got, c); | |
| 489 | ||
| 490 | if (c > 0) { | |
| 491 | sc->want += c; | |
| 492 | ||
| 493 | /* | |
| 494 | * Try to prevent possible buffer overrun | |
| 495 | * | |
| 496 | * XXX I'm *really* confused here. It turns out | |
| 497 | * that Xircom card sends us packets with length | |
| 498 | * greater then 512 bytes! This is greater then | |
| 499 | * our old receive buffer (ibuf) size. In the same | |
| 500 | * time the card demands from us *not* to send | |
| 501 | * packets greater then 192 bytes. Weird! How the | |
| 502 | * hell i should know how big *receive* buffer | |
| 503 | * should be? For now increase receiving buffer | |
| 504 | * size to 1K and add the following check. | |
| 505 | */ | |
| 506 | ||
| 507 | if (sc->want >= sizeof(sc->ibuf)) { | |
| 508 | int b; | |
| 509 | ||
| 510 | NG_H4_ALERT("%s: %s - packet too big for " \ | |
| 511 | "buffer, type=%#x, got=%d, want=%d, " \ | |
| 512 | "length=%d\n", __func__, | |
| 513 | NG_NODE_NAME(sc->node), sc->ibuf[0], | |
| 514 | sc->got, sc->want, c); | |
| 515 | ||
| 516 | NG_H4_ALERT("Packet header:\n"); | |
| 517 | for (b = 0; b < sc->got; b++) | |
| 518 | NG_H4_ALERT("%#x ", sc->ibuf[b]); | |
| 519 | NG_H4_ALERT("\n"); | |
| 520 | ||
| 521 | /* Reset state */ | |
| 522 | NG_H4_STAT_IERROR(sc->stat); | |
| 523 | ||
| 524 | sc->state = NG_H4_W4_PKT_IND; | |
| 525 | sc->want = 1; | |
| 526 | sc->got = 0; | |
| 527 | } | |
| 528 | ||
| 529 | break; | |
| 530 | } | |
| 531 | ||
| 532 | /* else FALLTHROUGH and deliver frame */ | |
| 533 | /* XXX Is this true? Should we deliver empty frame? */ | |
| 534 | ||
| 535 | /* Got packet data */ | |
| 536 | case NG_H4_W4_PKT_DATA: | |
| 537 | NG_H4_INFO("%s: %s - got full packet, packet type=%#x, " \ | |
| 538 | "packet size=%d\n", __func__, | |
| 539 | NG_NODE_NAME(sc->node), sc->ibuf[0], sc->got); | |
| 540 | ||
| 541 | if (sc->hook != NULL && NG_HOOK_IS_VALID(sc->hook)) { | |
| 542 | struct mbuf *m = NULL; | |
| 543 | ||
| 5a975a3d | 544 | MGETHDR(m, MB_DONTWAIT, MT_DATA); |
| b06ebda0 MD |
545 | if (m != NULL) { |
| 546 | m->m_pkthdr.len = 0; | |
| 547 | ||
| 548 | /* XXX m_copyback() is stupid */ | |
| 549 | m->m_len = min(MHLEN, sc->got); | |
| 550 | ||
| 551 | m_copyback(m, 0, sc->got, sc->ibuf); | |
| 552 | NG_SEND_DATA_ONLY(c, sc->hook, m); | |
| 553 | } else { | |
| 554 | NG_H4_ERR("%s: %s - could not get mbuf\n", | |
| 555 | __func__, NG_NODE_NAME(sc->node)); | |
| 556 | ||
| 557 | NG_H4_STAT_IERROR(sc->stat); | |
| 558 | } | |
| 559 | } | |
| 560 | ||
| 561 | sc->state = NG_H4_W4_PKT_IND; | |
| 562 | sc->want = 1; | |
| 563 | sc->got = 0; | |
| 564 | ||
| 565 | NG_H4_STAT_PCKTS_RECV(sc->stat); | |
| 566 | break; | |
| 567 | ||
| 568 | default: | |
| 569 | KASSERT((0), ("Invalid H4 node state=%d", sc->state)); | |
| 570 | break; | |
| 571 | } | |
| 572 | ||
| 573 | NG_H4_UNLOCK(sc); | |
| 574 | ||
| 22ff886e | 575 | lwkt_reltoken(&tty_token); |
| b06ebda0 MD |
576 | return (0); |
| 577 | } /* ng_h4_input */ | |
| 578 | ||
| 579 | /* | |
| 580 | * This is called when the device driver is ready for more output. Called from | |
| 581 | * tty system. | |
| 582 | */ | |
| 583 | ||
| 584 | static int | |
| 585 | ng_h4_start(struct tty *tp) | |
| 586 | { | |
| 587 | ng_h4_info_p sc = (ng_h4_info_p) tp->t_lsc; | |
| 588 | struct mbuf *m = NULL; | |
| 589 | int size; | |
| 590 | ||
| 22ff886e | 591 | lwkt_gettoken(&tty_token); |
| b06ebda0 | 592 | if (sc == NULL || tp != sc->tp || |
| 22ff886e AH |
593 | sc->node == NULL || NG_NODE_NOT_VALID(sc->node)) { |
| 594 | lwkt_reltoken(&tty_token); | |
| b06ebda0 | 595 | return (0); |
| 22ff886e | 596 | } |
| b06ebda0 MD |
597 | |
| 598 | #if 0 | |
| 599 | while (tp->t_outq.c_cc < NG_H4_HIWATER) { /* XXX 2.2 specific ? */ | |
| 600 | #else | |
| 601 | while (1) { | |
| 602 | #endif | |
| 603 | /* Remove first mbuf from queue */ | |
| 604 | IF_DEQUEUE(&sc->outq, m); | |
| 605 | if (m == NULL) | |
| 606 | break; | |
| 607 | ||
| 608 | /* Send as much of it as possible */ | |
| 609 | while (m != NULL) { | |
| 610 | size = m->m_len - b_to_q(mtod(m, u_char *), | |
| 611 | m->m_len, &tp->t_outq); | |
| 612 | ||
| 613 | NG_H4_LOCK(sc); | |
| 614 | NG_H4_STAT_BYTES_SENT(sc->stat, size); | |
| 615 | NG_H4_UNLOCK(sc); | |
| 616 | ||
| 617 | m->m_data += size; | |
| 618 | m->m_len -= size; | |
| 619 | if (m->m_len > 0) | |
| 620 | break; /* device can't take no more */ | |
| 621 | ||
| 622 | m = m_free(m); | |
| 623 | } | |
| 624 | ||
| 625 | /* Put remainder of mbuf chain (if any) back on queue */ | |
| 626 | if (m != NULL) { | |
| 627 | IF_PREPEND(&sc->outq, m); | |
| 628 | break; | |
| 629 | } | |
| 630 | ||
| 631 | /* Full packet has been sent */ | |
| 632 | NG_H4_LOCK(sc); | |
| 633 | NG_H4_STAT_PCKTS_SENT(sc->stat); | |
| 634 | NG_H4_UNLOCK(sc); | |
| 635 | } | |
| 636 | ||
| 637 | /* | |
| 638 | * Call output process whether or not there is any output. We are | |
| 639 | * being called in lieu of ttstart and must do what it would. | |
| 640 | */ | |
| 641 | ||
| 642 | tt_oproc(sc->tp); | |
| 643 | ||
| 644 | /* | |
| 645 | * This timeout is needed for operation on a pseudo-tty, because the | |
| 646 | * pty code doesn't call pppstart after it has drained the t_outq. | |
| 647 | */ | |
| 648 | ||
| 649 | NG_H4_LOCK(sc); | |
| 650 | ||
| 651 | if (!IFQ_IS_EMPTY(&sc->outq) && !callout_pending(&sc->timo)) | |
| 652 | ng_callout(&sc->timo, sc->node, NULL, 1, | |
| 653 | ng_h4_process_timeout, NULL, 0); | |
| 654 | ||
| 655 | NG_H4_UNLOCK(sc); | |
| 656 | ||
| 22ff886e | 657 | lwkt_reltoken(&tty_token); |
| b06ebda0 MD |
658 | return (0); |
| 659 | } /* ng_h4_start */ | |
| 660 | ||
| 661 | /***************************************************************************** | |
| 662 | ***************************************************************************** | |
| 663 | ** Netgraph node methods | |
| 664 | ***************************************************************************** | |
| 665 | *****************************************************************************/ | |
| 666 | ||
| 667 | /* | |
| 668 | * Initialize a new node of this type. We only allow nodes to be created as | |
| 669 | * a result of setting the line discipline on a tty, so always return an error | |
| 670 | * if not. | |
| 671 | */ | |
| 672 | ||
| 673 | static int | |
| 674 | ng_h4_constructor(node_p node) | |
| 675 | { | |
| 676 | return (EOPNOTSUPP); | |
| 677 | } /* ng_h4_constructor */ | |
| 678 | ||
| 679 | /* | |
| 680 | * Add a new hook. There can only be one. | |
| 681 | */ | |
| 682 | ||
| 683 | static int | |
| 684 | ng_h4_newhook(node_p node, hook_p hook, const char *name) | |
| 685 | { | |
| 686 | ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(node); | |
| 687 | ||
| 688 | if (strcmp(name, NG_H4_HOOK) != 0) | |
| 689 | return (EINVAL); | |
| 690 | ||
| 691 | NG_H4_LOCK(sc); | |
| 692 | ||
| 693 | if (sc->hook != NULL) { | |
| 694 | NG_H4_UNLOCK(sc); | |
| 695 | return (EISCONN); | |
| 696 | } | |
| 697 | sc->hook = hook; | |
| 698 | ||
| 699 | NG_H4_UNLOCK(sc); | |
| 700 | ||
| 701 | return (0); | |
| 702 | } /* ng_h4_newhook */ | |
| 703 | ||
| 704 | /* | |
| 705 | * Connect hook. Just say yes. | |
| 706 | */ | |
| 707 | ||
| 708 | static int | |
| 709 | ng_h4_connect(hook_p hook) | |
| 710 | { | |
| 711 | ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); | |
| 712 | ||
| 713 | if (hook != sc->hook) | |
| 714 | panic("%s: hook != sc->hook\n", __func__); | |
| 715 | ||
| 716 | NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); | |
| 717 | NG_HOOK_FORCE_QUEUE(hook); | |
| 718 | ||
| 719 | return (0); | |
| 720 | } /* ng_h4_connect */ | |
| 721 | ||
| 722 | /* | |
| 723 | * Disconnect the hook | |
| 724 | */ | |
| 725 | ||
| 726 | static int | |
| 727 | ng_h4_disconnect(hook_p hook) | |
| 728 | { | |
| 729 | ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); | |
| 730 | ||
| 731 | /* | |
| 732 | * We need to check for sc != NULL because we can be called from | |
| 733 | * ng_h4_close() via ng_rmnode_self() | |
| 734 | */ | |
| 735 | ||
| 736 | if (sc != NULL) { | |
| 737 | if (hook != sc->hook) | |
| 738 | panic("%s: hook != sc->hook\n", __func__); | |
| 739 | ||
| 740 | NG_H4_LOCK(sc); | |
| 741 | ||
| 742 | /* XXX do we have to untimeout and drain out queue? */ | |
| 743 | if (callout_pending(&sc->timo)) | |
| 744 | ng_uncallout(&sc->timo, sc->node); | |
| 745 | ||
| 746 | _IF_DRAIN(&sc->outq); | |
| 747 | ||
| 748 | sc->state = NG_H4_W4_PKT_IND; | |
| 749 | sc->want = 1; | |
| 750 | sc->got = 0; | |
| 751 | ||
| 752 | sc->hook = NULL; | |
| 753 | ||
| 754 | NG_H4_UNLOCK(sc); | |
| 755 | } | |
| 756 | ||
| 757 | return (0); | |
| 758 | } /* ng_h4_disconnect */ | |
| 759 | ||
| 760 | /* | |
| 761 | * Remove this node. The does the netgraph portion of the shutdown. | |
| 762 | * This should only be called indirectly from ng_h4_close(). | |
| 763 | */ | |
| 764 | ||
| 765 | static int | |
| 766 | ng_h4_shutdown(node_p node) | |
| 767 | { | |
| 768 | ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(node); | |
| 769 | ||
| 770 | NG_H4_LOCK(sc); | |
| 771 | ||
| 772 | if (!sc->dying) { | |
| 773 | NG_H4_UNLOCK(sc); | |
| 774 | ||
| 775 | NG_NODE_REVIVE(node); /* we will persist */ | |
| 776 | ||
| 777 | return (EOPNOTSUPP); | |
| 778 | } | |
| 779 | ||
| 780 | NG_H4_UNLOCK(sc); | |
| 781 | ||
| 782 | NG_NODE_SET_PRIVATE(node, NULL); | |
| 783 | ||
| 784 | _IF_DRAIN(&sc->outq); | |
| 785 | ||
| 786 | NG_NODE_UNREF(node); | |
| 787 | mtx_destroy(&sc->outq.ifq_mtx); | |
| 788 | bzero(sc, sizeof(*sc)); | |
| fc025606 | 789 | kfree(sc, M_NETGRAPH_H4); |
| b06ebda0 MD |
790 | |
| 791 | return (0); | |
| 792 | } /* ng_h4_shutdown */ | |
| 793 | ||
| 794 | /* | |
| 795 | * Receive incoming data from Netgraph system. Put it on our | |
| 796 | * output queue and start output if necessary. | |
| 797 | */ | |
| 798 | ||
| 799 | static int | |
| 800 | ng_h4_rcvdata(hook_p hook, item_p item) | |
| 801 | { | |
| 802 | ng_h4_info_p sc = (ng_h4_info_p)NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); | |
| 803 | struct mbuf *m = NULL; | |
| 804 | int qlen; | |
| 805 | ||
| 806 | if (sc == NULL) | |
| 807 | return (EHOSTDOWN); | |
| 808 | ||
| 809 | if (hook != sc->hook) | |
| 810 | panic("%s: hook != sc->hook\n", __func__); | |
| 811 | ||
| 812 | NGI_GET_M(item, m); | |
| 813 | NG_FREE_ITEM(item); | |
| 814 | ||
| 815 | NG_H4_LOCK(sc); | |
| 816 | ||
| 817 | if (_IF_QFULL(&sc->outq)) { | |
| 818 | NG_H4_ERR("%s: %s - dropping mbuf, len=%d\n", __func__, | |
| 819 | NG_NODE_NAME(sc->node), m->m_pkthdr.len); | |
| 820 | ||
| 821 | NG_H4_STAT_OERROR(sc->stat); | |
| 822 | _IF_DROP(&sc->outq); | |
| 823 | ||
| 824 | NG_H4_UNLOCK(sc); | |
| 825 | ||
| 826 | NG_FREE_M(m); | |
| 827 | ||
| 828 | return (ENOBUFS); | |
| 829 | } | |
| 830 | ||
| 831 | NG_H4_INFO("%s: %s - queue mbuf, len=%d\n", __func__, | |
| 832 | NG_NODE_NAME(sc->node), m->m_pkthdr.len); | |
| 833 | ||
| 834 | _IF_ENQUEUE(&sc->outq, m); | |
| 835 | qlen = _IF_QLEN(&sc->outq); | |
| 836 | ||
| 837 | NG_H4_UNLOCK(sc); | |
| 838 | ||
| 839 | /* | |
| 840 | * If qlen > 1, then we should already have a scheduled callout | |
| 841 | */ | |
| 842 | ||
| 843 | if (qlen == 1) { | |
| 844 | mtx_lock(&Giant); | |
| 845 | ng_h4_start(sc->tp); | |
| 846 | mtx_unlock(&Giant); | |
| 847 | } | |
| 848 | ||
| 849 | return (0); | |
| 850 | } /* ng_h4_rcvdata */ | |
| 851 | ||
| 852 | /* | |
| 853 | * Receive control message | |
| 854 | */ | |
| 855 | ||
| 856 | static int | |
| 857 | ng_h4_rcvmsg(node_p node, item_p item, hook_p lasthook) | |
| 858 | { | |
| 859 | ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(node); | |
| 860 | struct ng_mesg *msg = NULL, *resp = NULL; | |
| 861 | int error = 0; | |
| 862 | ||
| 863 | if (sc == NULL) | |
| 864 | return (EHOSTDOWN); | |
| 865 | ||
| 866 | NGI_GET_MSG(item, msg); | |
| 867 | NG_H4_LOCK(sc); | |
| 868 | ||
| 869 | switch (msg->header.typecookie) { | |
| 870 | case NGM_GENERIC_COOKIE: | |
| 871 | switch (msg->header.cmd) { | |
| 872 | case NGM_TEXT_STATUS: | |
| 5a975a3d | 873 | NG_MKRESPONSE(resp, msg, NG_TEXTRESPONSE, M_WAITOK | M_NULLOK); |
| b06ebda0 MD |
874 | if (resp == NULL) |
| 875 | error = ENOMEM; | |
| 876 | else | |
| 877 | snprintf(resp->data, NG_TEXTRESPONSE, | |
| 878 | "Hook: %s\n" \ | |
| 879 | "Debug: %d\n" \ | |
| 880 | "State: %d\n" \ | |
| 881 | "Queue: [have:%d,max:%d]\n" \ | |
| 882 | "Input: [got:%d,want:%d]", | |
| 883 | (sc->hook != NULL)? NG_H4_HOOK : "", | |
| 884 | sc->debug, | |
| 885 | sc->state, | |
| 886 | _IF_QLEN(&sc->outq), | |
| 887 | sc->outq.ifq_maxlen, | |
| 888 | sc->got, | |
| 889 | sc->want); | |
| 890 | break; | |
| 891 | ||
| 892 | default: | |
| 893 | error = EINVAL; | |
| 894 | break; | |
| 895 | } | |
| 896 | break; | |
| 897 | ||
| 898 | case NGM_H4_COOKIE: | |
| 899 | switch (msg->header.cmd) { | |
| 900 | case NGM_H4_NODE_RESET: | |
| 901 | _IF_DRAIN(&sc->outq); | |
| 902 | sc->state = NG_H4_W4_PKT_IND; | |
| 903 | sc->want = 1; | |
| 904 | sc->got = 0; | |
| 905 | break; | |
| 906 | ||
| 907 | case NGM_H4_NODE_GET_STATE: | |
| 908 | NG_MKRESPONSE(resp, msg, sizeof(ng_h4_node_state_ep), | |
| 5a975a3d | 909 | M_WAITOK | M_NULLOK); |
| b06ebda0 MD |
910 | if (resp == NULL) |
| 911 | error = ENOMEM; | |
| 912 | else | |
| 913 | *((ng_h4_node_state_ep *)(resp->data)) = | |
| 914 | sc->state; | |
| 915 | break; | |
| 916 | ||
| 917 | case NGM_H4_NODE_GET_DEBUG: | |
| 918 | NG_MKRESPONSE(resp, msg, sizeof(ng_h4_node_debug_ep), | |
| 5a975a3d | 919 | M_WAITOK | M_NULLOK); |
| b06ebda0 MD |
920 | if (resp == NULL) |
| 921 | error = ENOMEM; | |
| 922 | else | |
| 923 | *((ng_h4_node_debug_ep *)(resp->data)) = | |
| 924 | sc->debug; | |
| 925 | break; | |
| 926 | ||
| 927 | case NGM_H4_NODE_SET_DEBUG: | |
| 928 | if (msg->header.arglen != sizeof(ng_h4_node_debug_ep)) | |
| 929 | error = EMSGSIZE; | |
| 930 | else | |
| 931 | sc->debug = | |
| 932 | *((ng_h4_node_debug_ep *)(msg->data)); | |
| 933 | break; | |
| 934 | ||
| 935 | case NGM_H4_NODE_GET_QLEN: | |
| 936 | NG_MKRESPONSE(resp, msg, sizeof(ng_h4_node_qlen_ep), | |
| 5a975a3d | 937 | M_WAITOK | M_NULLOK); |
| b06ebda0 MD |
938 | if (resp == NULL) |
| 939 | error = ENOMEM; | |
| 940 | else | |
| 941 | *((ng_h4_node_qlen_ep *)(resp->data)) = | |
| 942 | sc->outq.ifq_maxlen; | |
| 943 | break; | |
| 944 | ||
| 945 | case NGM_H4_NODE_SET_QLEN: | |
| 946 | if (msg->header.arglen != sizeof(ng_h4_node_qlen_ep)) | |
| 947 | error = EMSGSIZE; | |
| 948 | else if (*((ng_h4_node_qlen_ep *)(msg->data)) <= 0) | |
| 949 | error = EINVAL; | |
| 950 | else | |
| 951 | IFQ_SET_MAXLEN(&sc->outq, | |
| 952 | *((ng_h4_node_qlen_ep *)(msg->data))); | |
| 953 | break; | |
| 954 | ||
| 955 | case NGM_H4_NODE_GET_STAT: | |
| 956 | NG_MKRESPONSE(resp, msg, sizeof(ng_h4_node_stat_ep), | |
| 5a975a3d | 957 | M_WAITOK | M_NULLOK); |
| b06ebda0 MD |
958 | if (resp == NULL) |
| 959 | error = ENOMEM; | |
| 960 | else | |
| 961 | bcopy(&sc->stat, resp->data, | |
| 962 | sizeof(ng_h4_node_stat_ep)); | |
| 963 | break; | |
| 964 | ||
| 965 | case NGM_H4_NODE_RESET_STAT: | |
| 966 | NG_H4_STAT_RESET(sc->stat); | |
| 967 | break; | |
| 968 | ||
| 969 | default: | |
| 970 | error = EINVAL; | |
| 971 | break; | |
| 972 | } | |
| 973 | break; | |
| 974 | ||
| 975 | default: | |
| 976 | error = EINVAL; | |
| 977 | break; | |
| 978 | } | |
| 979 | ||
| 980 | NG_H4_UNLOCK(sc); | |
| 981 | ||
| 982 | NG_RESPOND_MSG(error, node, item, resp); | |
| 983 | NG_FREE_MSG(msg); | |
| 984 | ||
| 985 | return (error); | |
| 986 | } /* ng_h4_rcvmsg */ | |
| 987 | ||
| 988 | /* | |
| 989 | * Timeout processing function. | |
| 990 | * We still have data to output to the device, so try sending more. | |
| 991 | */ | |
| 992 | ||
| 993 | static void | |
| 994 | ng_h4_process_timeout(node_p node, hook_p hook, void *arg1, int arg2) | |
| 995 | { | |
| 996 | ng_h4_info_p sc = (ng_h4_info_p) NG_NODE_PRIVATE(node); | |
| 997 | ||
| 998 | mtx_lock(&Giant); | |
| 999 | ng_h4_start(sc->tp); | |
| 1000 | mtx_unlock(&Giant); | |
| 1001 | } /* ng_h4_process_timeout */ | |
| 1002 | ||
| 1003 | /* | |
| 1004 | * Handle loading and unloading for this node type | |
| 1005 | */ | |
| 1006 | ||
| 1007 | static int | |
| 1008 | ng_h4_mod_event(module_t mod, int event, void *data) | |
| 1009 | { | |
| 1010 | static int ng_h4_ldisc; | |
| 1011 | int error = 0; | |
| 1012 | ||
| 1013 | switch (event) { | |
| 1014 | case MOD_LOAD: | |
| 1015 | /* Register line discipline */ | |
| 1016 | mtx_lock(&Giant); | |
| 1017 | ng_h4_ldisc = ldisc_register(H4DISC, &ng_h4_disc); | |
| 1018 | mtx_unlock(&Giant); | |
| 1019 | ||
| 1020 | if (ng_h4_ldisc < 0) { | |
| 1021 | printf("%s: can't register H4 line discipline\n", | |
| 1022 | __func__); | |
| 1023 | error = EIO; | |
| 1024 | } | |
| 1025 | break; | |
| 1026 | ||
| 1027 | case MOD_UNLOAD: | |
| 1028 | /* Unregister line discipline */ | |
| 1029 | mtx_lock(&Giant); | |
| 1030 | ldisc_deregister(ng_h4_ldisc); | |
| 1031 | mtx_unlock(&Giant); | |
| 1032 | break; | |
| 1033 | ||
| 1034 | default: | |
| 1035 | error = EOPNOTSUPP; | |
| 1036 | break; | |
| 1037 | } | |
| 1038 | ||
| 1039 | return (error); | |
| 1040 | } /* ng_h4_mod_event */ | |
| 1041 |