18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/completion.h>
78c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
88c2ecf20Sopenharmony_ci#include <linux/list.h>
98c2ecf20Sopenharmony_ci#include <linux/mutex.h>
108c2ecf20Sopenharmony_ci#include <linux/slab.h>
118c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
128c2ecf20Sopenharmony_ci#include <asm/atomic.h>
138c2ecf20Sopenharmony_ci#include <init.h>
148c2ecf20Sopenharmony_ci#include <irq_kern.h>
158c2ecf20Sopenharmony_ci#include <os.h>
168c2ecf20Sopenharmony_ci#include "port.h"
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistruct port_list {
198c2ecf20Sopenharmony_ci	struct list_head list;
208c2ecf20Sopenharmony_ci	atomic_t wait_count;
218c2ecf20Sopenharmony_ci	int has_connection;
228c2ecf20Sopenharmony_ci	struct completion done;
238c2ecf20Sopenharmony_ci	int port;
248c2ecf20Sopenharmony_ci	int fd;
258c2ecf20Sopenharmony_ci	spinlock_t lock;
268c2ecf20Sopenharmony_ci	struct list_head pending;
278c2ecf20Sopenharmony_ci	struct list_head connections;
288c2ecf20Sopenharmony_ci};
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistruct port_dev {
318c2ecf20Sopenharmony_ci	struct port_list *port;
328c2ecf20Sopenharmony_ci	int helper_pid;
338c2ecf20Sopenharmony_ci	int telnetd_pid;
348c2ecf20Sopenharmony_ci};
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistruct connection {
378c2ecf20Sopenharmony_ci	struct list_head list;
388c2ecf20Sopenharmony_ci	int fd;
398c2ecf20Sopenharmony_ci	int helper_pid;
408c2ecf20Sopenharmony_ci	int socket[2];
418c2ecf20Sopenharmony_ci	int telnetd_pid;
428c2ecf20Sopenharmony_ci	struct port_list *port;
438c2ecf20Sopenharmony_ci};
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistatic irqreturn_t pipe_interrupt(int irq, void *data)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	struct connection *conn = data;
488c2ecf20Sopenharmony_ci	int fd;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	fd = os_rcv_fd(conn->socket[0], &conn->helper_pid);
518c2ecf20Sopenharmony_ci	if (fd < 0) {
528c2ecf20Sopenharmony_ci		if (fd == -EAGAIN)
538c2ecf20Sopenharmony_ci			return IRQ_NONE;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci		printk(KERN_ERR "pipe_interrupt : os_rcv_fd returned %d\n",
568c2ecf20Sopenharmony_ci		       -fd);
578c2ecf20Sopenharmony_ci		os_close_file(conn->fd);
588c2ecf20Sopenharmony_ci	}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	list_del(&conn->list);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	conn->fd = fd;
638c2ecf20Sopenharmony_ci	list_add(&conn->list, &conn->port->connections);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	complete(&conn->port->done);
668c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci#define NO_WAITER_MSG \
708c2ecf20Sopenharmony_ci    "****\n" \
718c2ecf20Sopenharmony_ci    "There are currently no UML consoles waiting for port connections.\n" \
728c2ecf20Sopenharmony_ci    "Either disconnect from one to make it available or activate some more\n" \
738c2ecf20Sopenharmony_ci    "by enabling more consoles in the UML /etc/inittab.\n" \
748c2ecf20Sopenharmony_ci    "****\n"
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistatic int port_accept(struct port_list *port)
778c2ecf20Sopenharmony_ci{
788c2ecf20Sopenharmony_ci	struct connection *conn;
798c2ecf20Sopenharmony_ci	int fd, socket[2], pid;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	fd = port_connection(port->fd, socket, &pid);
828c2ecf20Sopenharmony_ci	if (fd < 0) {
838c2ecf20Sopenharmony_ci		if (fd != -EAGAIN)
848c2ecf20Sopenharmony_ci			printk(KERN_ERR "port_accept : port_connection "
858c2ecf20Sopenharmony_ci			       "returned %d\n", -fd);
868c2ecf20Sopenharmony_ci		goto out;
878c2ecf20Sopenharmony_ci	}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	conn = kmalloc(sizeof(*conn), GFP_ATOMIC);
908c2ecf20Sopenharmony_ci	if (conn == NULL) {
918c2ecf20Sopenharmony_ci		printk(KERN_ERR "port_accept : failed to allocate "
928c2ecf20Sopenharmony_ci		       "connection\n");
938c2ecf20Sopenharmony_ci		goto out_close;
948c2ecf20Sopenharmony_ci	}
958c2ecf20Sopenharmony_ci	*conn = ((struct connection)
968c2ecf20Sopenharmony_ci		{ .list 	= LIST_HEAD_INIT(conn->list),
978c2ecf20Sopenharmony_ci		  .fd 		= fd,
988c2ecf20Sopenharmony_ci		  .socket  	= { socket[0], socket[1] },
998c2ecf20Sopenharmony_ci		  .telnetd_pid 	= pid,
1008c2ecf20Sopenharmony_ci		  .port 	= port });
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	if (um_request_irq(TELNETD_IRQ, socket[0], IRQ_READ, pipe_interrupt,
1038c2ecf20Sopenharmony_ci			  IRQF_SHARED, "telnetd", conn)) {
1048c2ecf20Sopenharmony_ci		printk(KERN_ERR "port_accept : failed to get IRQ for "
1058c2ecf20Sopenharmony_ci		       "telnetd\n");
1068c2ecf20Sopenharmony_ci		goto out_free;
1078c2ecf20Sopenharmony_ci	}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	if (atomic_read(&port->wait_count) == 0) {
1108c2ecf20Sopenharmony_ci		os_write_file(fd, NO_WAITER_MSG, sizeof(NO_WAITER_MSG));
1118c2ecf20Sopenharmony_ci		printk(KERN_ERR "No one waiting for port\n");
1128c2ecf20Sopenharmony_ci	}
1138c2ecf20Sopenharmony_ci	list_add(&conn->list, &port->pending);
1148c2ecf20Sopenharmony_ci	return 1;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci out_free:
1178c2ecf20Sopenharmony_ci	kfree(conn);
1188c2ecf20Sopenharmony_ci out_close:
1198c2ecf20Sopenharmony_ci	os_close_file(fd);
1208c2ecf20Sopenharmony_ci	os_kill_process(pid, 1);
1218c2ecf20Sopenharmony_ci out:
1228c2ecf20Sopenharmony_ci	return 0;
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(ports_mutex);
1268c2ecf20Sopenharmony_cistatic LIST_HEAD(ports);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_cistatic void port_work_proc(struct work_struct *unused)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	struct port_list *port;
1318c2ecf20Sopenharmony_ci	struct list_head *ele;
1328c2ecf20Sopenharmony_ci	unsigned long flags;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	local_irq_save(flags);
1358c2ecf20Sopenharmony_ci	list_for_each(ele, &ports) {
1368c2ecf20Sopenharmony_ci		port = list_entry(ele, struct port_list, list);
1378c2ecf20Sopenharmony_ci		if (!port->has_connection)
1388c2ecf20Sopenharmony_ci			continue;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci		while (port_accept(port))
1418c2ecf20Sopenharmony_ci			;
1428c2ecf20Sopenharmony_ci		port->has_connection = 0;
1438c2ecf20Sopenharmony_ci	}
1448c2ecf20Sopenharmony_ci	local_irq_restore(flags);
1458c2ecf20Sopenharmony_ci}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ciDECLARE_WORK(port_work, port_work_proc);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic irqreturn_t port_interrupt(int irq, void *data)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	struct port_list *port = data;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	port->has_connection = 1;
1548c2ecf20Sopenharmony_ci	schedule_work(&port_work);
1558c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_civoid *port_data(int port_num)
1598c2ecf20Sopenharmony_ci{
1608c2ecf20Sopenharmony_ci	struct list_head *ele;
1618c2ecf20Sopenharmony_ci	struct port_list *port;
1628c2ecf20Sopenharmony_ci	struct port_dev *dev = NULL;
1638c2ecf20Sopenharmony_ci	int fd;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	mutex_lock(&ports_mutex);
1668c2ecf20Sopenharmony_ci	list_for_each(ele, &ports) {
1678c2ecf20Sopenharmony_ci		port = list_entry(ele, struct port_list, list);
1688c2ecf20Sopenharmony_ci		if (port->port == port_num)
1698c2ecf20Sopenharmony_ci			goto found;
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci	port = kmalloc(sizeof(struct port_list), GFP_KERNEL);
1728c2ecf20Sopenharmony_ci	if (port == NULL) {
1738c2ecf20Sopenharmony_ci		printk(KERN_ERR "Allocation of port list failed\n");
1748c2ecf20Sopenharmony_ci		goto out;
1758c2ecf20Sopenharmony_ci	}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	fd = port_listen_fd(port_num);
1788c2ecf20Sopenharmony_ci	if (fd < 0) {
1798c2ecf20Sopenharmony_ci		printk(KERN_ERR "binding to port %d failed, errno = %d\n",
1808c2ecf20Sopenharmony_ci		       port_num, -fd);
1818c2ecf20Sopenharmony_ci		goto out_free;
1828c2ecf20Sopenharmony_ci	}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	if (um_request_irq(ACCEPT_IRQ, fd, IRQ_READ, port_interrupt,
1858c2ecf20Sopenharmony_ci			  IRQF_SHARED, "port", port)) {
1868c2ecf20Sopenharmony_ci		printk(KERN_ERR "Failed to get IRQ for port %d\n", port_num);
1878c2ecf20Sopenharmony_ci		goto out_close;
1888c2ecf20Sopenharmony_ci	}
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	*port = ((struct port_list)
1918c2ecf20Sopenharmony_ci		{ .list 	 	= LIST_HEAD_INIT(port->list),
1928c2ecf20Sopenharmony_ci		  .wait_count		= ATOMIC_INIT(0),
1938c2ecf20Sopenharmony_ci		  .has_connection 	= 0,
1948c2ecf20Sopenharmony_ci		  .port 	 	= port_num,
1958c2ecf20Sopenharmony_ci		  .fd  			= fd,
1968c2ecf20Sopenharmony_ci		  .pending 		= LIST_HEAD_INIT(port->pending),
1978c2ecf20Sopenharmony_ci		  .connections 		= LIST_HEAD_INIT(port->connections) });
1988c2ecf20Sopenharmony_ci	spin_lock_init(&port->lock);
1998c2ecf20Sopenharmony_ci	init_completion(&port->done);
2008c2ecf20Sopenharmony_ci	list_add(&port->list, &ports);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci found:
2038c2ecf20Sopenharmony_ci	dev = kmalloc(sizeof(struct port_dev), GFP_KERNEL);
2048c2ecf20Sopenharmony_ci	if (dev == NULL) {
2058c2ecf20Sopenharmony_ci		printk(KERN_ERR "Allocation of port device entry failed\n");
2068c2ecf20Sopenharmony_ci		goto out;
2078c2ecf20Sopenharmony_ci	}
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	*dev = ((struct port_dev) { .port  		= port,
2108c2ecf20Sopenharmony_ci				    .helper_pid  	= -1,
2118c2ecf20Sopenharmony_ci				    .telnetd_pid  	= -1 });
2128c2ecf20Sopenharmony_ci	goto out;
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci out_close:
2158c2ecf20Sopenharmony_ci	os_close_file(fd);
2168c2ecf20Sopenharmony_ci out_free:
2178c2ecf20Sopenharmony_ci	kfree(port);
2188c2ecf20Sopenharmony_ci out:
2198c2ecf20Sopenharmony_ci	mutex_unlock(&ports_mutex);
2208c2ecf20Sopenharmony_ci	return dev;
2218c2ecf20Sopenharmony_ci}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ciint port_wait(void *data)
2248c2ecf20Sopenharmony_ci{
2258c2ecf20Sopenharmony_ci	struct port_dev *dev = data;
2268c2ecf20Sopenharmony_ci	struct connection *conn;
2278c2ecf20Sopenharmony_ci	struct port_list *port = dev->port;
2288c2ecf20Sopenharmony_ci	int fd;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	atomic_inc(&port->wait_count);
2318c2ecf20Sopenharmony_ci	while (1) {
2328c2ecf20Sopenharmony_ci		fd = -ERESTARTSYS;
2338c2ecf20Sopenharmony_ci		if (wait_for_completion_interruptible(&port->done))
2348c2ecf20Sopenharmony_ci			goto out;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci		spin_lock(&port->lock);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci		conn = list_entry(port->connections.next, struct connection,
2398c2ecf20Sopenharmony_ci				  list);
2408c2ecf20Sopenharmony_ci		list_del(&conn->list);
2418c2ecf20Sopenharmony_ci		spin_unlock(&port->lock);
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci		os_shutdown_socket(conn->socket[0], 1, 1);
2448c2ecf20Sopenharmony_ci		os_close_file(conn->socket[0]);
2458c2ecf20Sopenharmony_ci		os_shutdown_socket(conn->socket[1], 1, 1);
2468c2ecf20Sopenharmony_ci		os_close_file(conn->socket[1]);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci		/* This is done here because freeing an IRQ can't be done
2498c2ecf20Sopenharmony_ci		 * within the IRQ handler.  So, pipe_interrupt always ups
2508c2ecf20Sopenharmony_ci		 * the semaphore regardless of whether it got a successful
2518c2ecf20Sopenharmony_ci		 * connection.  Then we loop here throwing out failed
2528c2ecf20Sopenharmony_ci		 * connections until a good one is found.
2538c2ecf20Sopenharmony_ci		 */
2548c2ecf20Sopenharmony_ci		um_free_irq(TELNETD_IRQ, conn);
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci		if (conn->fd >= 0)
2578c2ecf20Sopenharmony_ci			break;
2588c2ecf20Sopenharmony_ci		os_close_file(conn->fd);
2598c2ecf20Sopenharmony_ci		kfree(conn);
2608c2ecf20Sopenharmony_ci	}
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	fd = conn->fd;
2638c2ecf20Sopenharmony_ci	dev->helper_pid = conn->helper_pid;
2648c2ecf20Sopenharmony_ci	dev->telnetd_pid = conn->telnetd_pid;
2658c2ecf20Sopenharmony_ci	kfree(conn);
2668c2ecf20Sopenharmony_ci out:
2678c2ecf20Sopenharmony_ci	atomic_dec(&port->wait_count);
2688c2ecf20Sopenharmony_ci	return fd;
2698c2ecf20Sopenharmony_ci}
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_civoid port_remove_dev(void *d)
2728c2ecf20Sopenharmony_ci{
2738c2ecf20Sopenharmony_ci	struct port_dev *dev = d;
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	if (dev->helper_pid != -1)
2768c2ecf20Sopenharmony_ci		os_kill_process(dev->helper_pid, 0);
2778c2ecf20Sopenharmony_ci	if (dev->telnetd_pid != -1)
2788c2ecf20Sopenharmony_ci		os_kill_process(dev->telnetd_pid, 1);
2798c2ecf20Sopenharmony_ci	dev->helper_pid = -1;
2808c2ecf20Sopenharmony_ci	dev->telnetd_pid = -1;
2818c2ecf20Sopenharmony_ci}
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_civoid port_kern_free(void *d)
2848c2ecf20Sopenharmony_ci{
2858c2ecf20Sopenharmony_ci	struct port_dev *dev = d;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	port_remove_dev(dev);
2888c2ecf20Sopenharmony_ci	kfree(dev);
2898c2ecf20Sopenharmony_ci}
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_cistatic void free_port(void)
2928c2ecf20Sopenharmony_ci{
2938c2ecf20Sopenharmony_ci	struct list_head *ele;
2948c2ecf20Sopenharmony_ci	struct port_list *port;
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	list_for_each(ele, &ports) {
2978c2ecf20Sopenharmony_ci		port = list_entry(ele, struct port_list, list);
2988c2ecf20Sopenharmony_ci		free_irq_by_fd(port->fd);
2998c2ecf20Sopenharmony_ci		os_close_file(port->fd);
3008c2ecf20Sopenharmony_ci	}
3018c2ecf20Sopenharmony_ci}
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_ci__uml_exitcall(free_port);
304