Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7ce9090a authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "usb: serial: Enable dynamic transport type switch for DUN calls"

parents ee9f4693 d03413c2
Loading
Loading
Loading
Loading
+264 −4
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
 * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
 * Copyright (C) 2008 by David Brownell
 * Copyright (C) 2008 by Nokia Corporation
 * Copyright (c) 2014, The Linux Foundation. All rights reserved.
 *
 * This software is distributed under the terms of the GNU General
 * Public License ("GPL") as published by the Free Software Foundation,
@@ -14,8 +15,13 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/usb/composite.h>

#include "usb_gadget_xport.h"

#include "u_serial.h"
#include "gadget_chips.h"

@@ -28,8 +34,23 @@
 * CDC ACM driver.  However, for many purposes it's just as functional
 * if you can arrange appropriate host side drivers.
 */

#define GSERIAL_IOCTL_MAGIC		'G'
#define GSERIAL_SET_XPORT_TYPE		_IOW(GSERIAL_IOCTL_MAGIC, 0, u32)
#define GSERIAL_SMD_WRITE		_IOW(GSERIAL_IOCTL_MAGIC, 1, \
					struct ioctl_smd_write_arg_type)

#define GSERIAL_SET_XPORT_TYPE_TTY 0
#define GSERIAL_SET_XPORT_TYPE_SMD 1

#define GSERIAL_BUF_LEN  256
#define GSERIAL_NO_PORTS 3

struct ioctl_smd_write_arg_type {
	char		*buf;
	unsigned int	size;
};

struct f_gser {
	struct gserial			port;
	u8				data_id;
@@ -38,6 +59,9 @@ struct f_gser {
	u8				online;
	enum transport_type		transport;

	atomic_t			ioctl_excl;
	atomic_t			open_excl;

#ifdef CONFIG_MODEM_SUPPORT
	u8				pending;
	spinlock_t			lock;
@@ -63,6 +87,7 @@ struct f_gser {
#endif
};


static unsigned int no_tty_ports;
static unsigned int no_smd_ports;
static unsigned int no_hsic_sports;
@@ -74,8 +99,31 @@ static struct port_info {
	enum transport_type	transport;
	unsigned		port_num;
	unsigned char		client_port_num;
	struct f_gser		*gser_ptr;
} gserial_ports[GSERIAL_NO_PORTS];

static int gser_open_dev(struct inode *ip, struct file *fp);
static int gser_release_dev(struct inode *ip, struct file *fp);
static long gser_ioctl(struct file *fp, unsigned cmd, unsigned long arg);
static void gser_ioctl_set_transport(struct f_gser *gser,
				unsigned int transport);


static const struct file_operations gser_fops = {
	.owner = THIS_MODULE,
	.open = gser_open_dev,
	.release = gser_release_dev,
	.unlocked_ioctl = gser_ioctl,
};

static struct miscdevice gser_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "android_serial_device",
	.fops = &gser_fops,
};

static int registered;

static inline struct f_gser *func_to_gser(struct usb_function *f)
{
	return container_of(f, struct f_gser, port.func);
@@ -412,12 +460,12 @@ static int gport_disconnect(struct f_gser *gser)
{
	unsigned port_num;

	port_num = gserial_ports[gser->port_num].client_port_num;

	pr_debug("%s: transport: %s f_gser: %p gserial: %p port_num: %d\n",
			__func__, xport_to_str(gser->transport),
			gser, &gser->port, gser->port_num);

	port_num = gserial_ports[gser->port_num].client_port_num;

	switch (gser->transport) {
	case USB_GADGET_XPORT_TTY:
		gserial_disconnect(&gser->port);
@@ -857,6 +905,9 @@ static int gser_bind(struct usb_configuration *c, struct usb_function *f)
			gser_ss_function);
	if (status)
		goto fail;

	gserial_ports[gser->port_num].gser_ptr = gser;

	DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n",
			gser->port_num,
			gadget_is_superspeed(c->cdev->gadget) ? "super" :
@@ -978,19 +1029,41 @@ static void gser_free(struct usb_function *f)
	struct f_gser *serial;

	serial = func_to_gser(f);
	pr_debug("%s: port %d", __func__, serial->port_num);

	gserial_ports[serial->port_num].gser_ptr = NULL;
	kfree(serial);
	gser_next_free_port--;
}

static void gser_unbind(struct usb_configuration *c, struct usb_function *f)
{
#ifdef CONFIG_MODEM_SUPPORT
	struct f_gser *gser = func_to_gser(f);
#endif

	usb_free_all_descriptors(f);
#ifdef CONFIG_MODEM_SUPPORT
	gs_free_req(gser->notify, gser->notify_req);
#endif

	gserial_ports[gser->port_num].gser_ptr = NULL;
}

static int gser_init(void)
{
	int ret;

	pr_debug("%s: initialize serial function instance", __func__);

	if (registered)
		return 0;

	ret = misc_register(&gser_device);
	if (ret)
		pr_err("Serial driver failed to register");
	else
		registered = 1;

	return ret;
}

struct usb_function *gser_alloc(struct usb_function_instance *fi)
@@ -1039,6 +1112,8 @@ struct usb_function *gser_alloc(struct usb_function_instance *fi)
	gser->port.disconnect = gser_disconnect;
	gser->port.send_break = gser_send_break;
#endif
	gserial_ports[gser->port_num].gser_ptr = gser;
	gser_init();

	return &gser->port.func;
}
@@ -1096,3 +1171,188 @@ int gserial_init_port(int port_num, const char *name,

	return ret;
}

static inline int gser_device_lock(atomic_t *excl)
{
	if (atomic_inc_return(excl) == 1) {
		return 0;
	} else {
		atomic_dec(excl);
		return -EBUSY;
	}
}

static inline void gser_device_unlock(atomic_t *excl)
{
	atomic_dec(excl);
}

static int gser_open_dev(struct inode *ip, struct file *fp)
{
	struct f_gser *gser = gserial_ports[0].gser_ptr;

	pr_debug("%s: Open serial device", __func__);

	if (!gser) {
		pr_err("%s: Serial device not created yet", __func__);
		return -ENODEV;
	}

	if (gser_device_lock(&gser->open_excl)) {
		pr_err("%s: Already opened", __func__);
		return -EBUSY;
	}

	fp->private_data = gser;
	pr_debug("%s: Serial device opened", __func__);

	return 0;
}

static int gser_release_dev(struct inode *ip, struct file *fp)
{
	struct f_gser *gser = fp->private_data;

	pr_debug("%s: Close serial device", __func__);

	if (!gser) {
		pr_err("Serial device not created yet\n");
		return -ENODEV;
	}

	gser_device_unlock(&gser->open_excl);

	return 0;
}

static void gser_ioctl_set_transport(struct f_gser *gser,
	unsigned int transport)
{
	int ret;
	enum transport_type new_transport;
	const struct usb_endpoint_descriptor *ep_in_desc_backup;
	const struct usb_endpoint_descriptor *ep_out_desc_backup;

	if (transport == GSERIAL_SET_XPORT_TYPE_TTY) {
		new_transport = USB_GADGET_XPORT_TTY;
		pr_debug("%s: Switching modem transport to TTY.", __func__);
	} else if (transport == GSERIAL_SET_XPORT_TYPE_SMD) {
		new_transport = USB_GADGET_XPORT_SMD;
		pr_debug("%s: Switching modem transport to SMD.", __func__);
	} else {
		pr_err("%s: Wrong transport type %d", __func__, transport);
		return;
	}

	if (gser->transport == new_transport) {
		pr_debug("%s: Modem transport aready set to this type.",
			__func__);
		return;
	}

	ep_in_desc_backup  = gser->port.in->desc;
	ep_out_desc_backup = gser->port.out->desc;
	gport_disconnect(gser);
	if (new_transport == USB_GADGET_XPORT_TTY) {
		ret = gserial_alloc_line(
			&gserial_ports[gser->port_num].client_port_num);
		if (ret)
			pr_debug("%s: Unable to alloc TTY line", __func__);
	}

	gser->port.in->desc  = ep_in_desc_backup;
	gser->port.out->desc = ep_out_desc_backup;
	gser->transport = new_transport;
	gport_connect(gser);
	pr_debug("%s: Modem transport switch is complete.", __func__);

}

static long gser_ioctl(struct file *fp, unsigned cmd, unsigned long arg)
{
	int ret = 0;
	int count;
	int xport_type;
	int smd_port_num;
	char smd_write_buf[GSERIAL_BUF_LEN];
	struct ioctl_smd_write_arg_type smd_write_arg;
	struct f_gser *gser;
	void __user *argp = (void __user *)arg;

	if (!fp || !fp->private_data) {
		pr_err("%s: Invalid file handle", __func__);
		return -EBADFD;
	}

	gser = fp->private_data;
	pr_debug("Received command %d", cmd);

	if (gser_device_lock(&gser->ioctl_excl))
		return -EBUSY;

	switch (cmd) {
	case GSERIAL_SET_XPORT_TYPE:
		if (copy_from_user(&xport_type, argp, sizeof(xport_type))) {
			pr_err("%s: failed to copy IOCTL set transport type",
				__func__);
			ret = -EFAULT;
			break;
		}

		gser_ioctl_set_transport(gser, xport_type);
		break;

	case GSERIAL_SMD_WRITE:
		if (gser->transport != USB_GADGET_XPORT_SMD) {
			pr_err("%s: ERR: Got SMD WR cmd when not in SMD mode",
				__func__);

			break;
		}

		pr_debug("%s: Copy GSERIAL_SMD_WRITE IOCTL command argument",
			__func__);
		if (copy_from_user(&smd_write_arg, argp,
			sizeof(smd_write_arg))) {
			ret = -EFAULT;

			pr_err("%s: failed to copy IOCTL GSERIAL_SMD_WRITE arg",
				__func__);

			break;
		}
		smd_port_num =
			gserial_ports[gser->port_num].client_port_num;

		pr_debug("%s: Copying %d bytes from user buffer to local\n",
			__func__, smd_write_arg.size);

		if (copy_from_user(smd_write_buf, smd_write_arg.buf,
			smd_write_arg.size)) {

			pr_err("%s: failed to copy buf for GSERIAL_SMD_WRITE",
				__func__);

			ret = -EFAULT;
			break;
		}

		pr_debug("%s: Writing %d bytes to SMD channel\n",
			__func__, smd_write_arg.size);

		count = gsmd_write(smd_port_num, smd_write_buf,
			smd_write_arg.size);

		if (count != smd_write_arg.size)
			ret = -EFAULT;

		break;
	default:
		pr_err("Unsupported IOCTL");
		ret = -EINVAL;
	}

	gser_device_unlock(&gser->ioctl_excl);
	return ret;
}
+2 −0
Original line number Diff line number Diff line
@@ -83,6 +83,8 @@ void gsdio_disconnect(struct gserial *, u8 portno);
int gsmd_setup(struct usb_gadget *g, unsigned n_ports);
int gsmd_connect(struct gserial *, u8 port_num);
void gsmd_disconnect(struct gserial *, u8 portno);
int gsmd_write(u8 portno, char *buf, unsigned int size);


/* functions are bound to configurations by a config or gadget driver */
int gser_bind_config(struct usb_configuration *c, u8 port_num);
+23 −4
Original line number Diff line number Diff line
@@ -681,8 +681,8 @@ int gsmd_connect(struct gserial *gser, u8 portno)

	ret = usb_ep_enable(gser->in);
	if (ret) {
		pr_err("%s: usb_ep_enable failed eptype:IN ep:%p",
				__func__, gser->in);
		pr_err("%s: usb_ep_enable failed eptype:IN ep:%p, err:%d",
				__func__, gser->in, ret);
		port->port_usb = 0;
		return ret;
	}
@@ -690,8 +690,8 @@ int gsmd_connect(struct gserial *gser, u8 portno)

	ret = usb_ep_enable(gser->out);
	if (ret) {
		pr_err("%s: usb_ep_enable failed eptype:OUT ep:%p",
				__func__, gser->out);
		pr_err("%s: usb_ep_enable failed eptype:OUT ep:%p, err: %d",
				__func__, gser->out, ret);
		port->port_usb = 0;
		gser->in->driver_data = 0;
		return ret;
@@ -747,6 +747,8 @@ void gsmd_disconnect(struct gserial *gser, u8 portno)
				~port->cbits_to_modem);
	}

	gser->notify_modem = NULL;

	if (port->pi->ch)
		queue_work(gsmd_wq, &port->disconnect_work);
}
@@ -994,3 +996,20 @@ void gsmd_cleanup(struct usb_gadget *g, unsigned count)
{
	/* TBD */
}

int gsmd_write(u8 portno, char *buf, unsigned int size)
{
	int count, avail;
	struct gsmd_port const *port = smd_ports[portno].port;

	if (portno > SMD_N_PORTS)
		return -EINVAL;

	avail = smd_write_avail(port->pi->ch);
	if (avail < size)
		return -EAGAIN;

	count = smd_write(port->pi->ch, buf, size);
	return count;
}