blob: b626c7afe380d1194b59dc160fc40a10b366bd05 [file] [log] [blame] [raw]
/*-
* Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org>
* All rights reserved.
* Copyright 2015-2024 Rivoreo
*
* Portions of this software were developed under sponsorship from Snow
* B.V., the Netherlands.
*
* 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.
*/
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/malloc.h>
#include <sys/fcntl.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/tty.h>
#include <fs/devfs/devfs_int.h>
/*
* This driver implements a BSD-style compatibility naming scheme for
* the pts(4) driver. We just call into pts(4) to create the actual PTY.
* To make sure we don't use the same PTY multiple times, we abuse
* si_drv1 inside the cdev to mark whether the PTY is in use.
*
* This version preallocates pty master devices in devfs.
*/
struct pts_softc {
int pts_unit; /* (c) Device unit number. */
unsigned int pts_flags; /* (t) Device flags. */
char pts_pkt; /* (t) Unread packet mode data. */
struct cv pts_inwait; /* (t) Blocking write() on master. */
struct selinfo pts_inpoll; /* (t) Select queue for write(). */
struct cv pts_outwait; /* (t) Blocking read() on master. */
struct selinfo pts_outpoll; /* (t) Select queue for read(). */
struct cdev *pts_cdev; /* (c) Master device node. */
struct ucred *pts_cred; /* (c) Resource limit. */
};
static unsigned int pty_warningcnt = 1;
SYSCTL_UINT(_kern, OID_AUTO, pty_warningcnt, CTLFLAG_RW,
&pty_warningcnt, 0,
"Warnings that will be triggered upon legacy PTY allocation");
static unsigned int pty_preallocate_count = 256;
static void remove_master_dev_pointer_from_pts(const char *slave_name) {
struct cdev_priv *cdp;
TAILQ_FOREACH(cdp, &cdevp_list, cdp_list) {
if(strcmp(cdp->cdp_c.si_name, slave_name) == 0) {
struct tty *tty = cdp->cdp_c.si_drv1;
if(!tty) break;
struct pts_softc *ptsc = tty->t_devswsoftc;
if(!ptsc) break;
ptsc->pts_cdev = NULL;
break;
}
}
}
static int
ptydev_fdopen(struct cdev *dev, int fflags, struct thread *td, struct file *fp)
{
int error;
char name[6]; /* "ttyXX" */
if (!atomic_cmpset_ptr((uintptr_t *)&dev->si_drv1, 0, 1))
return (EBUSY);
/* Generate device name and create PTY. */
strlcpy(name, devtoname(dev), sizeof(name));
name[0] = 't';
error = pts_alloc_external(fflags & (FREAD|FWRITE), td, fp, dev, name);
if(error) return error;
remove_master_dev_pointer_from_pts(name);
/* Raise a warning when a legacy PTY has been allocated. */
counted_warning(&pty_warningcnt, "is using legacy pty devices");
return (0);
}
static int ptydev_close(struct cdev *dev, int __unused f_flags, int __unused dev_type, struct thread *__unused td) {
char slave_name[6];
slave_name[0] = 't';
strlcpy(slave_name + 1, devtoname(dev) + 1, sizeof slave_name - 1);
remove_master_dev_pointer_from_pts(slave_name);
dev->si_drv1 = 0;
return 0;
}
static struct cdevsw ptydev_cdevsw = {
.d_version = D_VERSION,
.d_fdopen = ptydev_fdopen,
.d_close = ptydev_close,
.d_name = "ptydev",
};
MALLOC_DEFINE(M_PTYDEV, "ptydev_array", "Pool for holding pointers to pty cdevs");
static unsigned int preallocated_pty_count = 0;
static struct cdev **preallocated_pty;
#define PTY_NAME_1 "pqrslmnoPQRSLMNO"
#define PTY_NAME_2 "0123456789abcdefghijklmnopqrstuv"
static int reallocate_pty() {
int e = 0;
//printf("reallocate_pty: pty_preallocate_count = %u, preallocated_pty_count = %u\n", pty_preallocate_count, preallocated_pty_count);
if(pty_preallocate_count < preallocated_pty_count) {
do {
struct cdev *dev = preallocated_pty[--preallocated_pty_count];
if(!atomic_cmpset_ptr((uintptr_t *)&dev->si_drv1, 0, 1)) {
preallocated_pty_count++;
e = EBUSY;
break;
}
destroy_dev(dev);
} while(pty_preallocate_count < preallocated_pty_count);
preallocated_pty = realloc(preallocated_pty, preallocated_pty_count * sizeof(struct cdev *), M_PTYDEV, M_WAITOK);
} else if(pty_preallocate_count > preallocated_pty_count) {
const char *u1 = PTY_NAME_1;
const char *u2 = PTY_NAME_2;
unsigned int count = 0;
preallocated_pty = realloc(preallocated_pty, pty_preallocate_count * sizeof(struct cdev *), M_PTYDEV, M_WAITOK);
for(u1 = PTY_NAME_1; *u1; u1++) {
for(u2 = PTY_NAME_2; *u2; u2++) {
struct cdev *dev;
if(count < preallocated_pty_count) {
count++;
continue;
}
e = make_dev_p(MAKEDEV_ETERNAL_KLD, &dev, &ptydev_cdevsw, NULL, UID_ROOT, GID_WHEEL, 0666, "pty%c%c", *u1, *u2);
if(e) goto done;
preallocated_pty[count++] = dev;
if(count >= pty_preallocate_count) goto done;
}
}
done:
preallocated_pty_count = count;
}
pty_preallocate_count = preallocated_pty_count;
return e;
}
static int pty_preallocate_sysctl_handler(SYSCTL_HANDLER_ARGS) {
unsigned int value = pty_preallocate_count;
int e = sysctl_handle_int(oidp, &value, 0, req);
if(e) return e;
if(!req->newptr) return 0;
if(value > (sizeof PTY_NAME_1 - 1) * (sizeof PTY_NAME_2 - 1)) {
//printf("pty_prealloc: requested preallocation number too large\n");
return EINVAL;
}
pty_preallocate_count = value;
if(!M_PTYDEV->ks_handle) return 0; // Due to CTLFLAG_TUN
e = reallocate_pty();
if(e) printf("pty_prealloc: kern.pty_preallocate: reallocate_pty error %d\n", e);
return 0;
}
SYSCTL_OID(_kern, OID_AUTO, pty_preallocate, CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_TUN, SYSCTL_NULL_INT_PTR, 0, pty_preallocate_sysctl_handler, "I", "Number of pty master devices to preallocate");
static int
pty_prealloc_modevent(module_t __unused mod, int type, void *__unused data)
{
int e;
switch(type) {
case MOD_LOAD:
if(module_lookupbyname("pty")) {
printf("pty_prealloc: conflicting module pty already loaded\n");
return EBUSY;
}
e = reallocate_pty();
if(e) return e;
break;
case MOD_SHUTDOWN:
break;
case MOD_UNLOAD:
// TODO: need locking
pty_preallocate_count = 0;
e = reallocate_pty();
if(e) return e;
free(preallocated_pty, M_PTYDEV);
break;
default:
return (EOPNOTSUPP);
}
return (0);
}
DEV_MODULE(pty_prealloc, pty_prealloc_modevent, NULL);