1999-05-08 11:07:56 +00:00
|
|
|
/*-
|
|
|
|
* Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
* SUCH DAMAGE.
|
|
|
|
*
|
1999-08-28 01:35:59 +00:00
|
|
|
* $FreeBSD$
|
1999-05-08 11:07:56 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/un.h>
|
|
|
|
#if defined(__OpenBSD__) || defined(__NetBSD__)
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
2002-03-30 12:30:09 +00:00
|
|
|
#include <stdio.h>
|
1999-05-08 11:07:56 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
1999-06-05 21:36:00 +00:00
|
|
|
#include <sysexits.h>
|
1999-05-12 09:49:12 +00:00
|
|
|
#include <sys/uio.h>
|
1999-05-08 11:07:56 +00:00
|
|
|
#include <termios.h>
|
2002-05-14 12:55:39 +00:00
|
|
|
#include <ttyent.h>
|
1999-05-08 11:07:56 +00:00
|
|
|
#include <unistd.h>
|
2002-03-31 01:56:34 +00:00
|
|
|
#ifndef NONETGRAPH
|
2002-03-30 12:30:09 +00:00
|
|
|
#include <netgraph.h>
|
|
|
|
#include <netgraph/ng_async.h>
|
|
|
|
#include <netgraph/ng_message.h>
|
|
|
|
#include <netgraph/ng_ppp.h>
|
|
|
|
#include <netgraph/ng_tty.h>
|
2002-03-31 01:56:34 +00:00
|
|
|
#endif
|
1999-05-08 11:07:56 +00:00
|
|
|
|
|
|
|
#include "layer.h"
|
|
|
|
#include "defs.h"
|
|
|
|
#include "mbuf.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "timer.h"
|
|
|
|
#include "lqr.h"
|
|
|
|
#include "hdlc.h"
|
|
|
|
#include "throughput.h"
|
|
|
|
#include "fsm.h"
|
|
|
|
#include "lcp.h"
|
|
|
|
#include "ccp.h"
|
|
|
|
#include "link.h"
|
|
|
|
#include "async.h"
|
|
|
|
#include "descriptor.h"
|
|
|
|
#include "physical.h"
|
|
|
|
#include "mp.h"
|
|
|
|
#include "chat.h"
|
|
|
|
#include "auth.h"
|
|
|
|
#include "chap.h"
|
|
|
|
#include "cbcp.h"
|
|
|
|
#include "datalink.h"
|
1999-06-05 21:36:00 +00:00
|
|
|
#include "main.h"
|
2002-03-30 12:30:09 +00:00
|
|
|
#include "id.h"
|
1999-05-08 11:07:56 +00:00
|
|
|
#include "tty.h"
|
|
|
|
|
1999-10-15 20:30:44 +00:00
|
|
|
#if defined(__mac68k__) || defined(__macppc__)
|
|
|
|
#undef CRTS_IFLOW
|
|
|
|
#undef CCTS_OFLOW
|
|
|
|
#define CRTS_IFLOW CDTRCTS
|
|
|
|
#define CCTS_OFLOW CDTRCTS
|
|
|
|
#endif
|
|
|
|
|
1999-05-12 09:49:12 +00:00
|
|
|
#define Online(dev) ((dev)->mbits & TIOCM_CD)
|
|
|
|
|
|
|
|
struct ttydevice {
|
|
|
|
struct device dev; /* What struct physical knows about */
|
|
|
|
struct pppTimer Timer; /* CD checks */
|
|
|
|
int mbits; /* Current DCD status */
|
1999-08-06 20:04:08 +00:00
|
|
|
int carrier_seconds; /* seconds before CD is *required* */
|
2002-03-30 12:30:09 +00:00
|
|
|
#ifndef NONETGRAPH
|
|
|
|
struct {
|
|
|
|
int speed; /* Pre-line-discipline speed */
|
|
|
|
int fd; /* Pre-line-discipline fd */
|
|
|
|
int disc; /* Old line-discipline */
|
|
|
|
} real;
|
|
|
|
char hook[sizeof NG_ASYNC_HOOK_SYNC]; /* our ng_socket hook */
|
|
|
|
int cs; /* A netgraph control socket (maybe) */
|
|
|
|
#endif
|
1999-05-12 09:49:12 +00:00
|
|
|
struct termios ios; /* To be able to reset from raw mode */
|
|
|
|
};
|
|
|
|
|
|
|
|
#define device2tty(d) ((d)->type == TTY_DEVICE ? (struct ttydevice *)d : NULL)
|
1999-05-08 11:07:56 +00:00
|
|
|
|
1999-06-05 21:36:00 +00:00
|
|
|
int
|
|
|
|
tty_DeviceSize(void)
|
|
|
|
{
|
|
|
|
return sizeof(struct ttydevice);
|
|
|
|
}
|
|
|
|
|
1999-05-08 11:07:56 +00:00
|
|
|
/*
|
|
|
|
* tty_Timeout() watches the DCD signal and mentions it if it's status
|
|
|
|
* changes.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
tty_Timeout(void *data)
|
|
|
|
{
|
|
|
|
struct physical *p = data;
|
1999-05-12 09:49:12 +00:00
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
1999-05-08 11:07:56 +00:00
|
|
|
int ombits, change;
|
|
|
|
|
1999-05-12 09:49:12 +00:00
|
|
|
timer_Stop(&dev->Timer);
|
|
|
|
dev->Timer.load = SECTICKS; /* Once a second please */
|
|
|
|
timer_Start(&dev->Timer);
|
|
|
|
ombits = dev->mbits;
|
1999-05-08 11:07:56 +00:00
|
|
|
|
|
|
|
if (p->fd >= 0) {
|
1999-05-12 09:49:12 +00:00
|
|
|
if (ioctl(p->fd, TIOCMGET, &dev->mbits) < 0) {
|
1999-08-06 20:04:08 +00:00
|
|
|
/* we must be a pty ? */
|
1999-11-26 22:44:33 +00:00
|
|
|
if (p->cfg.cd.necessity != CD_DEFAULT)
|
|
|
|
log_Printf(LogWARN, "%s: Carrier ioctl not supported, "
|
|
|
|
"using ``set cd off''\n", p->link.name);
|
1999-05-12 09:49:12 +00:00
|
|
|
timer_Stop(&dev->Timer);
|
1999-12-13 18:45:21 +00:00
|
|
|
dev->mbits = TIOCM_CD;
|
1999-05-08 11:07:56 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else
|
1999-05-12 09:49:12 +00:00
|
|
|
dev->mbits = 0;
|
1999-05-08 11:07:56 +00:00
|
|
|
|
|
|
|
if (ombits == -1) {
|
|
|
|
/* First time looking for carrier */
|
1999-05-12 09:49:12 +00:00
|
|
|
if (Online(dev))
|
1999-08-06 20:04:08 +00:00
|
|
|
log_Printf(LogPHASE, "%s: %s: CD detected\n", p->link.name, p->name.full);
|
1999-11-26 22:44:33 +00:00
|
|
|
else if (++dev->carrier_seconds >= dev->dev.cd.delay) {
|
|
|
|
if (dev->dev.cd.necessity == CD_REQUIRED)
|
1999-08-06 20:04:08 +00:00
|
|
|
log_Printf(LogPHASE, "%s: %s: Required CD not detected\n",
|
|
|
|
p->link.name, p->name.full);
|
|
|
|
else {
|
|
|
|
log_Printf(LogPHASE, "%s: %s doesn't support CD\n",
|
|
|
|
p->link.name, p->name.full);
|
|
|
|
dev->mbits = TIOCM_CD; /* Dodgy null-modem cable ? */
|
|
|
|
}
|
1999-05-12 09:49:12 +00:00
|
|
|
timer_Stop(&dev->Timer);
|
1999-08-06 20:04:08 +00:00
|
|
|
/* tty_AwaitCarrier() will notice */
|
|
|
|
} else {
|
|
|
|
/* Keep waiting */
|
|
|
|
log_Printf(LogDEBUG, "%s: %s: Still no carrier (%d/%d)\n",
|
|
|
|
p->link.name, p->name.full, dev->carrier_seconds,
|
1999-11-26 22:44:33 +00:00
|
|
|
dev->dev.cd.delay);
|
1999-08-06 20:04:08 +00:00
|
|
|
dev->mbits = -1;
|
1999-05-08 11:07:56 +00:00
|
|
|
}
|
|
|
|
} else {
|
1999-05-12 09:49:12 +00:00
|
|
|
change = ombits ^ dev->mbits;
|
1999-05-08 11:07:56 +00:00
|
|
|
if (change & TIOCM_CD) {
|
1999-05-12 09:49:12 +00:00
|
|
|
if (dev->mbits & TIOCM_CD)
|
1999-05-08 11:07:56 +00:00
|
|
|
log_Printf(LogDEBUG, "%s: offline -> online\n", p->link.name);
|
|
|
|
else {
|
|
|
|
log_Printf(LogDEBUG, "%s: online -> offline\n", p->link.name);
|
|
|
|
log_Printf(LogPHASE, "%s: Carrier lost\n", p->link.name);
|
|
|
|
datalink_Down(p->dl, CLOSE_NORMAL);
|
1999-05-12 09:49:12 +00:00
|
|
|
timer_Stop(&dev->Timer);
|
1999-05-08 11:07:56 +00:00
|
|
|
}
|
|
|
|
} else
|
|
|
|
log_Printf(LogDEBUG, "%s: Still %sline\n", p->link.name,
|
1999-05-12 09:49:12 +00:00
|
|
|
Online(dev) ? "on" : "off");
|
1999-05-08 11:07:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tty_StartTimer(struct physical *p)
|
|
|
|
{
|
1999-05-12 09:49:12 +00:00
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
|
|
|
|
|
|
|
timer_Stop(&dev->Timer);
|
1999-08-06 20:04:08 +00:00
|
|
|
dev->Timer.load = SECTICKS;
|
1999-05-12 09:49:12 +00:00
|
|
|
dev->Timer.func = tty_Timeout;
|
|
|
|
dev->Timer.name = "tty CD";
|
|
|
|
dev->Timer.arg = p;
|
1999-05-08 11:07:56 +00:00
|
|
|
log_Printf(LogDEBUG, "%s: Using tty_Timeout [%p]\n",
|
|
|
|
p->link.name, tty_Timeout);
|
1999-05-12 09:49:12 +00:00
|
|
|
timer_Start(&dev->Timer);
|
1999-05-08 11:07:56 +00:00
|
|
|
}
|
|
|
|
|
1999-08-06 20:04:08 +00:00
|
|
|
static int
|
|
|
|
tty_AwaitCarrier(struct physical *p)
|
|
|
|
{
|
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
|
|
|
|
1999-11-26 22:44:33 +00:00
|
|
|
if (dev->dev.cd.necessity == CD_NOTREQUIRED || physical_IsSync(p))
|
1999-08-06 20:04:08 +00:00
|
|
|
return CARRIER_OK;
|
|
|
|
|
|
|
|
if (dev->mbits == -1) {
|
|
|
|
if (dev->Timer.state == TIMER_STOPPED) {
|
|
|
|
dev->carrier_seconds = 0;
|
|
|
|
tty_StartTimer(p);
|
|
|
|
}
|
|
|
|
return CARRIER_PENDING; /* Not yet ! */
|
|
|
|
}
|
|
|
|
|
1999-12-13 18:45:21 +00:00
|
|
|
return Online(dev) ? CARRIER_OK : CARRIER_LOST;
|
1999-08-06 20:04:08 +00:00
|
|
|
}
|
|
|
|
|
2002-03-30 12:30:09 +00:00
|
|
|
#ifdef NONETGRAPH
|
|
|
|
#define tty_SetAsyncParams NULL
|
|
|
|
#define tty_Write NULL
|
|
|
|
#define tty_Read NULL
|
|
|
|
#else
|
|
|
|
|
|
|
|
static int
|
|
|
|
isngtty(struct ttydevice *dev)
|
|
|
|
{
|
|
|
|
return dev->real.fd != -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tty_SetAsyncParams(struct physical *p, u_int32_t mymap, u_int32_t hismap)
|
|
|
|
{
|
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
|
|
|
char asyncpath[NG_PATHLEN + 1];
|
|
|
|
struct ng_async_cfg cfg;
|
|
|
|
|
|
|
|
if (isngtty(dev)) {
|
|
|
|
/* Configure the async converter node */
|
|
|
|
|
|
|
|
snprintf(asyncpath, sizeof asyncpath, ".:%s", dev->hook);
|
|
|
|
memset(&cfg, 0, sizeof cfg);
|
|
|
|
cfg.enabled = 1;
|
|
|
|
cfg.accm = mymap | hismap;
|
|
|
|
cfg.amru = MAX_MTU;
|
|
|
|
cfg.smru = MAX_MRU;
|
|
|
|
log_Printf(LogDEBUG, "Configure async node at %s\n", asyncpath);
|
|
|
|
if (NgSendMsg(dev->cs, asyncpath, NGM_ASYNC_COOKIE,
|
|
|
|
NGM_ASYNC_CMD_SET_CONFIG, &cfg, sizeof cfg) < 0)
|
|
|
|
log_Printf(LogWARN, "%s: Can't configure async node at %s\n",
|
|
|
|
p->link.name, asyncpath);
|
|
|
|
} else
|
|
|
|
/* No netgraph node, just config the async layer */
|
|
|
|
async_SetLinkParams(&p->async, mymap, hismap);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
LoadLineDiscipline(struct physical *p)
|
|
|
|
{
|
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
|
|
|
u_char rbuf[sizeof(struct ng_mesg) + sizeof(struct nodeinfo)];
|
|
|
|
struct ng_mesg *reply;
|
|
|
|
struct nodeinfo *info;
|
|
|
|
char ttypath[NG_NODELEN + 1];
|
|
|
|
struct ngm_mkpeer ngm;
|
|
|
|
struct ngm_connect ngc;
|
|
|
|
int ldisc, cs, ds, hot, speed;
|
|
|
|
|
2002-04-21 02:00:29 +00:00
|
|
|
/*
|
|
|
|
* Don't use the netgraph line discipline for now. Using it works, but
|
|
|
|
* carrier cannot be detected via TIOCMGET and the device doesn't become
|
|
|
|
* selectable with 0 bytes to read when carrier is lost :(
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
|
2002-03-30 12:30:09 +00:00
|
|
|
reply = (struct ng_mesg *)rbuf;
|
|
|
|
info = (struct nodeinfo *)reply->data;
|
|
|
|
|
|
|
|
loadmodules(LOAD_VERBOSLY, "netgraph", "ng_tty", "ng_async", "ng_socket",
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
/* Get the speed before loading the line discipline */
|
|
|
|
speed = physical_GetSpeed(p);
|
|
|
|
|
|
|
|
if (ioctl(p->fd, TIOCGETD, &dev->real.disc) < 0) {
|
|
|
|
log_Printf(LogDEBUG, "%s: Couldn't get tty line discipline\n",
|
|
|
|
p->link.name);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
ldisc = NETGRAPHDISC;
|
|
|
|
if (ID0ioctl(p->fd, TIOCSETD, &ldisc) < 0) {
|
|
|
|
log_Printf(LogDEBUG, "%s: Couldn't set NETGRAPHDISC line discipline\n",
|
|
|
|
p->link.name);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the name of the tty node */
|
|
|
|
if (ioctl(p->fd, NGIOCGINFO, info) < 0) {
|
|
|
|
log_Printf(LogWARN, "%s: ioctl(NGIOCGINFO): %s\n", p->link.name,
|
|
|
|
strerror(errno));
|
|
|
|
ID0ioctl(p->fd, TIOCSETD, &dev->real.disc);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
snprintf(ttypath, sizeof ttypath, "%s:", info->name);
|
|
|
|
|
|
|
|
/* Create a socket node for our endpoint (and to send messages via) */
|
|
|
|
if (ID0NgMkSockNode(NULL, &cs, &ds) == -1) {
|
|
|
|
log_Printf(LogWARN, "%s: NgMkSockNode: %s\n", p->link.name,
|
|
|
|
strerror(errno));
|
|
|
|
ID0ioctl(p->fd, TIOCSETD, &dev->real.disc);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the ``hot char'' on the TTY node */
|
|
|
|
hot = HDLC_SYN;
|
|
|
|
log_Printf(LogDEBUG, "%s: Set tty hotchar to 0x%02x\n", p->link.name, hot);
|
|
|
|
if (NgSendMsg(cs, ttypath, NGM_TTY_COOKIE,
|
|
|
|
NGM_TTY_SET_HOTCHAR, &hot, sizeof hot) < 0) {
|
|
|
|
log_Printf(LogWARN, "%s: Can't set hot char\n", p->link.name);
|
|
|
|
goto failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Attach an async converter node */
|
|
|
|
snprintf(ngm.type, sizeof ngm.type, "%s", NG_ASYNC_NODE_TYPE);
|
|
|
|
snprintf(ngm.ourhook, sizeof ngm.ourhook, "%s", NG_TTY_HOOK);
|
|
|
|
snprintf(ngm.peerhook, sizeof ngm.peerhook, "%s", NG_ASYNC_HOOK_ASYNC);
|
|
|
|
log_Printf(LogDEBUG, "%s: Send mkpeer async:%s to %s:%s\n", p->link.name,
|
|
|
|
ngm.peerhook, ttypath, ngm.ourhook);
|
|
|
|
if (NgSendMsg(cs, ttypath, NGM_GENERIC_COOKIE,
|
|
|
|
NGM_MKPEER, &ngm, sizeof ngm) < 0) {
|
|
|
|
log_Printf(LogWARN, "%s: Can't create %s node\n", p->link.name,
|
|
|
|
NG_ASYNC_NODE_TYPE);
|
|
|
|
goto failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Connect the async node to our socket */
|
|
|
|
snprintf(ngc.path, sizeof ngc.path, "%s%s", ttypath, NG_TTY_HOOK);
|
|
|
|
snprintf(ngc.peerhook, sizeof ngc.peerhook, "%s", NG_ASYNC_HOOK_SYNC);
|
|
|
|
memcpy(ngc.ourhook, ngc.peerhook, sizeof ngc.ourhook);
|
|
|
|
log_Printf(LogDEBUG, "%s: Send connect %s:%s to .:%s\n", p->link.name,
|
|
|
|
ngc.path, ngc.peerhook, ngc.ourhook);
|
|
|
|
if (NgSendMsg(cs, ".:", NGM_GENERIC_COOKIE, NGM_CONNECT,
|
|
|
|
&ngc, sizeof ngc) < 0) {
|
|
|
|
log_Printf(LogWARN, "%s: Can't connect .:%s -> %s.%s: %s\n",
|
|
|
|
p->link.name, ngc.ourhook, ngc.path, ngc.peerhook,
|
|
|
|
strerror(errno));
|
|
|
|
goto failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the async node id */
|
|
|
|
if (NgSendMsg(cs, ngc.path, NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) < 0) {
|
|
|
|
log_Printf(LogWARN, "%s: Can't request async node info at %s: %s\n",
|
|
|
|
p->link.name, ngc.path, strerror(errno));
|
|
|
|
goto failed;
|
|
|
|
}
|
|
|
|
if (NgRecvMsg(cs, reply, sizeof rbuf, NULL) < 0) {
|
|
|
|
log_Printf(LogWARN, "%s: Can't obtain async node info at %s: %s\n",
|
|
|
|
p->link.name, ngc.path, strerror(errno));
|
|
|
|
goto failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* All done, set up our device state */
|
|
|
|
snprintf(dev->hook, sizeof dev->hook, "%s", ngc.ourhook);
|
|
|
|
dev->cs = cs;
|
|
|
|
dev->real.fd = p->fd;
|
|
|
|
p->fd = ds;
|
|
|
|
dev->real.speed = speed;
|
|
|
|
physical_SetSync(p);
|
|
|
|
|
|
|
|
tty_SetAsyncParams(p, 0xffffffff, 0xffffffff);
|
|
|
|
physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
|
|
|
|
log_Printf(LogPHASE, "%s: Loaded netgraph tty line discipline\n",
|
|
|
|
p->link.name);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
failed:
|
|
|
|
ID0ioctl(p->fd, TIOCSETD, &dev->real.disc);
|
|
|
|
close(ds);
|
|
|
|
close(cs);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
UnloadLineDiscipline(struct physical *p)
|
|
|
|
{
|
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
|
|
|
|
|
|
|
if (isngtty(dev)) {
|
|
|
|
log_Printf(LogPHASE, "back to speed %d\n", dev->real.speed);
|
|
|
|
if (!physical_SetSpeed(p, dev->real.speed))
|
|
|
|
log_Printf(LogWARN, "Couldn't reset tty speed to %d\n", dev->real.speed);
|
|
|
|
dev->real.speed = 0;
|
|
|
|
close(p->fd);
|
|
|
|
p->fd = dev->real.fd;
|
|
|
|
dev->real.fd = -1;
|
|
|
|
close(dev->cs);
|
|
|
|
dev->cs = -1;
|
|
|
|
*dev->hook = '\0';
|
|
|
|
if (ID0ioctl(p->fd, TIOCSETD, &dev->real.disc) == 0) {
|
|
|
|
physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
|
|
|
|
log_Printf(LogPHASE, "%s: Unloaded netgraph tty line discipline\n",
|
|
|
|
p->link.name);
|
|
|
|
} else
|
|
|
|
log_Printf(LogWARN, "%s: Failed to unload netgraph tty line discipline\n",
|
|
|
|
p->link.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
tty_Write(struct physical *p, const void *v, size_t n)
|
|
|
|
{
|
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
|
|
|
|
|
|
|
if (isngtty(dev))
|
|
|
|
return NgSendData(p->fd, dev->hook, v, n) == -1 ? -1 : n;
|
|
|
|
else
|
|
|
|
return write(p->fd, v, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
tty_Read(struct physical *p, void *v, size_t n)
|
|
|
|
{
|
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
|
|
|
char hook[sizeof NG_ASYNC_HOOK_SYNC];
|
|
|
|
|
|
|
|
if (isngtty(dev))
|
|
|
|
return NgRecvData(p->fd, v, n, hook);
|
|
|
|
else
|
|
|
|
return read(p->fd, v, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* NETGRAPH */
|
|
|
|
|
1999-05-08 11:07:56 +00:00
|
|
|
static int
|
|
|
|
tty_Raw(struct physical *p)
|
|
|
|
{
|
1999-05-12 09:49:12 +00:00
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
1999-05-13 16:35:13 +00:00
|
|
|
struct termios ios;
|
1999-05-08 11:07:56 +00:00
|
|
|
int oldflag;
|
|
|
|
|
1999-08-06 20:04:08 +00:00
|
|
|
log_Printf(LogDEBUG, "%s: Entering tty_Raw\n", p->link.name);
|
1999-05-08 11:07:56 +00:00
|
|
|
|
1999-05-12 09:49:12 +00:00
|
|
|
if (p->type != PHYS_DIRECT && p->fd >= 0 && !Online(dev))
|
2000-03-14 01:47:07 +00:00
|
|
|
log_Printf(LogDEBUG, "%s: Raw: descriptor = %d, mbits = %x\n",
|
1999-05-12 09:49:12 +00:00
|
|
|
p->link.name, p->fd, dev->mbits);
|
1999-05-08 11:07:56 +00:00
|
|
|
|
1999-08-06 20:04:08 +00:00
|
|
|
if (!physical_IsSync(p)) {
|
2002-03-30 12:30:09 +00:00
|
|
|
#ifndef NONETGRAPH
|
|
|
|
if (!LoadLineDiscipline(p))
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
tcgetattr(p->fd, &ios);
|
|
|
|
cfmakeraw(&ios);
|
|
|
|
if (p->cfg.rts_cts)
|
|
|
|
ios.c_cflag |= CLOCAL | CCTS_OFLOW | CRTS_IFLOW;
|
|
|
|
else
|
|
|
|
ios.c_cflag |= CLOCAL;
|
|
|
|
|
|
|
|
if (p->type != PHYS_DEDICATED)
|
|
|
|
ios.c_cflag |= HUPCL;
|
|
|
|
|
|
|
|
if (tcsetattr(p->fd, TCSANOW, &ios) == -1)
|
|
|
|
log_Printf(LogWARN, "%s: tcsetattr: Failed configuring device\n",
|
|
|
|
p->link.name);
|
|
|
|
}
|
1999-08-06 20:04:08 +00:00
|
|
|
}
|
1999-05-08 11:07:56 +00:00
|
|
|
|
|
|
|
oldflag = fcntl(p->fd, F_GETFL, 0);
|
|
|
|
if (oldflag < 0)
|
|
|
|
return 0;
|
|
|
|
fcntl(p->fd, F_SETFL, oldflag | O_NONBLOCK);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tty_Offline(struct physical *p)
|
|
|
|
{
|
1999-05-12 09:49:12 +00:00
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
|
|
|
|
1999-05-08 11:07:56 +00:00
|
|
|
if (p->fd >= 0) {
|
1999-05-12 09:49:12 +00:00
|
|
|
timer_Stop(&dev->Timer);
|
1999-08-06 20:04:08 +00:00
|
|
|
dev->mbits &= ~TIOCM_DTR; /* XXX: Hmm, what's this supposed to do ? */
|
1999-05-12 09:49:12 +00:00
|
|
|
if (Online(dev)) {
|
1999-05-08 11:07:56 +00:00
|
|
|
struct termios tio;
|
|
|
|
|
|
|
|
tcgetattr(p->fd, &tio);
|
2000-10-31 23:27:35 +00:00
|
|
|
if (cfsetspeed(&tio, B0) == -1 || tcsetattr(p->fd, TCSANOW, &tio) == -1)
|
1999-05-08 11:07:56 +00:00
|
|
|
log_Printf(LogWARN, "%s: Unable to set physical to speed 0\n",
|
|
|
|
p->link.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
tty_Cooked(struct physical *p)
|
|
|
|
{
|
1999-05-12 09:49:12 +00:00
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
1999-05-08 11:07:56 +00:00
|
|
|
int oldflag;
|
|
|
|
|
1999-05-27 08:42:49 +00:00
|
|
|
tty_Offline(p); /* In case of emergency close()s */
|
|
|
|
|
1999-05-08 11:07:56 +00:00
|
|
|
tcflush(p->fd, TCIOFLUSH);
|
1999-08-06 20:04:08 +00:00
|
|
|
|
2000-10-31 23:27:35 +00:00
|
|
|
if (!physical_IsSync(p) && tcsetattr(p->fd, TCSAFLUSH, &dev->ios) == -1)
|
|
|
|
log_Printf(LogWARN, "%s: tcsetattr: Unable to restore device settings\n",
|
|
|
|
p->link.name);
|
1999-08-06 20:04:08 +00:00
|
|
|
|
2002-03-30 12:30:09 +00:00
|
|
|
#ifndef NONETGRAPH
|
|
|
|
UnloadLineDiscipline(p);
|
|
|
|
#endif
|
|
|
|
|
1999-08-06 20:04:08 +00:00
|
|
|
if ((oldflag = fcntl(p->fd, F_GETFL, 0)) != -1)
|
|
|
|
fcntl(p->fd, F_SETFL, oldflag & ~O_NONBLOCK);
|
1999-05-08 11:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
1999-05-12 09:49:12 +00:00
|
|
|
tty_StopTimer(struct physical *p)
|
1999-05-08 11:07:56 +00:00
|
|
|
{
|
1999-05-12 09:49:12 +00:00
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
|
|
|
|
|
|
|
timer_Stop(&dev->Timer);
|
1999-05-08 11:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
1999-05-12 09:49:12 +00:00
|
|
|
tty_Free(struct physical *p)
|
1999-05-08 11:07:56 +00:00
|
|
|
{
|
1999-05-12 09:49:12 +00:00
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
|
|
|
|
1999-05-27 08:42:49 +00:00
|
|
|
tty_Offline(p); /* In case of emergency close()s */
|
1999-05-12 09:49:12 +00:00
|
|
|
free(dev);
|
1999-05-08 11:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
tty_Speed(struct physical *p)
|
|
|
|
{
|
1999-05-13 16:35:13 +00:00
|
|
|
struct termios ios;
|
1999-05-08 11:07:56 +00:00
|
|
|
|
1999-05-13 16:35:13 +00:00
|
|
|
if (tcgetattr(p->fd, &ios) == -1)
|
1999-05-08 11:07:56 +00:00
|
|
|
return 0;
|
|
|
|
|
1999-05-13 16:35:13 +00:00
|
|
|
return SpeedToInt(cfgetispeed(&ios));
|
1999-05-08 11:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
tty_OpenInfo(struct physical *p)
|
|
|
|
{
|
1999-05-12 09:49:12 +00:00
|
|
|
struct ttydevice *dev = device2tty(p->handler);
|
1999-05-08 11:07:56 +00:00
|
|
|
static char buf[13];
|
|
|
|
|
1999-05-12 09:49:12 +00:00
|
|
|
if (Online(dev))
|
1999-05-08 11:07:56 +00:00
|
|
|
strcpy(buf, "with");
|
|
|
|
else
|
|
|
|
strcpy(buf, "no");
|
|
|
|
strcat(buf, " carrier");
|
1999-08-06 20:04:08 +00:00
|
|
|
|
1999-05-08 11:07:56 +00:00
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2002-05-14 12:55:39 +00:00
|
|
|
static int
|
|
|
|
tty_Slot(struct physical *p)
|
|
|
|
{
|
|
|
|
struct ttyent *ttyp;
|
|
|
|
int slot;
|
|
|
|
|
|
|
|
setttyent();
|
|
|
|
for (slot = 1; (ttyp = getttyent()); ++slot)
|
|
|
|
if (!strcmp(ttyp->ty_name, p->name.base)) {
|
|
|
|
endttyent();
|
|
|
|
return slot;
|
|
|
|
}
|
|
|
|
|
|
|
|
endttyent();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
1999-05-12 09:49:12 +00:00
|
|
|
static void
|
1999-06-05 21:36:00 +00:00
|
|
|
tty_device2iov(struct device *d, struct iovec *iov, int *niov,
|
Rewrite the link descriptor transfer code in MP mode.
Previously, ppp attempted to bind() to a local domain tcp socket
based on the peer authname & enddisc. If it succeeded, it listen()ed
and became MP server. If it failed, it connect()ed and became MP
client. The server then select()ed on the descriptor, accept()ed
it and wrote its pid to it then read the link data & link file descriptor,
and finally sent an ack (``!''). The client would read() the server
pid, transfer the link lock to that pid, send the link data & descriptor
and read the ack. It would then close the descriptor and clean up.
There was a race between the bind() and listen() where someone could
attempt to connect() and fail.
This change removes the race. Now ppp makes the RCVBUF big enough on a
socket descriptor and attempts to bind() to a local domain *udp* socket
(same name as before). If it succeeds, it becomes MP server. If it
fails, it sets the SNDBUF and connect()s, becoming MP client. The server
select()s on the descriptor and recvmsg()s the message, insisting on at
least two descriptors (plus the link data). It uses the second descriptor
to write() its pid then read()s an ack (``!''). The client creates a
socketpair() and sendmsg()s the link data, link descriptor and one of
the socketpair descriptors. It then read()s the server pid from the
other socketpair descriptor, transfers any locks and write()s an ack.
Now, there can be no race, and a connect() failure indicates a stale
socket file.
This also fixes MP ppp over ethernet, where the struct msghdr was being
misconstructed when transferring the control socket descriptor.
Also, if we fail to send the link, don't hang around in a ``session
owner'' state, just do the setsid() and fork() if it's required to
disown a tty.
UDP idea suggested by: Chris Bennet from Mindspring at FreeBSDCon
1999-11-25 02:47:04 +00:00
|
|
|
int maxiov, int *auxfd, int *nauxfd)
|
1999-05-12 09:49:12 +00:00
|
|
|
{
|
1999-06-05 21:36:00 +00:00
|
|
|
struct ttydevice *dev = device2tty(d);
|
|
|
|
int sz = physical_MaxDeviceSize();
|
1999-05-12 09:49:12 +00:00
|
|
|
|
1999-06-05 21:36:00 +00:00
|
|
|
iov[*niov].iov_base = realloc(d, sz);
|
|
|
|
if (iov[*niov].iov_base == NULL) {
|
|
|
|
log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
|
|
|
|
AbortProgram(EX_OSERR);
|
|
|
|
}
|
|
|
|
iov[*niov].iov_len = sz;
|
1999-05-12 09:49:12 +00:00
|
|
|
(*niov)++;
|
|
|
|
|
2002-03-30 12:30:09 +00:00
|
|
|
#ifndef NONETGRAPH
|
|
|
|
if (dev->cs >= 0) {
|
|
|
|
*auxfd = dev->cs;
|
|
|
|
(*nauxfd)++;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
1999-05-12 09:49:12 +00:00
|
|
|
if (dev->Timer.state != TIMER_STOPPED) {
|
|
|
|
timer_Stop(&dev->Timer);
|
|
|
|
dev->Timer.state = TIMER_RUNNING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct device basettydevice = {
|
1999-05-08 11:07:56 +00:00
|
|
|
TTY_DEVICE,
|
|
|
|
"tty",
|
2001-06-18 14:59:36 +00:00
|
|
|
0,
|
1999-11-26 22:44:33 +00:00
|
|
|
{ CD_VARIABLE, DEF_TTYCDDELAY },
|
1999-08-06 20:04:08 +00:00
|
|
|
tty_AwaitCarrier,
|
1999-11-06 22:50:59 +00:00
|
|
|
NULL,
|
1999-05-08 11:07:56 +00:00
|
|
|
tty_Raw,
|
|
|
|
tty_Offline,
|
|
|
|
tty_Cooked,
|
2002-03-30 12:30:09 +00:00
|
|
|
tty_SetAsyncParams,
|
1999-05-12 09:49:12 +00:00
|
|
|
tty_StopTimer,
|
|
|
|
tty_Free,
|
2002-03-30 12:30:09 +00:00
|
|
|
tty_Read,
|
|
|
|
tty_Write,
|
1999-05-12 09:49:12 +00:00
|
|
|
tty_device2iov,
|
1999-05-08 11:07:56 +00:00
|
|
|
tty_Speed,
|
2002-05-14 12:55:39 +00:00
|
|
|
tty_OpenInfo,
|
|
|
|
tty_Slot
|
1999-05-08 11:07:56 +00:00
|
|
|
};
|
1999-05-12 09:49:12 +00:00
|
|
|
|
1999-05-18 01:37:46 +00:00
|
|
|
struct device *
|
|
|
|
tty_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
|
1999-11-06 22:50:59 +00:00
|
|
|
int maxiov, int *auxfd, int *nauxfd)
|
1999-05-18 01:37:46 +00:00
|
|
|
{
|
|
|
|
if (type == TTY_DEVICE) {
|
1999-05-24 16:39:17 +00:00
|
|
|
struct ttydevice *dev = (struct ttydevice *)iov[(*niov)++].iov_base;
|
1999-05-18 01:37:46 +00:00
|
|
|
|
1999-06-05 21:36:00 +00:00
|
|
|
dev = realloc(dev, sizeof *dev); /* Reduce to the correct size */
|
|
|
|
if (dev == NULL) {
|
|
|
|
log_Printf(LogALERT, "Failed to allocate memory: %d\n",
|
|
|
|
(int)(sizeof *dev));
|
|
|
|
AbortProgram(EX_OSERR);
|
|
|
|
}
|
|
|
|
|
2002-03-30 12:30:09 +00:00
|
|
|
#ifndef NONETGRAPH
|
|
|
|
if (*nauxfd) {
|
|
|
|
dev->cs = *auxfd;
|
|
|
|
(*nauxfd)--;
|
|
|
|
} else
|
|
|
|
dev->cs = -1;
|
|
|
|
#endif
|
|
|
|
|
1999-05-18 01:37:46 +00:00
|
|
|
/* Refresh function pointers etc */
|
|
|
|
memcpy(&dev->dev, &basettydevice, sizeof dev->dev);
|
|
|
|
|
1999-05-24 16:39:17 +00:00
|
|
|
physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
|
1999-05-18 01:37:46 +00:00
|
|
|
if (dev->Timer.state != TIMER_STOPPED) {
|
|
|
|
dev->Timer.state = TIMER_STOPPED;
|
1999-06-05 21:36:00 +00:00
|
|
|
p->handler = &dev->dev; /* For the benefit of StartTimer */
|
1999-05-18 01:37:46 +00:00
|
|
|
tty_StartTimer(p);
|
|
|
|
}
|
|
|
|
return &dev->dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct device *
|
|
|
|
tty_Create(struct physical *p)
|
1999-05-13 19:29:40 +00:00
|
|
|
{
|
|
|
|
struct ttydevice *dev;
|
|
|
|
struct termios ios;
|
|
|
|
int oldflag;
|
|
|
|
|
1999-05-18 01:37:46 +00:00
|
|
|
if (p->fd < 0 || !isatty(p->fd))
|
|
|
|
/* Don't want this */
|
1999-05-13 19:29:40 +00:00
|
|
|
return NULL;
|
|
|
|
|
1999-05-18 01:37:46 +00:00
|
|
|
if (*p->name.full == '\0') {
|
|
|
|
physical_SetDevice(p, ttyname(p->fd));
|
|
|
|
log_Printf(LogDEBUG, "%s: Input is a tty (%s)\n",
|
|
|
|
p->link.name, p->name.full);
|
|
|
|
} else
|
|
|
|
log_Printf(LogDEBUG, "%s: Opened %s\n", p->link.name, p->name.full);
|
|
|
|
|
|
|
|
/* We're gonna return a ttydevice (unless something goes horribly wrong) */
|
|
|
|
|
|
|
|
if ((dev = malloc(sizeof *dev)) == NULL) {
|
|
|
|
/* Complete failure - parent doesn't continue trying to ``create'' */
|
|
|
|
close(p->fd);
|
|
|
|
p->fd = -1;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
1999-05-13 19:29:40 +00:00
|
|
|
memcpy(&dev->dev, &basettydevice, sizeof dev->dev);
|
1999-05-16 11:58:48 +00:00
|
|
|
memset(&dev->Timer, '\0', sizeof dev->Timer);
|
1999-08-06 20:04:08 +00:00
|
|
|
dev->mbits = -1;
|
2002-03-30 12:30:09 +00:00
|
|
|
#ifndef NONETGRAPH
|
|
|
|
dev->real.speed = 0;
|
|
|
|
dev->real.fd = -1;
|
|
|
|
dev->real.disc = -1;
|
|
|
|
*dev->hook = '\0';
|
|
|
|
#endif
|
1999-05-13 19:29:40 +00:00
|
|
|
tcgetattr(p->fd, &ios);
|
|
|
|
dev->ios = ios;
|
|
|
|
|
1999-11-26 22:44:33 +00:00
|
|
|
if (p->cfg.cd.necessity != CD_DEFAULT)
|
|
|
|
/* Any override is ok for the tty device */
|
|
|
|
dev->dev.cd = p->cfg.cd;
|
|
|
|
|
1999-05-18 01:37:46 +00:00
|
|
|
log_Printf(LogDEBUG, "%s: tty_Create: physical (get): fd = %d,"
|
1999-05-13 19:29:40 +00:00
|
|
|
" iflag = %lx, oflag = %lx, cflag = %lx\n", p->link.name, p->fd,
|
|
|
|
(u_long)ios.c_iflag, (u_long)ios.c_oflag, (u_long)ios.c_cflag);
|
|
|
|
|
|
|
|
cfmakeraw(&ios);
|
|
|
|
if (p->cfg.rts_cts)
|
|
|
|
ios.c_cflag |= CLOCAL | CCTS_OFLOW | CRTS_IFLOW;
|
|
|
|
else {
|
|
|
|
ios.c_cflag |= CLOCAL;
|
|
|
|
ios.c_iflag |= IXOFF;
|
|
|
|
}
|
|
|
|
ios.c_iflag |= IXON;
|
1999-08-09 23:49:59 +00:00
|
|
|
if (p->type != PHYS_DEDICATED)
|
1999-05-13 19:29:40 +00:00
|
|
|
ios.c_cflag |= HUPCL;
|
|
|
|
|
|
|
|
if (p->type != PHYS_DIRECT) {
|
|
|
|
/* Change tty speed when we're not in -direct mode */
|
|
|
|
ios.c_cflag &= ~(CSIZE | PARODD | PARENB);
|
|
|
|
ios.c_cflag |= p->cfg.parity;
|
|
|
|
if (cfsetspeed(&ios, IntToSpeed(p->cfg.speed)) == -1)
|
|
|
|
log_Printf(LogWARN, "%s: %s: Unable to set speed to %d\n",
|
|
|
|
p->link.name, p->name.full, p->cfg.speed);
|
|
|
|
}
|
2000-10-31 23:27:35 +00:00
|
|
|
|
|
|
|
if (tcsetattr(p->fd, TCSADRAIN, &ios) == -1) {
|
|
|
|
log_Printf(LogWARN, "%s: tcsetattr: Failed configuring device\n",
|
|
|
|
p->link.name);
|
|
|
|
if (p->type != PHYS_DIRECT && p->cfg.speed > 115200)
|
|
|
|
log_Printf(LogWARN, "%.*s Perhaps the speed is unsupported\n",
|
|
|
|
(int)strlen(p->link.name), "");
|
|
|
|
}
|
|
|
|
|
1999-05-13 19:29:40 +00:00
|
|
|
log_Printf(LogDEBUG, "%s: physical (put): iflag = %lx, oflag = %lx, "
|
|
|
|
"cflag = %lx\n", p->link.name, (u_long)ios.c_iflag,
|
|
|
|
(u_long)ios.c_oflag, (u_long)ios.c_cflag);
|
|
|
|
|
|
|
|
oldflag = fcntl(p->fd, F_GETFL, 0);
|
|
|
|
if (oldflag < 0) {
|
1999-05-18 01:37:46 +00:00
|
|
|
/* Complete failure - parent doesn't continue trying to ``create'' */
|
|
|
|
|
1999-05-13 19:29:40 +00:00
|
|
|
log_Printf(LogWARN, "%s: Open: Cannot get physical flags: %s\n",
|
|
|
|
p->link.name, strerror(errno));
|
1999-05-18 01:37:46 +00:00
|
|
|
tty_Cooked(p);
|
|
|
|
close(p->fd);
|
|
|
|
p->fd = -1;
|
1999-08-06 20:04:08 +00:00
|
|
|
free(dev);
|
1999-05-13 19:29:40 +00:00
|
|
|
return NULL;
|
|
|
|
} else
|
|
|
|
fcntl(p->fd, F_SETFL, oldflag & ~O_NONBLOCK);
|
|
|
|
|
1999-05-24 16:39:17 +00:00
|
|
|
physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
|
1999-05-13 19:29:40 +00:00
|
|
|
|
|
|
|
return &dev->dev;
|
|
|
|
}
|