Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 7d10ae3..b55746c 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -64,6 +64,8 @@
source "drivers/tty/serial/Kconfig"
+source "drivers/char/diag/Kconfig"
+
config TTY_PRINTK
bool "TTY driver to output user messages via printk"
depends on EXPERT
@@ -633,5 +635,46 @@
Enables userspace clients to read and write to some packet SMD
ports via device interface for MSM chipset.
+config MSM_ROTATOR
+ tristate "MSM Offline Image Rotator Driver"
+ depends on (ARCH_MSM7X30 || ARCH_MSM8X60 || ARCH_MSM8960) && ANDROID_PMEM
+ default y
+ help
+ This driver provides support for the image rotator HW block in the
+ MSM 7x30 SoC.
+
+config MSM_ROTATOR_USE_IMEM
+ bool "Enable rotator driver to use iMem"
+ depends on ARCH_MSM7X30 && MSM_ROTATOR
+ default y
+ help
+ This option enables the msm_rotator driver to use the move efficient
+ iMem. Some MSM platforms may not have iMem available for the rotator
+ block. Or some systems may want the iMem to be dedicated to a
+ different function.
+
+config MMC_GENERIC_CSDIO
+ tristate "Generic sdio driver"
+ default n
+ help
+ SDIO function driver that extends SDIO card as character device
+ in user space.
+
+config CSDIO_VENDOR_ID
+ hex "Card VendorId"
+ depends on MMC_GENERIC_CSDIO
+ default "0"
+ help
+ Enter vendor id for targeted sdio device, this may be overwritten by
+ module parameters.
+
+config CSDIO_DEVICE_ID
+ hex "CardDeviceId"
+ depends on MMC_GENERIC_CSDIO
+ default "0"
+ help
+ Enter device id for targeted sdio device, this may be overwritten by
+ module parameters.
+.
endmenu
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 3f63254..1a295b8 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -9,7 +9,6 @@
obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o
obj-$(CONFIG_RAW_DRIVER) += raw.o
obj-$(CONFIG_SGI_SNSC) += snsc.o snsc_event.o
-obj-$(CONFIG_MSM_SMD_PKT) += msm_smd_pkt.o
obj-$(CONFIG_MSPEC) += mspec.o
obj-$(CONFIG_MMTIMER) += mmtimer.o
obj-$(CONFIG_UV_MMTIMER) += uv_mmtimer.o
@@ -64,3 +63,6 @@
obj-$(CONFIG_JS_RTC) += js-rtc.o
js-rtc-y = rtc.o
+
+obj-$(CONFIG_MSM_ROTATOR) += msm_rotator.o
+obj-$(CONFIG_MMC_GENERIC_CSDIO) += csdio.o
diff --git a/drivers/char/csdio.c b/drivers/char/csdio.c
new file mode 100644
index 0000000..ca7e986
--- /dev/null
+++ b/drivers/char/csdio.c
@@ -0,0 +1,1074 @@
+/*
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/serial_reg.h>
+#include <linux/circ_buf.h>
+#include <linux/gfp.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+/* Char device */
+#include <linux/cdev.h>
+#include <linux/fs.h>
+
+/* Sdio device */
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+
+#include <linux/csdio.h>
+
+#define FALSE 0
+#define TRUE 1
+
+#define VERSION "0.5"
+#define CSDIO_NUM_OF_SDIO_FUNCTIONS 7
+#define CSDIO_DEV_NAME "csdio"
+#define TP_DEV_NAME CSDIO_DEV_NAME"f"
+#define CSDIO_DEV_PERMISSIONS 0666
+
+#define CSDIO_SDIO_BUFFER_SIZE (64*512)
+
+int csdio_major;
+int csdio_minor;
+int csdio_transport_nr_devs = CSDIO_NUM_OF_SDIO_FUNCTIONS;
+static uint csdio_vendor_id;
+static uint csdio_device_id;
+static char *host_name;
+
+static struct csdio_func_t {
+ struct sdio_func *m_func;
+ int m_enabled;
+ struct cdev m_cdev; /* char device structure */
+ struct device *m_device;
+ u32 m_block_size;
+} *g_csdio_func_table[CSDIO_NUM_OF_SDIO_FUNCTIONS] = {0};
+
+struct csdio_t {
+ struct cdev m_cdev;
+ struct device *m_device;
+ struct class *m_driver_class;
+ struct fasync_struct *m_async_queue;
+ unsigned char m_current_irq_mask; /* currently enabled irqs */
+ struct mmc_host *m_host;
+ unsigned int m_num_of_func;
+} g_csdio;
+
+struct csdio_file_descriptor {
+ struct csdio_func_t *m_port;
+ u32 m_block_mode;/* data tran. byte(0)/block(1) */
+ u32 m_op_code; /* address auto increment flag */
+ u32 m_address;
+};
+
+static void *g_sdio_buffer;
+
+/*
+ * Open and release
+ */
+static int csdio_transport_open(struct inode *inode, struct file *filp)
+{
+ int ret = 0;
+ struct csdio_func_t *port = NULL; /* device information */
+ struct sdio_func *func = NULL;
+ struct csdio_file_descriptor *descriptor = NULL;
+
+ port = container_of(inode->i_cdev, struct csdio_func_t, m_cdev);
+ func = port->m_func;
+ descriptor = kzalloc(sizeof(struct csdio_file_descriptor), GFP_KERNEL);
+ if (!descriptor) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ pr_info(TP_DEV_NAME"%d: open: func=%p, port=%p\n",
+ func->num, func, port);
+ sdio_claim_host(func);
+ ret = sdio_enable_func(func);
+ if (ret) {
+ pr_err(TP_DEV_NAME"%d:Enable func failed (%d)\n",
+ func->num, ret);
+ ret = -EIO;
+ goto free_descriptor;
+ }
+ descriptor->m_port = port;
+ filp->private_data = descriptor;
+ goto release_host;
+
+free_descriptor:
+ kfree(descriptor);
+release_host:
+ sdio_release_host(func);
+exit:
+ return ret;
+}
+
+static int csdio_transport_release(struct inode *inode, struct file *filp)
+{
+ int ret = 0;
+ struct csdio_file_descriptor *descriptor = filp->private_data;
+ struct csdio_func_t *port = descriptor->m_port;
+ struct sdio_func *func = port->m_func;
+
+ pr_info(TP_DEV_NAME"%d: release\n", func->num);
+ sdio_claim_host(func);
+ ret = sdio_disable_func(func);
+ if (ret) {
+ pr_err(TP_DEV_NAME"%d:Disable func failed(%d)\n",
+ func->num, ret);
+ ret = -EIO;
+ }
+ sdio_release_host(func);
+ kfree(descriptor);
+ return ret;
+}
+
+/*
+ * Data management: read and write
+ */
+static ssize_t csdio_transport_read(struct file *filp,
+ char __user *buf,
+ size_t count,
+ loff_t *f_pos)
+{
+ ssize_t ret = 0;
+ struct csdio_file_descriptor *descriptor = filp->private_data;
+ struct csdio_func_t *port = descriptor->m_port;
+ struct sdio_func *func = port->m_func;
+ size_t t_count = count;
+
+ if (descriptor->m_block_mode) {
+ pr_info(TP_DEV_NAME "%d: CMD53 read, Md:%d, Addr:0x%04X,"
+ " Un:%d (Bl:%d, BlSz:%d)\n", func->num,
+ descriptor->m_block_mode,
+ descriptor->m_address,
+ count*port->m_block_size,
+ count, port->m_block_size);
+ /* recalculate size */
+ count *= port->m_block_size;
+ }
+ sdio_claim_host(func);
+ if (descriptor->m_op_code) {
+ /* auto increment */
+ ret = sdio_memcpy_fromio(func, g_sdio_buffer,
+ descriptor->m_address, count);
+ } else { /* FIFO */
+ ret = sdio_readsb(func, g_sdio_buffer,
+ descriptor->m_address, count);
+ }
+ sdio_release_host(func);
+ if (!ret) {
+ if (copy_to_user(buf, g_sdio_buffer, count))
+ ret = -EFAULT;
+ else
+ ret = t_count;
+ }
+ if (ret < 0) {
+ pr_err(TP_DEV_NAME "%d: CMD53 read failed (%d)"
+ "(Md:%d, Addr:0x%04X, Sz:%d)\n",
+ func->num, ret,
+ descriptor->m_block_mode,
+ descriptor->m_address, count);
+ }
+ return ret;
+}
+
+static ssize_t csdio_transport_write(struct file *filp,
+ const char __user *buf,
+ size_t count,
+ loff_t *f_pos)
+{
+ ssize_t ret = 0;
+ struct csdio_file_descriptor *descriptor = filp->private_data;
+ struct csdio_func_t *port = descriptor->m_port;
+ struct sdio_func *func = port->m_func;
+ size_t t_count = count;
+
+ if (descriptor->m_block_mode)
+ count *= port->m_block_size;
+
+ if (copy_from_user(g_sdio_buffer, buf, count)) {
+ pr_err(TP_DEV_NAME"%d:copy_from_user failed\n", func->num);
+ ret = -EFAULT;
+ } else {
+ sdio_claim_host(func);
+ if (descriptor->m_op_code) {
+ /* auto increment */
+ ret = sdio_memcpy_toio(func, descriptor->m_address,
+ g_sdio_buffer, count);
+ } else {
+ /* FIFO */
+ ret = sdio_writesb(func, descriptor->m_address,
+ g_sdio_buffer, count);
+ }
+ sdio_release_host(func);
+ if (!ret) {
+ ret = t_count;
+ } else {
+ pr_err(TP_DEV_NAME "%d: CMD53 write failed (%d)"
+ "(Md:%d, Addr:0x%04X, Sz:%d)\n",
+ func->num, ret, descriptor->m_block_mode,
+ descriptor->m_address, count);
+ }
+ }
+ return ret;
+}
+
+/* disable interrupt for sdio client */
+static int disable_sdio_client_isr(struct sdio_func *func)
+{
+ int ret;
+
+ /* disable for all functions, to restore interrupts
+ * use g_csdio.m_current_irq_mask */
+ sdio_f0_writeb(func, 0, SDIO_CCCR_IENx, &ret);
+ if (ret)
+ pr_err(CSDIO_DEV_NAME" Can't sdio_f0_writeb (%d)\n", ret);
+
+ return ret;
+}
+
+/*
+ * This handles the interrupt from SDIO.
+ */
+static void csdio_sdio_irq(struct sdio_func *func)
+{
+ int ret;
+
+ pr_info(CSDIO_DEV_NAME" csdio_sdio_irq: func=%d\n", func->num);
+ ret = disable_sdio_client_isr(func);
+ if (ret) {
+ pr_err(CSDIO_DEV_NAME" Can't disable client isr(%d)\n", ret);
+ return;
+ }
+ /* signal asynchronous readers */
+ if (g_csdio.m_async_queue)
+ kill_fasync(&g_csdio.m_async_queue, SIGIO, POLL_IN);
+}
+
+/*
+ * The ioctl() implementation
+ */
+static int csdio_transport_ioctl(struct inode *inode,
+ struct file *filp,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ int err = 0;
+ int ret = 0;
+ struct csdio_file_descriptor *descriptor = filp->private_data;
+ struct csdio_func_t *port = descriptor->m_port;
+ struct sdio_func *func = port->m_func;
+
+ /* extract the type and number bitfields
+ sanity check: return ENOTTY (inappropriate ioctl) before
+ access_ok()
+ */
+ if ((_IOC_TYPE(cmd) != CSDIO_IOC_MAGIC) ||
+ (_IOC_NR(cmd) > CSDIO_IOC_MAXNR)) {
+ pr_err(TP_DEV_NAME "Wrong ioctl command parameters\n");
+ ret = -ENOTTY;
+ goto exit;
+ }
+
+ /* the direction is a bitmask, and VERIFY_WRITE catches R/W
+ * transfers. `Type' is user-oriented, while access_ok is
+ kernel-oriented, so the concept of "read" and "write" is reversed
+ */
+ if (_IOC_DIR(cmd) & _IOC_READ) {
+ err = !access_ok(VERIFY_WRITE, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ } else {
+ if (_IOC_DIR(cmd) & _IOC_WRITE) {
+ err = !access_ok(VERIFY_READ, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ }
+ }
+ if (err) {
+ pr_err(TP_DEV_NAME "Wrong ioctl access direction\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ switch (cmd) {
+ case CSDIO_IOC_SET_OP_CODE:
+ {
+ pr_info(TP_DEV_NAME"%d:SET_OP_CODE=%d\n",
+ func->num, descriptor->m_op_code);
+ ret = get_user(descriptor->m_op_code,
+ (unsigned char __user *)arg);
+ if (ret) {
+ pr_err(TP_DEV_NAME"%d:SET_OP_CODE get data"
+ " from user space failed(%d)\n",
+ func->num, ret);
+ ret = -ENOTTY;
+ break;
+ }
+ }
+ break;
+ case CSDIO_IOC_FUNCTION_SET_BLOCK_SIZE:
+ {
+ unsigned block_size;
+
+ ret = get_user(block_size, (unsigned __user *)arg);
+ if (ret) {
+ pr_err(TP_DEV_NAME"%d:SET_BLOCK_SIZE get data"
+ " from user space failed(%d)\n",
+ func->num, ret);
+ ret = -ENOTTY;
+ break;
+ }
+ pr_info(TP_DEV_NAME"%d:SET_BLOCK_SIZE=%d\n",
+ func->num, block_size);
+ sdio_claim_host(func);
+ ret = sdio_set_block_size(func, block_size);
+ if (!ret) {
+ port->m_block_size = block_size;
+ } else {
+ pr_err(TP_DEV_NAME"%d:SET_BLOCK_SIZE set block"
+ " size to %d failed (%d)\n",
+ func->num, block_size, ret);
+ ret = -ENOTTY;
+ break;
+ }
+ sdio_release_host(func);
+ }
+ break;
+ case CSDIO_IOC_SET_BLOCK_MODE:
+ {
+ pr_info(TP_DEV_NAME"%d:SET_BLOCK_MODE=%d\n",
+ func->num, descriptor->m_block_mode);
+ ret = get_user(descriptor->m_block_mode,
+ (unsigned char __user *)arg);
+ if (ret) {
+ pr_err(TP_DEV_NAME"%d:SET_BLOCK_MODE get data"
+ " from user space failed\n",
+ func->num);
+ ret = -ENOTTY;
+ break;
+ }
+ }
+ break;
+ case CSDIO_IOC_CMD52:
+ {
+ struct csdio_cmd52_ctrl_t cmd52ctrl;
+ int cmd52ret;
+
+ if (copy_from_user(&cmd52ctrl,
+ (const unsigned char __user *)arg,
+ sizeof(cmd52ctrl))) {
+ pr_err(TP_DEV_NAME"%d:IOC_CMD52 get data"
+ " from user space failed\n",
+ func->num);
+ ret = -ENOTTY;
+ break;
+ }
+ sdio_claim_host(func);
+ if (cmd52ctrl.m_write)
+ sdio_writeb(func, cmd52ctrl.m_data,
+ cmd52ctrl.m_address, &cmd52ret);
+ else
+ cmd52ctrl.m_data = sdio_readb(func,
+ cmd52ctrl.m_address, &cmd52ret);
+
+ cmd52ctrl.m_ret = cmd52ret;
+ sdio_release_host(func);
+ if (cmd52ctrl.m_ret)
+ pr_err(TP_DEV_NAME"%d:IOC_CMD52 failed (%d)\n",
+ func->num, cmd52ctrl.m_ret);
+
+ if (copy_to_user((unsigned char __user *)arg,
+ &cmd52ctrl,
+ sizeof(cmd52ctrl))) {
+ pr_err(TP_DEV_NAME"%d:IOC_CMD52 put data"
+ " to user space failed\n",
+ func->num);
+ ret = -ENOTTY;
+ break;
+ }
+ }
+ break;
+ case CSDIO_IOC_CMD53:
+ {
+ struct csdio_cmd53_ctrl_t csdio_cmd53_ctrl;
+
+ if (copy_from_user(&csdio_cmd53_ctrl,
+ (const char __user *)arg,
+ sizeof(csdio_cmd53_ctrl))) {
+ ret = -EPERM;
+ pr_err(TP_DEV_NAME"%d:"
+ "Get data from user space failed\n",
+ func->num);
+ break;
+ }
+ descriptor->m_block_mode =
+ csdio_cmd53_ctrl.m_block_mode;
+ descriptor->m_op_code = csdio_cmd53_ctrl.m_op_code;
+ descriptor->m_address = csdio_cmd53_ctrl.m_address;
+ }
+ break;
+ case CSDIO_IOC_CONNECT_ISR:
+ {
+ pr_info(CSDIO_DEV_NAME" SDIO_CONNECT_ISR"
+ " func=%d, csdio_sdio_irq=%x\n",
+ func->num, (unsigned int)csdio_sdio_irq);
+ sdio_claim_host(func);
+ ret = sdio_claim_irq(func, csdio_sdio_irq);
+ sdio_release_host(func);
+ if (ret) {
+ pr_err(CSDIO_DEV_NAME" SDIO_CONNECT_ISR"
+ " claim irq failed(%d)\n", ret);
+ } else {
+ /* update current irq mask for disable/enable */
+ g_csdio.m_current_irq_mask |= (1 << func->num);
+ }
+ }
+ break;
+ case CSDIO_IOC_DISCONNECT_ISR:
+ {
+ pr_info(CSDIO_DEV_NAME " SDIO_DISCONNECT_ISR func=%d\n",
+ func->num);
+ sdio_claim_host(func);
+ sdio_release_irq(func);
+ sdio_release_host(func);
+ /* update current irq mask for disable/enable */
+ g_csdio.m_current_irq_mask &= ~(1 << func->num);
+ }
+ break;
+ default: /* redundant, as cmd was checked against MAXNR */
+ pr_warning(TP_DEV_NAME"%d: Redundant IOCTL\n",
+ func->num);
+ ret = -ENOTTY;
+ }
+exit:
+ return ret;
+}
+
+static const struct file_operations csdio_transport_fops = {
+ .owner = THIS_MODULE,
+ .read = csdio_transport_read,
+ .write = csdio_transport_write,
+ .ioctl = csdio_transport_ioctl,
+ .open = csdio_transport_open,
+ .release = csdio_transport_release,
+};
+
+static void csdio_transport_cleanup(struct csdio_func_t *port)
+{
+ int devno = MKDEV(csdio_major, csdio_minor + port->m_func->num);
+ device_destroy(g_csdio.m_driver_class, devno);
+ port->m_device = NULL;
+ cdev_del(&port->m_cdev);
+}
+
+#if defined(CONFIG_DEVTMPFS)
+static inline int csdio_cdev_update_permissions(
+ const char *devname, int dev_minor)
+{
+ return 0;
+}
+#else
+static int csdio_cdev_update_permissions(
+ const char *devname, int dev_minor)
+{
+ int ret = 0;
+ mm_segment_t fs;
+ struct file *file;
+ struct inode *inode;
+ struct iattr newattrs;
+ int mode = CSDIO_DEV_PERMISSIONS;
+ char dev_file[64];
+
+ fs = get_fs();
+ set_fs(get_ds());
+
+ snprintf(dev_file, sizeof(dev_file), "/dev/%s%d",
+ devname, dev_minor);
+ file = filp_open(dev_file, O_RDWR, 0);
+ if (IS_ERR(file)) {
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ inode = file->f_path.dentry->d_inode;
+
+ mutex_lock(&inode->i_mutex);
+ newattrs.ia_mode =
+ (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
+ newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
+ ret = notify_change(file->f_path.dentry, &newattrs);
+ mutex_unlock(&inode->i_mutex);
+
+ filp_close(file, NULL);
+
+exit:
+ set_fs(fs);
+ return ret;
+}
+#endif
+
+static struct device *csdio_cdev_init(struct cdev *char_dev,
+ const struct file_operations *file_op, int dev_minor,
+ const char *devname, struct device *parent)
+{
+ int ret = 0;
+ struct device *new_device = NULL;
+ dev_t devno = MKDEV(csdio_major, dev_minor);
+
+ /* Initialize transport device */
+ cdev_init(char_dev, file_op);
+ char_dev->owner = THIS_MODULE;
+ char_dev->ops = file_op;
+ ret = cdev_add(char_dev, devno, 1);
+
+ /* Fail gracefully if need be */
+ if (ret) {
+ pr_warning("Error %d adding CSDIO char device '%s%d'",
+ ret, devname, dev_minor);
+ goto exit;
+ }
+ pr_info("'%s%d' char driver registered\n", devname, dev_minor);
+
+ /* create a /dev entry for transport drivers */
+ new_device = device_create(g_csdio.m_driver_class, parent, devno, NULL,
+ "%s%d", devname, dev_minor);
+ if (!new_device) {
+ pr_err("Can't create device node '/dev/%s%d'\n",
+ devname, dev_minor);
+ goto cleanup;
+ }
+ /* no irq attached */
+ g_csdio.m_current_irq_mask = 0;
+
+ if (csdio_cdev_update_permissions(devname, dev_minor)) {
+ pr_warning("%s%d: Unable to update access permissions of the"
+ " '/dev/%s%d'\n",
+ devname, dev_minor, devname, dev_minor);
+ }
+
+ pr_info("%s%d: Device node '/dev/%s%d' created successfully\n",
+ devname, dev_minor, devname, dev_minor);
+ goto exit;
+cleanup:
+ cdev_del(char_dev);
+exit:
+ return new_device;
+}
+
+/* Looks for first non empty function, returns NULL otherwise */
+static struct sdio_func *get_active_func(void)
+{
+ int i;
+
+ for (i = 0; i < CSDIO_NUM_OF_SDIO_FUNCTIONS; i++) {
+ if (g_csdio_func_table[i])
+ return g_csdio_func_table[i]->m_func;
+ }
+ return NULL;
+}
+
+static ssize_t
+show_vdd(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ if (NULL == g_csdio.m_host)
+ return snprintf(buf, PAGE_SIZE, "N/A\n");
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ g_csdio.m_host->ios.vdd);
+}
+
+static int
+set_vdd_helper(int value)
+{
+ struct mmc_ios *ios = NULL;
+
+ if (NULL == g_csdio.m_host) {
+ pr_err("%s0: Set VDD, no MMC host assigned\n", CSDIO_DEV_NAME);
+ return -ENXIO;
+ }
+
+ mmc_claim_host(g_csdio.m_host);
+ ios = &g_csdio.m_host->ios;
+ ios->vdd = value;
+ g_csdio.m_host->ops->set_ios(g_csdio.m_host, ios);
+ mmc_release_host(g_csdio.m_host);
+ return 0;
+}
+
+static ssize_t
+set_vdd(struct device *dev, struct device_attribute *att,
+ const char *buf, size_t count)
+{
+ int value = 0;
+
+ sscanf(buf, "%d", &value);
+ if (set_vdd_helper(value))
+ return -ENXIO;
+ return count;
+}
+
+static DEVICE_ATTR(vdd, S_IRUGO | S_IWUSR,
+ show_vdd, set_vdd);
+
+static struct attribute *dev_attrs[] = {
+ &dev_attr_vdd.attr,
+ NULL,
+};
+
+static struct attribute_group dev_attr_grp = {
+ .attrs = dev_attrs,
+};
+
+/*
+ * The ioctl() implementation for control device
+ */
+static int csdio_ctrl_ioctl(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ int ret = 0;
+
+ pr_info("CSDIO ctrl ioctl.\n");
+
+ /* extract the type and number bitfields
+ sanity check: return ENOTTY (inappropriate ioctl) before
+ access_ok()
+ */
+ if ((_IOC_TYPE(cmd) != CSDIO_IOC_MAGIC) ||
+ (_IOC_NR(cmd) > CSDIO_IOC_MAXNR)) {
+ pr_err(CSDIO_DEV_NAME "Wrong ioctl command parameters\n");
+ ret = -ENOTTY;
+ goto exit;
+ }
+
+ /* the direction is a bitmask, and VERIFY_WRITE catches R/W
+ transfers. `Type' is user-oriented, while access_ok is
+ kernel-oriented, so the concept of "read" and "write" is reversed
+ */
+ if (_IOC_DIR(cmd) & _IOC_READ) {
+ err = !access_ok(VERIFY_WRITE, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ } else {
+ if (_IOC_DIR(cmd) & _IOC_WRITE)
+ err = !access_ok(VERIFY_READ, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ }
+ if (err) {
+ pr_err(CSDIO_DEV_NAME "Wrong ioctl access direction\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ switch (cmd) {
+ case CSDIO_IOC_ENABLE_HIGHSPEED_MODE:
+ pr_info(CSDIO_DEV_NAME" ENABLE_HIGHSPEED_MODE\n");
+ break;
+ case CSDIO_IOC_SET_DATA_TRANSFER_CLOCKS:
+ {
+ struct mmc_host *host = g_csdio.m_host;
+ struct mmc_ios *ios = NULL;
+
+ if (NULL == host) {
+ pr_err("%s0: "
+ "CSDIO_IOC_SET_DATA_TRANSFER_CLOCKS,"
+ " no MMC host assigned\n",
+ CSDIO_DEV_NAME);
+ ret = -EFAULT;
+ goto exit;
+ }
+ ios = &host->ios;
+
+ mmc_claim_host(host);
+ ret = get_user(host->ios.clock,
+ (unsigned int __user *)arg);
+ if (ret) {
+ pr_err(CSDIO_DEV_NAME
+ " get data from user space failed\n");
+ } else {
+ pr_err(CSDIO_DEV_NAME
+ "SET_DATA_TRANSFER_CLOCKS(%d-%d)(%d)\n",
+ host->f_min, host->f_max,
+ host->ios.clock);
+ host->ops->set_ios(host, ios);
+ }
+ mmc_release_host(host);
+ }
+ break;
+ case CSDIO_IOC_ENABLE_ISR:
+ {
+ int ret;
+ unsigned char reg;
+ struct sdio_func *func = get_active_func();
+
+ if (!func) {
+ pr_err(CSDIO_DEV_NAME " CSDIO_IOC_ENABLE_ISR"
+ " no active sdio function\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+ pr_info(CSDIO_DEV_NAME
+ " CSDIO_IOC_ENABLE_ISR func=%d\n",
+ func->num);
+ reg = g_csdio.m_current_irq_mask | 1;
+
+ sdio_claim_host(func);
+ sdio_f0_writeb(func, reg, SDIO_CCCR_IENx, &ret);
+ sdio_release_host(func);
+ if (ret) {
+ pr_err(CSDIO_DEV_NAME
+ " Can't sdio_f0_writeb (%d)\n",
+ ret);
+ goto exit;
+ }
+ }
+ break;
+ case CSDIO_IOC_DISABLE_ISR:
+ {
+ int ret;
+ struct sdio_func *func = get_active_func();
+ if (!func) {
+ pr_err(CSDIO_DEV_NAME " CSDIO_IOC_ENABLE_ISR"
+ " no active sdio function\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+ pr_info(CSDIO_DEV_NAME
+ " CSDIO_IOC_DISABLE_ISR func=%p\n",
+ func);
+
+ sdio_claim_host(func);
+ ret = disable_sdio_client_isr(func);
+ sdio_release_host(func);
+ if (ret) {
+ pr_err("%s0: Can't disable client isr (%d)\n",
+ CSDIO_DEV_NAME, ret);
+ goto exit;
+ }
+ }
+ break;
+ case CSDIO_IOC_SET_VDD:
+ {
+ unsigned int vdd = 0;
+
+ ret = get_user(vdd, (unsigned int __user *)arg);
+ if (ret) {
+ pr_err("%s0: CSDIO_IOC_SET_VDD,"
+ " get data from user space failed\n",
+ CSDIO_DEV_NAME);
+ goto exit;
+ }
+ pr_info(CSDIO_DEV_NAME" CSDIO_IOC_SET_VDD - %d\n", vdd);
+
+ ret = set_vdd_helper(vdd);
+ if (ret)
+ goto exit;
+ }
+ break;
+ case CSDIO_IOC_GET_VDD:
+ {
+ if (NULL == g_csdio.m_host) {
+ pr_err("%s0: CSDIO_IOC_GET_VDD,"
+ " no MMC host assigned\n",
+ CSDIO_DEV_NAME);
+ ret = -EFAULT;
+ goto exit;
+ }
+ ret = put_user(g_csdio.m_host->ios.vdd,
+ (unsigned short __user *)arg);
+ if (ret) {
+ pr_err("%s0: CSDIO_IOC_GET_VDD, put data"
+ " to user space failed\n",
+ CSDIO_DEV_NAME);
+ goto exit;
+ }
+ }
+ break;
+ default: /* redundant, as cmd was checked against MAXNR */
+ pr_warning(CSDIO_DEV_NAME" Redundant IOCTL\n");
+ ret = -ENOTTY;
+ }
+exit:
+ return ret;
+}
+
+static int csdio_ctrl_fasync(int fd, struct file *filp, int mode)
+{
+ pr_info(CSDIO_DEV_NAME
+ " csdio_ctrl_fasync: fd=%d, filp=%p, mode=%d\n",
+ fd, filp, mode);
+ return fasync_helper(fd, filp, mode, &g_csdio.m_async_queue);
+}
+
+/*
+ * Open and close
+ */
+static int csdio_ctrl_open(struct inode *inode, struct file *filp)
+{
+ int ret = 0;
+ struct csdio_t *csdio_ctrl_drv = NULL; /* device information */
+
+ pr_info("CSDIO ctrl open.\n");
+ csdio_ctrl_drv = container_of(inode->i_cdev, struct csdio_t, m_cdev);
+ filp->private_data = csdio_ctrl_drv; /* for other methods */
+ return ret;
+}
+
+static int csdio_ctrl_release(struct inode *inode, struct file *filp)
+{
+ pr_info("CSDIO ctrl release.\n");
+ /* remove this filp from the asynchronously notified filp's */
+ csdio_ctrl_fasync(-1, filp, 0);
+ return 0;
+}
+
+static const struct file_operations csdio_ctrl_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = csdio_ctrl_ioctl,
+ .open = csdio_ctrl_open,
+ .release = csdio_ctrl_release,
+ .fasync = csdio_ctrl_fasync,
+};
+
+static int csdio_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ struct csdio_func_t *port;
+ int ret = 0;
+ struct mmc_host *host = func->card->host;
+
+ if (NULL != g_csdio.m_host && g_csdio.m_host != host) {
+ pr_info("%s: Device is on unexpected host\n",
+ CSDIO_DEV_NAME);
+ ret = -ENODEV;
+ goto exit;
+ }
+
+ /* enforce single instance policy */
+ if (g_csdio_func_table[func->num-1]) {
+ pr_err("%s - only single SDIO device supported",
+ sdio_func_id(func));
+ ret = -EEXIST;
+ goto exit;
+ }
+
+ port = kzalloc(sizeof(struct csdio_func_t), GFP_KERNEL);
+ if (!port) {
+ pr_err("Can't allocate memory\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ /* initialize SDIO side */
+ port->m_func = func;
+ sdio_set_drvdata(func, port);
+
+ pr_info("%s - SDIO device found. Function %d\n",
+ sdio_func_id(func), func->num);
+
+ port->m_device = csdio_cdev_init(&port->m_cdev, &csdio_transport_fops,
+ csdio_minor + port->m_func->num,
+ TP_DEV_NAME, &port->m_func->dev);
+
+ /* create appropriate char device */
+ if (!port->m_device)
+ goto free;
+
+ if (0 == g_csdio.m_num_of_func && NULL == host_name)
+ g_csdio.m_host = host;
+ g_csdio.m_num_of_func++;
+ g_csdio_func_table[func->num-1] = port;
+ port->m_enabled = TRUE;
+ goto exit;
+free:
+ kfree(port);
+exit:
+ return ret;
+}
+
+static void csdio_remove(struct sdio_func *func)
+{
+ struct csdio_func_t *port = sdio_get_drvdata(func);
+
+ csdio_transport_cleanup(port);
+ sdio_claim_host(func);
+ sdio_release_irq(func);
+ sdio_disable_func(func);
+ sdio_release_host(func);
+ kfree(port);
+ g_csdio_func_table[func->num-1] = NULL;
+ g_csdio.m_num_of_func--;
+ if (0 == g_csdio.m_num_of_func && NULL == host_name)
+ g_csdio.m_host = NULL;
+ pr_info("%s%d: Device removed (%s). Function %d\n",
+ CSDIO_DEV_NAME, func->num, sdio_func_id(func), func->num);
+}
+
+/* CONFIG_CSDIO_VENDOR_ID and CONFIG_CSDIO_DEVICE_ID are defined in Kconfig.
+ * Use kernel configuration to change the values or overwrite them through
+ * module parameters */
+static struct sdio_device_id csdio_ids[] = {
+ { SDIO_DEVICE(CONFIG_CSDIO_VENDOR_ID, CONFIG_CSDIO_DEVICE_ID) },
+ { /* end: all zeroes */},
+};
+
+MODULE_DEVICE_TABLE(sdio, csdio_ids);
+
+static struct sdio_driver csdio_driver = {
+ .probe = csdio_probe,
+ .remove = csdio_remove,
+ .name = "csdio",
+ .id_table = csdio_ids,
+};
+
+static void __exit csdio_exit(void)
+{
+ dev_t devno = MKDEV(csdio_major, csdio_minor);
+
+ sdio_unregister_driver(&csdio_driver);
+ sysfs_remove_group(&g_csdio.m_device->kobj, &dev_attr_grp);
+ kfree(g_sdio_buffer);
+ device_destroy(g_csdio.m_driver_class, devno);
+ cdev_del(&g_csdio.m_cdev);
+ class_destroy(g_csdio.m_driver_class);
+ unregister_chrdev_region(devno, csdio_transport_nr_devs);
+ pr_info("%s: Exit driver module\n", CSDIO_DEV_NAME);
+}
+
+static char *csdio_devnode(struct device *dev, mode_t *mode)
+{
+ *mode = CSDIO_DEV_PERMISSIONS;
+ return NULL;
+}
+
+static int __init csdio_init(void)
+{
+ int ret = 0;
+ dev_t devno = 0;
+
+ pr_info("Init CSDIO driver module.\n");
+
+ /* Get a range of minor numbers to work with, asking for a dynamic */
+ /* major unless directed otherwise at load time. */
+ if (csdio_major) {
+ devno = MKDEV(csdio_major, csdio_minor);
+ ret = register_chrdev_region(devno, csdio_transport_nr_devs,
+ CSDIO_DEV_NAME);
+ } else {
+ ret = alloc_chrdev_region(&devno, csdio_minor,
+ csdio_transport_nr_devs, CSDIO_DEV_NAME);
+ csdio_major = MAJOR(devno);
+ }
+ if (ret < 0) {
+ pr_err("CSDIO: can't get major %d\n", csdio_major);
+ goto exit;
+ }
+ pr_info("CSDIO char driver major number is %d\n", csdio_major);
+
+ /* kernel module got parameters: overwrite vendor and device id's */
+ if ((csdio_vendor_id != 0) && (csdio_device_id != 0)) {
+ csdio_ids[0].vendor = (u16)csdio_vendor_id;
+ csdio_ids[0].device = (u16)csdio_device_id;
+ }
+
+ /* prepare create /dev/... instance */
+ g_csdio.m_driver_class = class_create(THIS_MODULE, CSDIO_DEV_NAME);
+ if (IS_ERR(g_csdio.m_driver_class)) {
+ ret = -ENOMEM;
+ pr_err(CSDIO_DEV_NAME " class_create failed\n");
+ goto unregister_region;
+ }
+ g_csdio.m_driver_class->devnode = csdio_devnode;
+
+ /* create CSDIO ctrl driver */
+ g_csdio.m_device = csdio_cdev_init(&g_csdio.m_cdev,
+ &csdio_ctrl_fops, csdio_minor, CSDIO_DEV_NAME, NULL);
+ if (!g_csdio.m_device) {
+ pr_err("%s: Unable to create ctrl driver\n",
+ CSDIO_DEV_NAME);
+ goto destroy_class;
+ }
+
+ g_sdio_buffer = kmalloc(CSDIO_SDIO_BUFFER_SIZE, GFP_KERNEL);
+ if (!g_sdio_buffer) {
+ pr_err("Unable to allocate %d bytes\n", CSDIO_SDIO_BUFFER_SIZE);
+ ret = -ENOMEM;
+ goto destroy_cdev;
+ }
+
+ ret = sysfs_create_group(&g_csdio.m_device->kobj, &dev_attr_grp);
+ if (ret) {
+ pr_err("%s: Unable to create device attribute\n",
+ CSDIO_DEV_NAME);
+ goto free_sdio_buff;
+ }
+
+ g_csdio.m_num_of_func = 0;
+ g_csdio.m_host = NULL;
+
+ if (NULL != host_name) {
+ struct device *dev = bus_find_device_by_name(&platform_bus_type,
+ NULL, host_name);
+ if (NULL != dev) {
+ g_csdio.m_host = dev_get_drvdata(dev);
+ } else {
+ pr_err("%s: Host '%s' doesn't exist!\n", CSDIO_DEV_NAME,
+ host_name);
+ }
+ }
+
+ pr_info("%s: Match with VendorId=0x%X, DeviceId=0x%X, Host = %s\n",
+ CSDIO_DEV_NAME, csdio_device_id, csdio_vendor_id,
+ (NULL == host_name) ? "Any" : host_name);
+
+ /* register sdio driver */
+ ret = sdio_register_driver(&csdio_driver);
+ if (ret) {
+ pr_err("%s: Unable to register as SDIO driver\n",
+ CSDIO_DEV_NAME);
+ goto remove_group;
+ }
+
+ goto exit;
+
+remove_group:
+ sysfs_remove_group(&g_csdio.m_device->kobj, &dev_attr_grp);
+free_sdio_buff:
+ kfree(g_sdio_buffer);
+destroy_cdev:
+ cdev_del(&g_csdio.m_cdev);
+destroy_class:
+ class_destroy(g_csdio.m_driver_class);
+unregister_region:
+ unregister_chrdev_region(devno, csdio_transport_nr_devs);
+exit:
+ return ret;
+}
+module_param(csdio_vendor_id, uint, S_IRUGO);
+module_param(csdio_device_id, uint, S_IRUGO);
+module_param(host_name, charp, S_IRUGO);
+
+module_init(csdio_init);
+module_exit(csdio_exit);
+
+MODULE_AUTHOR("Code Aurora Forum");
+MODULE_DESCRIPTION("CSDIO device driver version " VERSION);
+MODULE_VERSION(VERSION);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/char/dcc_tty.c b/drivers/char/dcc_tty.c
index a787acc..7643f50 100644
--- a/drivers/char/dcc_tty.c
+++ b/drivers/char/dcc_tty.c
@@ -21,12 +21,13 @@
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
+#include <linux/spinlock.h>
MODULE_DESCRIPTION("DCC TTY Driver");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");
-static spinlock_t g_dcc_tty_lock = SPIN_LOCK_UNLOCKED;
+static spinlock_t g_dcc_tty_lock = __SPIN_LOCK_UNLOCKED(g_dcc_tty_lock);
static struct hrtimer g_dcc_timer;
static char g_dcc_buffer[16];
static int g_dcc_buffer_head;
diff --git a/drivers/char/diag/Kconfig b/drivers/char/diag/Kconfig
new file mode 100644
index 0000000..eb0b21e
--- /dev/null
+++ b/drivers/char/diag/Kconfig
@@ -0,0 +1,31 @@
+menu "Diag Support"
+
+config DIAG_CHAR
+ tristate "char driver interface and diag forwarding to/from modem"
+ default m
+ depends on USB_G_ANDROID || USB_FUNCTION_DIAG || USB_QCOM_MAEMO
+ depends on ARCH_MSM
+ help
+ Char driver interface for diag user space and diag-forwarding to modem ARM and back.
+ This enables diagchar for maemo usb gadget or android usb gadget based on config selected.
+endmenu
+
+menu "DIAG traffic over USB"
+
+config DIAG_OVER_USB
+ bool "Enable DIAG traffic to go over USB"
+ depends on ARCH_MSM
+ default y
+ help
+ This feature helps segregate code required for DIAG traffic to go over USB.
+endmenu
+
+menu "SDIO support for DIAG"
+
+config DIAG_SDIO_PIPE
+ depends on MSM_SDIO_AL
+ default y
+ bool "Enable 9K DIAG traffic over SDIO"
+ help
+ SDIO Transport Layer for DIAG Router
+endmenu
diff --git a/drivers/char/diag/Makefile b/drivers/char/diag/Makefile
new file mode 100644
index 0000000..52ab2b9
--- /dev/null
+++ b/drivers/char/diag/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_DIAG_CHAR) := diagchar.o
+obj-$(CONFIG_DIAG_SDIO_PIPE) += diagfwd_sdio.o
+diagchar-objs := diagchar_core.o diagchar_hdlc.o diagfwd.o diagmem.o diagfwd_cntl.o
diff --git a/drivers/char/diag/diagchar.h b/drivers/char/diag/diagchar.h
new file mode 100644
index 0000000..6041954
--- /dev/null
+++ b/drivers/char/diag/diagchar.h
@@ -0,0 +1,222 @@
+/* Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef DIAGCHAR_H
+#define DIAGCHAR_H
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mempool.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <mach/msm_smd.h>
+#include <asm/atomic.h>
+#include <asm/mach-types.h>
+/* Size of the USB buffers used for read and write*/
+#define USB_MAX_OUT_BUF 4096
+#define IN_BUF_SIZE 16384
+#define MAX_IN_BUF_SIZE 32768
+#define MAX_SYNC_OBJ_NAME_SIZE 32
+/* Size of the buffer used for deframing a packet
+ reveived from the PC tool*/
+#define HDLC_MAX 4096
+#define HDLC_OUT_BUF_SIZE 8192
+#define POOL_TYPE_COPY 1
+#define POOL_TYPE_HDLC 2
+#define POOL_TYPE_WRITE_STRUCT 4
+#define POOL_TYPE_ALL 7
+#define MODEM_DATA 1
+#define QDSP_DATA 2
+#define APPS_DATA 3
+#define SDIO_DATA 4
+#define WCNSS_DATA 5
+#define MODEM_PROC 0
+#define APPS_PROC 1
+#define QDSP_PROC 2
+#define WCNSS_PROC 3
+#define MSG_MASK_SIZE 8000
+#define LOG_MASK_SIZE 8000
+#define EVENT_MASK_SIZE 1000
+#define PKT_SIZE 4096
+#define MAX_EQUIP_ID 12
+
+/* Maximum number of pkt reg supported at initialization*/
+extern unsigned int diag_max_registration;
+extern unsigned int diag_threshold_registration;
+
+#define APPEND_DEBUG(ch) \
+do { \
+ diag_debug_buf[diag_debug_buf_idx] = ch; \
+ (diag_debug_buf_idx < 1023) ? \
+ (diag_debug_buf_idx++) : (diag_debug_buf_idx = 0); \
+} while (0)
+
+struct diag_master_table {
+ uint16_t cmd_code;
+ uint16_t subsys_id;
+ uint32_t client_id;
+ uint16_t cmd_code_lo;
+ uint16_t cmd_code_hi;
+ int process_id;
+};
+
+struct bindpkt_params_per_process {
+ /* Name of the synchronization object associated with this proc */
+ char sync_obj_name[MAX_SYNC_OBJ_NAME_SIZE];
+ uint32_t count; /* Number of entries in this bind */
+ struct bindpkt_params *params; /* first bind params */
+};
+
+struct bindpkt_params {
+ uint16_t cmd_code;
+ uint16_t subsys_id;
+ uint16_t cmd_code_lo;
+ uint16_t cmd_code_hi;
+ /* For Central Routing, used to store Processor number */
+ uint16_t proc_id;
+ uint32_t event_id;
+ uint32_t log_code;
+ /* For Central Routing, used to store SMD channel pointer */
+ uint32_t client_id;
+};
+
+struct diag_write_device {
+ void *buf;
+ int length;
+};
+
+struct diag_client_map {
+ char name[20];
+ int pid;
+};
+
+/* This structure is defined in USB header file */
+#ifndef CONFIG_DIAG_OVER_USB
+struct diag_request {
+ char *buf;
+ int length;
+ int actual;
+ int status;
+ void *context;
+};
+#endif
+
+struct diagchar_dev {
+
+ /* State for the char driver */
+ unsigned int major;
+ unsigned int minor_start;
+ int num;
+ struct cdev *cdev;
+ char *name;
+ int dropped_count;
+ struct class *diagchar_class;
+ int ref_count;
+ struct mutex diagchar_mutex;
+ wait_queue_head_t wait_q;
+ struct diag_client_map *client_map;
+ int *data_ready;
+ int num_clients;
+ struct diag_write_device *buf_tbl;
+
+ /* Memory pool parameters */
+ unsigned int itemsize;
+ unsigned int poolsize;
+ unsigned int itemsize_hdlc;
+ unsigned int poolsize_hdlc;
+ unsigned int itemsize_write_struct;
+ unsigned int poolsize_write_struct;
+ unsigned int debug_flag;
+ /* State for the mempool for the char driver */
+ mempool_t *diagpool;
+ mempool_t *diag_hdlc_pool;
+ mempool_t *diag_write_struct_pool;
+ struct mutex diagmem_mutex;
+ int count;
+ int count_hdlc_pool;
+ int count_write_struct_pool;
+ int used;
+
+ /* State for diag forwarding */
+ unsigned char *buf_in_1;
+ unsigned char *buf_in_2;
+ unsigned char *buf_in_cntl;
+ unsigned char *buf_in_qdsp_1;
+ unsigned char *buf_in_qdsp_2;
+ unsigned char *buf_in_qdsp_cntl;
+ unsigned char *buf_in_wcnss;
+ unsigned char *buf_in_wcnss_cntl;
+ unsigned char *usb_buf_out;
+ unsigned char *apps_rsp_buf;
+ smd_channel_t *ch;
+ smd_channel_t *ch_cntl;
+ smd_channel_t *chqdsp;
+ smd_channel_t *chqdsp_cntl;
+ smd_channel_t *ch_wcnss;
+ smd_channel_t *ch_wcnss_cntl;
+ int in_busy_1;
+ int in_busy_2;
+ int in_busy_qdsp_1;
+ int in_busy_qdsp_2;
+ int in_busy_wcnss;
+ int read_len_legacy;
+ unsigned char *hdlc_buf;
+ unsigned hdlc_count;
+ unsigned hdlc_escape;
+#ifdef CONFIG_DIAG_OVER_USB
+ int usb_connected;
+ struct usb_diag_ch *legacy_ch;
+ struct work_struct diag_proc_hdlc_work;
+ struct work_struct diag_read_work;
+#endif
+ struct workqueue_struct *diag_wq;
+ struct work_struct diag_drain_work;
+ struct work_struct diag_read_smd_work;
+ struct work_struct diag_read_smd_cntl_work;
+ struct work_struct diag_read_smd_qdsp_work;
+ struct work_struct diag_read_smd_qdsp_cntl_work;
+ struct work_struct diag_read_smd_wcnss_work;
+ struct work_struct diag_read_smd_wcnss_cntl_work;
+ uint8_t *msg_masks;
+ uint8_t *log_masks;
+ int log_masks_length;
+ uint8_t *event_masks;
+ struct diag_master_table *table;
+ uint8_t *pkt_buf;
+ int pkt_length;
+ struct diag_request *write_ptr_1;
+ struct diag_request *write_ptr_2;
+ struct diag_request *usb_read_ptr;
+ struct diag_request *write_ptr_svc;
+ struct diag_request *write_ptr_qdsp_1;
+ struct diag_request *write_ptr_qdsp_2;
+ struct diag_request *write_ptr_wcnss;
+ int logging_mode;
+ int logging_process_id;
+#ifdef CONFIG_DIAG_SDIO_PIPE
+ unsigned char *buf_in_sdio;
+ unsigned char *usb_buf_mdm_out;
+ struct sdio_channel *sdio_ch;
+ int read_len_mdm;
+ int in_busy_sdio;
+ struct usb_diag_ch *mdm_ch;
+ struct work_struct diag_read_mdm_work;
+ struct workqueue_struct *diag_sdio_wq;
+ struct work_struct diag_read_sdio_work;
+ struct work_struct diag_remove_sdio_work;
+ struct diag_request *usb_read_mdm_ptr;
+ struct diag_request *write_ptr_mdm;
+#endif
+};
+
+extern struct diagchar_dev *driver;
+#endif
diff --git a/drivers/char/diag/diagchar_core.c b/drivers/char/diag/diagchar_core.c
new file mode 100644
index 0000000..c9a9d57
--- /dev/null
+++ b/drivers/char/diag/diagchar_core.c
@@ -0,0 +1,982 @@
+/* Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/uaccess.h>
+#include <linux/diagchar.h>
+#include <linux/sched.h>
+#ifdef CONFIG_DIAG_OVER_USB
+#include <mach/usbdiag.h>
+#endif
+#include <asm/current.h>
+#include "diagchar_hdlc.h"
+#include "diagmem.h"
+#include "diagchar.h"
+#include "diagfwd.h"
+#include "diagfwd_cntl.h"
+#ifdef CONFIG_DIAG_SDIO_PIPE
+#include "diagfwd_sdio.h"
+#endif
+#include <linux/timer.h>
+
+MODULE_DESCRIPTION("Diag Char Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.0");
+
+#define INIT 1
+#define EXIT -1
+struct diagchar_dev *driver;
+struct diagchar_priv {
+ int pid;
+};
+/* The following variables can be specified by module options */
+ /* for copy buffer */
+static unsigned int itemsize = 2048; /*Size of item in the mempool */
+static unsigned int poolsize = 10; /*Number of items in the mempool */
+/* for hdlc buffer */
+static unsigned int itemsize_hdlc = 8192; /*Size of item in the mempool */
+static unsigned int poolsize_hdlc = 8; /*Number of items in the mempool */
+/* for write structure buffer */
+static unsigned int itemsize_write_struct = 20; /*Size of item in the mempool */
+static unsigned int poolsize_write_struct = 8; /* Num of items in the mempool */
+/* This is the max number of user-space clients supported at initialization*/
+static unsigned int max_clients = 15;
+static unsigned int threshold_client_limit = 30;
+/* This is the maximum number of pkt registrations supported at initialization*/
+unsigned int diag_max_registration = 500;
+unsigned int diag_threshold_registration = 650;
+
+/* Timer variables */
+static struct timer_list drain_timer;
+static int timer_in_progress;
+void *buf_hdlc;
+module_param(itemsize, uint, 0);
+module_param(poolsize, uint, 0);
+module_param(max_clients, uint, 0);
+
+/* delayed_rsp_id 0 represents no delay in the response. Any other number
+ means that the diag packet has a delayed response. */
+static uint16_t delayed_rsp_id = 1;
+#define DIAGPKT_MAX_DELAYED_RSP 0xFFFF
+/* This macro gets the next delayed respose id. Once it reaches
+ DIAGPKT_MAX_DELAYED_RSP, it stays at DIAGPKT_MAX_DELAYED_RSP */
+
+#define DIAGPKT_NEXT_DELAYED_RSP_ID(x) \
+((x < DIAGPKT_MAX_DELAYED_RSP) ? x++ : DIAGPKT_MAX_DELAYED_RSP)
+
+#define COPY_USER_SPACE_OR_EXIT(buf, data, length) \
+do { \
+ if ((count < ret+length) || (copy_to_user(buf, \
+ (void *)&data, length))) { \
+ ret = -EFAULT; \
+ goto exit; \
+ } \
+ ret += length; \
+} while (0)
+
+static void drain_timer_func(unsigned long data)
+{
+ queue_work(driver->diag_wq , &(driver->diag_drain_work));
+}
+
+void diag_drain_work_fn(struct work_struct *work)
+{
+ int err = 0;
+ timer_in_progress = 0;
+
+ mutex_lock(&driver->diagchar_mutex);
+ if (buf_hdlc) {
+ err = diag_device_write(buf_hdlc, APPS_DATA, NULL);
+ if (err) {
+ /*Free the buffer right away if write failed */
+ diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC);
+ diagmem_free(driver, (unsigned char *)driver->
+ write_ptr_svc, POOL_TYPE_WRITE_STRUCT);
+ }
+ buf_hdlc = NULL;
+#ifdef DIAG_DEBUG
+ pr_debug("diag: Number of bytes written "
+ "from timer is %d ", driver->used);
+#endif
+ driver->used = 0;
+ }
+ mutex_unlock(&driver->diagchar_mutex);
+}
+
+void diag_read_smd_work_fn(struct work_struct *work)
+{
+ __diag_smd_send_req();
+}
+
+void diag_read_smd_qdsp_work_fn(struct work_struct *work)
+{
+ __diag_smd_qdsp_send_req();
+}
+
+void diag_read_smd_wcnss_work_fn(struct work_struct *work)
+{
+ __diag_smd_wcnss_send_req();
+}
+
+void diag_add_client(int i, struct file *file)
+{
+ struct diagchar_priv *diagpriv_data;
+
+ driver->client_map[i].pid = current->tgid;
+ diagpriv_data = kmalloc(sizeof(struct diagchar_priv),
+ GFP_KERNEL);
+ if (diagpriv_data)
+ diagpriv_data->pid = current->tgid;
+ file->private_data = diagpriv_data;
+ strncpy(driver->client_map[i].name, current->comm, 20);
+ driver->client_map[i].name[19] = '\0';
+}
+
+static int diagchar_open(struct inode *inode, struct file *file)
+{
+ int i = 0;
+ void *temp;
+
+ if (driver) {
+ mutex_lock(&driver->diagchar_mutex);
+
+ for (i = 0; i < driver->num_clients; i++)
+ if (driver->client_map[i].pid == 0)
+ break;
+
+ if (i < driver->num_clients) {
+ diag_add_client(i, file);
+ } else {
+ if (i < threshold_client_limit) {
+ driver->num_clients++;
+ temp = krealloc(driver->client_map
+ , (driver->num_clients) * sizeof(struct
+ diag_client_map), GFP_KERNEL);
+ if (!temp)
+ goto fail;
+ else
+ driver->client_map = temp;
+ temp = krealloc(driver->data_ready
+ , (driver->num_clients) * sizeof(int),
+ GFP_KERNEL);
+ if (!temp)
+ goto fail;
+ else
+ driver->data_ready = temp;
+ diag_add_client(i, file);
+ } else {
+ mutex_unlock(&driver->diagchar_mutex);
+ pr_alert("Max client limit for DIAG reached\n");
+ pr_info("Cannot open handle %s"
+ " %d", current->comm, current->tgid);
+ for (i = 0; i < driver->num_clients; i++)
+ pr_debug("%d) %s PID=%d", i, driver->
+ client_map[i].name,
+ driver->client_map[i].pid);
+ return -ENOMEM;
+ }
+ }
+ driver->data_ready[i] |= MSG_MASKS_TYPE;
+ driver->data_ready[i] |= EVENT_MASKS_TYPE;
+ driver->data_ready[i] |= LOG_MASKS_TYPE;
+
+ if (driver->ref_count == 0)
+ diagmem_init(driver);
+ driver->ref_count++;
+ mutex_unlock(&driver->diagchar_mutex);
+ return 0;
+ }
+ return -ENOMEM;
+
+fail:
+ mutex_unlock(&driver->diagchar_mutex);
+ driver->num_clients--;
+ pr_alert("diag: Insufficient memory for new client");
+ return -ENOMEM;
+}
+
+static int diagchar_close(struct inode *inode, struct file *file)
+{
+ int i = 0;
+ struct diagchar_priv *diagpriv_data = file->private_data;
+
+ if (!(file->private_data)) {
+ pr_alert("diag: Invalid file pointer");
+ return -ENOMEM;
+ }
+
+#ifdef CONFIG_DIAG_OVER_USB
+ /* If the SD logging process exits, change logging to USB mode */
+ if (driver->logging_process_id == current->tgid) {
+ driver->logging_mode = USB_MODE;
+ diagfwd_connect();
+ }
+#endif /* DIAG over USB */
+ /* Delete the pkt response table entry for the exiting process */
+ for (i = 0; i < diag_max_registration; i++)
+ if (driver->table[i].process_id == current->tgid)
+ driver->table[i].process_id = 0;
+
+ if (driver) {
+ mutex_lock(&driver->diagchar_mutex);
+ driver->ref_count--;
+ /* On Client exit, try to destroy all 3 pools */
+ diagmem_exit(driver, POOL_TYPE_COPY);
+ diagmem_exit(driver, POOL_TYPE_HDLC);
+ diagmem_exit(driver, POOL_TYPE_WRITE_STRUCT);
+ for (i = 0; i < driver->num_clients; i++) {
+ if (NULL != diagpriv_data && diagpriv_data->pid ==
+ driver->client_map[i].pid) {
+ driver->client_map[i].pid = 0;
+ kfree(diagpriv_data);
+ diagpriv_data = NULL;
+ break;
+ }
+ }
+ mutex_unlock(&driver->diagchar_mutex);
+ return 0;
+ }
+ return -ENOMEM;
+}
+
+void diag_fill_reg_table(int j, struct bindpkt_params *params,
+ int *success, int *count_entries)
+{
+ *success = 1;
+ driver->table[j].cmd_code = params->cmd_code;
+ driver->table[j].subsys_id = params->subsys_id;
+ driver->table[j].cmd_code_lo = params->cmd_code_lo;
+ driver->table[j].cmd_code_hi = params->cmd_code_hi;
+ if (params->proc_id == APPS_PROC) {
+ driver->table[j].process_id = current->tgid;
+ driver->table[j].client_id = APPS_PROC;
+ } else {
+ driver->table[j].process_id = NON_APPS_PROC;
+ driver->table[j].client_id = params->client_id;
+ }
+ (*count_entries)++;
+}
+
+long diagchar_ioctl(struct file *filp,
+ unsigned int iocmd, unsigned long ioarg)
+{
+ int i, j, count_entries = 0, temp;
+ int success = -1;
+ void *temp_buf;
+
+ if (iocmd == DIAG_IOCTL_COMMAND_REG) {
+ struct bindpkt_params_per_process *pkt_params =
+ (struct bindpkt_params_per_process *) ioarg;
+ mutex_lock(&driver->diagchar_mutex);
+ for (i = 0; i < diag_max_registration; i++) {
+ if (driver->table[i].process_id == 0) {
+ diag_fill_reg_table(i, pkt_params->params,
+ &success, &count_entries);
+ if (pkt_params->count > count_entries) {
+ pkt_params->params++;
+ } else {
+ mutex_unlock(&driver->diagchar_mutex);
+ return success;
+ }
+ }
+ }
+ if (i < diag_threshold_registration) {
+ /* Increase table size by amount required */
+ diag_max_registration += pkt_params->count -
+ count_entries;
+ /* Make sure size doesnt go beyond threshold */
+ if (diag_max_registration > diag_threshold_registration)
+ diag_max_registration =
+ diag_threshold_registration;
+ temp_buf = krealloc(driver->table,
+ diag_max_registration*sizeof(struct
+ diag_master_table), GFP_KERNEL);
+ if (!temp_buf) {
+ diag_max_registration -= pkt_params->count -
+ count_entries;
+ pr_alert("diag: Insufficient memory for reg.");
+ mutex_unlock(&driver->diagchar_mutex);
+ return 0;
+ } else {
+ driver->table = temp_buf;
+ }
+ for (j = i; j < diag_max_registration; j++) {
+ diag_fill_reg_table(j, pkt_params->params,
+ &success, &count_entries);
+ if (pkt_params->count > count_entries) {
+ pkt_params->params++;
+ } else {
+ mutex_unlock(&driver->diagchar_mutex);
+ return success;
+ }
+ }
+ } else {
+ mutex_unlock(&driver->diagchar_mutex);
+ pr_err("Max size reached, Pkt Registration failed for"
+ " Process %d", current->tgid);
+ }
+ success = 0;
+ } else if (iocmd == DIAG_IOCTL_GET_DELAYED_RSP_ID) {
+ struct diagpkt_delay_params *delay_params =
+ (struct diagpkt_delay_params *) ioarg;
+
+ if ((delay_params->rsp_ptr) &&
+ (delay_params->size == sizeof(delayed_rsp_id)) &&
+ (delay_params->num_bytes_ptr)) {
+ *((uint16_t *)delay_params->rsp_ptr) =
+ DIAGPKT_NEXT_DELAYED_RSP_ID(delayed_rsp_id);
+ *(delay_params->num_bytes_ptr) = sizeof(delayed_rsp_id);
+ success = 0;
+ }
+ } else if (iocmd == DIAG_IOCTL_LSM_DEINIT) {
+ for (i = 0; i < driver->num_clients; i++)
+ if (driver->client_map[i].pid == current->tgid)
+ break;
+ if (i == -1)
+ return -EINVAL;
+ driver->data_ready[i] |= DEINIT_TYPE;
+ wake_up_interruptible(&driver->wait_q);
+ success = 1;
+ } else if (iocmd == DIAG_IOCTL_SWITCH_LOGGING) {
+ mutex_lock(&driver->diagchar_mutex);
+ temp = driver->logging_mode;
+ driver->logging_mode = (int)ioarg;
+ driver->logging_process_id = current->tgid;
+ mutex_unlock(&driver->diagchar_mutex);
+ if (temp == MEMORY_DEVICE_MODE && driver->logging_mode
+ == NO_LOGGING_MODE) {
+ driver->in_busy_1 = 1;
+ driver->in_busy_2 = 1;
+ driver->in_busy_qdsp_1 = 1;
+ driver->in_busy_qdsp_2 = 1;
+ } else if (temp == NO_LOGGING_MODE && driver->logging_mode
+ == MEMORY_DEVICE_MODE) {
+ driver->in_busy_1 = 0;
+ driver->in_busy_2 = 0;
+ driver->in_busy_qdsp_1 = 0;
+ driver->in_busy_qdsp_2 = 0;
+ /* Poll SMD channels to check for data*/
+ if (driver->ch)
+ queue_work(driver->diag_wq,
+ &(driver->diag_read_smd_work));
+ if (driver->chqdsp)
+ queue_work(driver->diag_wq,
+ &(driver->diag_read_smd_qdsp_work));
+ }
+#ifdef CONFIG_DIAG_OVER_USB
+ else if (temp == USB_MODE && driver->logging_mode
+ == NO_LOGGING_MODE)
+ diagfwd_disconnect();
+ else if (temp == NO_LOGGING_MODE && driver->logging_mode
+ == USB_MODE)
+ diagfwd_connect();
+ else if (temp == USB_MODE && driver->logging_mode
+ == MEMORY_DEVICE_MODE) {
+ diagfwd_disconnect();
+ driver->in_busy_1 = 0;
+ driver->in_busy_2 = 0;
+ driver->in_busy_qdsp_2 = 0;
+ driver->in_busy_qdsp_2 = 0;
+ /* Poll SMD channels to check for data*/
+ if (driver->ch)
+ queue_work(driver->diag_wq,
+ &(driver->diag_read_smd_work));
+ if (driver->chqdsp)
+ queue_work(driver->diag_wq,
+ &(driver->diag_read_smd_qdsp_work));
+ } else if (temp == MEMORY_DEVICE_MODE && driver->logging_mode
+ == USB_MODE)
+ diagfwd_connect();
+#endif /* DIAG over USB */
+ success = 1;
+ }
+
+ return success;
+}
+
+static int diagchar_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ int index = -1, i = 0, ret = 0;
+ int num_data = 0, data_type;
+ for (i = 0; i < driver->num_clients; i++)
+ if (driver->client_map[i].pid == current->tgid)
+ index = i;
+
+ if (index == -1) {
+ pr_err("diag: Client PID not found in table");
+ return -EINVAL;
+ }
+
+ wait_event_interruptible(driver->wait_q,
+ driver->data_ready[index]);
+ mutex_lock(&driver->diagchar_mutex);
+
+ if ((driver->data_ready[index] & MEMORY_DEVICE_LOG_TYPE) && (driver->
+ logging_mode == MEMORY_DEVICE_MODE)) {
+ /*Copy the type of data being passed*/
+ data_type = driver->data_ready[index] & MEMORY_DEVICE_LOG_TYPE;
+ COPY_USER_SPACE_OR_EXIT(buf, data_type, 4);
+ /* place holder for number of data field */
+ ret += 4;
+
+ for (i = 0; i < driver->poolsize_write_struct; i++) {
+ if (driver->buf_tbl[i].length > 0) {
+#ifdef DIAG_DEBUG
+ pr_debug("diag: WRITING the buf address "
+ "and length is %x , %d\n", (unsigned int)
+ (driver->buf_tbl[i].buf),
+ driver->buf_tbl[i].length);
+#endif
+ num_data++;
+ /* Copy the length of data being passed */
+ if (copy_to_user(buf+ret, (void *)&(driver->
+ buf_tbl[i].length), 4)) {
+ num_data--;
+ goto drop;
+ }
+ ret += 4;
+
+ /* Copy the actual data being passed */
+ if (copy_to_user(buf+ret, (void *)driver->
+ buf_tbl[i].buf, driver->buf_tbl[i].length)) {
+ ret -= 4;
+ num_data--;
+ goto drop;
+ }
+ ret += driver->buf_tbl[i].length;
+drop:
+#ifdef DIAG_DEBUG
+ pr_debug("diag: DEQUEUE buf address and"
+ " length is %x,%d\n", (unsigned int)
+ (driver->buf_tbl[i].buf), driver->
+ buf_tbl[i].length);
+#endif
+ diagmem_free(driver, (unsigned char *)
+ (driver->buf_tbl[i].buf), POOL_TYPE_HDLC);
+ driver->buf_tbl[i].length = 0;
+ driver->buf_tbl[i].buf = 0;
+ }
+ }
+
+ /* copy modem data */
+ if (driver->in_busy_1 == 1) {
+ num_data++;
+ /*Copy the length of data being passed*/
+ COPY_USER_SPACE_OR_EXIT(buf+ret,
+ (driver->write_ptr_1->length), 4);
+ /*Copy the actual data being passed*/
+ COPY_USER_SPACE_OR_EXIT(buf+ret,
+ *(driver->buf_in_1),
+ driver->write_ptr_1->length);
+ driver->in_busy_1 = 0;
+ }
+ if (driver->in_busy_2 == 1) {
+ num_data++;
+ /*Copy the length of data being passed*/
+ COPY_USER_SPACE_OR_EXIT(buf+ret,
+ (driver->write_ptr_2->length), 4);
+ /*Copy the actual data being passed*/
+ COPY_USER_SPACE_OR_EXIT(buf+ret,
+ *(driver->buf_in_2),
+ driver->write_ptr_2->length);
+ driver->in_busy_2 = 0;
+ }
+
+ /* copy q6 data */
+ if (driver->in_busy_qdsp_1 == 1) {
+ num_data++;
+ /*Copy the length of data being passed*/
+ COPY_USER_SPACE_OR_EXIT(buf+ret,
+ (driver->write_ptr_qdsp_1->length), 4);
+ /*Copy the actual data being passed*/
+ COPY_USER_SPACE_OR_EXIT(buf+ret, *(driver->
+ buf_in_qdsp_1),
+ driver->write_ptr_qdsp_1->length);
+ driver->in_busy_qdsp_1 = 0;
+ }
+ if (driver->in_busy_qdsp_2 == 1) {
+ num_data++;
+ /*Copy the length of data being passed*/
+ COPY_USER_SPACE_OR_EXIT(buf+ret,
+ (driver->write_ptr_qdsp_2->length), 4);
+ /*Copy the actual data being passed*/
+ COPY_USER_SPACE_OR_EXIT(buf+ret, *(driver->
+ buf_in_qdsp_2), driver->
+ write_ptr_qdsp_2->length);
+ driver->in_busy_qdsp_2 = 0;
+ }
+
+ /* copy number of data fields */
+ COPY_USER_SPACE_OR_EXIT(buf+4, num_data, 4);
+ ret -= 4;
+ driver->data_ready[index] ^= MEMORY_DEVICE_LOG_TYPE;
+ if (driver->ch)
+ queue_work(driver->diag_wq,
+ &(driver->diag_read_smd_work));
+ if (driver->chqdsp)
+ queue_work(driver->diag_wq,
+ &(driver->diag_read_smd_qdsp_work));
+ APPEND_DEBUG('n');
+ goto exit;
+ } else if (driver->data_ready[index] & MEMORY_DEVICE_LOG_TYPE) {
+ /* In case, the thread wakes up and the logging mode is
+ not memory device any more, the condition needs to be cleared */
+ driver->data_ready[index] ^= MEMORY_DEVICE_LOG_TYPE;
+ }
+
+ if (driver->data_ready[index] & DEINIT_TYPE) {
+ /*Copy the type of data being passed*/
+ data_type = driver->data_ready[index] & DEINIT_TYPE;
+ COPY_USER_SPACE_OR_EXIT(buf, data_type, 4);
+ driver->data_ready[index] ^= DEINIT_TYPE;
+ goto exit;
+ }
+
+ if (driver->data_ready[index] & MSG_MASKS_TYPE) {
+ /*Copy the type of data being passed*/
+ data_type = driver->data_ready[index] & MSG_MASKS_TYPE;
+ COPY_USER_SPACE_OR_EXIT(buf, data_type, 4);
+ COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->msg_masks),
+ MSG_MASK_SIZE);
+ driver->data_ready[index] ^= MSG_MASKS_TYPE;
+ goto exit;
+ }
+
+ if (driver->data_ready[index] & EVENT_MASKS_TYPE) {
+ /*Copy the type of data being passed*/
+ data_type = driver->data_ready[index] & EVENT_MASKS_TYPE;
+ COPY_USER_SPACE_OR_EXIT(buf, data_type, 4);
+ COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->event_masks),
+ EVENT_MASK_SIZE);
+ driver->data_ready[index] ^= EVENT_MASKS_TYPE;
+ goto exit;
+ }
+
+ if (driver->data_ready[index] & LOG_MASKS_TYPE) {
+ /*Copy the type of data being passed*/
+ data_type = driver->data_ready[index] & LOG_MASKS_TYPE;
+ COPY_USER_SPACE_OR_EXIT(buf, data_type, 4);
+ COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->log_masks),
+ LOG_MASK_SIZE);
+ driver->data_ready[index] ^= LOG_MASKS_TYPE;
+ goto exit;
+ }
+
+ if (driver->data_ready[index] & PKT_TYPE) {
+ /*Copy the type of data being passed*/
+ data_type = driver->data_ready[index] & PKT_TYPE;
+ COPY_USER_SPACE_OR_EXIT(buf, data_type, 4);
+ COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->pkt_buf),
+ driver->pkt_length);
+ driver->data_ready[index] ^= PKT_TYPE;
+ goto exit;
+ }
+
+exit:
+ mutex_unlock(&driver->diagchar_mutex);
+ return ret;
+}
+
+static int diagchar_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int err, ret = 0, pkt_type;
+#ifdef DIAG_DEBUG
+ int length = 0, i;
+#endif
+ struct diag_send_desc_type send = { NULL, NULL, DIAG_STATE_START, 0 };
+ struct diag_hdlc_dest_type enc = { NULL, NULL, 0 };
+ void *buf_copy = NULL;
+ int payload_size;
+#ifdef CONFIG_DIAG_OVER_USB
+ if (((driver->logging_mode == USB_MODE) && (!driver->usb_connected)) ||
+ (driver->logging_mode == NO_LOGGING_MODE)) {
+ /*Drop the diag payload */
+ return -EIO;
+ }
+#endif /* DIAG over USB */
+ /* Get the packet type F3/log/event/Pkt response */
+ err = copy_from_user((&pkt_type), buf, 4);
+ /*First 4 bytes indicate the type of payload - ignore these */
+ payload_size = count - 4;
+
+ if (pkt_type == MEMORY_DEVICE_LOG_TYPE) {
+ if (!mask_request_validate((unsigned char *)buf)) {
+ printk(KERN_ALERT "mask request Invalid ..cannot send to modem \n");
+ return -EFAULT;
+ }
+ buf = buf + 4;
+#ifdef DIAG_DEBUG
+ pr_debug("diag: masks: %d\n", payload_size);
+ for (i = 0; i < payload_size; i++)
+ printk(KERN_DEBUG "\t %x", *(((unsigned char *)buf)+i));
+#endif
+ diag_process_hdlc((void *)buf, payload_size);
+ return 0;
+ }
+
+ buf_copy = diagmem_alloc(driver, payload_size, POOL_TYPE_COPY);
+ if (!buf_copy) {
+ driver->dropped_count++;
+ return -ENOMEM;
+ }
+
+ err = copy_from_user(buf_copy, buf + 4, payload_size);
+ if (err) {
+ printk(KERN_INFO "diagchar : copy_from_user failed\n");
+ ret = -EFAULT;
+ goto fail_free_copy;
+ }
+#ifdef DIAG_DEBUG
+ printk(KERN_DEBUG "data is -->\n");
+ for (i = 0; i < payload_size; i++)
+ printk(KERN_DEBUG "\t %x \t", *(((unsigned char *)buf_copy)+i));
+#endif
+ send.state = DIAG_STATE_START;
+ send.pkt = buf_copy;
+ send.last = (void *)(buf_copy + payload_size - 1);
+ send.terminate = 1;
+#ifdef DIAG_DEBUG
+ pr_debug("diag: Already used bytes in buffer %d, and"
+ " incoming payload size is %d\n", driver->used, payload_size);
+ printk(KERN_DEBUG "hdlc encoded data is -->\n");
+ for (i = 0; i < payload_size + 8; i++) {
+ printk(KERN_DEBUG "\t %x \t", *(((unsigned char *)buf_hdlc)+i));
+ if (*(((unsigned char *)buf_hdlc)+i) != 0x7e)
+ length++;
+ }
+#endif
+ mutex_lock(&driver->diagchar_mutex);
+ if (!buf_hdlc)
+ buf_hdlc = diagmem_alloc(driver, HDLC_OUT_BUF_SIZE,
+ POOL_TYPE_HDLC);
+ if (!buf_hdlc) {
+ ret = -ENOMEM;
+ goto fail_free_hdlc;
+ }
+ if (HDLC_OUT_BUF_SIZE - driver->used <= (2*payload_size) + 3) {
+ err = diag_device_write(buf_hdlc, APPS_DATA, NULL);
+ if (err) {
+ /*Free the buffer right away if write failed */
+ diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC);
+ diagmem_free(driver, (unsigned char *)driver->
+ write_ptr_svc, POOL_TYPE_WRITE_STRUCT);
+ ret = -EIO;
+ goto fail_free_hdlc;
+ }
+ buf_hdlc = NULL;
+ driver->used = 0;
+ buf_hdlc = diagmem_alloc(driver, HDLC_OUT_BUF_SIZE,
+ POOL_TYPE_HDLC);
+ if (!buf_hdlc) {
+ ret = -ENOMEM;
+ goto fail_free_hdlc;
+ }
+ }
+
+ enc.dest = buf_hdlc + driver->used;
+ enc.dest_last = (void *)(buf_hdlc + driver->used + 2*payload_size + 3);
+ diag_hdlc_encode(&send, &enc);
+
+ /* This is to check if after HDLC encoding, we are still within the
+ limits of aggregation buffer. If not, we write out the current buffer
+ and start aggregation in a newly allocated buffer */
+ if ((unsigned int) enc.dest >=
+ (unsigned int)(buf_hdlc + HDLC_OUT_BUF_SIZE)) {
+ err = diag_device_write(buf_hdlc, APPS_DATA, NULL);
+ if (err) {
+ /*Free the buffer right away if write failed */
+ diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC);
+ diagmem_free(driver, (unsigned char *)driver->
+ write_ptr_svc, POOL_TYPE_WRITE_STRUCT);
+ ret = -EIO;
+ goto fail_free_hdlc;
+ }
+ buf_hdlc = NULL;
+ driver->used = 0;
+ buf_hdlc = diagmem_alloc(driver, HDLC_OUT_BUF_SIZE,
+ POOL_TYPE_HDLC);
+ if (!buf_hdlc) {
+ ret = -ENOMEM;
+ goto fail_free_hdlc;
+ }
+ enc.dest = buf_hdlc + driver->used;
+ enc.dest_last = (void *)(buf_hdlc + driver->used +
+ (2*payload_size) + 3);
+ diag_hdlc_encode(&send, &enc);
+ }
+
+ driver->used = (uint32_t) enc.dest - (uint32_t) buf_hdlc;
+ if (pkt_type == DATA_TYPE_RESPONSE) {
+ err = diag_device_write(buf_hdlc, APPS_DATA, NULL);
+ if (err) {
+ /*Free the buffer right away if write failed */
+ diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC);
+ diagmem_free(driver, (unsigned char *)driver->
+ write_ptr_svc, POOL_TYPE_WRITE_STRUCT);
+ ret = -EIO;
+ goto fail_free_hdlc;
+ }
+ buf_hdlc = NULL;
+ driver->used = 0;
+ }
+
+ mutex_unlock(&driver->diagchar_mutex);
+ diagmem_free(driver, buf_copy, POOL_TYPE_COPY);
+ if (!timer_in_progress) {
+ timer_in_progress = 1;
+ ret = mod_timer(&drain_timer, jiffies + msecs_to_jiffies(500));
+ }
+ return 0;
+
+fail_free_hdlc:
+ buf_hdlc = NULL;
+ driver->used = 0;
+ diagmem_free(driver, buf_copy, POOL_TYPE_COPY);
+ mutex_unlock(&driver->diagchar_mutex);
+ return ret;
+
+fail_free_copy:
+ diagmem_free(driver, buf_copy, POOL_TYPE_COPY);
+ return ret;
+}
+
+int mask_request_validate(unsigned char mask_buf[])
+{
+ uint8_t packet_id;
+ uint8_t subsys_id;
+ uint16_t ss_cmd;
+
+ packet_id = mask_buf[4];
+
+ if (packet_id == 0x4B) {
+ subsys_id = mask_buf[5];
+ ss_cmd = *(uint16_t *)(mask_buf + 6);
+ /* Packets with SSID which are allowed */
+ switch (subsys_id) {
+ case 0x04: /* DIAG_SUBSYS_WCDMA */
+ if ((ss_cmd == 0) || (ss_cmd == 0xF))
+ return 1;
+ break;
+ case 0x08: /* DIAG_SUBSYS_GSM */
+ if ((ss_cmd == 0) || (ss_cmd == 0x1))
+ return 1;
+ break;
+ case 0x09: /* DIAG_SUBSYS_UMTS */
+ case 0x0F: /* DIAG_SUBSYS_CM */
+ if (ss_cmd == 0)
+ return 1;
+ break;
+ case 0x0C: /* DIAG_SUBSYS_OS */
+ if ((ss_cmd == 2) || (ss_cmd == 0x100))
+ return 1; /* MPU and APU */
+ break;
+ case 0x12: /* DIAG_SUBSYS_DIAG_SERV */
+ if ((ss_cmd == 0) || (ss_cmd == 0x6) || (ss_cmd == 0x7))
+ return 1;
+ break;
+ case 0x13: /* DIAG_SUBSYS_FS */
+ if ((ss_cmd == 0) || (ss_cmd == 0x1))
+ return 1;
+ break;
+ default:
+ return 0;
+ break;
+ }
+ } else {
+ switch (packet_id) {
+ case 0x00: /* Version Number */
+ case 0x0C: /* CDMA status packet */
+ case 0x1C: /* Diag Version */
+ case 0x1D: /* Time Stamp */
+ case 0x60: /* Event Report Control */
+ case 0x63: /* Status snapshot */
+ case 0x73: /* Logging Configuration */
+ case 0x7C: /* Extended build ID */
+ case 0x7D: /* Extended Message configuration */
+ case 0x81: /* Event get mask */
+ case 0x82: /* Set the event mask */
+ return 1;
+ break;
+ default:
+ return 0;
+ break;
+ }
+ }
+ return 0;
+}
+
+static const struct file_operations diagcharfops = {
+ .owner = THIS_MODULE,
+ .read = diagchar_read,
+ .write = diagchar_write,
+ .unlocked_ioctl = diagchar_ioctl,
+ .open = diagchar_open,
+ .release = diagchar_close
+};
+
+static int diagchar_setup_cdev(dev_t devno)
+{
+
+ int err;
+
+ cdev_init(driver->cdev, &diagcharfops);
+
+ driver->cdev->owner = THIS_MODULE;
+ driver->cdev->ops = &diagcharfops;
+
+ err = cdev_add(driver->cdev, devno, 1);
+
+ if (err) {
+ printk(KERN_INFO "diagchar cdev registration failed !\n\n");
+ return -1;
+ }
+
+ driver->diagchar_class = class_create(THIS_MODULE, "diag");
+
+ if (IS_ERR(driver->diagchar_class)) {
+ printk(KERN_ERR "Error creating diagchar class.\n");
+ return -1;
+ }
+
+ device_create(driver->diagchar_class, NULL, devno,
+ (void *)driver, "diag");
+
+ return 0;
+
+}
+
+static int diagchar_cleanup(void)
+{
+ if (driver) {
+ if (driver->cdev) {
+ /* TODO - Check if device exists before deleting */
+ device_destroy(driver->diagchar_class,
+ MKDEV(driver->major,
+ driver->minor_start));
+ cdev_del(driver->cdev);
+ }
+ if (!IS_ERR(driver->diagchar_class))
+ class_destroy(driver->diagchar_class);
+ kfree(driver);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_DIAG_SDIO_PIPE
+void diag_sdio_fn(int type)
+{
+ if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) {
+ if (type == INIT)
+ diagfwd_sdio_init();
+ else if (type == EXIT)
+ diagfwd_sdio_exit();
+ }
+}
+#else
+inline void diag_sdio_fn(int type) {}
+#endif
+
+static int __init diagchar_init(void)
+{
+ dev_t dev;
+ int error;
+
+ pr_debug("diagfwd initializing ..\n");
+ driver = kzalloc(sizeof(struct diagchar_dev) + 5, GFP_KERNEL);
+
+ if (driver) {
+ driver->used = 0;
+ timer_in_progress = 0;
+ driver->debug_flag = 1;
+ setup_timer(&drain_timer, drain_timer_func, 1234);
+ driver->itemsize = itemsize;
+ driver->poolsize = poolsize;
+ driver->itemsize_hdlc = itemsize_hdlc;
+ driver->poolsize_hdlc = poolsize_hdlc;
+ driver->itemsize_write_struct = itemsize_write_struct;
+ driver->poolsize_write_struct = poolsize_write_struct;
+ driver->num_clients = max_clients;
+ driver->logging_mode = USB_MODE;
+ mutex_init(&driver->diagchar_mutex);
+ init_waitqueue_head(&driver->wait_q);
+ INIT_WORK(&(driver->diag_drain_work), diag_drain_work_fn);
+ INIT_WORK(&(driver->diag_read_smd_work), diag_read_smd_work_fn);
+ INIT_WORK(&(driver->diag_read_smd_cntl_work),
+ diag_read_smd_cntl_work_fn);
+ INIT_WORK(&(driver->diag_read_smd_qdsp_work),
+ diag_read_smd_qdsp_work_fn);
+ INIT_WORK(&(driver->diag_read_smd_qdsp_cntl_work),
+ diag_read_smd_qdsp_cntl_work_fn);
+ INIT_WORK(&(driver->diag_read_smd_wcnss_work),
+ diag_read_smd_wcnss_work_fn);
+ INIT_WORK(&(driver->diag_read_smd_wcnss_cntl_work),
+ diag_read_smd_wcnss_cntl_work_fn);
+ diagfwd_init();
+ diagfwd_cntl_init();
+ diag_sdio_fn(INIT);
+ pr_debug("diagchar initializing ..\n");
+ driver->num = 1;
+ driver->name = ((void *)driver) + sizeof(struct diagchar_dev);
+ strlcpy(driver->name, "diag", 4);
+
+ /* Get major number from kernel and initialize */
+ error = alloc_chrdev_region(&dev, driver->minor_start,
+ driver->num, driver->name);
+ if (!error) {
+ driver->major = MAJOR(dev);
+ driver->minor_start = MINOR(dev);
+ } else {
+ printk(KERN_INFO "Major number not allocated\n");
+ goto fail;
+ }
+ driver->cdev = cdev_alloc();
+ error = diagchar_setup_cdev(dev);
+ if (error)
+ goto fail;
+ } else {
+ printk(KERN_INFO "kzalloc failed\n");
+ goto fail;
+ }
+
+ pr_info("diagchar initialized now");
+ return 0;
+
+fail:
+ diagchar_cleanup();
+ diagfwd_exit();
+ diagfwd_cntl_exit();
+ diag_sdio_fn(EXIT);
+ return -1;
+}
+
+static void __exit diagchar_exit(void)
+{
+ printk(KERN_INFO "diagchar exiting ..\n");
+ /* On Driver exit, send special pool type to
+ ensure no memory leaks */
+ diagmem_exit(driver, POOL_TYPE_ALL);
+ diagfwd_exit();
+ diagfwd_cntl_exit();
+ diag_sdio_fn(EXIT);
+ diagchar_cleanup();
+ printk(KERN_INFO "done diagchar exit\n");
+}
+
+module_init(diagchar_init);
+module_exit(diagchar_exit);
diff --git a/drivers/char/diag/diagchar_hdlc.c b/drivers/char/diag/diagchar_hdlc.c
new file mode 100644
index 0000000..ef57d52
--- /dev/null
+++ b/drivers/char/diag/diagchar_hdlc.c
@@ -0,0 +1,223 @@
+/* Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/uaccess.h>
+#include <linux/crc-ccitt.h>
+#include "diagchar_hdlc.h"
+
+
+MODULE_LICENSE("GPL v2");
+
+#define CRC_16_L_SEED 0xFFFF
+
+#define CRC_16_L_STEP(xx_crc, xx_c) \
+ crc_ccitt_byte(xx_crc, xx_c)
+
+void diag_hdlc_encode(struct diag_send_desc_type *src_desc,
+ struct diag_hdlc_dest_type *enc)
+{
+ uint8_t *dest;
+ uint8_t *dest_last;
+ const uint8_t *src;
+ const uint8_t *src_last;
+ uint16_t crc;
+ unsigned char src_byte = 0;
+ enum diag_send_state_enum_type state;
+ unsigned int used = 0;
+
+ if (src_desc && enc) {
+
+ /* Copy parts to local variables. */
+ src = src_desc->pkt;
+ src_last = src_desc->last;
+ state = src_desc->state;
+ dest = enc->dest;
+ dest_last = enc->dest_last;
+
+ if (state == DIAG_STATE_START) {
+ crc = CRC_16_L_SEED;
+ state++;
+ } else {
+ /* Get a local copy of the CRC */
+ crc = enc->crc;
+ }
+
+ /* dest or dest_last may be NULL to trigger a
+ state transition only */
+ if (dest && dest_last) {
+ /* This condition needs to include the possibility
+ of 2 dest bytes for an escaped byte */
+ while (src <= src_last && dest <= dest_last) {
+
+ src_byte = *src++;
+
+ if ((src_byte == CONTROL_CHAR) ||
+ (src_byte == ESC_CHAR)) {
+
+ /* If the escape character is not the
+ last byte */
+ if (dest != dest_last) {
+ crc = CRC_16_L_STEP(crc,
+ src_byte);
+
+ *dest++ = ESC_CHAR;
+ used++;
+
+ *dest++ = src_byte
+ ^ ESC_MASK;
+ used++;
+ } else {
+
+ src--;
+ break;
+ }
+
+ } else {
+ crc = CRC_16_L_STEP(crc, src_byte);
+ *dest++ = src_byte;
+ used++;
+ }
+ }
+
+ if (src > src_last) {
+
+ if (state == DIAG_STATE_BUSY) {
+ if (src_desc->terminate) {
+ crc = ~crc;
+ state++;
+ } else {
+ /* Done with fragment */
+ state = DIAG_STATE_COMPLETE;
+ }
+ }
+
+ while (dest <= dest_last &&
+ state >= DIAG_STATE_CRC1 &&
+ state < DIAG_STATE_TERM) {
+ /* Encode a byte of the CRC next */
+ src_byte = crc & 0xFF;
+
+ if ((src_byte == CONTROL_CHAR)
+ || (src_byte == ESC_CHAR)) {
+
+ if (dest != dest_last) {
+
+ *dest++ = ESC_CHAR;
+ used++;
+ *dest++ = src_byte ^
+ ESC_MASK;
+ used++;
+
+ crc >>= 8;
+ } else {
+
+ break;
+ }
+ } else {
+
+ crc >>= 8;
+ *dest++ = src_byte;
+ used++;
+ }
+
+ state++;
+ }
+
+ if (state == DIAG_STATE_TERM) {
+ if (dest_last >= dest) {
+ *dest++ = CONTROL_CHAR;
+ used++;
+ state++; /* Complete */
+ }
+ }
+ }
+ }
+ /* Copy local variables back into the encode structure. */
+
+ enc->dest = dest;
+ enc->dest_last = dest_last;
+ enc->crc = crc;
+ src_desc->pkt = src;
+ src_desc->last = src_last;
+ src_desc->state = state;
+ }
+
+ return;
+}
+
+
+int diag_hdlc_decode(struct diag_hdlc_decode_type *hdlc)
+{
+ uint8_t *src_ptr = NULL, *dest_ptr = NULL;
+ unsigned int src_length = 0, dest_length = 0;
+
+ unsigned int len = 0;
+ unsigned int i;
+ uint8_t src_byte;
+
+ int pkt_bnd = 0;
+
+ if (hdlc && hdlc->src_ptr && hdlc->dest_ptr &&
+ (hdlc->src_size - hdlc->src_idx > 0) &&
+ (hdlc->dest_size - hdlc->dest_idx > 0)) {
+
+ src_ptr = hdlc->src_ptr;
+ src_ptr = &src_ptr[hdlc->src_idx];
+ src_length = hdlc->src_size - hdlc->src_idx;
+
+ dest_ptr = hdlc->dest_ptr;
+ dest_ptr = &dest_ptr[hdlc->dest_idx];
+ dest_length = hdlc->dest_size - hdlc->dest_idx;
+
+ for (i = 0; i < src_length; i++) {
+
+ src_byte = src_ptr[i];
+
+ if (hdlc->escaping) {
+ dest_ptr[len++] = src_byte ^ ESC_MASK;
+ hdlc->escaping = 0;
+ } else if (src_byte == ESC_CHAR) {
+ if (i == (src_length - 1)) {
+ hdlc->escaping = 1;
+ i++;
+ break;
+ } else {
+ dest_ptr[len++] = src_ptr[++i]
+ ^ ESC_MASK;
+ }
+ } else if (src_byte == CONTROL_CHAR) {
+ dest_ptr[len++] = src_byte;
+ pkt_bnd = 1;
+ i++;
+ break;
+ } else {
+ dest_ptr[len++] = src_byte;
+ }
+
+ if (len >= dest_length) {
+ i++;
+ break;
+ }
+ }
+
+ hdlc->src_idx += i;
+ hdlc->dest_idx += len;
+ }
+
+ return pkt_bnd;
+}
diff --git a/drivers/char/diag/diagchar_hdlc.h b/drivers/char/diag/diagchar_hdlc.h
new file mode 100644
index 0000000..2df81de
--- /dev/null
+++ b/drivers/char/diag/diagchar_hdlc.h
@@ -0,0 +1,60 @@
+/* Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef DIAGCHAR_HDLC
+#define DIAGCHAR_HDLC
+
+enum diag_send_state_enum_type {
+ DIAG_STATE_START,
+ DIAG_STATE_BUSY,
+ DIAG_STATE_CRC1,
+ DIAG_STATE_CRC2,
+ DIAG_STATE_TERM,
+ DIAG_STATE_COMPLETE
+};
+
+struct diag_send_desc_type {
+ const void *pkt;
+ const void *last; /* Address of last byte to send. */
+ enum diag_send_state_enum_type state;
+ unsigned char terminate; /* True if this fragment
+ terminates the packet */
+};
+
+struct diag_hdlc_dest_type {
+ void *dest;
+ void *dest_last;
+ /* Below: internal use only */
+ uint16_t crc;
+};
+
+struct diag_hdlc_decode_type {
+ uint8_t *src_ptr;
+ unsigned int src_idx;
+ unsigned int src_size;
+ uint8_t *dest_ptr;
+ unsigned int dest_idx;
+ unsigned int dest_size;
+ int escaping;
+
+};
+
+void diag_hdlc_encode(struct diag_send_desc_type *src_desc,
+ struct diag_hdlc_dest_type *enc);
+
+int diag_hdlc_decode(struct diag_hdlc_decode_type *hdlc);
+
+#define ESC_CHAR 0x7D
+#define CONTROL_CHAR 0x7E
+#define ESC_MASK 0x20
+
+#endif
diff --git a/drivers/char/diag/diagfwd.c b/drivers/char/diag/diagfwd.c
new file mode 100644
index 0000000..433f09a
--- /dev/null
+++ b/drivers/char/diag/diagfwd.c
@@ -0,0 +1,1384 @@
+/* Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/workqueue.h>
+#include <linux/pm_runtime.h>
+#include <linux/diagchar.h>
+#include <linux/delay.h>
+#include <linux/reboot.h>
+#ifdef CONFIG_DIAG_OVER_USB
+#include <mach/usbdiag.h>
+#endif
+#include <mach/msm_smd.h>
+#include <mach/socinfo.h>
+#include <mach/restart.h>
+#include "diagmem.h"
+#include "diagchar.h"
+#include "diagfwd.h"
+#include "diagfwd_cntl.h"
+#include "diagchar_hdlc.h"
+#ifdef CONFIG_DIAG_SDIO_PIPE
+#include "diagfwd_sdio.h"
+#endif
+#define MODE_CMD 41
+#define RESET_ID 2
+
+int diag_debug_buf_idx;
+unsigned char diag_debug_buf[1024];
+static unsigned int buf_tbl_size = 8; /*Number of entries in table of buffers */
+struct diag_master_table entry;
+
+struct diag_send_desc_type send = { NULL, NULL, DIAG_STATE_START, 0 };
+struct diag_hdlc_dest_type enc = { NULL, NULL, 0 };
+
+#define ENCODE_RSP_AND_SEND(buf_length) \
+do { \
+ send.state = DIAG_STATE_START; \
+ send.pkt = driver->apps_rsp_buf; \
+ send.last = (void *)(driver->apps_rsp_buf + buf_length); \
+ send.terminate = 1; \
+ if (!driver->in_busy_1) { \
+ enc.dest = driver->buf_in_1; \
+ enc.dest_last = (void *)(driver->buf_in_1 + 499); \
+ diag_hdlc_encode(&send, &enc); \
+ driver->write_ptr_1->buf = driver->buf_in_1; \
+ driver->write_ptr_1->length = (int)(enc.dest - \
+ (void *)(driver->buf_in_1)); \
+ usb_diag_write(driver->legacy_ch, driver->write_ptr_1); \
+ memset(driver->apps_rsp_buf, '\0', 500); \
+ } \
+} while (0)
+
+#define CHK_OVERFLOW(bufStart, start, end, length) \
+((bufStart <= start) && (end - start >= length)) ? 1 : 0
+
+int chk_config_get_id()
+{
+ switch (socinfo_get_id()) {
+ case APQ8060_MACHINE_ID:
+ case MSM8660_MACHINE_ID:
+ return APQ8060_TOOLS_ID;
+ case AO8960_MACHINE_ID:
+ return AO8960_TOOLS_ID;
+ default:
+ return 0;
+ }
+}
+
+void __diag_smd_send_req(void)
+{
+ void *buf = NULL;
+ int *in_busy_ptr = NULL;
+ struct diag_request *write_ptr_modem = NULL;
+
+ if (!driver->in_busy_1) {
+ buf = driver->buf_in_1;
+ write_ptr_modem = driver->write_ptr_1;
+ in_busy_ptr = &(driver->in_busy_1);
+ } else if (!driver->in_busy_2) {
+ buf = driver->buf_in_2;
+ write_ptr_modem = driver->write_ptr_2;
+ in_busy_ptr = &(driver->in_busy_2);
+ }
+
+ if (driver->ch && buf) {
+ int r = smd_read_avail(driver->ch);
+
+ if (r > IN_BUF_SIZE) {
+ if (r < MAX_IN_BUF_SIZE) {
+ pr_err("diag: SMD sending in "
+ "packets upto %d bytes", r);
+ buf = krealloc(buf, r, GFP_KERNEL);
+ } else {
+ pr_err("diag: SMD sending in "
+ "packets more than %d bytes", MAX_IN_BUF_SIZE);
+ return;
+ }
+ }
+ if (r > 0) {
+ if (!buf)
+ pr_info("Out of diagmem for Modem\n");
+ else {
+ APPEND_DEBUG('i');
+ smd_read(driver->ch, buf, r);
+ APPEND_DEBUG('j');
+ write_ptr_modem->length = r;
+ *in_busy_ptr = 1;
+ diag_device_write(buf, MODEM_DATA,
+ write_ptr_modem);
+ }
+ }
+ }
+}
+
+int diag_device_write(void *buf, int proc_num, struct diag_request *write_ptr)
+{
+ int i, err = 0;
+
+ if (driver->logging_mode == MEMORY_DEVICE_MODE) {
+ if (proc_num == APPS_DATA) {
+ for (i = 0; i < driver->poolsize_write_struct; i++)
+ if (driver->buf_tbl[i].length == 0) {
+ driver->buf_tbl[i].buf = buf;
+ driver->buf_tbl[i].length =
+ driver->used;
+#ifdef DIAG_DEBUG
+ pr_debug("diag: ENQUEUE buf ptr"
+ " and length is %x , %d\n",
+ (unsigned int)(driver->buf_
+ tbl[i].buf), driver->buf_tbl[i].length);
+#endif
+ break;
+ }
+ }
+ for (i = 0; i < driver->num_clients; i++)
+ if (driver->client_map[i].pid ==
+ driver->logging_process_id)
+ break;
+ if (i < driver->num_clients) {
+ driver->data_ready[i] |= MEMORY_DEVICE_LOG_TYPE;
+ wake_up_interruptible(&driver->wait_q);
+ } else
+ return -EINVAL;
+ } else if (driver->logging_mode == NO_LOGGING_MODE) {
+ if (proc_num == MODEM_DATA) {
+ driver->in_busy_1 = 0;
+ driver->in_busy_2 = 0;
+ queue_work(driver->diag_wq, &(driver->
+ diag_read_smd_work));
+ } else if (proc_num == QDSP_DATA) {
+ driver->in_busy_qdsp_1 = 0;
+ driver->in_busy_qdsp_2 = 0;
+ queue_work(driver->diag_wq, &(driver->
+ diag_read_smd_qdsp_work));
+ } else if (proc_num == WCNSS_DATA) {
+ driver->in_busy_wcnss = 0;
+ queue_work(driver->diag_wq, &(driver->
+ diag_read_smd_wcnss_work));
+ }
+ err = -1;
+ }
+#ifdef CONFIG_DIAG_OVER_USB
+ else if (driver->logging_mode == USB_MODE) {
+ if (proc_num == APPS_DATA) {
+ driver->write_ptr_svc = (struct diag_request *)
+ (diagmem_alloc(driver, sizeof(struct diag_request),
+ POOL_TYPE_WRITE_STRUCT));
+ if (driver->write_ptr_svc) {
+ driver->write_ptr_svc->length = driver->used;
+ driver->write_ptr_svc->buf = buf;
+ err = usb_diag_write(driver->legacy_ch,
+ driver->write_ptr_svc);
+ } else
+ err = -1;
+ } else if (proc_num == MODEM_DATA) {
+ write_ptr->buf = buf;
+#ifdef DIAG_DEBUG
+ printk(KERN_INFO "writing data to USB,"
+ "pkt length %d\n", write_ptr->length);
+ print_hex_dump(KERN_DEBUG, "Written Packet Data to"
+ " USB: ", 16, 1, DUMP_PREFIX_ADDRESS,
+ buf, write_ptr->length, 1);
+#endif /* DIAG DEBUG */
+ err = usb_diag_write(driver->legacy_ch, write_ptr);
+ } else if (proc_num == QDSP_DATA) {
+ write_ptr->buf = buf;
+ err = usb_diag_write(driver->legacy_ch, write_ptr);
+ } else if (proc_num == WCNSS_DATA) {
+ write_ptr->buf = buf;
+ err = usb_diag_write(driver->legacy_ch, write_ptr);
+ }
+#ifdef CONFIG_DIAG_SDIO_PIPE
+ else if (proc_num == SDIO_DATA) {
+ if (machine_is_msm8x60_fusion() ||
+ machine_is_msm8x60_fusn_ffa()) {
+ write_ptr->buf = buf;
+ err = usb_diag_write(driver->mdm_ch, write_ptr);
+ } else
+ pr_err("diag: Incorrect data while USB write");
+ }
+#endif
+ APPEND_DEBUG('d');
+ }
+#endif /* DIAG OVER USB */
+ return err;
+}
+
+void __diag_smd_wcnss_send_req(void)
+{
+ void *buf = driver->buf_in_wcnss;
+ int *in_busy_wcnss_ptr = &(driver->in_busy_wcnss);
+ struct diag_request *write_ptr_wcnss = driver->write_ptr_wcnss;
+
+ if (driver->ch_wcnss && buf) {
+ int r = smd_read_avail(driver->ch_wcnss);
+ if (r > IN_BUF_SIZE) {
+ if (r < MAX_IN_BUF_SIZE) {
+ pr_err("diag: wcnss packets > %d bytes", r);
+ buf = krealloc(buf, r, GFP_KERNEL);
+ } else {
+ pr_err("diag: wcnss pkt > %d", MAX_IN_BUF_SIZE);
+ return;
+ }
+ }
+ if (r > 0) {
+ if (!buf) {
+ pr_err("Out of diagmem for wcnss\n");
+ } else {
+ APPEND_DEBUG('i');
+ smd_read(driver->ch_wcnss, buf, r);
+ APPEND_DEBUG('j');
+ write_ptr_wcnss->length = r;
+ *in_busy_wcnss_ptr = 1;
+ diag_device_write(buf, WCNSS_DATA,
+ write_ptr_wcnss);
+ }
+ }
+ }
+}
+
+void __diag_smd_qdsp_send_req(void)
+{
+ void *buf = NULL;
+ int *in_busy_qdsp_ptr = NULL;
+ struct diag_request *write_ptr_qdsp = NULL;
+
+ if (!driver->in_busy_qdsp_1) {
+ buf = driver->buf_in_qdsp_1;
+ write_ptr_qdsp = driver->write_ptr_qdsp_1;
+ in_busy_qdsp_ptr = &(driver->in_busy_qdsp_1);
+ } else if (!driver->in_busy_qdsp_2) {
+ buf = driver->buf_in_qdsp_2;
+ write_ptr_qdsp = driver->write_ptr_qdsp_2;
+ in_busy_qdsp_ptr = &(driver->in_busy_qdsp_2);
+ }
+
+ if (driver->chqdsp && buf) {
+ int r = smd_read_avail(driver->chqdsp);
+
+ if (r > IN_BUF_SIZE) {
+ if (r < MAX_IN_BUF_SIZE) {
+ pr_err("diag: SMD sending in "
+ "packets upto %d bytes", r);
+ buf = krealloc(buf, r, GFP_KERNEL);
+ } else {
+ pr_err("diag: SMD sending in "
+ "packets more than %d bytes", MAX_IN_BUF_SIZE);
+ return;
+ }
+ }
+ if (r > 0) {
+ if (!buf)
+ printk(KERN_INFO "Out of diagmem for QDSP\n");
+ else {
+ APPEND_DEBUG('i');
+ smd_read(driver->chqdsp, buf, r);
+ APPEND_DEBUG('j');
+ write_ptr_qdsp->length = r;
+ *in_busy_qdsp_ptr = 1;
+ diag_device_write(buf, QDSP_DATA,
+ write_ptr_qdsp);
+ }
+ }
+ }
+}
+
+static void diag_print_mask_table(void)
+{
+/* Enable this to print mask table when updated */
+#ifdef MASK_DEBUG
+ int first;
+ int last;
+ uint8_t *ptr = driver->msg_masks;
+ int i = 0;
+
+ while (*(uint32_t *)(ptr + 4)) {
+ first = *(uint32_t *)ptr;
+ ptr += 4;
+ last = *(uint32_t *)ptr;
+ ptr += 4;
+ printk(KERN_INFO "SSID %d - %d\n", first, last);
+ for (i = 0 ; i <= last - first ; i++)
+ printk(KERN_INFO "MASK:%x\n", *((uint32_t *)ptr + i));
+ ptr += ((last - first) + 1)*4;
+
+ }
+#endif
+}
+
+static void diag_update_msg_mask(int start, int end , uint8_t *buf)
+{
+ int found = 0;
+ int first;
+ int last;
+ uint8_t *ptr = driver->msg_masks;
+ uint8_t *ptr_buffer_start = &(*(driver->msg_masks));
+ uint8_t *ptr_buffer_end = &(*(driver->msg_masks)) + MSG_MASK_SIZE;
+
+ mutex_lock(&driver->diagchar_mutex);
+ /* First SSID can be zero : So check that last is non-zero */
+
+ while (*(uint32_t *)(ptr + 4)) {
+ first = *(uint32_t *)ptr;
+ ptr += 4;
+ last = *(uint32_t *)ptr;
+ ptr += 4;
+ if (start >= first && start <= last) {
+ ptr += (start - first)*4;
+ if (end <= last)
+ if (CHK_OVERFLOW(ptr_buffer_start, ptr,
+ ptr_buffer_end,
+ (((end - start)+1)*4)))
+ memcpy(ptr, buf , ((end - start)+1)*4);
+ else
+ printk(KERN_CRIT "Not enough"
+ " buffer space for"
+ " MSG_MASK\n");
+ else
+ printk(KERN_INFO "Unable to copy"
+ " mask change\n");
+
+ found = 1;
+ break;
+ } else {
+ ptr += ((last - first) + 1)*4;
+ }
+ }
+ /* Entry was not found - add new table */
+ if (!found) {
+ if (CHK_OVERFLOW(ptr_buffer_start, ptr, ptr_buffer_end,
+ 8 + ((end - start) + 1)*4)) {
+ memcpy(ptr, &(start) , 4);
+ ptr += 4;
+ memcpy(ptr, &(end), 4);
+ ptr += 4;
+ memcpy(ptr, buf , ((end - start) + 1)*4);
+ } else
+ printk(KERN_CRIT " Not enough buffer"
+ " space for MSG_MASK\n");
+ }
+ mutex_unlock(&driver->diagchar_mutex);
+ diag_print_mask_table();
+
+}
+
+static void diag_update_event_mask(uint8_t *buf, int toggle, int num_bits)
+{
+ uint8_t *ptr = driver->event_masks;
+ uint8_t *temp = buf + 2;
+
+ mutex_lock(&driver->diagchar_mutex);
+ if (!toggle)
+ memset(ptr, 0 , EVENT_MASK_SIZE);
+ else
+ if (CHK_OVERFLOW(ptr, ptr,
+ ptr+EVENT_MASK_SIZE,
+ num_bits/8 + 1))
+ memcpy(ptr, temp , num_bits/8 + 1);
+ else
+ printk(KERN_CRIT "Not enough buffer space "
+ "for EVENT_MASK\n");
+ mutex_unlock(&driver->diagchar_mutex);
+}
+
+static void diag_update_log_mask(int equip_id, uint8_t *buf, int num_items)
+{
+ uint8_t *temp = buf;
+ struct mask_info {
+ int equip_id;
+ int index;
+ };
+ int i = 0;
+ unsigned char *ptr_data;
+ int offset = 8*MAX_EQUIP_ID;
+ struct mask_info *ptr = (struct mask_info *)driver->log_masks;
+
+ mutex_lock(&driver->diagchar_mutex);
+ /* Check if we already know index of this equipment ID */
+ for (i = 0; i < MAX_EQUIP_ID; i++) {
+ if ((ptr->equip_id == equip_id) && (ptr->index != 0)) {
+ offset = ptr->index;
+ break;
+ }
+ if ((ptr->equip_id == 0) && (ptr->index == 0)) {
+ /*Reached a null entry */
+ ptr->equip_id = equip_id;
+ ptr->index = driver->log_masks_length;
+ offset = driver->log_masks_length;
+ driver->log_masks_length += ((num_items+7)/8);
+ break;
+ }
+ ptr++;
+ }
+ ptr_data = driver->log_masks + offset;
+ if (CHK_OVERFLOW(driver->log_masks, ptr_data, driver->log_masks
+ + LOG_MASK_SIZE, (num_items+7)/8))
+ memcpy(ptr_data, temp , (num_items+7)/8);
+ else
+ printk(KERN_CRIT " Not enough buffer space for LOG_MASK\n");
+ mutex_unlock(&driver->diagchar_mutex);
+}
+
+static void diag_update_pkt_buffer(unsigned char *buf)
+{
+ unsigned char *ptr = driver->pkt_buf;
+ unsigned char *temp = buf;
+
+ mutex_lock(&driver->diagchar_mutex);
+ if (CHK_OVERFLOW(ptr, ptr, ptr + PKT_SIZE, driver->pkt_length))
+ memcpy(ptr, temp , driver->pkt_length);
+ else
+ printk(KERN_CRIT " Not enough buffer space for PKT_RESP\n");
+ mutex_unlock(&driver->diagchar_mutex);
+}
+
+void diag_update_userspace_clients(unsigned int type)
+{
+ int i;
+
+ mutex_lock(&driver->diagchar_mutex);
+ for (i = 0; i < driver->num_clients; i++)
+ if (driver->client_map[i].pid != 0)
+ driver->data_ready[i] |= type;
+ wake_up_interruptible(&driver->wait_q);
+ mutex_unlock(&driver->diagchar_mutex);
+}
+
+void diag_update_sleeping_process(int process_id)
+{
+ int i;
+
+ mutex_lock(&driver->diagchar_mutex);
+ for (i = 0; i < driver->num_clients; i++)
+ if (driver->client_map[i].pid == process_id) {
+ driver->data_ready[i] |= PKT_TYPE;
+ break;
+ }
+ wake_up_interruptible(&driver->wait_q);
+ mutex_unlock(&driver->diagchar_mutex);
+}
+
+void diag_send_data(struct diag_master_table entry, unsigned char *buf,
+ int len, int type)
+{
+ driver->pkt_length = len;
+ if (entry.process_id != NON_APPS_PROC && type != MODEM_DATA) {
+ diag_update_pkt_buffer(buf);
+ diag_update_sleeping_process(entry.process_id);
+ } else {
+ if (len > 0) {
+ if (entry.client_id == MODEM_PROC && driver->ch)
+ smd_write(driver->ch, buf, len);
+ else if (entry.client_id == QDSP_PROC &&
+ driver->chqdsp)
+ smd_write(driver->chqdsp, buf, len);
+ else if (entry.client_id == WCNSS_PROC &&
+ driver->ch_wcnss)
+ smd_write(driver->ch_wcnss, buf, len);
+ else
+ pr_alert("diag: incorrect channel");
+ }
+ }
+}
+
+static int diag_process_apps_pkt(unsigned char *buf, int len)
+{
+ uint16_t subsys_cmd_code;
+ int subsys_id, ssid_first, ssid_last, ssid_range;
+ int packet_type = 1, i, cmd_code;
+ unsigned char *temp = buf;
+ int data_type;
+#if defined(CONFIG_DIAG_OVER_USB)
+ int payload_length;
+ unsigned char *ptr;
+#endif
+
+ /* Check for registered clients and forward packet to apropriate proc */
+ cmd_code = (int)(*(char *)buf);
+ temp++;
+ subsys_id = (int)(*(char *)temp);
+ temp++;
+ subsys_cmd_code = *(uint16_t *)temp;
+ temp += 2;
+ data_type = APPS_DATA;
+ /* Dont send any command other than mode reset */
+ if (cpu_is_msm8960() && cmd_code == MODE_CMD) {
+ if (subsys_id != RESET_ID)
+ data_type = MODEM_DATA;
+ }
+
+ pr_debug("diag: %d %d %d", cmd_code, subsys_id, subsys_cmd_code);
+ for (i = 0; i < diag_max_registration; i++) {
+ entry = driver->table[i];
+ if (entry.process_id != NO_PROCESS) {
+ if (entry.cmd_code == cmd_code && entry.subsys_id ==
+ subsys_id && entry.cmd_code_lo <=
+ subsys_cmd_code &&
+ entry.cmd_code_hi >= subsys_cmd_code) {
+ diag_send_data(entry, buf, len, data_type);
+ packet_type = 0;
+ } else if (entry.cmd_code == 255
+ && cmd_code == 75) {
+ if (entry.subsys_id ==
+ subsys_id &&
+ entry.cmd_code_lo <=
+ subsys_cmd_code &&
+ entry.cmd_code_hi >=
+ subsys_cmd_code) {
+ diag_send_data(entry, buf, len,
+ data_type);
+ packet_type = 0;
+ }
+ } else if (entry.cmd_code == 255 &&
+ entry.subsys_id == 255) {
+ if (entry.cmd_code_lo <=
+ cmd_code &&
+ entry.
+ cmd_code_hi >= cmd_code) {
+ diag_send_data(entry, buf, len,
+ data_type);
+ packet_type = 0;
+ }
+ }
+ }
+ }
+ /* set event mask */
+ if (*buf == 0x82) {
+ buf += 4;
+ diag_update_event_mask(buf, 1, *(uint16_t *)buf);
+ diag_update_userspace_clients(EVENT_MASKS_TYPE);
+ }
+ /* event mask change */
+ else if ((*buf == 0x60) && (*(buf+1) == 0x0)) {
+ diag_update_event_mask(buf+1, 0, 0);
+ diag_update_userspace_clients(EVENT_MASKS_TYPE);
+#if defined(CONFIG_DIAG_OVER_USB)
+ /* Check for Apps Only 8960 */
+ if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID)) {
+ /* echo response back for apps only DIAG */
+ driver->apps_rsp_buf[0] = 0x60;
+ driver->apps_rsp_buf[1] = 0x0;
+ driver->apps_rsp_buf[2] = 0x0;
+ ENCODE_RSP_AND_SEND(2);
+ return 0;
+ }
+#endif
+ }
+ /* Set log masks */
+ else if (*buf == 0x73 && *(int *)(buf+4) == 3) {
+ buf += 8;
+ /* Read Equip ID and pass as first param below*/
+ diag_update_log_mask(*(int *)buf, buf+8, *(int *)(buf+4));
+ diag_update_userspace_clients(LOG_MASKS_TYPE);
+#if defined(CONFIG_DIAG_OVER_USB)
+ /* Check for Apps Only 8960 */
+ if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID)) {
+ /* echo response back for Apps only DIAG */
+ driver->apps_rsp_buf[0] = 0x73;
+ *(int *)(driver->apps_rsp_buf + 4) = 0x3; /* op. ID */
+ *(int *)(driver->apps_rsp_buf + 8) = 0x0; /* success */
+ payload_length = 8 + ((*(int *)(buf + 4)) + 7)/8;
+ for (i = 0; i < payload_length; i++)
+ *(int *)(driver->apps_rsp_buf+12+i) =
+ *(buf+8+i);
+ ENCODE_RSP_AND_SEND(12 + payload_length - 1);
+ return 0;
+ }
+#endif
+ }
+ /* Check for set message mask */
+ else if ((*buf == 0x7d) && (*(buf+1) == 0x4)) {
+ ssid_first = *(uint16_t *)(buf + 2);
+ ssid_last = *(uint16_t *)(buf + 4);
+ ssid_range = 4 * (ssid_last - ssid_first + 1);
+ diag_update_msg_mask(ssid_first, ssid_last , buf + 8);
+ diag_update_userspace_clients(MSG_MASKS_TYPE);
+#if defined(CONFIG_DIAG_OVER_USB)
+ if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID)) {
+ /* echo response back for apps only DIAG */
+ for (i = 0; i < 8 + ssid_range; i++)
+ *(driver->apps_rsp_buf + i) = *(buf+i);
+ ENCODE_RSP_AND_SEND(8 + ssid_range - 1);
+ return 0;
+ }
+#endif
+ }
+#if defined(CONFIG_DIAG_OVER_USB)
+ /* Check for Apps Only 8960 & get event mask request */
+ else if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID)
+ && *buf == 0x81) {
+ driver->apps_rsp_buf[0] = 0x81;
+ driver->apps_rsp_buf[1] = 0x0;
+ *(uint16_t *)(driver->apps_rsp_buf + 2) = 0x0;
+ *(uint16_t *)(driver->apps_rsp_buf + 4) = EVENT_LAST_ID + 1;
+ for (i = 0; i < EVENT_LAST_ID/8 + 1; i++)
+ *(unsigned char *)(driver->apps_rsp_buf + 6 + i) = 0x0;
+ ENCODE_RSP_AND_SEND(6 + EVENT_LAST_ID/8);
+ return 0;
+ }
+ /* Get log ID range & Check for Apps Only 8960 */
+ else if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID)
+ && (*buf == 0x73) && *(int *)(buf+4) == 1) {
+ driver->apps_rsp_buf[0] = 0x73;
+ *(int *)(driver->apps_rsp_buf + 4) = 0x1; /* operation ID */
+ *(int *)(driver->apps_rsp_buf + 8) = 0x0; /* success code */
+ *(int *)(driver->apps_rsp_buf + 12) = LOG_GET_ITEM_NUM(LOG_0);
+ *(int *)(driver->apps_rsp_buf + 16) = LOG_GET_ITEM_NUM(LOG_1);
+ *(int *)(driver->apps_rsp_buf + 20) = LOG_GET_ITEM_NUM(LOG_2);
+ *(int *)(driver->apps_rsp_buf + 24) = LOG_GET_ITEM_NUM(LOG_3);
+ *(int *)(driver->apps_rsp_buf + 28) = LOG_GET_ITEM_NUM(LOG_4);
+ *(int *)(driver->apps_rsp_buf + 32) = LOG_GET_ITEM_NUM(LOG_5);
+ *(int *)(driver->apps_rsp_buf + 36) = LOG_GET_ITEM_NUM(LOG_6);
+ *(int *)(driver->apps_rsp_buf + 40) = LOG_GET_ITEM_NUM(LOG_7);
+ *(int *)(driver->apps_rsp_buf + 44) = LOG_GET_ITEM_NUM(LOG_8);
+ *(int *)(driver->apps_rsp_buf + 48) = LOG_GET_ITEM_NUM(LOG_9);
+ *(int *)(driver->apps_rsp_buf + 52) = LOG_GET_ITEM_NUM(LOG_10);
+ *(int *)(driver->apps_rsp_buf + 56) = LOG_GET_ITEM_NUM(LOG_11);
+ *(int *)(driver->apps_rsp_buf + 60) = LOG_GET_ITEM_NUM(LOG_12);
+ *(int *)(driver->apps_rsp_buf + 64) = LOG_GET_ITEM_NUM(LOG_13);
+ *(int *)(driver->apps_rsp_buf + 68) = LOG_GET_ITEM_NUM(LOG_14);
+ *(int *)(driver->apps_rsp_buf + 72) = LOG_GET_ITEM_NUM(LOG_15);
+ ENCODE_RSP_AND_SEND(75);
+ return 0;
+ }
+ /* Respond to Get SSID Range request message */
+ else if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID)
+ && (*buf == 0x7d) && (*(buf+1) == 0x1)) {
+ driver->apps_rsp_buf[0] = 0x7d;
+ driver->apps_rsp_buf[1] = 0x1;
+ driver->apps_rsp_buf[2] = 0x1;
+ driver->apps_rsp_buf[3] = 0x0;
+ *(int *)(driver->apps_rsp_buf + 4) = MSG_MASK_TBL_CNT;
+ *(uint16_t *)(driver->apps_rsp_buf + 8) = MSG_SSID_0;
+ *(uint16_t *)(driver->apps_rsp_buf + 10) = MSG_SSID_0_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 12) = MSG_SSID_1;
+ *(uint16_t *)(driver->apps_rsp_buf + 14) = MSG_SSID_1_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 16) = MSG_SSID_2;
+ *(uint16_t *)(driver->apps_rsp_buf + 18) = MSG_SSID_2_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 20) = MSG_SSID_3;
+ *(uint16_t *)(driver->apps_rsp_buf + 22) = MSG_SSID_3_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 24) = MSG_SSID_4;
+ *(uint16_t *)(driver->apps_rsp_buf + 26) = MSG_SSID_4_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 28) = MSG_SSID_5;
+ *(uint16_t *)(driver->apps_rsp_buf + 30) = MSG_SSID_5_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 32) = MSG_SSID_6;
+ *(uint16_t *)(driver->apps_rsp_buf + 34) = MSG_SSID_6_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 36) = MSG_SSID_7;
+ *(uint16_t *)(driver->apps_rsp_buf + 38) = MSG_SSID_7_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 40) = MSG_SSID_8;
+ *(uint16_t *)(driver->apps_rsp_buf + 42) = MSG_SSID_8_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 44) = MSG_SSID_9;
+ *(uint16_t *)(driver->apps_rsp_buf + 46) = MSG_SSID_9_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 48) = MSG_SSID_10;
+ *(uint16_t *)(driver->apps_rsp_buf + 50) = MSG_SSID_10_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 52) = MSG_SSID_11;
+ *(uint16_t *)(driver->apps_rsp_buf + 54) = MSG_SSID_11_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 56) = MSG_SSID_12;
+ *(uint16_t *)(driver->apps_rsp_buf + 58) = MSG_SSID_12_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 60) = MSG_SSID_13;
+ *(uint16_t *)(driver->apps_rsp_buf + 62) = MSG_SSID_13_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 64) = MSG_SSID_14;
+ *(uint16_t *)(driver->apps_rsp_buf + 66) = MSG_SSID_14_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 68) = MSG_SSID_15;
+ *(uint16_t *)(driver->apps_rsp_buf + 70) = MSG_SSID_15_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 72) = MSG_SSID_16;
+ *(uint16_t *)(driver->apps_rsp_buf + 74) = MSG_SSID_16_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 76) = MSG_SSID_17;
+ *(uint16_t *)(driver->apps_rsp_buf + 78) = MSG_SSID_17_LAST;
+ *(uint16_t *)(driver->apps_rsp_buf + 80) = MSG_SSID_18;
+ *(uint16_t *)(driver->apps_rsp_buf + 82) = MSG_SSID_18_LAST;
+ ENCODE_RSP_AND_SEND(83);
+ return 0;
+ }
+ /* Check for AO8960 Respond to Get Subsys Build mask */
+ else if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID)
+ && (*buf == 0x7d) && (*(buf+1) == 0x2)) {
+ ssid_first = *(uint16_t *)(buf + 2);
+ ssid_last = *(uint16_t *)(buf + 4);
+ ssid_range = 4 * (ssid_last - ssid_first + 1);
+ /* frame response */
+ driver->apps_rsp_buf[0] = 0x7d;
+ driver->apps_rsp_buf[1] = 0x2;
+ *(uint16_t *)(driver->apps_rsp_buf + 2) = ssid_first;
+ *(uint16_t *)(driver->apps_rsp_buf + 4) = ssid_last;
+ driver->apps_rsp_buf[6] = 0x1;
+ driver->apps_rsp_buf[7] = 0x0;
+ ptr = driver->apps_rsp_buf + 8;
+ /* bld time masks */
+ switch (ssid_first) {
+ case MSG_SSID_0:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_0[i/4];
+ break;
+ case MSG_SSID_1:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_1[i/4];
+ break;
+ case MSG_SSID_2:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_2[i/4];
+ break;
+ case MSG_SSID_3:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_3[i/4];
+ break;
+ case MSG_SSID_4:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_4[i/4];
+ break;
+ case MSG_SSID_5:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_5[i/4];
+ break;
+ case MSG_SSID_6:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_6[i/4];
+ break;
+ case MSG_SSID_7:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_7[i/4];
+ break;
+ case MSG_SSID_8:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_8[i/4];
+ break;
+ case MSG_SSID_9:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_9[i/4];
+ break;
+ case MSG_SSID_10:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_10[i/4];
+ break;
+ case MSG_SSID_11:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_11[i/4];
+ break;
+ case MSG_SSID_12:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_12[i/4];
+ break;
+ case MSG_SSID_13:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_13[i/4];
+ break;
+ case MSG_SSID_14:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_14[i/4];
+ break;
+ case MSG_SSID_15:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_15[i/4];
+ break;
+ case MSG_SSID_16:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_16[i/4];
+ break;
+ case MSG_SSID_17:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_17[i/4];
+ break;
+ case MSG_SSID_18:
+ for (i = 0; i < ssid_range; i += 4)
+ *(int *)(ptr + i) = msg_bld_masks_18[i/4];
+ break;
+ }
+ ENCODE_RSP_AND_SEND(8 + ssid_range - 1);
+ return 0;
+ }
+ /* Check for download command */
+ else if ((cpu_is_msm8x60() || cpu_is_msm8960()) && (*buf == 0x3A)) {
+ /* send response back */
+ driver->apps_rsp_buf[0] = *buf;
+ ENCODE_RSP_AND_SEND(0);
+ msleep(5000);
+ /* call download API */
+ msm_set_restart_mode(RESTART_DLOAD);
+ printk(KERN_CRIT "diag: download mode set, Rebooting SoC..\n");
+ kernel_restart(NULL);
+ /* Not required, represents that command isnt sent to modem */
+ return 0;
+ }
+ /* Check for ID for NO MODEM present */
+ else if (!(driver->ch)) {
+ /* Respond to polling for Apps only DIAG */
+ if ((*buf == 0x4b) && (*(buf+1) == 0x32) &&
+ (*(buf+2) == 0x03)) {
+ for (i = 0; i < 3; i++)
+ driver->apps_rsp_buf[i] = *(buf+i);
+ for (i = 0; i < 13; i++)
+ driver->apps_rsp_buf[i+3] = 0;
+
+ ENCODE_RSP_AND_SEND(15);
+ return 0;
+ }
+ /* respond to 0x0 command */
+ else if (*buf == 0x00) {
+ for (i = 0; i < 55; i++)
+ driver->apps_rsp_buf[i] = 0;
+
+ ENCODE_RSP_AND_SEND(54);
+ return 0;
+ }
+ /* respond to 0x7c command */
+ else if (*buf == 0x7c) {
+ driver->apps_rsp_buf[0] = 0x7c;
+ for (i = 1; i < 8; i++)
+ driver->apps_rsp_buf[i] = 0;
+ /* Tools ID for APQ 8060 */
+ *(int *)(driver->apps_rsp_buf + 8) =
+ chk_config_get_id();
+ *(unsigned char *)(driver->apps_rsp_buf + 12) = '\0';
+ *(unsigned char *)(driver->apps_rsp_buf + 13) = '\0';
+ ENCODE_RSP_AND_SEND(13);
+ return 0;
+ }
+ }
+#endif
+ return packet_type;
+}
+
+#ifdef CONFIG_DIAG_OVER_USB
+void diag_send_error_rsp(int index)
+{
+ int i;
+ driver->apps_rsp_buf[0] = 0x13; /* error code 13 */
+ for (i = 0; i < index; i++)
+ driver->apps_rsp_buf[i+1] = *(driver->hdlc_buf+i);
+ ENCODE_RSP_AND_SEND(index - 3);
+}
+#else
+static inline void diag_send_error_rsp(int index) {}
+#endif
+
+void diag_process_hdlc(void *data, unsigned len)
+{
+ struct diag_hdlc_decode_type hdlc;
+ int ret, type = 0;
+ pr_debug("diag: HDLC decode fn, len of data %d\n", len);
+ hdlc.dest_ptr = driver->hdlc_buf;
+ hdlc.dest_size = USB_MAX_OUT_BUF;
+ hdlc.src_ptr = data;
+ hdlc.src_size = len;
+ hdlc.src_idx = 0;
+ hdlc.dest_idx = 0;
+ hdlc.escaping = 0;
+
+ ret = diag_hdlc_decode(&hdlc);
+
+ if (ret)
+ type = diag_process_apps_pkt(driver->hdlc_buf,
+ hdlc.dest_idx - 3);
+ else if (driver->debug_flag) {
+ printk(KERN_ERR "Packet dropped due to bad HDLC coding/CRC"
+ " errors or partial packet received, packet"
+ " length = %d\n", len);
+ print_hex_dump(KERN_DEBUG, "Dropped Packet Data: ", 16, 1,
+ DUMP_PREFIX_ADDRESS, data, len, 1);
+ driver->debug_flag = 0;
+ }
+ /* send error responses from APPS for Central Routing */
+ if (type == 1 && chk_config_get_id() == AO8960_TOOLS_ID) {
+ diag_send_error_rsp(hdlc.dest_idx);
+ type = 0;
+ }
+ /* implies this packet is NOT meant for apps */
+ if (!(driver->ch) && type == 1) {
+ if (chk_config_get_id() == AO8960_TOOLS_ID) {
+ diag_send_error_rsp(hdlc.dest_idx);
+ } else { /* APQ 8060, Let Q6 respond */
+ if (driver->chqdsp)
+ smd_write(driver->chqdsp, driver->hdlc_buf,
+ hdlc.dest_idx - 3);
+ }
+ type = 0;
+ }
+
+#ifdef DIAG_DEBUG
+ pr_debug("diag: hdlc.dest_idx = %d", hdlc.dest_idx);
+ for (i = 0; i < hdlc.dest_idx; i++)
+ printk(KERN_DEBUG "\t%x", *(((unsigned char *)
+ driver->hdlc_buf)+i));
+#endif /* DIAG DEBUG */
+ /* ignore 2 bytes for CRC, one for 7E and send */
+ if ((driver->ch) && (ret) && (type) && (hdlc.dest_idx > 3)) {
+ APPEND_DEBUG('g');
+ smd_write(driver->ch, driver->hdlc_buf, hdlc.dest_idx - 3);
+ APPEND_DEBUG('h');
+#ifdef DIAG_DEBUG
+ printk(KERN_INFO "writing data to SMD, pkt length %d\n", len);
+ print_hex_dump(KERN_DEBUG, "Written Packet Data to SMD: ", 16,
+ 1, DUMP_PREFIX_ADDRESS, data, len, 1);
+#endif /* DIAG DEBUG */
+ }
+}
+
+#ifdef CONFIG_DIAG_OVER_USB
+#define N_LEGACY_WRITE (driver->poolsize + 5) /* 2+1 for modem ; 2 for q6 */
+#define N_LEGACY_READ 1
+
+int diagfwd_connect(void)
+{
+ int err;
+
+ printk(KERN_DEBUG "diag: USB connected\n");
+ err = usb_diag_alloc_req(driver->legacy_ch, N_LEGACY_WRITE,
+ N_LEGACY_READ);
+ if (err)
+ printk(KERN_ERR "diag: unable to alloc USB req on legacy ch");
+
+ driver->usb_connected = 1;
+ driver->in_busy_1 = 0;
+ driver->in_busy_2 = 0;
+ driver->in_busy_qdsp_1 = 0;
+ driver->in_busy_qdsp_2 = 0;
+ driver->in_busy_wcnss = 0;
+
+ /* Poll SMD channels to check for data*/
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_work));
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_qdsp_work));
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_wcnss_work));
+ /* Poll USB channel to check for data*/
+ queue_work(driver->diag_wq, &(driver->diag_read_work));
+#ifdef CONFIG_DIAG_SDIO_PIPE
+ if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) {
+ if (driver->mdm_ch && !IS_ERR(driver->mdm_ch))
+ diagfwd_connect_sdio();
+ else
+ printk(KERN_INFO "diag: No USB MDM ch");
+ }
+#endif
+ return 0;
+}
+
+int diagfwd_disconnect(void)
+{
+ printk(KERN_DEBUG "diag: USB disconnected\n");
+ driver->usb_connected = 0;
+ driver->in_busy_1 = 1;
+ driver->in_busy_2 = 1;
+ driver->in_busy_qdsp_1 = 1;
+ driver->in_busy_qdsp_2 = 1;
+ driver->in_busy_wcnss = 1;
+ driver->debug_flag = 1;
+ usb_diag_free_req(driver->legacy_ch);
+#ifdef CONFIG_DIAG_SDIO_PIPE
+ if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa())
+ if (driver->mdm_ch && !IS_ERR(driver->mdm_ch))
+ diagfwd_disconnect_sdio();
+#endif
+ /* TBD - notify and flow control SMD */
+ return 0;
+}
+
+int diagfwd_write_complete(struct diag_request *diag_write_ptr)
+{
+ unsigned char *buf = diag_write_ptr->buf;
+ /*Determine if the write complete is for data from modem/apps/q6 */
+ /* Need a context variable here instead */
+ if (buf == (void *)driver->buf_in_1) {
+ driver->in_busy_1 = 0;
+ APPEND_DEBUG('o');
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_work));
+ } else if (buf == (void *)driver->buf_in_2) {
+ driver->in_busy_2 = 0;
+ APPEND_DEBUG('O');
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_work));
+ } else if (buf == (void *)driver->buf_in_qdsp_1) {
+ driver->in_busy_qdsp_1 = 0;
+ APPEND_DEBUG('p');
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_qdsp_work));
+ } else if (buf == (void *)driver->buf_in_qdsp_2) {
+ driver->in_busy_qdsp_2 = 0;
+ APPEND_DEBUG('P');
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_qdsp_work));
+ } else if (buf == (void *)driver->buf_in_wcnss) {
+ driver->in_busy_wcnss = 0;
+ APPEND_DEBUG('R');
+ queue_work(driver->diag_wq,
+ &(driver->diag_read_smd_wcnss_work));
+ }
+#ifdef CONFIG_DIAG_SDIO_PIPE
+ else if (buf == (void *)driver->buf_in_sdio)
+ if (machine_is_msm8x60_fusion() ||
+ machine_is_msm8x60_fusn_ffa())
+ diagfwd_write_complete_sdio();
+ else
+ pr_err("diag: Incorrect buffer pointer while WRITE");
+#endif
+ else {
+ diagmem_free(driver, (unsigned char *)buf, POOL_TYPE_HDLC);
+ diagmem_free(driver, (unsigned char *)diag_write_ptr,
+ POOL_TYPE_WRITE_STRUCT);
+ APPEND_DEBUG('q');
+ }
+ return 0;
+}
+
+int diagfwd_read_complete(struct diag_request *diag_read_ptr)
+{
+ int status = diag_read_ptr->status;
+ unsigned char *buf = diag_read_ptr->buf;
+
+ /* Determine if the read complete is for data on legacy/mdm ch */
+ if (buf == (void *)driver->usb_buf_out) {
+ driver->read_len_legacy = diag_read_ptr->actual;
+ APPEND_DEBUG('s');
+#ifdef DIAG_DEBUG
+ printk(KERN_INFO "read data from USB, pkt length %d",
+ diag_read_ptr->actual);
+ print_hex_dump(KERN_DEBUG, "Read Packet Data from USB: ", 16, 1,
+ DUMP_PREFIX_ADDRESS, diag_read_ptr->buf,
+ diag_read_ptr->actual, 1);
+#endif /* DIAG DEBUG */
+ if (driver->logging_mode == USB_MODE) {
+ if (status != -ECONNRESET && status != -ESHUTDOWN)
+ queue_work(driver->diag_wq,
+ &(driver->diag_proc_hdlc_work));
+ else
+ queue_work(driver->diag_wq,
+ &(driver->diag_read_work));
+ }
+ }
+#ifdef CONFIG_DIAG_SDIO_PIPE
+ else if (buf == (void *)driver->usb_buf_mdm_out) {
+ if (machine_is_msm8x60_fusion() ||
+ machine_is_msm8x60_fusn_ffa()) {
+ driver->read_len_mdm = diag_read_ptr->actual;
+ diagfwd_read_complete_sdio();
+ } else
+ pr_err("diag: Incorrect buffer pointer while READ");
+ }
+#endif
+ else
+ printk(KERN_ERR "diag: Unknown buffer ptr from USB");
+
+ return 0;
+}
+
+void diag_read_work_fn(struct work_struct *work)
+{
+ APPEND_DEBUG('d');
+ driver->usb_read_ptr->buf = driver->usb_buf_out;
+ driver->usb_read_ptr->length = USB_MAX_OUT_BUF;
+ usb_diag_read(driver->legacy_ch, driver->usb_read_ptr);
+ APPEND_DEBUG('e');
+}
+
+void diag_process_hdlc_fn(struct work_struct *work)
+{
+ APPEND_DEBUG('D');
+ diag_process_hdlc(driver->usb_buf_out, driver->read_len_legacy);
+ diag_read_work_fn(work);
+ APPEND_DEBUG('E');
+}
+
+void diag_usb_legacy_notifier(void *priv, unsigned event,
+ struct diag_request *d_req)
+{
+ switch (event) {
+ case USB_DIAG_CONNECT:
+ diagfwd_connect();
+ break;
+ case USB_DIAG_DISCONNECT:
+ diagfwd_disconnect();
+ break;
+ case USB_DIAG_READ_DONE:
+ diagfwd_read_complete(d_req);
+ break;
+ case USB_DIAG_WRITE_DONE:
+ diagfwd_write_complete(d_req);
+ break;
+ default:
+ printk(KERN_ERR "Unknown event from USB diag\n");
+ break;
+ }
+}
+
+#endif /* DIAG OVER USB */
+
+static void diag_smd_notify(void *ctxt, unsigned event)
+{
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_work));
+}
+
+#if defined(CONFIG_MSM_N_WAY_SMD)
+static void diag_smd_qdsp_notify(void *ctxt, unsigned event)
+{
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_qdsp_work));
+}
+#endif
+
+static void diag_smd_wcnss_notify(void *ctxt, unsigned event)
+{
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_wcnss_work));
+}
+
+static int diag_smd_probe(struct platform_device *pdev)
+{
+ int r = 0;
+
+ if (pdev->id == SMD_APPS_MODEM)
+ r = smd_open("DIAG", &driver->ch, driver, diag_smd_notify);
+#if defined(CONFIG_MSM_N_WAY_SMD)
+ if (pdev->id == SMD_APPS_QDSP)
+ r = smd_named_open_on_edge("DIAG", SMD_APPS_QDSP
+ , &driver->chqdsp, driver, diag_smd_qdsp_notify);
+#endif
+ if (pdev->id == SMD_APPS_WCNSS)
+ r = smd_named_open_on_edge("APPS_RIVA_DATA", SMD_APPS_WCNSS
+ , &driver->ch_wcnss, driver, diag_smd_wcnss_notify);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ pr_debug("diag: open SMD port, Id = %d, r = %d\n", pdev->id, r);
+
+ return 0;
+}
+
+static int diagfwd_runtime_suspend(struct device *dev)
+{
+ dev_dbg(dev, "pm_runtime: suspending...\n");
+ return 0;
+}
+
+static int diagfwd_runtime_resume(struct device *dev)
+{
+ dev_dbg(dev, "pm_runtime: resuming...\n");
+ return 0;
+}
+
+static const struct dev_pm_ops diagfwd_dev_pm_ops = {
+ .runtime_suspend = diagfwd_runtime_suspend,
+ .runtime_resume = diagfwd_runtime_resume,
+};
+
+static struct platform_driver msm_smd_ch1_driver = {
+
+ .probe = diag_smd_probe,
+ .driver = {
+ .name = "DIAG",
+ .owner = THIS_MODULE,
+ .pm = &diagfwd_dev_pm_ops,
+ },
+};
+
+static struct platform_driver diag_smd_lite_driver = {
+
+ .probe = diag_smd_probe,
+ .driver = {
+ .name = "APPS_RIVA_DATA",
+ .owner = THIS_MODULE,
+ .pm = &diagfwd_dev_pm_ops,
+ },
+};
+
+void diagfwd_init(void)
+{
+ diag_debug_buf_idx = 0;
+ driver->read_len_legacy = 0;
+ if (driver->buf_in_1 == NULL) {
+ driver->buf_in_1 = kzalloc(IN_BUF_SIZE, GFP_KERNEL);
+ if (driver->buf_in_1 == NULL)
+ goto err;
+ }
+ if (driver->buf_in_2 == NULL) {
+ driver->buf_in_2 = kzalloc(IN_BUF_SIZE, GFP_KERNEL);
+ if (driver->buf_in_2 == NULL)
+ goto err;
+ }
+ if (driver->buf_in_qdsp_1 == NULL) {
+ driver->buf_in_qdsp_1 = kzalloc(IN_BUF_SIZE, GFP_KERNEL);
+ if (driver->buf_in_qdsp_1 == NULL)
+ goto err;
+ }
+ if (driver->buf_in_qdsp_2 == NULL) {
+ driver->buf_in_qdsp_2 = kzalloc(IN_BUF_SIZE, GFP_KERNEL);
+ if (driver->buf_in_qdsp_2 == NULL)
+ goto err;
+ }
+ if (driver->buf_in_wcnss == NULL) {
+ driver->buf_in_wcnss = kzalloc(IN_BUF_SIZE, GFP_KERNEL);
+ if (driver->buf_in_wcnss == NULL)
+ goto err;
+ }
+ if (driver->usb_buf_out == NULL &&
+ (driver->usb_buf_out = kzalloc(USB_MAX_OUT_BUF,
+ GFP_KERNEL)) == NULL)
+ goto err;
+ if (driver->hdlc_buf == NULL
+ && (driver->hdlc_buf = kzalloc(HDLC_MAX, GFP_KERNEL)) == NULL)
+ goto err;
+ if (driver->msg_masks == NULL
+ && (driver->msg_masks = kzalloc(MSG_MASK_SIZE,
+ GFP_KERNEL)) == NULL)
+ goto err;
+ if (driver->log_masks == NULL &&
+ (driver->log_masks = kzalloc(LOG_MASK_SIZE, GFP_KERNEL)) == NULL)
+ goto err;
+ driver->log_masks_length = 8*MAX_EQUIP_ID;
+ if (driver->event_masks == NULL &&
+ (driver->event_masks = kzalloc(EVENT_MASK_SIZE,
+ GFP_KERNEL)) == NULL)
+ goto err;
+ if (driver->client_map == NULL &&
+ (driver->client_map = kzalloc
+ ((driver->num_clients) * sizeof(struct diag_client_map),
+ GFP_KERNEL)) == NULL)
+ goto err;
+ if (driver->buf_tbl == NULL)
+ driver->buf_tbl = kzalloc(buf_tbl_size *
+ sizeof(struct diag_write_device), GFP_KERNEL);
+ if (driver->buf_tbl == NULL)
+ goto err;
+ if (driver->data_ready == NULL &&
+ (driver->data_ready = kzalloc(driver->num_clients * sizeof(int)
+ , GFP_KERNEL)) == NULL)
+ goto err;
+ if (driver->table == NULL &&
+ (driver->table = kzalloc(diag_max_registration*
+ sizeof(struct diag_master_table),
+ GFP_KERNEL)) == NULL)
+ goto err;
+ if (driver->write_ptr_1 == NULL) {
+ driver->write_ptr_1 = kzalloc(
+ sizeof(struct diag_request), GFP_KERNEL);
+ if (driver->write_ptr_1 == NULL)
+ goto err;
+ }
+ if (driver->write_ptr_2 == NULL) {
+ driver->write_ptr_2 = kzalloc(
+ sizeof(struct diag_request), GFP_KERNEL);
+ if (driver->write_ptr_2 == NULL)
+ goto err;
+ }
+ if (driver->write_ptr_qdsp_1 == NULL) {
+ driver->write_ptr_qdsp_1 = kzalloc(
+ sizeof(struct diag_request), GFP_KERNEL);
+ if (driver->write_ptr_qdsp_1 == NULL)
+ goto err;
+ }
+ if (driver->write_ptr_qdsp_2 == NULL) {
+ driver->write_ptr_qdsp_2 = kzalloc(
+ sizeof(struct diag_request), GFP_KERNEL);
+ if (driver->write_ptr_qdsp_2 == NULL)
+ goto err;
+ }
+ if (driver->write_ptr_wcnss == NULL) {
+ driver->write_ptr_wcnss = kzalloc(
+ sizeof(struct diag_request), GFP_KERNEL);
+ if (driver->write_ptr_wcnss == NULL)
+ goto err;
+ }
+ if (driver->usb_read_ptr == NULL) {
+ driver->usb_read_ptr = kzalloc(
+ sizeof(struct diag_request), GFP_KERNEL);
+ if (driver->usb_read_ptr == NULL)
+ goto err;
+ }
+ if (driver->pkt_buf == NULL &&
+ (driver->pkt_buf = kzalloc(PKT_SIZE,
+ GFP_KERNEL)) == NULL)
+ goto err;
+ if (driver->apps_rsp_buf == NULL) {
+ driver->apps_rsp_buf = kzalloc(500, GFP_KERNEL);
+ if (driver->apps_rsp_buf == NULL)
+ goto err;
+ }
+ driver->diag_wq = create_singlethread_workqueue("diag_wq");
+#ifdef CONFIG_DIAG_OVER_USB
+ INIT_WORK(&(driver->diag_proc_hdlc_work), diag_process_hdlc_fn);
+ INIT_WORK(&(driver->diag_read_work), diag_read_work_fn);
+ driver->legacy_ch = usb_diag_open(DIAG_LEGACY, driver,
+ diag_usb_legacy_notifier);
+ if (IS_ERR(driver->legacy_ch)) {
+ printk(KERN_ERR "Unable to open USB diag legacy channel\n");
+ goto err;
+ }
+#endif
+ platform_driver_register(&msm_smd_ch1_driver);
+ platform_driver_register(&diag_smd_lite_driver);
+
+ return;
+err:
+ pr_err("diag: Could not initialize diag buffers");
+ kfree(driver->buf_in_1);
+ kfree(driver->buf_in_2);
+ kfree(driver->buf_in_qdsp_1);
+ kfree(driver->buf_in_qdsp_2);
+ kfree(driver->buf_in_wcnss);
+ kfree(driver->usb_buf_out);
+ kfree(driver->hdlc_buf);
+ kfree(driver->msg_masks);
+ kfree(driver->log_masks);
+ kfree(driver->event_masks);
+ kfree(driver->client_map);
+ kfree(driver->buf_tbl);
+ kfree(driver->data_ready);
+ kfree(driver->table);
+ kfree(driver->pkt_buf);
+ kfree(driver->write_ptr_1);
+ kfree(driver->write_ptr_2);
+ kfree(driver->write_ptr_qdsp_1);
+ kfree(driver->write_ptr_qdsp_2);
+ kfree(driver->write_ptr_wcnss);
+ kfree(driver->usb_read_ptr);
+ kfree(driver->apps_rsp_buf);
+ if (driver->diag_wq)
+ destroy_workqueue(driver->diag_wq);
+}
+
+void diagfwd_exit(void)
+{
+ smd_close(driver->ch);
+ smd_close(driver->chqdsp);
+ smd_close(driver->ch_wcnss);
+ driver->ch = 0; /* SMD can make this NULL */
+ driver->chqdsp = 0;
+ driver->ch_wcnss = 0;
+#ifdef CONFIG_DIAG_OVER_USB
+ if (driver->usb_connected)
+ usb_diag_free_req(driver->legacy_ch);
+ usb_diag_close(driver->legacy_ch);
+#endif
+ platform_driver_unregister(&msm_smd_ch1_driver);
+ platform_driver_unregister(&diag_smd_lite_driver);
+ kfree(driver->buf_in_1);
+ kfree(driver->buf_in_2);
+ kfree(driver->buf_in_qdsp_1);
+ kfree(driver->buf_in_qdsp_2);
+ kfree(driver->buf_in_wcnss);
+ kfree(driver->usb_buf_out);
+ kfree(driver->hdlc_buf);
+ kfree(driver->msg_masks);
+ kfree(driver->log_masks);
+ kfree(driver->event_masks);
+ kfree(driver->client_map);
+ kfree(driver->buf_tbl);
+ kfree(driver->data_ready);
+ kfree(driver->table);
+ kfree(driver->pkt_buf);
+ kfree(driver->write_ptr_1);
+ kfree(driver->write_ptr_2);
+ kfree(driver->write_ptr_qdsp_1);
+ kfree(driver->write_ptr_qdsp_2);
+ kfree(driver->write_ptr_wcnss);
+ kfree(driver->usb_read_ptr);
+ kfree(driver->apps_rsp_buf);
+ destroy_workqueue(driver->diag_wq);
+}
diff --git a/drivers/char/diag/diagfwd.h b/drivers/char/diag/diagfwd.h
new file mode 100644
index 0000000..cc24cbc
--- /dev/null
+++ b/drivers/char/diag/diagfwd.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef DIAGFWD_H
+#define DIAGFWD_H
+
+#define NO_PROCESS 0
+#define NON_APPS_PROC -1
+
+void diagfwd_init(void);
+void diagfwd_exit(void);
+void diag_process_hdlc(void *data, unsigned len);
+void __diag_smd_send_req(void);
+void __diag_smd_qdsp_send_req(void);
+void __diag_smd_wcnss_send_req(void);
+void diag_usb_legacy_notifier(void *, unsigned, struct diag_request *);
+long diagchar_ioctl(struct file *, unsigned int, unsigned long);
+int diag_device_write(void *, int, struct diag_request *);
+int mask_request_validate(unsigned char mask_buf[]);
+int chk_config_get_id(void);
+/* State for diag forwarding */
+#ifdef CONFIG_DIAG_OVER_USB
+int diagfwd_connect(void);
+int diagfwd_disconnect(void);
+#endif
+extern int diag_debug_buf_idx;
+extern unsigned char diag_debug_buf[1024];
+
+#endif
diff --git a/drivers/char/diag/diagfwd_cntl.c b/drivers/char/diag/diagfwd_cntl.c
new file mode 100644
index 0000000..45226ba
--- /dev/null
+++ b/drivers/char/diag/diagfwd_cntl.c
@@ -0,0 +1,226 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/slab.h>
+#include <linux/diagchar.h>
+#include <linux/platform_device.h>
+#include "diagchar.h"
+#include "diagfwd.h"
+#include "diagfwd_cntl.h"
+
+#define HDR_SIZ 8
+
+static void diag_smd_cntl_send_req(int proc_num)
+{
+ int data_len = 0, type = -1, count_bytes = 0, j, r;
+ struct bindpkt_params_per_process *pkt_params =
+ kzalloc(sizeof(struct bindpkt_params_per_process), GFP_KERNEL);
+ struct diag_ctrl_msg *msg;
+ struct cmd_code_range *range;
+ struct bindpkt_params *temp;
+ void *buf = NULL;
+ smd_channel_t *smd_ch = NULL;
+
+ if (proc_num == MODEM_PROC) {
+ buf = driver->buf_in_cntl;
+ smd_ch = driver->ch_cntl;
+ } else if (proc_num == QDSP_PROC) {
+ buf = driver->buf_in_qdsp_cntl;
+ smd_ch = driver->chqdsp_cntl;
+ } else if (proc_num == WCNSS_PROC) {
+ buf = driver->buf_in_wcnss_cntl;
+ smd_ch = driver->ch_wcnss_cntl;
+ }
+
+ if (!smd_ch || !buf)
+ return;
+
+ r = smd_read_avail(smd_ch);
+ if (r > IN_BUF_SIZE) {
+ if (r < MAX_IN_BUF_SIZE) {
+ pr_err("diag: SMD CNTL sending pkt upto %d bytes", r);
+ buf = krealloc(buf, r, GFP_KERNEL);
+ } else {
+ pr_err("diag: CNTL pkt > %d bytes", MAX_IN_BUF_SIZE);
+ kfree(pkt_params);
+ return;
+ }
+ }
+ if (buf && r > 0) {
+ smd_read(smd_ch, buf, r);
+ while (count_bytes + HDR_SIZ <= r) {
+ type = *(uint32_t *)(buf);
+ data_len = *(uint32_t *)(buf + 4);
+ count_bytes = count_bytes+HDR_SIZ+data_len;
+ if (type == DIAG_CTRL_MSG_REG && r >= count_bytes) {
+ msg = buf+HDR_SIZ;
+ range = buf+HDR_SIZ+
+ sizeof(struct diag_ctrl_msg);
+ pkt_params->count = msg->count_entries;
+ temp = kzalloc(pkt_params->count * sizeof(struct
+ bindpkt_params), GFP_KERNEL);
+ for (j = 0; j < pkt_params->count; j++) {
+ temp->cmd_code = msg->cmd_code;
+ temp->subsys_id = msg->subsysid;
+ temp->client_id = proc_num;
+ temp->proc_id = proc_num;
+ temp->cmd_code_lo = range->cmd_code_lo;
+ temp->cmd_code_hi = range->cmd_code_hi;
+ range++;
+ temp++;
+ }
+ temp -= pkt_params->count;
+ pkt_params->params = temp;
+ diagchar_ioctl(NULL, DIAG_IOCTL_COMMAND_REG,
+ (unsigned long)pkt_params);
+ kfree(temp);
+ buf = buf + HDR_SIZ + data_len;
+ }
+ }
+ }
+ kfree(pkt_params);
+}
+
+void diag_read_smd_cntl_work_fn(struct work_struct *work)
+{
+ diag_smd_cntl_send_req(MODEM_PROC);
+}
+
+void diag_read_smd_qdsp_cntl_work_fn(struct work_struct *work)
+{
+ diag_smd_cntl_send_req(QDSP_PROC);
+}
+
+void diag_read_smd_wcnss_cntl_work_fn(struct work_struct *work)
+{
+ diag_smd_cntl_send_req(WCNSS_PROC);
+}
+
+static void diag_smd_cntl_notify(void *ctxt, unsigned event)
+{
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_cntl_work));
+}
+
+#if defined(CONFIG_MSM_N_WAY_SMD)
+static void diag_smd_qdsp_cntl_notify(void *ctxt, unsigned event)
+{
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_qdsp_cntl_work));
+}
+#endif
+
+static void diag_smd_wcnss_cntl_notify(void *ctxt, unsigned event)
+{
+ queue_work(driver->diag_wq, &(driver->diag_read_smd_wcnss_cntl_work));
+}
+
+static int diag_smd_cntl_probe(struct platform_device *pdev)
+{
+ int r = 0;
+
+ /* open control ports only on 8960 */
+ if (chk_config_get_id() == AO8960_TOOLS_ID) {
+ if (pdev->id == SMD_APPS_MODEM)
+ r = smd_open("DIAG_CNTL", &driver->ch_cntl, driver,
+ diag_smd_cntl_notify);
+ if (pdev->id == SMD_APPS_QDSP)
+ r = smd_named_open_on_edge("DIAG_CNTL", SMD_APPS_QDSP
+ , &driver->chqdsp_cntl, driver,
+ diag_smd_qdsp_cntl_notify);
+ if (pdev->id == SMD_APPS_WCNSS)
+ r = smd_named_open_on_edge("APPS_RIVA_CTRL",
+ SMD_APPS_WCNSS, &driver->ch_wcnss_cntl,
+ driver, diag_smd_wcnss_cntl_notify);
+ pr_debug("diag: open CNTL port, ID = %d,r = %d\n", pdev->id, r);
+ }
+ return 0;
+}
+
+static int diagfwd_cntl_runtime_suspend(struct device *dev)
+{
+ dev_dbg(dev, "pm_runtime: suspending...\n");
+ return 0;
+}
+
+static int diagfwd_cntl_runtime_resume(struct device *dev)
+{
+ dev_dbg(dev, "pm_runtime: resuming...\n");
+ return 0;
+}
+
+static const struct dev_pm_ops diagfwd_cntl_dev_pm_ops = {
+ .runtime_suspend = diagfwd_cntl_runtime_suspend,
+ .runtime_resume = diagfwd_cntl_runtime_resume,
+};
+
+static struct platform_driver msm_smd_ch1_cntl_driver = {
+
+ .probe = diag_smd_cntl_probe,
+ .driver = {
+ .name = "DIAG_CNTL",
+ .owner = THIS_MODULE,
+ .pm = &diagfwd_cntl_dev_pm_ops,
+ },
+};
+
+static struct platform_driver diag_smd_lite_cntl_driver = {
+
+ .probe = diag_smd_cntl_probe,
+ .driver = {
+ .name = "APPS_RIVA_CTRL",
+ .owner = THIS_MODULE,
+ .pm = &diagfwd_cntl_dev_pm_ops,
+ },
+};
+
+void diagfwd_cntl_init(void)
+{
+ if (driver->buf_in_cntl == NULL) {
+ driver->buf_in_cntl = kzalloc(IN_BUF_SIZE, GFP_KERNEL);
+ if (driver->buf_in_cntl == NULL)
+ goto err;
+ }
+ if (driver->buf_in_qdsp_cntl == NULL) {
+ driver->buf_in_qdsp_cntl = kzalloc(IN_BUF_SIZE, GFP_KERNEL);
+ if (driver->buf_in_qdsp_cntl == NULL)
+ goto err;
+ }
+ if (driver->buf_in_wcnss_cntl == NULL) {
+ driver->buf_in_wcnss_cntl = kzalloc(IN_BUF_SIZE, GFP_KERNEL);
+ if (driver->buf_in_wcnss_cntl == NULL)
+ goto err;
+ }
+ platform_driver_register(&msm_smd_ch1_cntl_driver);
+ platform_driver_register(&diag_smd_lite_cntl_driver);
+
+ return;
+err:
+ pr_err("diag: Could not initialize diag buffers");
+ kfree(driver->buf_in_cntl);
+ kfree(driver->buf_in_qdsp_cntl);
+ kfree(driver->buf_in_wcnss_cntl);
+}
+
+void diagfwd_cntl_exit(void)
+{
+ smd_close(driver->ch_cntl);
+ smd_close(driver->chqdsp_cntl);
+ smd_close(driver->ch_wcnss_cntl);
+ driver->ch_cntl = 0;
+ driver->chqdsp_cntl = 0;
+ driver->ch_wcnss_cntl = 0;
+ platform_driver_unregister(&msm_smd_ch1_cntl_driver);
+ platform_driver_unregister(&diag_smd_lite_cntl_driver);
+
+ kfree(driver->buf_in_cntl);
+ kfree(driver->buf_in_qdsp_cntl);
+ kfree(driver->buf_in_wcnss_cntl);
+}
diff --git a/drivers/char/diag/diagfwd_cntl.h b/drivers/char/diag/diagfwd_cntl.h
new file mode 100644
index 0000000..542138d
--- /dev/null
+++ b/drivers/char/diag/diagfwd_cntl.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef DIAGFWD_CNTL_H
+#define DIAGFWD_CNTL_H
+
+#define DIAG_CTRL_MSG_REG 1 /* Message registration commands */
+
+struct cmd_code_range {
+ uint16_t cmd_code_lo;
+ uint16_t cmd_code_hi;
+ uint32_t data;
+};
+
+struct diag_ctrl_msg {
+ uint32_t version;
+ uint16_t cmd_code;
+ uint16_t subsysid;
+ uint16_t count_entries;
+ uint16_t port;
+};
+
+void diagfwd_cntl_init(void);
+void diagfwd_cntl_exit(void);
+void diag_read_smd_cntl_work_fn(struct work_struct *);
+void diag_read_smd_qdsp_cntl_work_fn(struct work_struct *);
+void diag_read_smd_wcnss_cntl_work_fn(struct work_struct *);
+
+#endif
diff --git a/drivers/char/diag/diagfwd_sdio.c b/drivers/char/diag/diagfwd_sdio.c
new file mode 100644
index 0000000..8d43286
--- /dev/null
+++ b/drivers/char/diag/diagfwd_sdio.c
@@ -0,0 +1,261 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/diagchar.h>
+#include <linux/sched.h>
+#include <linux/err.h>
+#include <linux/workqueue.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+#include <asm/current.h>
+#ifdef CONFIG_DIAG_OVER_USB
+#include <mach/usbdiag.h>
+#endif
+#include "diagchar_hdlc.h"
+#include "diagmem.h"
+#include "diagchar.h"
+#include "diagfwd.h"
+#include "diagfwd_sdio.h"
+
+void __diag_sdio_send_req(void)
+{
+ int r = 0;
+ void *buf = driver->buf_in_sdio;
+
+ if (driver->sdio_ch && (!driver->in_busy_sdio)) {
+ r = sdio_read_avail(driver->sdio_ch);
+
+ if (r > IN_BUF_SIZE) {
+ if (r < MAX_IN_BUF_SIZE) {
+ pr_err("diag: SDIO sending"
+ " in packets more than %d bytes", r);
+ buf = krealloc(buf, r, GFP_KERNEL);
+ } else {
+ pr_err("diag: SDIO sending"
+ " in packets more than %d bytes", MAX_IN_BUF_SIZE);
+ return;
+ }
+ }
+ if (r > 0) {
+ if (!buf)
+ printk(KERN_INFO "Out of diagmem for SDIO\n");
+ else {
+ APPEND_DEBUG('i');
+ sdio_read(driver->sdio_ch, buf, r);
+ APPEND_DEBUG('j');
+ driver->write_ptr_mdm->length = r;
+ driver->in_busy_sdio = 1;
+ diag_device_write(buf, SDIO_DATA,
+ driver->write_ptr_mdm);
+ }
+ }
+ }
+}
+
+static void diag_read_sdio_work_fn(struct work_struct *work)
+{
+ __diag_sdio_send_req();
+}
+
+int diagfwd_connect_sdio(void)
+{
+ int err;
+
+ err = usb_diag_alloc_req(driver->mdm_ch, N_MDM_WRITE,
+ N_MDM_READ);
+ if (err)
+ printk(KERN_ERR "diag: unable to alloc USB req on mdm ch");
+
+ driver->in_busy_sdio = 0;
+
+ /* Poll USB channel to check for data*/
+ queue_work(driver->diag_sdio_wq, &(driver->diag_read_mdm_work));
+ /* Poll SDIO channel to check for data*/
+ queue_work(driver->diag_sdio_wq, &(driver->diag_read_sdio_work));
+ return 0;
+}
+
+int diagfwd_disconnect_sdio(void)
+{
+ driver->in_busy_sdio = 1;
+ usb_diag_free_req(driver->mdm_ch);
+ return 0;
+}
+
+int diagfwd_write_complete_sdio(void)
+{
+ driver->in_busy_sdio = 0;
+ APPEND_DEBUG('q');
+ queue_work(driver->diag_sdio_wq, &(driver->diag_read_sdio_work));
+ return 0;
+}
+
+int diagfwd_read_complete_sdio(void)
+{
+ queue_work(driver->diag_sdio_wq, &(driver->diag_read_mdm_work));
+ return 0;
+}
+
+void diag_read_mdm_work_fn(struct work_struct *work)
+{
+ if (driver->sdio_ch) {
+ wait_event_interruptible(driver->wait_q, (sdio_write_avail
+ (driver->sdio_ch) >= driver->read_len_mdm));
+ if (driver->sdio_ch && driver->usb_buf_mdm_out &&
+ (driver->read_len_mdm > 0))
+ sdio_write(driver->sdio_ch, driver->usb_buf_mdm_out,
+ driver->read_len_mdm);
+ APPEND_DEBUG('x');
+ driver->usb_read_mdm_ptr->buf = driver->usb_buf_mdm_out;
+ driver->usb_read_mdm_ptr->length = USB_MAX_OUT_BUF;
+ usb_diag_read(driver->mdm_ch, driver->usb_read_mdm_ptr);
+ APPEND_DEBUG('y');
+ }
+}
+
+static void diag_sdio_notify(void *ctxt, unsigned event)
+{
+ if (event == SDIO_EVENT_DATA_READ_AVAIL)
+ queue_work(driver->diag_sdio_wq,
+ &(driver->diag_read_sdio_work));
+
+ if (event == SDIO_EVENT_DATA_WRITE_AVAIL)
+ wake_up_interruptible(&driver->wait_q);
+}
+
+static int diag_sdio_probe(struct platform_device *pdev)
+{
+ int err;
+
+ err = sdio_open("SDIO_DIAG", &driver->sdio_ch, driver,
+ diag_sdio_notify);
+ if (err)
+ printk(KERN_INFO "DIAG could not open SDIO channel");
+ else {
+ printk(KERN_INFO "DIAG opened SDIO channel");
+ queue_work(driver->diag_sdio_wq, &(driver->diag_read_mdm_work));
+ }
+
+ return err;
+}
+
+static int diag_sdio_remove(struct platform_device *pdev)
+{
+ queue_work(driver->diag_sdio_wq, &(driver->diag_remove_sdio_work));
+ return 0;
+}
+
+static void diag_remove_sdio_work_fn(struct work_struct *work)
+{
+ pr_debug("\n diag: sdio remove called");
+ /*Disable SDIO channel to prevent further read/write */
+ driver->sdio_ch = NULL;
+}
+
+static int diagfwd_sdio_runtime_suspend(struct device *dev)
+{
+ dev_dbg(dev, "pm_runtime: suspending...\n");
+ return 0;
+}
+
+static int diagfwd_sdio_runtime_resume(struct device *dev)
+{
+ dev_dbg(dev, "pm_runtime: resuming...\n");
+ return 0;
+}
+
+static const struct dev_pm_ops diagfwd_sdio_dev_pm_ops = {
+ .runtime_suspend = diagfwd_sdio_runtime_suspend,
+ .runtime_resume = diagfwd_sdio_runtime_resume,
+};
+
+static struct platform_driver msm_sdio_ch_driver = {
+ .probe = diag_sdio_probe,
+ .remove = diag_sdio_remove,
+ .driver = {
+ .name = "SDIO_DIAG",
+ .owner = THIS_MODULE,
+ .pm = &diagfwd_sdio_dev_pm_ops,
+ },
+};
+
+void diagfwd_sdio_init(void)
+{
+ int ret;
+
+ driver->read_len_mdm = 0;
+ if (driver->buf_in_sdio == NULL)
+ driver->buf_in_sdio = kzalloc(IN_BUF_SIZE, GFP_KERNEL);
+ if (driver->buf_in_sdio == NULL)
+ goto err;
+ if (driver->usb_buf_mdm_out == NULL)
+ driver->usb_buf_mdm_out = kzalloc(USB_MAX_OUT_BUF, GFP_KERNEL);
+ if (driver->usb_buf_mdm_out == NULL)
+ goto err;
+ if (driver->write_ptr_mdm == NULL)
+ driver->write_ptr_mdm = kzalloc(
+ sizeof(struct diag_request), GFP_KERNEL);
+ if (driver->write_ptr_mdm == NULL)
+ goto err;
+ if (driver->usb_read_mdm_ptr == NULL)
+ driver->usb_read_mdm_ptr = kzalloc(
+ sizeof(struct diag_request), GFP_KERNEL);
+ if (driver->usb_read_mdm_ptr == NULL)
+ goto err;
+ driver->diag_sdio_wq = create_singlethread_workqueue("diag_sdio_wq");
+#ifdef CONFIG_DIAG_OVER_USB
+ driver->mdm_ch = usb_diag_open(DIAG_MDM, driver,
+ diag_usb_legacy_notifier);
+ if (IS_ERR(driver->mdm_ch)) {
+ printk(KERN_ERR "Unable to open USB diag MDM channel\n");
+ goto err;
+ }
+ INIT_WORK(&(driver->diag_read_mdm_work), diag_read_mdm_work_fn);
+#endif
+ INIT_WORK(&(driver->diag_read_sdio_work), diag_read_sdio_work_fn);
+ INIT_WORK(&(driver->diag_remove_sdio_work), diag_remove_sdio_work_fn);
+ ret = platform_driver_register(&msm_sdio_ch_driver);
+ if (ret)
+ printk(KERN_INFO "DIAG could not register SDIO device");
+ else
+ printk(KERN_INFO "DIAG registered SDIO device");
+
+ return;
+err:
+ printk(KERN_INFO "\n Could not initialize diag buf for SDIO");
+ kfree(driver->buf_in_sdio);
+ kfree(driver->usb_buf_mdm_out);
+ kfree(driver->write_ptr_mdm);
+ kfree(driver->usb_read_mdm_ptr);
+ if (driver->diag_sdio_wq)
+ destroy_workqueue(driver->diag_sdio_wq);
+}
+
+void diagfwd_sdio_exit(void)
+{
+#ifdef CONFIG_DIAG_OVER_USB
+ if (driver->usb_connected)
+ usb_diag_free_req(driver->mdm_ch);
+#endif
+ platform_driver_unregister(&msm_sdio_ch_driver);
+#ifdef CONFIG_DIAG_OVER_USB
+ usb_diag_close(driver->mdm_ch);
+#endif
+ kfree(driver->buf_in_sdio);
+ kfree(driver->usb_buf_mdm_out);
+ kfree(driver->write_ptr_mdm);
+ kfree(driver->usb_read_mdm_ptr);
+ destroy_workqueue(driver->diag_sdio_wq);
+}
diff --git a/drivers/char/diag/diagfwd_sdio.h b/drivers/char/diag/diagfwd_sdio.h
new file mode 100644
index 0000000..40982c3
--- /dev/null
+++ b/drivers/char/diag/diagfwd_sdio.h
@@ -0,0 +1,27 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef DIAGFWD_SDIO_H
+#define DIAGFWD_SDIO_H
+
+#include <mach/sdio_al.h>
+#define N_MDM_WRITE 1 /* Upgrade to 2 with ping pong buffer */
+#define N_MDM_READ 1
+
+void diagfwd_sdio_init(void);
+void diagfwd_sdio_exit(void);
+int diagfwd_connect_sdio(void);
+int diagfwd_disconnect_sdio(void);
+int diagfwd_read_complete_sdio(void);
+int diagfwd_write_complete_sdio(void);
+
+#endif
diff --git a/drivers/char/diag/diagmem.c b/drivers/char/diag/diagmem.c
new file mode 100644
index 0000000..0b5c27a
--- /dev/null
+++ b/drivers/char/diag/diagmem.c
@@ -0,0 +1,145 @@
+/* Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mempool.h>
+#include <linux/mutex.h>
+#include <asm/atomic.h>
+#include "diagchar.h"
+
+void *diagmem_alloc(struct diagchar_dev *driver, int size, int pool_type)
+{
+ void *buf = NULL;
+
+ if (pool_type == POOL_TYPE_COPY) {
+ if (driver->diagpool) {
+ mutex_lock(&driver->diagmem_mutex);
+ if (driver->count < driver->poolsize) {
+ atomic_add(1, (atomic_t *)&driver->count);
+ buf = mempool_alloc(driver->diagpool,
+ GFP_ATOMIC);
+ }
+ mutex_unlock(&driver->diagmem_mutex);
+ }
+ } else if (pool_type == POOL_TYPE_HDLC) {
+ if (driver->diag_hdlc_pool) {
+ if (driver->count_hdlc_pool < driver->poolsize_hdlc) {
+ atomic_add(1,
+ (atomic_t *)&driver->count_hdlc_pool);
+ buf = mempool_alloc(driver->diag_hdlc_pool,
+ GFP_ATOMIC);
+ }
+ }
+ } else if (pool_type == POOL_TYPE_WRITE_STRUCT) {
+ if (driver->diag_write_struct_pool) {
+ if (driver->count_write_struct_pool <
+ driver->poolsize_write_struct) {
+ atomic_add(1,
+ (atomic_t *)&driver->count_write_struct_pool);
+ buf = mempool_alloc(
+ driver->diag_write_struct_pool, GFP_ATOMIC);
+ }
+ }
+ }
+ return buf;
+}
+
+void diagmem_exit(struct diagchar_dev *driver, int pool_type)
+{
+ if (driver->diagpool) {
+ if (driver->count == 0 && driver->ref_count == 0) {
+ mempool_destroy(driver->diagpool);
+ driver->diagpool = NULL;
+ } else if (driver->ref_count == 0 && pool_type == POOL_TYPE_ALL)
+ printk(KERN_ALERT "Unable to destroy COPY mempool");
+ }
+
+ if (driver->diag_hdlc_pool) {
+ if (driver->count_hdlc_pool == 0 && driver->ref_count == 0) {
+ mempool_destroy(driver->diag_hdlc_pool);
+ driver->diag_hdlc_pool = NULL;
+ } else if (driver->ref_count == 0 && pool_type == POOL_TYPE_ALL)
+ printk(KERN_ALERT "Unable to destroy HDLC mempool");
+ }
+
+ if (driver->diag_write_struct_pool) {
+ /* Free up struct pool ONLY if there are no outstanding
+ transactions(aggregation buffer) with USB */
+ if (driver->count_write_struct_pool == 0 &&
+ driver->count_hdlc_pool == 0 && driver->ref_count == 0) {
+ mempool_destroy(driver->diag_write_struct_pool);
+ driver->diag_write_struct_pool = NULL;
+ } else if (driver->ref_count == 0 && pool_type == POOL_TYPE_ALL)
+ printk(KERN_ALERT "Unable to destroy STRUCT mempool");
+ }
+}
+
+void diagmem_free(struct diagchar_dev *driver, void *buf, int pool_type)
+{
+ if (pool_type == POOL_TYPE_COPY) {
+ if (driver->diagpool != NULL && driver->count > 0) {
+ mempool_free(buf, driver->diagpool);
+ atomic_add(-1, (atomic_t *)&driver->count);
+ } else
+ pr_err("diag: Attempt to free up DIAG driver "
+ "mempool memory which is already free %d", driver->count);
+ } else if (pool_type == POOL_TYPE_HDLC) {
+ if (driver->diag_hdlc_pool != NULL &&
+ driver->count_hdlc_pool > 0) {
+ mempool_free(buf, driver->diag_hdlc_pool);
+ atomic_add(-1, (atomic_t *)&driver->count_hdlc_pool);
+ } else
+ pr_err("diag: Attempt to free up DIAG driver "
+ "HDLC mempool which is already free %d ", driver->count_hdlc_pool);
+ } else if (pool_type == POOL_TYPE_WRITE_STRUCT) {
+ if (driver->diag_write_struct_pool != NULL &&
+ driver->count_write_struct_pool > 0) {
+ mempool_free(buf, driver->diag_write_struct_pool);
+ atomic_add(-1,
+ (atomic_t *)&driver->count_write_struct_pool);
+ } else
+ pr_err("diag: Attempt to free up DIAG driver "
+ "USB structure mempool which is already free %d ",
+ driver->count_write_struct_pool);
+ }
+
+ diagmem_exit(driver, pool_type);
+}
+
+void diagmem_init(struct diagchar_dev *driver)
+{
+ mutex_init(&driver->diagmem_mutex);
+
+ if (driver->count == 0)
+ driver->diagpool = mempool_create_kmalloc_pool(
+ driver->poolsize, driver->itemsize);
+
+ if (driver->count_hdlc_pool == 0)
+ driver->diag_hdlc_pool = mempool_create_kmalloc_pool(
+ driver->poolsize_hdlc, driver->itemsize_hdlc);
+
+ if (driver->count_write_struct_pool == 0)
+ driver->diag_write_struct_pool = mempool_create_kmalloc_pool(
+ driver->poolsize_write_struct, driver->itemsize_write_struct);
+
+ if (!driver->diagpool)
+ printk(KERN_INFO "Cannot allocate diag mempool\n");
+
+ if (!driver->diag_hdlc_pool)
+ printk(KERN_INFO "Cannot allocate diag HDLC mempool\n");
+
+ if (!driver->diag_write_struct_pool)
+ printk(KERN_INFO "Cannot allocate diag USB struct mempool\n");
+}
+
diff --git a/drivers/char/diag/diagmem.h b/drivers/char/diag/diagmem.h
new file mode 100644
index 0000000..43829ae
--- /dev/null
+++ b/drivers/char/diag/diagmem.h
@@ -0,0 +1,22 @@
+/* Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef DIAGMEM_H
+#define DIAGMEM_H
+#include "diagchar.h"
+
+void *diagmem_alloc(struct diagchar_dev *driver, int size, int pool_type);
+void diagmem_free(struct diagchar_dev *driver, void *buf, int pool_type);
+void diagmem_init(struct diagchar_dev *driver);
+void diagmem_exit(struct diagchar_dev *driver, int pool_type);
+
+#endif
diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig
index a60043b..127bdc6 100644
--- a/drivers/char/hw_random/Kconfig
+++ b/drivers/char/hw_random/Kconfig
@@ -210,3 +210,16 @@
module will be called picoxcell-rng.
If unsure, say Y.
+
+config HW_RANDOM_MSM
+ tristate "Qualcomm MSM Random Number Generator support"
+ depends on HW_RANDOM && ARCH_MSM
+ default n
+ ---help---
+ This driver provides kernel-side support for the Random Number
+ Generator hardware found on Qualcomm MSM SoCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called msm_rng.
+
+ If unsure, say Y.
diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile
index 3db4eb8..d0c065d 100644
--- a/drivers/char/hw_random/Makefile
+++ b/drivers/char/hw_random/Makefile
@@ -20,3 +20,4 @@
obj-$(CONFIG_HW_RANDOM_OCTEON) += octeon-rng.o
obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o
obj-$(CONFIG_HW_RANDOM_PICOXCELL) += picoxcell-rng.o
+obj-$(CONFIG_HW_RANDOM_MSM) += msm_rng.o
diff --git a/drivers/char/hw_random/msm_rng.c b/drivers/char/hw_random/msm_rng.c
new file mode 100644
index 0000000..7051bf9
--- /dev/null
+++ b/drivers/char/hw_random/msm_rng.c
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/hw_random.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/types.h>
+
+#define DRIVER_NAME "msm_rng"
+
+/* Device specific register offsets */
+#define PRNG_DATA_OUT_OFFSET 0x0000
+#define PRNG_STATUS_OFFSET 0x0004
+#define PRNG_LFSR_CFG_OFFSET 0x0100
+#define PRNG_CONFIG_OFFSET 0x0104
+
+/* Device specific register masks and config values */
+#define PRNG_LFSR_CFG_MASK 0xFFFF0000
+#define PRNG_LFSR_CFG_CLOCKS 0x0000DDDD
+#define PRNG_CONFIG_MASK 0xFFFFFFFD
+#define PRNG_CONFIG_ENABLE 0x00000002
+
+#define MAX_HW_FIFO_DEPTH 16 /* FIFO is 16 words deep */
+#define MAX_HW_FIFO_SIZE (MAX_HW_FIFO_DEPTH * 4) /* FIFO is 32 bits wide */
+
+struct msm_rng_device {
+ struct platform_device *pdev;
+ void __iomem *base;
+ struct clk *prng_clk;
+};
+
+static int msm_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
+{
+ struct msm_rng_device *msm_rng_dev;
+ struct platform_device *pdev;
+ void __iomem *base;
+ size_t maxsize;
+ size_t currsize = 0;
+ unsigned long val;
+ unsigned long *retdata = data;
+ int ret;
+
+ msm_rng_dev = (struct msm_rng_device *)rng->priv;
+ pdev = msm_rng_dev->pdev;
+ base = msm_rng_dev->base;
+
+ /* calculate max size bytes to transfer back to caller */
+ maxsize = min_t(size_t, MAX_HW_FIFO_SIZE, max);
+
+ /* no room for word data */
+ if (maxsize < 4)
+ return 0;
+
+ /* enable PRNG clock */
+ ret = clk_enable(msm_rng_dev->prng_clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable clock in callback\n");
+ return 0;
+ }
+
+ /* read random data from h/w */
+ do {
+ /* check status bit if data is available */
+ if (!(readl(base + PRNG_STATUS_OFFSET) & 0x00000001))
+ break; /* no data to read so just bail */
+
+ /* read FIFO */
+ val = readl(base + PRNG_DATA_OUT_OFFSET);
+ if (!val)
+ break; /* no data to read so just bail */
+
+ /* write data back to callers pointer */
+ *(retdata++) = val;
+ currsize += 4;
+
+ /* make sure we stay on 32bit boundary */
+ if ((maxsize - currsize) < 4)
+ break;
+ } while (currsize < maxsize);
+
+ /* vote to turn off clock */
+ clk_disable(msm_rng_dev->prng_clk);
+
+ return currsize;
+}
+
+static struct hwrng msm_rng = {
+ .name = DRIVER_NAME,
+ .read = msm_rng_read,
+};
+
+static int __devinit msm_rng_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct msm_rng_device *msm_rng_dev = NULL;
+ void __iomem *base = NULL;
+ int error = 0;
+ unsigned long val;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "invalid address\n");
+ error = -EFAULT;
+ goto err_exit;
+ }
+
+ msm_rng_dev = kzalloc(sizeof(msm_rng_dev), GFP_KERNEL);
+ if (!msm_rng_dev) {
+ dev_err(&pdev->dev, "cannot allocate memory\n");
+ error = -ENOMEM;
+ goto err_exit;
+ }
+
+ base = ioremap(res->start, resource_size(res));
+ if (!base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ error = -ENOMEM;
+ goto err_iomap;
+ }
+ msm_rng_dev->base = base;
+
+ /* create a handle for clock control */
+ msm_rng_dev->prng_clk = clk_get(NULL, "prng_clk");
+ if (IS_ERR(msm_rng_dev->prng_clk)) {
+ dev_err(&pdev->dev, "failed to register clock source\n");
+ error = -EPERM;
+ goto err_clk_get;
+ }
+
+ /* save away pdev and register driver data */
+ msm_rng_dev->pdev = pdev;
+ platform_set_drvdata(pdev, msm_rng_dev);
+
+ ret = clk_enable(msm_rng_dev->prng_clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable clock in probe\n");
+ error = -EPERM;
+ goto err_clk_enable;
+ }
+
+ /* enable PRNG h/w (this may not work since XPU protect may be enabled
+ * elsewhere which case then the hardware should have already been set
+ * up)
+ */
+ val = readl(base + PRNG_LFSR_CFG_OFFSET) & PRNG_LFSR_CFG_MASK;
+ val |= PRNG_LFSR_CFG_CLOCKS;
+ writel(val, base + PRNG_LFSR_CFG_OFFSET);
+
+ val = readl(base + PRNG_CONFIG_OFFSET) & PRNG_CONFIG_MASK;
+ val |= PRNG_CONFIG_ENABLE;
+ writel(val, base + PRNG_CONFIG_OFFSET);
+
+ clk_disable(msm_rng_dev->prng_clk);
+
+ /* register with hwrng framework */
+ msm_rng.priv = (unsigned long) msm_rng_dev;
+ error = hwrng_register(&msm_rng);
+ if (error) {
+ dev_err(&pdev->dev, "failed to register hwrng\n");
+ error = -EPERM;
+ goto err_hw_register;
+ }
+
+ return 0;
+
+err_hw_register:
+err_clk_enable:
+ clk_put(msm_rng_dev->prng_clk);
+err_clk_get:
+ iounmap(msm_rng_dev->base);
+err_iomap:
+ kfree(msm_rng_dev);
+err_exit:
+ return error;
+}
+
+static int __devexit msm_rng_remove(struct platform_device *pdev)
+{
+ struct msm_rng_device *msm_rng_dev = platform_get_drvdata(pdev);
+
+ hwrng_unregister(&msm_rng);
+ clk_put(msm_rng_dev->prng_clk);
+ iounmap(msm_rng_dev->base);
+ platform_set_drvdata(pdev, NULL);
+ kfree(msm_rng_dev);
+ return 0;
+}
+
+static struct platform_driver rng_driver = {
+ .probe = msm_rng_probe,
+ .remove = __devexit_p(msm_rng_remove),
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ }
+};
+
+static int __init msm_rng_init(void)
+{
+ return platform_driver_register(&rng_driver);
+}
+
+module_init(msm_rng_init);
+
+static void __exit msm_rng_exit(void)
+{
+ platform_driver_unregister(&rng_driver);
+}
+
+module_exit(msm_rng_exit);
+
+MODULE_AUTHOR("Code Aurora Forum");
+MODULE_DESCRIPTION("Qualcomm MSM Random Number Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/char/msm_rotator.c b/drivers/char/msm_rotator.c
new file mode 100644
index 0000000..e7c790d
--- /dev/null
+++ b/drivers/char/msm_rotator.c
@@ -0,0 +1,1523 @@
+/* Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/platform_device.h>
+#include <linux/cdev.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <linux/clk.h>
+#include <mach/clk.h>
+#include <linux/android_pmem.h>
+#include <linux/msm_rotator.h>
+#include <linux/io.h>
+#include <mach/msm_rotator_imem.h>
+#include <linux/ktime.h>
+#include <linux/workqueue.h>
+#include <linux/file.h>
+#include <linux/major.h>
+#include <linux/regulator/consumer.h>
+
+#define DRIVER_NAME "msm_rotator"
+
+#define MSM_ROTATOR_BASE (msm_rotator_dev->io_base)
+#define MSM_ROTATOR_INTR_ENABLE (MSM_ROTATOR_BASE+0x0020)
+#define MSM_ROTATOR_INTR_STATUS (MSM_ROTATOR_BASE+0x0024)
+#define MSM_ROTATOR_INTR_CLEAR (MSM_ROTATOR_BASE+0x0028)
+#define MSM_ROTATOR_START (MSM_ROTATOR_BASE+0x0030)
+#define MSM_ROTATOR_MAX_BURST_SIZE (MSM_ROTATOR_BASE+0x0050)
+#define MSM_ROTATOR_HW_VERSION (MSM_ROTATOR_BASE+0x0070)
+#define MSM_ROTATOR_SRC_SIZE (MSM_ROTATOR_BASE+0x1108)
+#define MSM_ROTATOR_SRCP0_ADDR (MSM_ROTATOR_BASE+0x110c)
+#define MSM_ROTATOR_SRCP1_ADDR (MSM_ROTATOR_BASE+0x1110)
+#define MSM_ROTATOR_SRC_YSTRIDE1 (MSM_ROTATOR_BASE+0x111c)
+#define MSM_ROTATOR_SRC_YSTRIDE2 (MSM_ROTATOR_BASE+0x1120)
+#define MSM_ROTATOR_SRC_FORMAT (MSM_ROTATOR_BASE+0x1124)
+#define MSM_ROTATOR_SRC_UNPACK_PATTERN1 (MSM_ROTATOR_BASE+0x1128)
+#define MSM_ROTATOR_SUB_BLOCK_CFG (MSM_ROTATOR_BASE+0x1138)
+#define MSM_ROTATOR_OUT_PACK_PATTERN1 (MSM_ROTATOR_BASE+0x1154)
+#define MSM_ROTATOR_OUTP0_ADDR (MSM_ROTATOR_BASE+0x1168)
+#define MSM_ROTATOR_OUTP1_ADDR (MSM_ROTATOR_BASE+0x116c)
+#define MSM_ROTATOR_OUT_YSTRIDE1 (MSM_ROTATOR_BASE+0x1178)
+#define MSM_ROTATOR_OUT_YSTRIDE2 (MSM_ROTATOR_BASE+0x117c)
+#define MSM_ROTATOR_SRC_XY (MSM_ROTATOR_BASE+0x1200)
+#define MSM_ROTATOR_SRC_IMAGE_SIZE (MSM_ROTATOR_BASE+0x1208)
+
+#define MSM_ROTATOR_MAX_ROT 0x07
+#define MSM_ROTATOR_MAX_H 0x1fff
+#define MSM_ROTATOR_MAX_W 0x1fff
+
+/* from lsb to msb */
+#define GET_PACK_PATTERN(a, x, y, z, bit) \
+ (((a)<<((bit)*3))|((x)<<((bit)*2))|((y)<<(bit))|(z))
+#define CLR_G 0x0
+#define CLR_B 0x1
+#define CLR_R 0x2
+#define CLR_ALPHA 0x3
+
+#define CLR_Y CLR_G
+#define CLR_CB CLR_B
+#define CLR_CR CLR_R
+
+#define ROTATIONS_TO_BITMASK(r) ((((r) & MDP_ROT_90) ? 1 : 0) | \
+ (((r) & MDP_FLIP_LR) ? 2 : 0) | \
+ (((r) & MDP_FLIP_UD) ? 4 : 0))
+
+#define IMEM_NO_OWNER -1;
+
+#define MAX_SESSIONS 16
+#define INVALID_SESSION -1
+#define VERSION_KEY_MASK 0xFFFFFF00
+
+struct tile_parm {
+ unsigned int width; /* tile's width */
+ unsigned int height; /* tile's height */
+ unsigned int row_tile_w; /* tiles per row's width */
+ unsigned int row_tile_h; /* tiles per row's height */
+};
+
+struct msm_rotator_dev {
+ void __iomem *io_base;
+ int irq;
+ struct msm_rotator_img_info *img_info[MAX_SESSIONS];
+ struct clk *core_clk;
+ int pid_list[MAX_SESSIONS];
+ struct clk *pclk;
+ struct clk *axi_clk;
+ int rot_clk_state;
+ struct regulator *regulator;
+ struct delayed_work rot_clk_work;
+ struct clk *imem_clk;
+ int imem_clk_state;
+ struct delayed_work imem_clk_work;
+ struct platform_device *pdev;
+ struct cdev cdev;
+ struct device *device;
+ struct class *class;
+ dev_t dev_num;
+ int processing;
+ int last_session_idx;
+ struct mutex rotator_lock;
+ struct mutex imem_lock;
+ int imem_owner;
+ wait_queue_head_t wq;
+};
+
+#define chroma_addr(start, w, h, bpp) ((start) + ((h) * (w) * (bpp)))
+
+#define COMPONENT_5BITS 1
+#define COMPONENT_6BITS 2
+#define COMPONENT_8BITS 3
+
+static struct msm_rotator_dev *msm_rotator_dev;
+
+enum {
+ CLK_EN,
+ CLK_DIS,
+ CLK_SUSPEND,
+};
+
+int msm_rotator_imem_allocate(int requestor)
+{
+ int rc = 0;
+
+#ifdef CONFIG_MSM_ROTATOR_USE_IMEM
+ switch (requestor) {
+ case ROTATOR_REQUEST:
+ if (mutex_trylock(&msm_rotator_dev->imem_lock)) {
+ msm_rotator_dev->imem_owner = ROTATOR_REQUEST;
+ rc = 1;
+ } else
+ rc = 0;
+ break;
+ case JPEG_REQUEST:
+ mutex_lock(&msm_rotator_dev->imem_lock);
+ msm_rotator_dev->imem_owner = JPEG_REQUEST;
+ rc = 1;
+ break;
+ default:
+ rc = 0;
+ }
+#else
+ if (requestor == JPEG_REQUEST)
+ rc = 1;
+#endif
+ if (rc == 1) {
+ cancel_delayed_work(&msm_rotator_dev->imem_clk_work);
+ if (msm_rotator_dev->imem_clk_state != CLK_EN
+ && msm_rotator_dev->imem_clk) {
+ clk_enable(msm_rotator_dev->imem_clk);
+ msm_rotator_dev->imem_clk_state = CLK_EN;
+ }
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL(msm_rotator_imem_allocate);
+
+void msm_rotator_imem_free(int requestor)
+{
+#ifdef CONFIG_MSM_ROTATOR_USE_IMEM
+ if (msm_rotator_dev->imem_owner == requestor) {
+ schedule_delayed_work(&msm_rotator_dev->imem_clk_work, HZ);
+ mutex_unlock(&msm_rotator_dev->imem_lock);
+ }
+#else
+ if (requestor == JPEG_REQUEST)
+ schedule_delayed_work(&msm_rotator_dev->imem_clk_work, HZ);
+#endif
+}
+EXPORT_SYMBOL(msm_rotator_imem_free);
+
+static void msm_rotator_imem_clk_work_f(struct work_struct *work)
+{
+#ifdef CONFIG_MSM_ROTATOR_USE_IMEM
+ if (mutex_trylock(&msm_rotator_dev->imem_lock)) {
+ if (msm_rotator_dev->imem_clk_state == CLK_EN
+ && msm_rotator_dev->imem_clk) {
+ clk_disable(msm_rotator_dev->imem_clk);
+ msm_rotator_dev->imem_clk_state = CLK_DIS;
+ } else if (msm_rotator_dev->imem_clk_state == CLK_SUSPEND)
+ msm_rotator_dev->imem_clk_state = CLK_DIS;
+ mutex_unlock(&msm_rotator_dev->imem_lock);
+ }
+#endif
+}
+
+/* enable clocks needed by rotator block */
+static void enable_rot_clks(void)
+{
+ if (msm_rotator_dev->regulator)
+ regulator_enable(msm_rotator_dev->regulator);
+ if (msm_rotator_dev->core_clk != NULL)
+ clk_enable(msm_rotator_dev->core_clk);
+ if (msm_rotator_dev->pclk != NULL)
+ clk_enable(msm_rotator_dev->pclk);
+ if (msm_rotator_dev->axi_clk != NULL)
+ clk_enable(msm_rotator_dev->axi_clk);
+}
+
+/* disable clocks needed by rotator block */
+static void disable_rot_clks(void)
+{
+ if (msm_rotator_dev->core_clk != NULL)
+ clk_disable(msm_rotator_dev->core_clk);
+ if (msm_rotator_dev->pclk != NULL)
+ clk_disable(msm_rotator_dev->pclk);
+ if (msm_rotator_dev->axi_clk != NULL)
+ clk_disable(msm_rotator_dev->axi_clk);
+ if (msm_rotator_dev->regulator)
+ regulator_disable(msm_rotator_dev->regulator);
+}
+
+static void msm_rotator_rot_clk_work_f(struct work_struct *work)
+{
+ if (mutex_trylock(&msm_rotator_dev->rotator_lock)) {
+ if (msm_rotator_dev->rot_clk_state == CLK_EN) {
+ disable_rot_clks();
+ msm_rotator_dev->rot_clk_state = CLK_DIS;
+ } else if (msm_rotator_dev->rot_clk_state == CLK_SUSPEND)
+ msm_rotator_dev->rot_clk_state = CLK_DIS;
+ mutex_unlock(&msm_rotator_dev->rotator_lock);
+ }
+}
+
+static irqreturn_t msm_rotator_isr(int irq, void *dev_id)
+{
+ if (msm_rotator_dev->processing) {
+ msm_rotator_dev->processing = 0;
+ wake_up(&msm_rotator_dev->wq);
+ } else
+ printk(KERN_WARNING "%s: unexpected interrupt\n", DRIVER_NAME);
+
+ return IRQ_HANDLED;
+}
+
+static int get_bpp(int format)
+{
+ switch (format) {
+ case MDP_RGB_565:
+ case MDP_BGR_565:
+ return 2;
+
+ case MDP_XRGB_8888:
+ case MDP_ARGB_8888:
+ case MDP_RGBA_8888:
+ case MDP_BGRA_8888:
+ case MDP_RGBX_8888:
+ return 4;
+
+ case MDP_Y_CBCR_H2V2:
+ case MDP_Y_CRCB_H2V2:
+ case MDP_Y_CRCB_H2V2_TILE:
+ case MDP_Y_CBCR_H2V2_TILE:
+ return 1;
+
+ case MDP_RGB_888:
+ return 3;
+
+ case MDP_YCRYCB_H2V1:
+ return 2;/* YCrYCb interleave */
+
+ case MDP_Y_CRCB_H2V1:
+ case MDP_Y_CBCR_H2V1:
+ return 1;
+
+ default:
+ return -1;
+ }
+
+}
+
+static int msm_rotator_ycxcx_h2v1(struct msm_rotator_img_info *info,
+ unsigned int in_paddr,
+ unsigned int out_paddr,
+ unsigned int use_imem,
+ int new_session,
+ unsigned int in_chroma_paddr,
+ unsigned int out_chroma_paddr)
+{
+ int bpp;
+ unsigned int in_chr_addr, out_chr_addr;
+
+ if (info->src.format != info->dst.format)
+ return -EINVAL;
+
+ bpp = get_bpp(info->src.format);
+ if (bpp < 0)
+ return -ENOTTY;
+
+ if (!in_chroma_paddr) {
+ in_chr_addr = chroma_addr(in_paddr, info->src.width,
+ info->src.height,
+ bpp);
+ } else
+ in_chr_addr = in_chroma_paddr;
+
+ if (!out_chroma_paddr) {
+ out_chr_addr = chroma_addr(out_paddr, info->dst.width,
+ info->dst.height,
+ bpp);
+ } else
+ out_chr_addr = out_chroma_paddr;
+
+ iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR);
+
+ iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR);
+ iowrite32(in_chr_addr, MSM_ROTATOR_SRCP1_ADDR);
+ iowrite32(out_paddr +
+ ((info->dst_y * info->dst.width) + info->dst_x),
+ MSM_ROTATOR_OUTP0_ADDR);
+ iowrite32(out_chr_addr +
+ ((info->dst_y * info->dst.width) + info->dst_x),
+ MSM_ROTATOR_OUTP1_ADDR);
+
+ if (new_session) {
+ iowrite32(info->src.width |
+ info->src.width << 16,
+ MSM_ROTATOR_SRC_YSTRIDE1);
+ if (info->rotations & MDP_ROT_90)
+ iowrite32(info->dst.width |
+ info->dst.width*2 << 16,
+ MSM_ROTATOR_OUT_YSTRIDE1);
+ else
+ iowrite32(info->dst.width |
+ info->dst.width << 16,
+ MSM_ROTATOR_OUT_YSTRIDE1);
+ if (info->src.format == MDP_Y_CBCR_H2V1) {
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ } else {
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ }
+ iowrite32((1 << 18) | /* chroma sampling 1=H2V1 */
+ (ROTATIONS_TO_BITMASK(info->rotations) << 9) |
+ 1 << 8, /* ROT_EN */
+ MSM_ROTATOR_SUB_BLOCK_CFG);
+ iowrite32(0 << 29 | /* frame format 0 = linear */
+ (use_imem ? 0 : 1) << 22 | /* tile size */
+ 2 << 19 | /* fetch planes 2 = pseudo */
+ 0 << 18 | /* unpack align */
+ 1 << 17 | /* unpack tight */
+ 1 << 13 | /* unpack count 0=1 component */
+ (bpp-1) << 9 | /* src Bpp 0=1 byte ... */
+ 0 << 8 | /* has alpha */
+ 0 << 6 | /* alpha bits 3=8bits */
+ 3 << 4 | /* R/Cr bits 1=5 2=6 3=8 */
+ 3 << 2 | /* B/Cb bits 1=5 2=6 3=8 */
+ 3 << 0, /* G/Y bits 1=5 2=6 3=8 */
+ MSM_ROTATOR_SRC_FORMAT);
+ }
+
+ return 0;
+}
+
+static int msm_rotator_ycxcx_h2v2(struct msm_rotator_img_info *info,
+ unsigned int in_paddr,
+ unsigned int out_paddr,
+ unsigned int use_imem,
+ int new_session,
+ unsigned int in_chroma_paddr,
+ unsigned int out_chroma_paddr)
+{
+ int bpp;
+ unsigned int in_chr_addr, out_chr_addr;
+
+ if (info->src.format != info->dst.format)
+ return -EINVAL;
+
+ bpp = get_bpp(info->src.format);
+ if (bpp < 0)
+ return -ENOTTY;
+
+ if (!in_chroma_paddr) {
+ in_chr_addr = chroma_addr(in_paddr, info->src.width,
+ info->src.height,
+ bpp);
+ } else
+ in_chr_addr = in_chroma_paddr;
+
+ if (!out_chroma_paddr) {
+ out_chr_addr = chroma_addr(out_paddr, info->dst.width,
+ info->dst.height,
+ bpp);
+ } else
+ out_chr_addr = out_chroma_paddr;
+
+ iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR);
+ iowrite32(in_chr_addr,
+ MSM_ROTATOR_SRCP1_ADDR);
+ iowrite32(out_paddr +
+ ((info->dst_y * info->dst.width) + info->dst_x),
+ MSM_ROTATOR_OUTP0_ADDR);
+ iowrite32(out_chr_addr +
+ ((info->dst_y * info->dst.width)/2 + info->dst_x),
+ MSM_ROTATOR_OUTP1_ADDR);
+
+ if (new_session) {
+ iowrite32(info->src.width |
+ info->src.width << 16,
+ MSM_ROTATOR_SRC_YSTRIDE1);
+ iowrite32(info->dst.width |
+ info->dst.width << 16,
+ MSM_ROTATOR_OUT_YSTRIDE1);
+ if (info->src.format == MDP_Y_CBCR_H2V2) {
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ } else {
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ }
+ iowrite32((3 << 18) | /* chroma sampling 3=4:2:0 */
+ (ROTATIONS_TO_BITMASK(info->rotations) << 9) |
+ 1 << 8, /* ROT_EN */
+ MSM_ROTATOR_SUB_BLOCK_CFG);
+ iowrite32(0 << 29 | /* frame format 0 = linear */
+ (use_imem ? 0 : 1) << 22 | /* tile size */
+ 2 << 19 | /* fetch planes 2 = pseudo */
+ 0 << 18 | /* unpack align */
+ 1 << 17 | /* unpack tight */
+ 1 << 13 | /* unpack count 0=1 component */
+ (bpp-1) << 9 | /* src Bpp 0=1 byte ... */
+ 0 << 8 | /* has alpha */
+ 0 << 6 | /* alpha bits 3=8bits */
+ 3 << 4 | /* R/Cr bits 1=5 2=6 3=8 */
+ 3 << 2 | /* B/Cb bits 1=5 2=6 3=8 */
+ 3 << 0, /* G/Y bits 1=5 2=6 3=8 */
+ MSM_ROTATOR_SRC_FORMAT);
+ }
+ return 0;
+}
+
+static unsigned int tile_size(unsigned int src_width,
+ unsigned int src_height,
+ const struct tile_parm *tp)
+{
+ unsigned int tile_w, tile_h;
+ unsigned int row_num_w, row_num_h;
+ tile_w = tp->width * tp->row_tile_w;
+ tile_h = tp->height * tp->row_tile_h;
+ row_num_w = (src_width + tile_w - 1) / tile_w;
+ row_num_h = (src_height + tile_h - 1) / tile_h;
+ return ((row_num_w * row_num_h * tile_w * tile_h) + 8191) & ~8191;
+}
+
+static int msm_rotator_ycxcx_h2v2_tile(struct msm_rotator_img_info *info,
+ unsigned int in_paddr,
+ unsigned int out_paddr,
+ unsigned int use_imem,
+ int new_session,
+ unsigned in_chroma_paddr,
+ unsigned out_chroma_paddr)
+{
+ int bpp;
+ unsigned int offset = 0;
+ unsigned int in_chr_addr, out_chr_addr;
+ /*
+ * each row of samsung tile consists of two tiles in height
+ * and two tiles in width which means width should align to
+ * 64 x 2 bytes and height should align to 32 x 2 bytes.
+ * video decoder generate two tiles in width and one tile
+ * in height which ends up height align to 32 X 1 bytes.
+ */
+ const struct tile_parm tile = {64, 32, 2, 1};
+ if ((info->src.format == MDP_Y_CRCB_H2V2_TILE &&
+ info->dst.format != MDP_Y_CRCB_H2V2) ||
+ (info->src.format == MDP_Y_CBCR_H2V2_TILE &&
+ info->dst.format != MDP_Y_CBCR_H2V2))
+ return -EINVAL;
+
+ bpp = get_bpp(info->src.format);
+ if (bpp < 0)
+ return -ENOTTY;
+
+ offset = tile_size(info->src.width, info->src.height, &tile);
+ if (!in_chroma_paddr)
+ in_chr_addr = in_paddr + offset;
+ else
+ in_chr_addr = in_chroma_paddr;
+
+ if (!out_chroma_paddr) {
+ out_chr_addr = chroma_addr(out_paddr, info->dst.width,
+ info->dst.height,
+ bpp);
+ } else
+ out_chr_addr = out_chroma_paddr;
+
+ iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR);
+ iowrite32(in_paddr + offset, MSM_ROTATOR_SRCP1_ADDR);
+ iowrite32(out_paddr +
+ ((info->dst_y * info->dst.width) + info->dst_x),
+ MSM_ROTATOR_OUTP0_ADDR);
+ iowrite32(out_chr_addr +
+ ((info->dst_y * info->dst.width)/2 + info->dst_x),
+ MSM_ROTATOR_OUTP1_ADDR);
+
+ if (new_session) {
+ iowrite32(info->src.width |
+ info->src.width << 16,
+ MSM_ROTATOR_SRC_YSTRIDE1);
+
+ iowrite32(info->dst.width |
+ info->dst.width << 16,
+ MSM_ROTATOR_OUT_YSTRIDE1);
+ if (info->src.format == MDP_Y_CBCR_H2V2_TILE) {
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ } else {
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ }
+ iowrite32((3 << 18) | /* chroma sampling 3=4:2:0 */
+ (ROTATIONS_TO_BITMASK(info->rotations) << 9) |
+ 1 << 8, /* ROT_EN */
+ MSM_ROTATOR_SUB_BLOCK_CFG);
+ iowrite32(2 << 29 | /* frame format 2 = supertile */
+ (use_imem ? 0 : 1) << 22 | /* tile size */
+ 2 << 19 | /* fetch planes 2 = pseudo */
+ 0 << 18 | /* unpack align */
+ 1 << 17 | /* unpack tight */
+ 1 << 13 | /* unpack count 0=1 component */
+ (bpp-1) << 9 | /* src Bpp 0=1 byte ... */
+ 0 << 8 | /* has alpha */
+ 0 << 6 | /* alpha bits 3=8bits */
+ 3 << 4 | /* R/Cr bits 1=5 2=6 3=8 */
+ 3 << 2 | /* B/Cb bits 1=5 2=6 3=8 */
+ 3 << 0, /* G/Y bits 1=5 2=6 3=8 */
+ MSM_ROTATOR_SRC_FORMAT);
+ }
+ return 0;
+}
+
+static int msm_rotator_ycrycb(struct msm_rotator_img_info *info,
+ unsigned int in_paddr,
+ unsigned int out_paddr,
+ unsigned int use_imem,
+ int new_session)
+{
+ int bpp;
+
+ if (info->src.format != info->dst.format)
+ return -EINVAL;
+
+ bpp = get_bpp(info->src.format);
+ if (bpp < 0)
+ return -ENOTTY;
+
+ iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR);
+ iowrite32(out_paddr +
+ ((info->dst_y * info->dst.width) + info->dst_x),
+ MSM_ROTATOR_OUTP0_ADDR);
+
+ if (new_session) {
+ iowrite32(info->src.width,
+ MSM_ROTATOR_SRC_YSTRIDE1);
+ iowrite32(info->dst.width,
+ MSM_ROTATOR_OUT_YSTRIDE1);
+ iowrite32(GET_PACK_PATTERN(CLR_Y, CLR_CR, CLR_Y, CLR_CB, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(CLR_Y, CLR_CR, CLR_Y, CLR_CB, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ iowrite32((1 << 18) | /* chroma sampling 1=H2V1 */
+ (ROTATIONS_TO_BITMASK(info->rotations) << 9) |
+ 1 << 8, /* ROT_EN */
+ MSM_ROTATOR_SUB_BLOCK_CFG);
+ iowrite32(0 << 29 | /* frame format 0 = linear */
+ (use_imem ? 0 : 1) << 22 | /* tile size */
+ 0 << 19 | /* fetch planes 0=interleaved */
+ 0 << 18 | /* unpack align */
+ 1 << 17 | /* unpack tight */
+ 3 << 13 | /* unpack count 0=1 component */
+ (bpp-1) << 9 | /* src Bpp 0=1 byte ... */
+ 0 << 8 | /* has alpha */
+ 0 << 6 | /* alpha bits 3=8bits */
+ 3 << 4 | /* R/Cr bits 1=5 2=6 3=8 */
+ 3 << 2 | /* B/Cb bits 1=5 2=6 3=8 */
+ 3 << 0, /* G/Y bits 1=5 2=6 3=8 */
+ MSM_ROTATOR_SRC_FORMAT);
+ }
+
+ return 0;
+}
+
+static int msm_rotator_rgb_types(struct msm_rotator_img_info *info,
+ unsigned int in_paddr,
+ unsigned int out_paddr,
+ unsigned int use_imem,
+ int new_session)
+{
+ int bpp, abits, rbits, gbits, bbits;
+
+ if (info->src.format != info->dst.format)
+ return -EINVAL;
+
+ bpp = get_bpp(info->src.format);
+ if (bpp < 0)
+ return -ENOTTY;
+
+ iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR);
+ iowrite32(out_paddr +
+ ((info->dst_y * info->dst.width) + info->dst_x) * bpp,
+ MSM_ROTATOR_OUTP0_ADDR);
+
+ if (new_session) {
+ iowrite32(info->src.width * bpp, MSM_ROTATOR_SRC_YSTRIDE1);
+ iowrite32(info->dst.width * bpp, MSM_ROTATOR_OUT_YSTRIDE1);
+ iowrite32((0 << 18) | /* chroma sampling 0=rgb */
+ (ROTATIONS_TO_BITMASK(info->rotations) << 9) |
+ 1 << 8, /* ROT_EN */
+ MSM_ROTATOR_SUB_BLOCK_CFG);
+ switch (info->src.format) {
+ case MDP_RGB_565:
+ iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ abits = 0;
+ rbits = COMPONENT_5BITS;
+ gbits = COMPONENT_6BITS;
+ bbits = COMPONENT_5BITS;
+ break;
+
+ case MDP_BGR_565:
+ iowrite32(GET_PACK_PATTERN(0, CLR_B, CLR_G, CLR_R, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(0, CLR_B, CLR_G, CLR_R, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ abits = 0;
+ rbits = COMPONENT_5BITS;
+ gbits = COMPONENT_6BITS;
+ bbits = COMPONENT_5BITS;
+ break;
+
+ case MDP_RGB_888:
+ iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ abits = 0;
+ rbits = COMPONENT_8BITS;
+ gbits = COMPONENT_8BITS;
+ bbits = COMPONENT_8BITS;
+ break;
+
+ case MDP_ARGB_8888:
+ case MDP_RGBA_8888:
+ case MDP_XRGB_8888:
+ case MDP_RGBX_8888:
+ iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G,
+ CLR_B, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G,
+ CLR_B, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ abits = COMPONENT_8BITS;
+ rbits = COMPONENT_8BITS;
+ gbits = COMPONENT_8BITS;
+ bbits = COMPONENT_8BITS;
+ break;
+
+ case MDP_BGRA_8888:
+ iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_B, CLR_G,
+ CLR_R, 8),
+ MSM_ROTATOR_SRC_UNPACK_PATTERN1);
+ iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_B, CLR_G,
+ CLR_R, 8),
+ MSM_ROTATOR_OUT_PACK_PATTERN1);
+ abits = COMPONENT_8BITS;
+ rbits = COMPONENT_8BITS;
+ gbits = COMPONENT_8BITS;
+ bbits = COMPONENT_8BITS;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ iowrite32(0 << 29 | /* frame format 0 = linear */
+ (use_imem ? 0 : 1) << 22 | /* tile size */
+ 0 << 19 | /* fetch planes 0=interleaved */
+ 0 << 18 | /* unpack align */
+ 1 << 17 | /* unpack tight */
+ (abits ? 3 : 2) << 13 | /* unpack count 0=1 comp */
+ (bpp-1) << 9 | /* src Bpp 0=1 byte ... */
+ (abits ? 1 : 0) << 8 | /* has alpha */
+ abits << 6 | /* alpha bits 3=8bits */
+ rbits << 4 | /* R/Cr bits 1=5 2=6 3=8 */
+ bbits << 2 | /* B/Cb bits 1=5 2=6 3=8 */
+ gbits << 0, /* G/Y bits 1=5 2=6 3=8 */
+ MSM_ROTATOR_SRC_FORMAT);
+ }
+
+ return 0;
+}
+
+static int get_img(int memory_id, unsigned long *start, unsigned long *len,
+ struct file **pp_file)
+{
+ int ret = 0;
+#ifdef CONFIG_FB
+ struct file *file;
+ int put_needed, fb_num;
+#endif
+#ifdef CONFIG_ANDROID_PMEM
+ unsigned long vstart;
+#endif
+
+#ifdef CONFIG_ANDROID_PMEM
+ if (!get_pmem_file(memory_id, start, &vstart, len, pp_file))
+ return 0;
+#endif
+#ifdef CONFIG_FB
+ file = fget_light(memory_id, &put_needed);
+ if (file == NULL)
+ return -1;
+
+ if (MAJOR(file->f_dentry->d_inode->i_rdev) == FB_MAJOR) {
+ fb_num = MINOR(file->f_dentry->d_inode->i_rdev);
+ if (get_fb_phys_info(start, len, fb_num))
+ ret = -1;
+ else
+ *pp_file = file;
+ } else
+ ret = -1;
+ if (ret)
+ fput_light(file, put_needed);
+#endif
+ return ret;
+}
+
+static int msm_rotator_do_rotate(unsigned long arg)
+{
+ int rc = 0;
+ unsigned int status;
+ struct msm_rotator_data_info info;
+ unsigned int in_paddr, out_paddr;
+ unsigned long len;
+ struct file *src_file = 0;
+ struct file *dst_file = 0;
+ int use_imem = 0;
+ int s;
+ struct file *src_chroma_file = 0;
+ struct file *dst_chroma_file = 0;
+ unsigned int in_chroma_paddr = 0, out_chroma_paddr = 0;
+ uint32_t format;
+
+ if (copy_from_user(&info, (void __user *)arg, sizeof(info)))
+ return -EFAULT;
+
+ rc = get_img(info.src.memory_id, (unsigned long *)&in_paddr,
+ (unsigned long *)&len, &src_file);
+ if (rc) {
+ printk(KERN_ERR "%s: in get_img() failed id=0x%08x\n",
+ DRIVER_NAME, info.src.memory_id);
+ return rc;
+ }
+ in_paddr += info.src.offset;
+
+ rc = get_img(info.dst.memory_id, (unsigned long *)&out_paddr,
+ (unsigned long *)&len, &dst_file);
+ if (rc) {
+ printk(KERN_ERR "%s: out get_img() failed id=0x%08x\n",
+ DRIVER_NAME, info.dst.memory_id);
+ goto do_rotate_fail_dst_img;
+ }
+ out_paddr += info.dst.offset;
+
+ mutex_lock(&msm_rotator_dev->rotator_lock);
+ for (s = 0; s < MAX_SESSIONS; s++)
+ if ((msm_rotator_dev->img_info[s] != NULL) &&
+ (info.session_id ==
+ (unsigned int)msm_rotator_dev->img_info[s]
+ ))
+ break;
+
+ if (s == MAX_SESSIONS) {
+ dev_dbg(msm_rotator_dev->device,
+ "%s() : Attempt to use invalid session_id %d\n",
+ __func__, s);
+ rc = -EINVAL;
+ goto do_rotate_unlock_mutex;
+ }
+
+ if (msm_rotator_dev->img_info[s]->enable == 0) {
+ dev_dbg(msm_rotator_dev->device,
+ "%s() : Session_id %d not enabled \n",
+ __func__, s);
+ rc = -EINVAL;
+ goto do_rotate_unlock_mutex;
+ }
+
+ format = msm_rotator_dev->img_info[s]->src.format;
+ if (((info.version_key & VERSION_KEY_MASK) == 0xA5B4C300) &&
+ ((info.version_key & ~VERSION_KEY_MASK) > 0) &&
+ (format == MDP_Y_CBCR_H2V2 ||
+ format == MDP_Y_CRCB_H2V2 ||
+ format == MDP_Y_CRCB_H2V2_TILE ||
+ format == MDP_Y_CBCR_H2V2_TILE ||
+ format == MDP_Y_CBCR_H2V1 ||
+ format == MDP_Y_CRCB_H2V1)) {
+ rc = get_img(info.src_chroma.memory_id,
+ (unsigned long *)&in_chroma_paddr,
+ (unsigned long *)&len, &src_chroma_file);
+ if (rc) {
+ printk(KERN_ERR "%s: in chroma get_img() failed id=0x%08x\n",
+ DRIVER_NAME, info.src_chroma.memory_id);
+ goto do_rotate_unlock_mutex;
+ }
+ in_chroma_paddr += info.src_chroma.offset;
+
+ rc = get_img(info.dst_chroma.memory_id,
+ (unsigned long *)&out_chroma_paddr,
+ (unsigned long *)&len, &dst_chroma_file);
+ if (rc) {
+ printk(KERN_ERR "%s: out chroma get_img() failed id=0x%08x\n",
+ DRIVER_NAME, info.dst_chroma.memory_id);
+ goto do_rotate_fail_dst_chr_img;
+ }
+ out_chroma_paddr += info.dst_chroma.offset;
+ }
+
+ cancel_delayed_work(&msm_rotator_dev->rot_clk_work);
+ if (msm_rotator_dev->rot_clk_state != CLK_EN) {
+ enable_rot_clks();
+ msm_rotator_dev->rot_clk_state = CLK_EN;
+ }
+ enable_irq(msm_rotator_dev->irq);
+
+#ifdef CONFIG_MSM_ROTATOR_USE_IMEM
+ use_imem = msm_rotator_imem_allocate(ROTATOR_REQUEST);
+#else
+ use_imem = 0;
+#endif
+ /*
+ * workaround for a hardware bug. rotator hardware hangs when we
+ * use write burst beat size 16 on 128X128 tile fetch mode. As a
+ * temporary fix use 0x42 for BURST_SIZE when imem used.
+ */
+ if (use_imem)
+ iowrite32(0x42, MSM_ROTATOR_MAX_BURST_SIZE);
+
+ iowrite32(((msm_rotator_dev->img_info[s]->src_rect.h & 0x1fff)
+ << 16) |
+ (msm_rotator_dev->img_info[s]->src_rect.w & 0x1fff),
+ MSM_ROTATOR_SRC_SIZE);
+ iowrite32(((msm_rotator_dev->img_info[s]->src_rect.y & 0x1fff)
+ << 16) |
+ (msm_rotator_dev->img_info[s]->src_rect.x & 0x1fff),
+ MSM_ROTATOR_SRC_XY);
+ iowrite32(((msm_rotator_dev->img_info[s]->src.height & 0x1fff)
+ << 16) |
+ (msm_rotator_dev->img_info[s]->src.width & 0x1fff),
+ MSM_ROTATOR_SRC_IMAGE_SIZE);
+
+ switch (format) {
+ case MDP_RGB_565:
+ case MDP_BGR_565:
+ case MDP_RGB_888:
+ case MDP_ARGB_8888:
+ case MDP_RGBA_8888:
+ case MDP_XRGB_8888:
+ case MDP_BGRA_8888:
+ case MDP_RGBX_8888:
+ rc = msm_rotator_rgb_types(msm_rotator_dev->img_info[s],
+ in_paddr, out_paddr,
+ use_imem,
+ msm_rotator_dev->last_session_idx
+ != s);
+ break;
+ case MDP_Y_CBCR_H2V2:
+ case MDP_Y_CRCB_H2V2:
+ rc = msm_rotator_ycxcx_h2v2(msm_rotator_dev->img_info[s],
+ in_paddr, out_paddr, use_imem,
+ msm_rotator_dev->last_session_idx
+ != s,
+ in_chroma_paddr,
+ out_chroma_paddr);
+ break;
+ case MDP_Y_CRCB_H2V2_TILE:
+ case MDP_Y_CBCR_H2V2_TILE:
+ rc = msm_rotator_ycxcx_h2v2_tile(msm_rotator_dev->img_info[s],
+ in_paddr, out_paddr, use_imem,
+ msm_rotator_dev->last_session_idx
+ != s,
+ in_chroma_paddr,
+ out_chroma_paddr);
+ break;
+
+ case MDP_Y_CBCR_H2V1:
+ case MDP_Y_CRCB_H2V1:
+ rc = msm_rotator_ycxcx_h2v1(msm_rotator_dev->img_info[s],
+ in_paddr, out_paddr, use_imem,
+ msm_rotator_dev->last_session_idx
+ != s,
+ in_chroma_paddr,
+ out_chroma_paddr);
+ break;
+ case MDP_YCRYCB_H2V1:
+ rc = msm_rotator_ycrycb(msm_rotator_dev->img_info[s],
+ in_paddr, out_paddr, use_imem,
+ msm_rotator_dev->last_session_idx != s);
+ break;
+ default:
+ rc = -EINVAL;
+ goto do_rotate_exit;
+ }
+
+ if (rc != 0) {
+ msm_rotator_dev->last_session_idx = INVALID_SESSION;
+ goto do_rotate_exit;
+ }
+
+ iowrite32(3, MSM_ROTATOR_INTR_ENABLE);
+
+ msm_rotator_dev->processing = 1;
+ iowrite32(0x1, MSM_ROTATOR_START);
+
+ wait_event(msm_rotator_dev->wq,
+ (msm_rotator_dev->processing == 0));
+ status = (unsigned char)ioread32(MSM_ROTATOR_INTR_STATUS);
+ if ((status & 0x03) != 0x01)
+ rc = -EFAULT;
+ iowrite32(0, MSM_ROTATOR_INTR_ENABLE);
+ iowrite32(3, MSM_ROTATOR_INTR_CLEAR);
+
+do_rotate_exit:
+ disable_irq(msm_rotator_dev->irq);
+#ifdef CONFIG_MSM_ROTATOR_USE_IMEM
+ msm_rotator_imem_free(ROTATOR_REQUEST);
+#endif
+ schedule_delayed_work(&msm_rotator_dev->rot_clk_work, HZ);
+ if (dst_chroma_file)
+ put_pmem_file(dst_chroma_file);
+do_rotate_fail_dst_chr_img:
+ if (src_chroma_file)
+ put_pmem_file(src_chroma_file);
+do_rotate_unlock_mutex:
+ mutex_unlock(&msm_rotator_dev->rotator_lock);
+ if (dst_file)
+ put_pmem_file(dst_file);
+do_rotate_fail_dst_img:
+ if (src_file)
+ put_pmem_file(src_file);
+ dev_dbg(msm_rotator_dev->device, "%s() returning rc = %d\n",
+ __func__, rc);
+ return rc;
+}
+
+static int msm_rotator_start(unsigned long arg, int pid)
+{
+ struct msm_rotator_img_info info;
+ int rc = 0;
+ int s;
+ int first_free_index = INVALID_SESSION;
+
+ if (copy_from_user(&info, (void __user *)arg, sizeof(info)))
+ return -EFAULT;
+
+ if ((info.rotations > MSM_ROTATOR_MAX_ROT) ||
+ (info.src.height > MSM_ROTATOR_MAX_H) ||
+ (info.src.width > MSM_ROTATOR_MAX_W) ||
+ (info.dst.height > MSM_ROTATOR_MAX_H) ||
+ (info.dst.width > MSM_ROTATOR_MAX_W) ||
+ ((info.src_rect.x + info.src_rect.w) > info.src.width) ||
+ ((info.src_rect.y + info.src_rect.h) > info.src.height) ||
+ ((info.rotations & MDP_ROT_90) &&
+ ((info.dst_x + info.src_rect.h) > info.dst.width)) ||
+ ((info.rotations & MDP_ROT_90) &&
+ ((info.dst_y + info.src_rect.w) > info.dst.height)) ||
+ (!(info.rotations & MDP_ROT_90) &&
+ ((info.dst_x + info.src_rect.w) > info.dst.width)) ||
+ (!(info.rotations & MDP_ROT_90) &&
+ ((info.dst_y + info.src_rect.h) > info.dst.height)))
+ return -EINVAL;
+
+ switch (info.src.format) {
+ case MDP_RGB_565:
+ case MDP_BGR_565:
+ case MDP_RGB_888:
+ case MDP_ARGB_8888:
+ case MDP_RGBA_8888:
+ case MDP_XRGB_8888:
+ case MDP_RGBX_8888:
+ case MDP_BGRA_8888:
+ case MDP_Y_CBCR_H2V2:
+ case MDP_Y_CRCB_H2V2:
+ case MDP_Y_CBCR_H2V1:
+ case MDP_Y_CRCB_H2V1:
+ case MDP_YCRYCB_H2V1:
+ case MDP_Y_CRCB_H2V2_TILE:
+ case MDP_Y_CBCR_H2V2_TILE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (info.dst.format) {
+ case MDP_RGB_565:
+ case MDP_BGR_565:
+ case MDP_RGB_888:
+ case MDP_ARGB_8888:
+ case MDP_RGBA_8888:
+ case MDP_XRGB_8888:
+ case MDP_RGBX_8888:
+ case MDP_BGRA_8888:
+ case MDP_Y_CBCR_H2V2:
+ case MDP_Y_CRCB_H2V2:
+ case MDP_Y_CBCR_H2V1:
+ case MDP_Y_CRCB_H2V1:
+ case MDP_YCRYCB_H2V1:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&msm_rotator_dev->rotator_lock);
+ for (s = 0; s < MAX_SESSIONS; s++) {
+ if ((msm_rotator_dev->img_info[s] != NULL) &&
+ (info.session_id ==
+ (unsigned int)msm_rotator_dev->img_info[s]
+ )) {
+ *(msm_rotator_dev->img_info[s]) = info;
+ msm_rotator_dev->pid_list[s] = pid;
+
+ if (msm_rotator_dev->last_session_idx == s)
+ msm_rotator_dev->last_session_idx =
+ INVALID_SESSION;
+ break;
+ }
+
+ if ((msm_rotator_dev->img_info[s] == NULL) &&
+ (first_free_index ==
+ INVALID_SESSION))
+ first_free_index = s;
+ }
+
+ if ((s == MAX_SESSIONS) && (first_free_index != INVALID_SESSION)) {
+ /* allocate a session id */
+ msm_rotator_dev->img_info[first_free_index] =
+ kzalloc(sizeof(struct msm_rotator_img_info),
+ GFP_KERNEL);
+ if (!msm_rotator_dev->img_info[first_free_index]) {
+ printk(KERN_ERR "%s : unable to alloc mem\n",
+ __func__);
+ rc = -ENOMEM;
+ goto rotator_start_exit;
+ }
+ info.session_id = (unsigned int)
+ msm_rotator_dev->img_info[first_free_index];
+ *(msm_rotator_dev->img_info[first_free_index]) = info;
+ msm_rotator_dev->pid_list[first_free_index] = pid;
+
+ if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+ rc = -EFAULT;
+ } else if (s == MAX_SESSIONS) {
+ dev_dbg(msm_rotator_dev->device, "%s: all sessions in use\n",
+ __func__);
+ rc = -EBUSY;
+ }
+
+rotator_start_exit:
+ mutex_unlock(&msm_rotator_dev->rotator_lock);
+
+ return rc;
+}
+
+static int msm_rotator_finish(unsigned long arg)
+{
+ int rc = 0;
+ int s;
+ unsigned int session_id;
+
+ if (copy_from_user(&session_id, (void __user *)arg, sizeof(s)))
+ return -EFAULT;
+
+ mutex_lock(&msm_rotator_dev->rotator_lock);
+ for (s = 0; s < MAX_SESSIONS; s++) {
+ if ((msm_rotator_dev->img_info[s] != NULL) &&
+ (session_id ==
+ (unsigned int)msm_rotator_dev->img_info[s])) {
+ if (msm_rotator_dev->last_session_idx == s)
+ msm_rotator_dev->last_session_idx =
+ INVALID_SESSION;
+ kfree(msm_rotator_dev->img_info[s]);
+ msm_rotator_dev->img_info[s] = NULL;
+ msm_rotator_dev->pid_list[s] = 0;
+ break;
+ }
+ }
+
+ if (s == MAX_SESSIONS)
+ rc = -EINVAL;
+ mutex_unlock(&msm_rotator_dev->rotator_lock);
+ return rc;
+}
+
+static int
+msm_rotator_open(struct inode *inode, struct file *filp)
+{
+ int *id;
+ int i;
+
+ if (filp->private_data)
+ return -EBUSY;
+
+ mutex_lock(&msm_rotator_dev->rotator_lock);
+ id = &msm_rotator_dev->pid_list[0];
+ for (i = 0; i < MAX_SESSIONS; i++, id++) {
+ if (*id == 0)
+ break;
+ }
+ mutex_unlock(&msm_rotator_dev->rotator_lock);
+
+ if (i == MAX_SESSIONS)
+ return -EBUSY;
+
+ filp->private_data = (void *)task_tgid_nr(current);
+
+ return 0;
+}
+
+static int
+msm_rotator_close(struct inode *inode, struct file *filp)
+{
+ int s;
+ int pid;
+
+ pid = (int)filp->private_data;
+ mutex_lock(&msm_rotator_dev->rotator_lock);
+ for (s = 0; s < MAX_SESSIONS; s++) {
+ if (msm_rotator_dev->img_info[s] != NULL &&
+ msm_rotator_dev->pid_list[s] == pid) {
+ kfree(msm_rotator_dev->img_info[s]);
+ msm_rotator_dev->img_info[s] = NULL;
+ if (msm_rotator_dev->last_session_idx == s)
+ msm_rotator_dev->last_session_idx =
+ INVALID_SESSION;
+ }
+ }
+ mutex_unlock(&msm_rotator_dev->rotator_lock);
+
+ return 0;
+}
+
+static long msm_rotator_ioctl(struct file *file, unsigned cmd,
+ unsigned long arg)
+{
+ int pid;
+
+ if (_IOC_TYPE(cmd) != MSM_ROTATOR_IOCTL_MAGIC)
+ return -ENOTTY;
+
+ pid = (int)file->private_data;
+
+ switch (cmd) {
+ case MSM_ROTATOR_IOCTL_START:
+ return msm_rotator_start(arg, pid);
+ case MSM_ROTATOR_IOCTL_ROTATE:
+ return msm_rotator_do_rotate(arg);
+ case MSM_ROTATOR_IOCTL_FINISH:
+ return msm_rotator_finish(arg);
+
+ default:
+ dev_dbg(msm_rotator_dev->device,
+ "unexpected IOCTL %d\n", cmd);
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations msm_rotator_fops = {
+ .owner = THIS_MODULE,
+ .open = msm_rotator_open,
+ .release = msm_rotator_close,
+ .unlocked_ioctl = msm_rotator_ioctl,
+};
+
+static int __devinit msm_rotator_probe(struct platform_device *pdev)
+{
+ int rc = 0;
+ struct resource *res;
+ struct msm_rotator_platform_data *pdata = NULL;
+ int i, number_of_clks;
+ uint32_t ver;
+
+ msm_rotator_dev = kzalloc(sizeof(struct msm_rotator_dev), GFP_KERNEL);
+ if (!msm_rotator_dev) {
+ printk(KERN_ERR "%s Unable to allocate memory for struct\n",
+ __func__);
+ return -ENOMEM;
+ }
+ for (i = 0; i < MAX_SESSIONS; i++)
+ msm_rotator_dev->img_info[i] = NULL;
+ msm_rotator_dev->last_session_idx = INVALID_SESSION;
+
+ pdata = pdev->dev.platform_data;
+ number_of_clks = pdata->number_of_clocks;
+
+ msm_rotator_dev->imem_owner = IMEM_NO_OWNER;
+ mutex_init(&msm_rotator_dev->imem_lock);
+ msm_rotator_dev->imem_clk_state = CLK_DIS;
+ INIT_DELAYED_WORK(&msm_rotator_dev->imem_clk_work,
+ msm_rotator_imem_clk_work_f);
+ msm_rotator_dev->imem_clk = NULL;
+ msm_rotator_dev->pdev = pdev;
+
+ msm_rotator_dev->core_clk = NULL;
+ msm_rotator_dev->pclk = NULL;
+ msm_rotator_dev->axi_clk = NULL;
+
+ for (i = 0; i < number_of_clks; i++) {
+ if (pdata->rotator_clks[i].clk_type == ROTATOR_IMEM_CLK) {
+ msm_rotator_dev->imem_clk =
+ clk_get(&msm_rotator_dev->pdev->dev,
+ pdata->rotator_clks[i].clk_name);
+ if (IS_ERR(msm_rotator_dev->imem_clk)) {
+ rc = PTR_ERR(msm_rotator_dev->imem_clk);
+ msm_rotator_dev->imem_clk = NULL;
+ printk(KERN_ERR "%s: cannot get imem_clk "
+ "rc=%d\n", DRIVER_NAME, rc);
+ goto error_imem_clk;
+ }
+ if (pdata->rotator_clks[i].clk_rate)
+ clk_set_min_rate(msm_rotator_dev->imem_clk,
+ pdata->rotator_clks[i].clk_rate);
+ }
+ if (pdata->rotator_clks[i].clk_type == ROTATOR_PCLK) {
+ msm_rotator_dev->pclk =
+ clk_get(&msm_rotator_dev->pdev->dev,
+ pdata->rotator_clks[i].clk_name);
+ if (IS_ERR(msm_rotator_dev->pclk)) {
+ rc = PTR_ERR(msm_rotator_dev->pclk);
+ msm_rotator_dev->pclk = NULL;
+ printk(KERN_ERR "%s: cannot get pclk rc=%d\n",
+ DRIVER_NAME, rc);
+ goto error_pclk;
+ }
+
+ if (pdata->rotator_clks[i].clk_rate)
+ clk_set_min_rate(msm_rotator_dev->pclk,
+ pdata->rotator_clks[i].clk_rate);
+ }
+
+ if (pdata->rotator_clks[i].clk_type == ROTATOR_CORE_CLK) {
+ msm_rotator_dev->core_clk =
+ clk_get(&msm_rotator_dev->pdev->dev,
+ pdata->rotator_clks[i].clk_name);
+ if (IS_ERR(msm_rotator_dev->core_clk)) {
+ rc = PTR_ERR(msm_rotator_dev->core_clk);
+ msm_rotator_dev->core_clk = NULL;
+ printk(KERN_ERR "%s: cannot get core clk "
+ "rc=%d\n", DRIVER_NAME, rc);
+ goto error_core_clk;
+ }
+
+ if (pdata->rotator_clks[i].clk_rate)
+ clk_set_min_rate(msm_rotator_dev->core_clk,
+ pdata->rotator_clks[i].clk_rate);
+ }
+
+ if (pdata->rotator_clks[i].clk_type == ROTATOR_AXI_CLK) {
+ msm_rotator_dev->axi_clk =
+ clk_get(&msm_rotator_dev->pdev->dev,
+ pdata->rotator_clks[i].clk_name);
+ if (IS_ERR(msm_rotator_dev->axi_clk)) {
+ rc = PTR_ERR(msm_rotator_dev->axi_clk);
+ msm_rotator_dev->axi_clk = NULL;
+ printk(KERN_ERR "%s: cannot get axi clk "
+ "rc=%d\n", DRIVER_NAME, rc);
+ goto error_axi_clk;
+ }
+
+ if (pdata->rotator_clks[i].clk_rate)
+ clk_set_min_rate(msm_rotator_dev->axi_clk,
+ pdata->rotator_clks[i].clk_rate);
+ }
+ }
+
+ msm_rotator_dev->regulator = regulator_get(NULL, pdata->regulator_name);
+ if (IS_ERR(msm_rotator_dev->regulator))
+ msm_rotator_dev->regulator = NULL;
+
+ msm_rotator_dev->rot_clk_state = CLK_DIS;
+ INIT_DELAYED_WORK(&msm_rotator_dev->rot_clk_work,
+ msm_rotator_rot_clk_work_f);
+
+ mutex_init(&msm_rotator_dev->rotator_lock);
+
+ platform_set_drvdata(pdev, msm_rotator_dev);
+
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ printk(KERN_ALERT
+ "%s: could not get IORESOURCE_MEM\n", DRIVER_NAME);
+ rc = -ENODEV;
+ goto error_get_resource;
+ }
+ msm_rotator_dev->io_base = ioremap(res->start,
+ resource_size(res));
+
+#ifdef CONFIG_MSM_ROTATOR_USE_IMEM
+ if (msm_rotator_dev->imem_clk)
+ clk_enable(msm_rotator_dev->imem_clk);
+#endif
+ enable_rot_clks();
+ ver = ioread32(MSM_ROTATOR_HW_VERSION);
+ disable_rot_clks();
+
+#ifdef CONFIG_MSM_ROTATOR_USE_IMEM
+ if (msm_rotator_dev->imem_clk)
+ clk_disable(msm_rotator_dev->imem_clk);
+#endif
+ if (ver != pdata->hardware_version_number) {
+ printk(KERN_ALERT "%s: invalid HW version\n", DRIVER_NAME);
+ rc = -ENODEV;
+ goto error_get_resource;
+ }
+ msm_rotator_dev->irq = platform_get_irq(pdev, 0);
+ if (msm_rotator_dev->irq < 0) {
+ printk(KERN_ALERT "%s: could not get IORESOURCE_IRQ\n",
+ DRIVER_NAME);
+ rc = -ENODEV;
+ goto error_get_irq;
+ }
+ rc = request_irq(msm_rotator_dev->irq, msm_rotator_isr,
+ IRQF_TRIGGER_RISING, DRIVER_NAME, NULL);
+ if (rc) {
+ printk(KERN_ERR "%s: request_irq() failed\n", DRIVER_NAME);
+ goto error_get_irq;
+ }
+ /* we enable the IRQ when we need it in the ioctl */
+ disable_irq(msm_rotator_dev->irq);
+
+ rc = alloc_chrdev_region(&msm_rotator_dev->dev_num, 0, 1, DRIVER_NAME);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: alloc_chrdev_region Failed rc = %d\n",
+ __func__, rc);
+ goto error_get_irq;
+ }
+
+ msm_rotator_dev->class = class_create(THIS_MODULE, DRIVER_NAME);
+ if (IS_ERR(msm_rotator_dev->class)) {
+ rc = PTR_ERR(msm_rotator_dev->class);
+ printk(KERN_ERR "%s: couldn't create class rc = %d\n",
+ DRIVER_NAME, rc);
+ goto error_class_create;
+ }
+
+ msm_rotator_dev->device = device_create(msm_rotator_dev->class, NULL,
+ msm_rotator_dev->dev_num, NULL,
+ DRIVER_NAME);
+ if (IS_ERR(msm_rotator_dev->device)) {
+ rc = PTR_ERR(msm_rotator_dev->device);
+ printk(KERN_ERR "%s: device_create failed %d\n",
+ DRIVER_NAME, rc);
+ goto error_class_device_create;
+ }
+
+ cdev_init(&msm_rotator_dev->cdev, &msm_rotator_fops);
+ rc = cdev_add(&msm_rotator_dev->cdev,
+ MKDEV(MAJOR(msm_rotator_dev->dev_num), 0),
+ 1);
+ if (rc < 0) {
+ printk(KERN_ERR "%s: cdev_add failed %d\n", __func__, rc);
+ goto error_cdev_add;
+ }
+
+ init_waitqueue_head(&msm_rotator_dev->wq);
+
+ dev_dbg(msm_rotator_dev->device, "probe successful\n");
+ return rc;
+
+error_cdev_add:
+ device_destroy(msm_rotator_dev->class, msm_rotator_dev->dev_num);
+error_class_device_create:
+ class_destroy(msm_rotator_dev->class);
+error_class_create:
+ unregister_chrdev_region(msm_rotator_dev->dev_num, 1);
+error_get_irq:
+ iounmap(msm_rotator_dev->io_base);
+error_get_resource:
+ mutex_destroy(&msm_rotator_dev->rotator_lock);
+ if (msm_rotator_dev->regulator)
+ regulator_put(msm_rotator_dev->regulator);
+ clk_put(msm_rotator_dev->axi_clk);
+error_axi_clk:
+ clk_put(msm_rotator_dev->core_clk);
+error_core_clk:
+ clk_put(msm_rotator_dev->pclk);
+error_pclk:
+ if (msm_rotator_dev->imem_clk)
+ clk_put(msm_rotator_dev->imem_clk);
+error_imem_clk:
+ mutex_destroy(&msm_rotator_dev->imem_lock);
+ kfree(msm_rotator_dev);
+ return rc;
+}
+
+static int __devexit msm_rotator_remove(struct platform_device *plat_dev)
+{
+ int i;
+
+ free_irq(msm_rotator_dev->irq, NULL);
+ mutex_destroy(&msm_rotator_dev->rotator_lock);
+ cdev_del(&msm_rotator_dev->cdev);
+ device_destroy(msm_rotator_dev->class, msm_rotator_dev->dev_num);
+ class_destroy(msm_rotator_dev->class);
+ unregister_chrdev_region(msm_rotator_dev->dev_num, 1);
+ iounmap(msm_rotator_dev->io_base);
+ if (msm_rotator_dev->imem_clk) {
+ if (msm_rotator_dev->imem_clk_state == CLK_EN)
+ clk_disable(msm_rotator_dev->imem_clk);
+ clk_put(msm_rotator_dev->imem_clk);
+ msm_rotator_dev->imem_clk = NULL;
+ }
+ if (msm_rotator_dev->rot_clk_state == CLK_EN)
+ disable_rot_clks();
+ clk_put(msm_rotator_dev->core_clk);
+ clk_put(msm_rotator_dev->pclk);
+ clk_put(msm_rotator_dev->axi_clk);
+ if (msm_rotator_dev->regulator)
+ regulator_put(msm_rotator_dev->regulator);
+ msm_rotator_dev->core_clk = NULL;
+ msm_rotator_dev->pclk = NULL;
+ msm_rotator_dev->axi_clk = NULL;
+ mutex_destroy(&msm_rotator_dev->imem_lock);
+ for (i = 0; i < MAX_SESSIONS; i++)
+ if (msm_rotator_dev->img_info[i] != NULL)
+ kfree(msm_rotator_dev->img_info[i]);
+ kfree(msm_rotator_dev);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int msm_rotator_suspend(struct platform_device *dev, pm_message_t state)
+{
+ mutex_lock(&msm_rotator_dev->imem_lock);
+ if (msm_rotator_dev->imem_clk_state == CLK_EN
+ && msm_rotator_dev->imem_clk) {
+ clk_disable(msm_rotator_dev->imem_clk);
+ msm_rotator_dev->imem_clk_state = CLK_SUSPEND;
+ }
+ mutex_unlock(&msm_rotator_dev->imem_lock);
+ mutex_lock(&msm_rotator_dev->rotator_lock);
+ if (msm_rotator_dev->rot_clk_state == CLK_EN) {
+ disable_rot_clks();
+ msm_rotator_dev->rot_clk_state = CLK_SUSPEND;
+ }
+ mutex_unlock(&msm_rotator_dev->rotator_lock);
+ return 0;
+}
+
+static int msm_rotator_resume(struct platform_device *dev)
+{
+ mutex_lock(&msm_rotator_dev->imem_lock);
+ if (msm_rotator_dev->imem_clk_state == CLK_SUSPEND
+ && msm_rotator_dev->imem_clk) {
+ clk_enable(msm_rotator_dev->imem_clk);
+ msm_rotator_dev->imem_clk_state = CLK_EN;
+ }
+ mutex_unlock(&msm_rotator_dev->imem_lock);
+ mutex_lock(&msm_rotator_dev->rotator_lock);
+ if (msm_rotator_dev->rot_clk_state == CLK_SUSPEND) {
+ enable_rot_clks();
+ msm_rotator_dev->rot_clk_state = CLK_EN;
+ }
+ mutex_unlock(&msm_rotator_dev->rotator_lock);
+ return 0;
+}
+#endif
+
+static struct platform_driver msm_rotator_platform_driver = {
+ .probe = msm_rotator_probe,
+ .remove = __devexit_p(msm_rotator_remove),
+#ifdef CONFIG_PM
+ .suspend = msm_rotator_suspend,
+ .resume = msm_rotator_resume,
+#endif
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = DRIVER_NAME
+ }
+};
+
+static int __init msm_rotator_init(void)
+{
+ return platform_driver_register(&msm_rotator_platform_driver);
+}
+
+static void __exit msm_rotator_exit(void)
+{
+ return platform_driver_unregister(&msm_rotator_platform_driver);
+}
+
+module_init(msm_rotator_init);
+module_exit(msm_rotator_exit);
+
+MODULE_DESCRIPTION("MSM Offline Image Rotator driver");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/char/msm_smd_pkt.c b/drivers/char/msm_smd_pkt.c
index b6f8a65..a8e28d3 100644
--- a/drivers/char/msm_smd_pkt.c
+++ b/drivers/char/msm_smd_pkt.c
@@ -9,11 +9,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- * 02110-1301, USA.
- *
*/
/*
* SMD Packet Driver -- Provides userspace interface to SMD packet ports.
diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
index f6595ab..c720e9a 100644
--- a/drivers/char/tpm/Kconfig
+++ b/drivers/char/tpm/Kconfig
@@ -60,4 +60,18 @@
Further information on this driver and the supported hardware
can be found at http://www.trust.rub.de/projects/linux-device-driver-infineon-tpm/
+config TCG_ST_I2C
+ tristate "ST Micro ST19NP18-TPM-I2C TPM interface"
+ depends on I2C
+ default n
+ ---help---
+ If you have a ST19NP18-TPM-I2C TPM security chip from ST Micro
+ say Yes and it will be accessible from Linux.
+
+config TCG_TPMD_DEV
+ tristate "tpmd_dev TPM Emulator driver"
+ default n
+ ---help---
+ Enables the TPM emulator driver
+
endif # TCG_TPM
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
index ea3a1e0..c113cf1 100644
--- a/drivers/char/tpm/Makefile
+++ b/drivers/char/tpm/Makefile
@@ -6,6 +6,8 @@
obj-$(CONFIG_TCG_TPM) += tpm_bios.o
endif
obj-$(CONFIG_TCG_TIS) += tpm_tis.o
+obj-$(CONFIG_TCG_ST_I2C) += tpm_st_i2c.o
obj-$(CONFIG_TCG_NSC) += tpm_nsc.o
obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o
obj-$(CONFIG_TCG_INFINEON) += tpm_infineon.o
+obj-$(CONFIG_TCG_TPMD_DEV) += tpmd_dev/
diff --git a/drivers/char/tpm/tpm_st_i2c.c b/drivers/char/tpm/tpm_st_i2c.c
new file mode 100644
index 0000000..3a6e8c4f
--- /dev/null
+++ b/drivers/char/tpm/tpm_st_i2c.c
@@ -0,0 +1,361 @@
+/* Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <mach/gpio.h>
+#include <mach/tpm_st_i2c.h>
+#include <mach/msm_iomap.h>
+#include "tpm.h"
+
+#define DEVICE_NAME "tpm_st_i2c"
+
+#define TPM_HEADER_LEN sizeof(struct tpm_input_header)
+#define TPM_ST_I2C_BLOCK_MAX 40
+
+struct tpm_st_i2c_dev {
+ struct i2c_client *client;
+ struct tpm_st_i2c_platform_data *pd;
+ struct completion com[2];
+};
+
+/* for completion array */
+#define ACCEPT_CMD_INDEX 0
+#define DATA_AVAIL_INDEX 1
+
+static struct tpm_st_i2c_dev *tpm_st_i2c_dev;
+
+#define TPM_ST_I2C_REQ_COMPLETE_MASK 1
+
+static u8 tpm_st_i2c_status(struct tpm_chip *chip)
+{
+ int gpio = tpm_st_i2c_dev->pd->data_avail_gpio;
+ return gpio_get_value(gpio);
+}
+
+static void tpm_st_i2c_cancel(struct tpm_chip *chip)
+{
+ /* not supported */
+ return;
+}
+
+static int tpm_st_i2c_transfer_buf(struct tpm_chip *chip, u8 *buf, size_t count,
+ int recv)
+{
+ struct i2c_msg msg = {
+ .addr = tpm_st_i2c_dev->client->addr,
+ .flags = 0,
+ .buf = buf,
+ .len = TPM_HEADER_LEN, /* must read/write header first */
+ };
+ int gpio;
+ int irq;
+ struct completion *com;
+ __be32 *native_size;
+ int read_header = 0;
+ int rc = 0;
+ int len = count;
+ uint32_t size = count;
+ int tmp;
+
+ if (recv) {
+ msg.flags |= I2C_M_RD;
+ read_header = 1;
+ gpio = tpm_st_i2c_dev->pd->data_avail_gpio;
+ irq = tpm_st_i2c_dev->pd->data_avail_irq;
+ com = &tpm_st_i2c_dev->com[DATA_AVAIL_INDEX];
+ } else {
+ gpio = tpm_st_i2c_dev->pd->accept_cmd_gpio;
+ irq = tpm_st_i2c_dev->pd->accept_cmd_irq;
+ com = &tpm_st_i2c_dev->com[ACCEPT_CMD_INDEX];
+ }
+
+ if (len < TPM_HEADER_LEN) {
+ dev_dbg(chip->dev, "%s: invalid len\n", __func__);
+ return -EINVAL;
+ }
+
+ do {
+ if (!gpio_get_value(gpio)) {
+ /* reset the completion in case the irq fired
+ * during the probe
+ */
+ init_completion(com);
+ enable_irq(irq);
+ tmp = wait_for_completion_interruptible_timeout(
+ com, HZ/2);
+ if (!tmp) {
+ dev_dbg(chip->dev, "%s timeout\n",
+ __func__);
+ return -EBUSY;
+ }
+ }
+ rc = i2c_transfer(tpm_st_i2c_dev->client->adapter,
+ &msg, 1);
+ if (rc < 0) {
+ dev_dbg(chip->dev, "Error in I2C transfer\n");
+ return rc;
+ }
+ if (read_header) {
+ read_header = 0;
+ native_size = (__force __be32 *) (buf + 2);
+ size = be32_to_cpu(*native_size);
+ if (count < size) {
+ dev_dbg(chip->dev,
+ "%s: invalid count\n",
+ __func__);
+ rc = -EIO;
+ }
+ len = size;
+ }
+ len -= msg.len;
+ if (len) {
+ buf += msg.len;
+ msg.buf = buf;
+ if (len > TPM_ST_I2C_BLOCK_MAX)
+ msg.len = TPM_ST_I2C_BLOCK_MAX;
+ else
+ msg.len = len;
+ }
+ } while (len > 0);
+
+ if (rc >= 0)
+ return size;
+ else
+ return rc;
+}
+
+static int tpm_st_i2c_recv(struct tpm_chip *chip, u8 *buf, size_t count)
+{
+ return tpm_st_i2c_transfer_buf(chip, buf, count, 1);
+}
+
+static int tpm_st_i2c_send(struct tpm_chip *chip, u8 *buf, size_t len)
+{
+ return tpm_st_i2c_transfer_buf(chip, buf, len, 0);
+}
+
+#ifdef CONFIG_PM
+static int tpm_st_i2c_suspend(struct i2c_client *client, pm_message_t msg)
+{
+ return tpm_pm_suspend(&client->dev, msg);
+}
+
+static int tpm_st_i2c_resume(struct i2c_client *client)
+{
+ return tpm_pm_resume(&client->dev);
+}
+#endif
+
+static const struct file_operations tpm_st_i2c_fs_ops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .open = tpm_open,
+ .read = tpm_read,
+ .write = tpm_write,
+ .release = tpm_release,
+};
+
+static DEVICE_ATTR(pubek, S_IRUGO, tpm_show_pubek, NULL);
+static DEVICE_ATTR(pcrs, S_IRUGO, tpm_show_pcrs, NULL);
+static DEVICE_ATTR(enabled, S_IRUGO, tpm_show_enabled, NULL);
+static DEVICE_ATTR(active, S_IRUGO, tpm_show_active, NULL);
+static DEVICE_ATTR(owned, S_IRUGO, tpm_show_owned, NULL);
+static DEVICE_ATTR(temp_deactivated, S_IRUGO, tpm_show_temp_deactivated,
+ NULL);
+static DEVICE_ATTR(caps, S_IRUGO, tpm_show_caps_1_2, NULL);
+
+static struct attribute *tpm_st_i2c_attrs[] = {
+ &dev_attr_pubek.attr,
+ &dev_attr_pcrs.attr,
+ &dev_attr_enabled.attr,
+ &dev_attr_active.attr,
+ &dev_attr_owned.attr,
+ &dev_attr_temp_deactivated.attr,
+ &dev_attr_caps.attr,
+ NULL,
+};
+
+static struct attribute_group tpm_st_i2c_attr_grp = {
+ .attrs = tpm_st_i2c_attrs
+};
+
+static struct tpm_vendor_specific tpm_st_i2c_vendor = {
+ .status = tpm_st_i2c_status,
+ .recv = tpm_st_i2c_recv,
+ .send = tpm_st_i2c_send,
+ .cancel = tpm_st_i2c_cancel,
+ .req_complete_mask = TPM_ST_I2C_REQ_COMPLETE_MASK,
+ .req_complete_val = TPM_ST_I2C_REQ_COMPLETE_MASK,
+ .req_canceled = 0xff, /* not supported */
+ .attr_group = &tpm_st_i2c_attr_grp,
+ .miscdev = {
+ .fops = &tpm_st_i2c_fs_ops,},
+};
+
+static irqreturn_t tpm_st_i2c_isr(int irq, void *dev_id)
+{
+ disable_irq_nosync(irq);
+ if (irq == tpm_st_i2c_dev->pd->accept_cmd_irq)
+ complete(&tpm_st_i2c_dev->com[ACCEPT_CMD_INDEX]);
+ else
+ complete(&tpm_st_i2c_dev->com[DATA_AVAIL_INDEX]);
+ return IRQ_HANDLED;
+}
+
+static int tpm_st_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int rc = 0;
+ struct tpm_st_i2c_platform_data *pd;
+ struct tpm_chip *chip;
+ int high;
+
+ dev_dbg(&client->dev, "%s()\n", __func__);
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE |
+ I2C_FUNC_SMBUS_I2C_BLOCK |
+ I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "incompatible adapter\n");
+ return -ENODEV;
+ }
+
+ pd = client->dev.platform_data;
+ if (!pd || !pd->gpio_setup || !pd->gpio_release) {
+ dev_err(&client->dev, "platform data not setup\n");
+ rc = -EFAULT;
+ goto no_platform_data;
+ }
+ rc = pd->gpio_setup();
+ if (rc) {
+ dev_err(&client->dev, "gpio_setup failed\n");
+ goto gpio_setup_fail;
+ }
+
+ gpio_direction_input(pd->accept_cmd_gpio);
+ gpio_direction_input(pd->data_avail_gpio);
+
+ tpm_st_i2c_dev = kzalloc(sizeof(struct tpm_st_i2c_dev), GFP_KERNEL);
+ if (!tpm_st_i2c_dev) {
+ printk(KERN_ERR "%s Unable to allocate memory for struct\n",
+ __func__);
+ rc = -ENOMEM;
+ goto kzalloc_fail;
+ }
+
+ tpm_st_i2c_dev->client = client;
+ tpm_st_i2c_dev->pd = pd;
+
+ init_completion(&tpm_st_i2c_dev->com[ACCEPT_CMD_INDEX]);
+ init_completion(&tpm_st_i2c_dev->com[DATA_AVAIL_INDEX]);
+ /* This logic allows us to setup irq but not have it enabled, in
+ * case the lines are already active
+ */
+ high = gpio_get_value(pd->data_avail_gpio);
+ rc = request_irq(pd->data_avail_irq, tpm_st_i2c_isr, IRQF_TRIGGER_HIGH,
+ DEVICE_NAME "-data", NULL);
+ if (rc) {
+ dev_err(&client->dev, "request for data irq failed\n");
+ goto data_irq_fail;
+ }
+ if (!high)
+ disable_irq(pd->data_avail_irq);
+ high = gpio_get_value(pd->accept_cmd_gpio);
+ rc = request_irq(pd->accept_cmd_irq, tpm_st_i2c_isr, IRQF_TRIGGER_HIGH,
+ DEVICE_NAME "-cmd", NULL);
+ if (rc) {
+ dev_err(&client->dev, "request for cmd irq failed\n");
+ goto cmd_irq_fail;
+ }
+ if (!high)
+ disable_irq(pd->accept_cmd_irq);
+
+ tpm_st_i2c_vendor.irq = pd->data_avail_irq;
+
+ chip = tpm_register_hardware(&client->dev, &tpm_st_i2c_vendor);
+ if (!chip) {
+ dev_err(&client->dev, "Could not register tpm hardware\n");
+ rc = -ENODEV;
+ goto tpm_reg_fail;
+ }
+
+ dev_info(&client->dev, "added\n");
+
+ return 0;
+
+tpm_reg_fail:
+ free_irq(pd->accept_cmd_irq, NULL);
+cmd_irq_fail:
+ free_irq(pd->data_avail_irq, NULL);
+data_irq_fail:
+kzalloc_fail:
+ pd->gpio_release();
+gpio_setup_fail:
+no_platform_data:
+
+ return rc;
+}
+
+static int __exit tpm_st_i2c_remove(struct i2c_client *client)
+{
+ free_irq(tpm_st_i2c_dev->pd->accept_cmd_irq, NULL);
+ free_irq(tpm_st_i2c_dev->pd->data_avail_irq, NULL);
+ tpm_remove_hardware(&client->dev);
+ tpm_st_i2c_dev->pd->gpio_release();
+ kfree(tpm_st_i2c_dev);
+
+ return 0;
+}
+
+static const struct i2c_device_id tpm_st_i2c_id[] = {
+ { DEVICE_NAME, 0 },
+ { }
+};
+
+static struct i2c_driver tpm_st_i2c_driver = {
+ .driver = {
+ .name = DEVICE_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = tpm_st_i2c_probe,
+ .remove = __exit_p(tpm_st_i2c_remove),
+#ifdef CONFIG_PM
+ .suspend = tpm_st_i2c_suspend,
+ .resume = tpm_st_i2c_resume,
+#endif
+ .id_table = tpm_st_i2c_id,
+};
+
+static int __init tpm_st_i2c_init(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&tpm_st_i2c_driver);
+ if (ret)
+ printk(KERN_ERR "%s: failed to add i2c driver\n", __func__);
+
+ return ret;
+}
+
+static void __exit tpm_st_i2c_exit(void)
+{
+ i2c_del_driver(&tpm_st_i2c_driver);
+}
+
+module_init(tpm_st_i2c_init);
+module_exit(tpm_st_i2c_exit);
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.0");
+MODULE_AUTHOR("Qualcomm Innovation Center, Inc.");
+MODULE_DESCRIPTION("ST19NP18-TPM-I2C driver");
diff --git a/drivers/char/tpm/tpmd_dev/Makefile b/drivers/char/tpm/tpmd_dev/Makefile
new file mode 100644
index 0000000..7d62de4
--- /dev/null
+++ b/drivers/char/tpm/tpmd_dev/Makefile
@@ -0,0 +1,4 @@
+#
+# Makefile for the kernel tpm emulator device driver.
+#
+obj-$(CONFIG_TCG_TPM) += tpmd_dev.o
diff --git a/drivers/char/tpm/tpmd_dev/config.h b/drivers/char/tpm/tpmd_dev/config.h
new file mode 100644
index 0000000..ec8d93e
--- /dev/null
+++ b/drivers/char/tpm/tpmd_dev/config.h
@@ -0,0 +1,32 @@
+/* Software-based Trusted Platform Module (TPM) Emulator
+ * Copyright (C) 2004-2010 Mario Strasser <mast@gmx.net>
+ *
+ * This module is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * This module is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * $Id: config.h.in 426 2010-02-22 17:11:58Z mast $
+ */
+
+#ifndef _CONFIG_H_
+#define _CONFIG_H_
+
+/* project and build version */
+#define VERSION_MAJOR 0
+#define VERSION_MINOR 7
+#define VERSION_BUILD 424
+
+/* TDDL and LKM configuration */
+#define TPM_SOCKET_NAME "/var/run/tpm/tpmd_socket:0"
+#define TPM_STORAGE_NAME "/var/lib/tpm/tpm_emulator-1_2_0_7"
+#define TPM_DEVICE_NAME "/dev/tpm"
+#define TPM_LOG_FILE ""
+#define TPM_CMD_BUF_SIZE 4096
+
+#endif /* _CONFIG_H_ */
diff --git a/drivers/char/tpm/tpmd_dev/tpmd_dev.c b/drivers/char/tpm/tpmd_dev/tpmd_dev.c
new file mode 100644
index 0000000..cbfcbd8
--- /dev/null
+++ b/drivers/char/tpm/tpmd_dev/tpmd_dev.c
@@ -0,0 +1,272 @@
+/* Software-based Trusted Platform Module (TPM) Emulator
+ * Copyright (C) 2004-2010 Mario Strasser <mast@gmx.net>
+ *
+ * This module is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * This module is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * $Id: tpmd_dev.c 426 2010-02-22 17:11:58Z mast $
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+
+#include <linux/socket.h>
+#include <linux/net.h>
+#include <linux/un.h>
+
+#include "config.h"
+
+#define TPM_DEVICE_MINOR 224
+#define TPM_DEVICE_ID "tpm"
+#define TPM_MODULE_NAME "tpmd_dev"
+
+#define TPM_STATE_IS_OPEN 0
+
+#ifdef DEBUG
+#define debug(fmt, ...) printk(KERN_DEBUG "%s %s:%d: Debug: " fmt "\n", \
+ TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__)
+#else
+#define debug(fmt, ...)
+#endif
+#define info(fmt, ...) printk(KERN_INFO "%s %s:%d: Info: " fmt "\n", \
+ TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__)
+#define error(fmt, ...) printk(KERN_ERR "%s %s:%d: Error: " fmt "\n", \
+ TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__)
+#define alert(fmt, ...) printk(KERN_ALERT "%s %s:%d: Alert: " fmt "\n", \
+ TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__)
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mario Strasser <mast@gmx.net>");
+MODULE_DESCRIPTION("Trusted Platform Module (TPM) Emulator");
+MODULE_SUPPORTED_DEVICE(TPM_DEVICE_ID);
+
+/* module parameters */
+char *tpmd_socket_name = TPM_SOCKET_NAME;
+module_param(tpmd_socket_name, charp, 0444);
+MODULE_PARM_DESC(tpmd_socket_name, " Sets the name of the TPM daemon socket.");
+
+/* TPM lock */
+static struct semaphore tpm_mutex;
+
+/* TPM command response */
+static struct {
+ uint8_t *data;
+ uint32_t size;
+} tpm_response;
+
+/* module state */
+static uint32_t module_state;
+static struct socket *tpmd_sock;
+static struct sockaddr_un addr;
+
+static int tpmd_connect(char *socket_name)
+{
+ int res;
+ res = sock_create(PF_UNIX, SOCK_STREAM, 0, &tpmd_sock);
+ if (res != 0) {
+ error("sock_create() failed: %d\n", res);
+ tpmd_sock = NULL;
+ return res;
+ }
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path));
+ res = tpmd_sock->ops->connect(tpmd_sock,
+ (struct sockaddr*)&addr, sizeof(struct sockaddr_un), 0);
+ if (res != 0) {
+ error("sock_connect() failed: %d\n", res);
+ tpmd_sock->ops->release(tpmd_sock);
+ tpmd_sock = NULL;
+ return res;
+ }
+ return 0;
+}
+
+static void tpmd_disconnect(void)
+{
+ if (tpmd_sock != NULL) tpmd_sock->ops->release(tpmd_sock);
+ tpmd_sock = NULL;
+}
+
+static int tpmd_handle_command(const uint8_t *in, uint32_t in_size)
+{
+ int res;
+ mm_segment_t oldmm;
+ struct msghdr msg;
+ struct iovec iov;
+ /* send command to tpmd */
+ memset(&msg, 0, sizeof(msg));
+ iov.iov_base = (void*)in;
+ iov.iov_len = in_size;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ res = sock_sendmsg(tpmd_sock, &msg, in_size);
+ if (res < 0) {
+ error("sock_sendmsg() failed: %d\n", res);
+ return res;
+ }
+ /* receive response from tpmd */
+ tpm_response.size = TPM_CMD_BUF_SIZE;
+ tpm_response.data = kmalloc(tpm_response.size, GFP_KERNEL);
+ if (tpm_response.data == NULL) return -1;
+ memset(&msg, 0, sizeof(msg));
+ iov.iov_base = (void*)tpm_response.data;
+ iov.iov_len = tpm_response.size;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ oldmm = get_fs();
+ set_fs(KERNEL_DS);
+ res = sock_recvmsg(tpmd_sock, &msg, tpm_response.size, 0);
+ set_fs(oldmm);
+ if (res < 0) {
+ error("sock_recvmsg() failed: %d\n", res);
+ tpm_response.data = NULL;
+ return res;
+ }
+ tpm_response.size = res;
+ return 0;
+}
+
+static int tpm_open(struct inode *inode, struct file *file)
+{
+ int res;
+ debug("%s()", __FUNCTION__);
+ if (test_and_set_bit(TPM_STATE_IS_OPEN, (void*)&module_state)) return -EBUSY;
+ down(&tpm_mutex);
+ res = tpmd_connect(tpmd_socket_name);
+ up(&tpm_mutex);
+ if (res != 0) {
+ clear_bit(TPM_STATE_IS_OPEN, (void*)&module_state);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int tpm_release(struct inode *inode, struct file *file)
+{
+ debug("%s()", __FUNCTION__);
+ down(&tpm_mutex);
+ if (tpm_response.data != NULL) {
+ kfree(tpm_response.data);
+ tpm_response.data = NULL;
+ }
+ tpmd_disconnect();
+ up(&tpm_mutex);
+ clear_bit(TPM_STATE_IS_OPEN, (void*)&module_state);
+ return 0;
+}
+
+static ssize_t tpm_read(struct file *file, char *buf, size_t count, loff_t *ppos)
+{
+ debug("%s(%zd)", __FUNCTION__, count);
+ down(&tpm_mutex);
+ if (tpm_response.data != NULL) {
+ count = min(count, (size_t)tpm_response.size - (size_t)*ppos);
+ count -= copy_to_user(buf, &tpm_response.data[*ppos], count);
+ *ppos += count;
+ if ((size_t)tpm_response.size == (size_t)*ppos) {
+ kfree(tpm_response.data);
+ tpm_response.data = NULL;
+ }
+ } else {
+ count = 0;
+ }
+ up(&tpm_mutex);
+ return count;
+}
+
+static ssize_t tpm_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
+{
+ debug("%s(%zd)", __FUNCTION__, count);
+ down(&tpm_mutex);
+ *ppos = 0;
+ if (tpm_response.data != NULL) {
+ kfree(tpm_response.data);
+ tpm_response.data = NULL;
+ }
+ if (tpmd_handle_command(buf, count) != 0) {
+ count = -EILSEQ;
+ tpm_response.data = NULL;
+ }
+ up(&tpm_mutex);
+ return count;
+}
+
+#define TPMIOC_CANCEL _IO('T', 0x00)
+#define TPMIOC_TRANSMIT _IO('T', 0x01)
+
+static int tpm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ debug("%s(%d, %p)", __FUNCTION__, cmd, (char*)arg);
+ if (cmd == TPMIOC_TRANSMIT) {
+ uint32_t count = ntohl(*(uint32_t*)(arg + 2));
+ down(&tpm_mutex);
+ if (tpm_response.data != NULL) {
+ kfree(tpm_response.data);
+ tpm_response.data = NULL;
+ }
+ if (tpmd_handle_command((char*)arg, count) == 0) {
+ tpm_response.size -= copy_to_user((char*)arg, tpm_response.data, tpm_response.size);
+ kfree(tpm_response.data);
+ tpm_response.data = NULL;
+ } else {
+ tpm_response.size = 0;
+ tpm_response.data = NULL;
+ }
+ up(&tpm_mutex);
+ return tpm_response.size;
+ }
+ return -1;
+}
+
+struct file_operations fops = {
+ .owner = THIS_MODULE,
+ .open = tpm_open,
+ .release = tpm_release,
+ .read = tpm_read,
+ .write = tpm_write,
+ .ioctl = tpm_ioctl,
+};
+
+static struct miscdevice tpm_dev = {
+ .minor = TPM_DEVICE_MINOR,
+ .name = TPM_DEVICE_ID,
+ .fops = &fops,
+};
+
+int __init init_tpm_module(void)
+{
+ int res = misc_register(&tpm_dev);
+ if (res != 0) {
+ error("misc_register() failed for minor %d\n", TPM_DEVICE_MINOR);
+ return res;
+ }
+ /* initialize variables */
+ sema_init(&tpm_mutex, 1);
+ module_state = 0;
+ tpm_response.data = NULL;
+ tpm_response.size = 0;
+ tpmd_sock = NULL;
+ return 0;
+}
+
+void __exit cleanup_tpm_module(void)
+{
+ misc_deregister(&tpm_dev);
+ tpmd_disconnect();
+ if (tpm_response.data != NULL) kfree(tpm_response.data);
+}
+
+module_init(init_tpm_module);
+module_exit(cleanup_tpm_module);
+
diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c
new file mode 100644
index 0000000..94bb440
--- /dev/null
+++ b/drivers/char/tty_io.c
@@ -0,0 +1,3154 @@
+/*
+ * linux/drivers/char/tty_io.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ */
+
+/*
+ * 'tty_io.c' gives an orthogonal feeling to tty's, be they consoles
+ * or rs-channels. It also implements echoing, cooked mode etc.
+ *
+ * Kill-line thanks to John T Kohl, who also corrected VMIN = VTIME = 0.
+ *
+ * Modified by Theodore Ts'o, 9/14/92, to dynamically allocate the
+ * tty_struct and tty_queue structures. Previously there was an array
+ * of 256 tty_struct's which was statically allocated, and the
+ * tty_queue structures were allocated at boot time. Both are now
+ * dynamically allocated only when the tty is open.
+ *
+ * Also restructured routines so that there is more of a separation
+ * between the high-level tty routines (tty_io.c and tty_ioctl.c) and
+ * the low-level tty routines (serial.c, pty.c, console.c). This
+ * makes for cleaner and more compact code. -TYT, 9/17/92
+ *
+ * Modified by Fred N. van Kempen, 01/29/93, to add line disciplines
+ * which can be dynamically activated and de-activated by the line
+ * discipline handling modules (like SLIP).
+ *
+ * NOTE: pay no attention to the line discipline code (yet); its
+ * interface is still subject to change in this version...
+ * -- TYT, 1/31/92
+ *
+ * Added functionality to the OPOST tty handling. No delays, but all
+ * other bits should be there.
+ * -- Nick Holloway <alfie@dcs.warwick.ac.uk>, 27th May 1993.
+ *
+ * Rewrote canonical mode and added more termios flags.
+ * -- julian@uhunix.uhcc.hawaii.edu (J. Cowley), 13Jan94
+ *
+ * Reorganized FASYNC support so mouse code can share it.
+ * -- ctm@ardi.com, 9Sep95
+ *
+ * New TIOCLINUX variants added.
+ * -- mj@k332.feld.cvut.cz, 19-Nov-95
+ *
+ * Restrict vt switching via ioctl()
+ * -- grif@cs.ucr.edu, 5-Dec-95
+ *
+ * Move console and virtual terminal code to more appropriate files,
+ * implement CONFIG_VT and generalize console device interface.
+ * -- Marko Kohtala <Marko.Kohtala@hut.fi>, March 97
+ *
+ * Rewrote tty_init_dev and tty_release_dev to eliminate races.
+ * -- Bill Hawes <whawes@star.net>, June 97
+ *
+ * Added devfs support.
+ * -- C. Scott Ananian <cananian@alumni.princeton.edu>, 13-Jan-1998
+ *
+ * Added support for a Unix98-style ptmx device.
+ * -- C. Scott Ananian <cananian@alumni.princeton.edu>, 14-Jan-1998
+ *
+ * Reduced memory usage for older ARM systems
+ * -- Russell King <rmk@arm.linux.org.uk>
+ *
+ * Move do_SAK() into process context. Less stack use in devfs functions.
+ * alloc_tty_struct() always uses kmalloc()
+ * -- Andrew Morton <andrewm@uow.edu.eu> 17Mar01
+ */
+
+#include <linux/types.h>
+#include <linux/major.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/fcntl.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/devpts_fs.h>
+#include <linux/file.h>
+#include <linux/fdtable.h>
+#include <linux/console.h>
+#include <linux/timer.h>
+#include <linux/ctype.h>
+#include <linux/kd.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/smp_lock.h>
+#include <linux/device.h>
+#include <linux/wait.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/seq_file.h>
+
+#include <linux/uaccess.h>
+#include <asm/system.h>
+
+#include <linux/kbd_kern.h>
+#include <linux/vt_kern.h>
+#include <linux/selection.h>
+
+#include <linux/kmod.h>
+#include <linux/nsproxy.h>
+
+#undef TTY_DEBUG_HANGUP
+
+#define TTY_PARANOIA_CHECK 1
+#define CHECK_TTY_COUNT 1
+
+struct ktermios tty_std_termios = { /* for the benefit of tty drivers */
+ .c_iflag = ICRNL | IXON,
+ .c_oflag = OPOST | ONLCR,
+ .c_cflag = B38400 | CS8 | CREAD | HUPCL,
+ .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
+ ECHOCTL | ECHOKE | IEXTEN,
+ .c_cc = INIT_C_CC,
+ .c_ispeed = 38400,
+ .c_ospeed = 38400
+};
+
+EXPORT_SYMBOL(tty_std_termios);
+
+/* This list gets poked at by procfs and various bits of boot up code. This
+ could do with some rationalisation such as pulling the tty proc function
+ into this file */
+
+LIST_HEAD(tty_drivers); /* linked list of tty drivers */
+
+/* Mutex to protect creating and releasing a tty. This is shared with
+ vt.c for deeply disgusting hack reasons */
+DEFINE_MUTEX(tty_mutex);
+EXPORT_SYMBOL(tty_mutex);
+
+static ssize_t tty_read(struct file *, char __user *, size_t, loff_t *);
+static ssize_t tty_write(struct file *, const char __user *, size_t, loff_t *);
+ssize_t redirected_tty_write(struct file *, const char __user *,
+ size_t, loff_t *);
+static unsigned int tty_poll(struct file *, poll_table *);
+static int tty_open(struct inode *, struct file *);
+long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+#ifdef CONFIG_COMPAT
+static long tty_compat_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg);
+#else
+#define tty_compat_ioctl NULL
+#endif
+static int tty_fasync(int fd, struct file *filp, int on);
+static void release_tty(struct tty_struct *tty, int idx);
+static void __proc_set_tty(struct task_struct *tsk, struct tty_struct *tty);
+static void proc_set_tty(struct task_struct *tsk, struct tty_struct *tty);
+
+/**
+ * alloc_tty_struct - allocate a tty object
+ *
+ * Return a new empty tty structure. The data fields have not
+ * been initialized in any way but has been zeroed
+ *
+ * Locking: none
+ */
+
+struct tty_struct *alloc_tty_struct(void)
+{
+ return kzalloc(sizeof(struct tty_struct), GFP_KERNEL);
+}
+
+/**
+ * free_tty_struct - free a disused tty
+ * @tty: tty struct to free
+ *
+ * Free the write buffers, tty queue and tty memory itself.
+ *
+ * Locking: none. Must be called after tty is definitely unused
+ */
+
+void free_tty_struct(struct tty_struct *tty)
+{
+ kfree(tty->write_buf);
+ tty_buffer_free_all(tty);
+ kfree(tty);
+}
+
+#define TTY_NUMBER(tty) ((tty)->index + (tty)->driver->name_base)
+
+/**
+ * tty_name - return tty naming
+ * @tty: tty structure
+ * @buf: buffer for output
+ *
+ * Convert a tty structure into a name. The name reflects the kernel
+ * naming policy and if udev is in use may not reflect user space
+ *
+ * Locking: none
+ */
+
+char *tty_name(struct tty_struct *tty, char *buf)
+{
+ if (!tty) /* Hmm. NULL pointer. That's fun. */
+ strcpy(buf, "NULL tty");
+ else
+ strcpy(buf, tty->name);
+ return buf;
+}
+
+EXPORT_SYMBOL(tty_name);
+
+int tty_paranoia_check(struct tty_struct *tty, struct inode *inode,
+ const char *routine)
+{
+#ifdef TTY_PARANOIA_CHECK
+ if (!tty) {
+ printk(KERN_WARNING
+ "null TTY for (%d:%d) in %s\n",
+ imajor(inode), iminor(inode), routine);
+ return 1;
+ }
+ if (tty->magic != TTY_MAGIC) {
+ printk(KERN_WARNING
+ "bad magic number for tty struct (%d:%d) in %s\n",
+ imajor(inode), iminor(inode), routine);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+static int check_tty_count(struct tty_struct *tty, const char *routine)
+{
+#ifdef CHECK_TTY_COUNT
+ struct list_head *p;
+ int count = 0;
+
+ file_list_lock();
+ list_for_each(p, &tty->tty_files) {
+ count++;
+ }
+ file_list_unlock();
+ if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+ tty->driver->subtype == PTY_TYPE_SLAVE &&
+ tty->link && tty->link->count)
+ count++;
+ if (tty->count != count) {
+ printk(KERN_WARNING "Warning: dev (%s) tty->count(%d) "
+ "!= #fd's(%d) in %s\n",
+ tty->name, tty->count, count, routine);
+ return count;
+ }
+#endif
+ return 0;
+}
+
+/**
+ * get_tty_driver - find device of a tty
+ * @dev_t: device identifier
+ * @index: returns the index of the tty
+ *
+ * This routine returns a tty driver structure, given a device number
+ * and also passes back the index number.
+ *
+ * Locking: caller must hold tty_mutex
+ */
+
+static struct tty_driver *get_tty_driver(dev_t device, int *index)
+{
+ struct tty_driver *p;
+
+ list_for_each_entry(p, &tty_drivers, tty_drivers) {
+ dev_t base = MKDEV(p->major, p->minor_start);
+ if (device < base || device >= base + p->num)
+ continue;
+ *index = device - base;
+ return tty_driver_kref_get(p);
+ }
+ return NULL;
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+
+/**
+ * tty_find_polling_driver - find device of a polled tty
+ * @name: name string to match
+ * @line: pointer to resulting tty line nr
+ *
+ * This routine returns a tty driver structure, given a name
+ * and the condition that the tty driver is capable of polled
+ * operation.
+ */
+struct tty_driver *tty_find_polling_driver(char *name, int *line)
+{
+ struct tty_driver *p, *res = NULL;
+ int tty_line = 0;
+ int len;
+ char *str, *stp;
+
+ for (str = name; *str; str++)
+ if ((*str >= '0' && *str <= '9') || *str == ',')
+ break;
+ if (!*str)
+ return NULL;
+
+ len = str - name;
+ tty_line = simple_strtoul(str, &str, 10);
+
+ mutex_lock(&tty_mutex);
+ /* Search through the tty devices to look for a match */
+ list_for_each_entry(p, &tty_drivers, tty_drivers) {
+ if (strncmp(name, p->name, len) != 0)
+ continue;
+ stp = str;
+ if (*stp == ',')
+ stp++;
+ if (*stp == '\0')
+ stp = NULL;
+
+ if (tty_line >= 0 && tty_line <= p->num && p->ops &&
+ p->ops->poll_init && !p->ops->poll_init(p, tty_line, stp)) {
+ res = tty_driver_kref_get(p);
+ *line = tty_line;
+ break;
+ }
+ }
+ mutex_unlock(&tty_mutex);
+
+ return res;
+}
+EXPORT_SYMBOL_GPL(tty_find_polling_driver);
+#endif
+
+/**
+ * tty_check_change - check for POSIX terminal changes
+ * @tty: tty to check
+ *
+ * If we try to write to, or set the state of, a terminal and we're
+ * not in the foreground, send a SIGTTOU. If the signal is blocked or
+ * ignored, go ahead and perform the operation. (POSIX 7.2)
+ *
+ * Locking: ctrl_lock
+ */
+
+int tty_check_change(struct tty_struct *tty)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ if (current->signal->tty != tty)
+ return 0;
+
+ spin_lock_irqsave(&tty->ctrl_lock, flags);
+
+ if (!tty->pgrp) {
+ printk(KERN_WARNING "tty_check_change: tty->pgrp == NULL!\n");
+ goto out_unlock;
+ }
+ if (task_pgrp(current) == tty->pgrp)
+ goto out_unlock;
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+ if (is_ignored(SIGTTOU))
+ goto out;
+ if (is_current_pgrp_orphaned()) {
+ ret = -EIO;
+ goto out;
+ }
+ kill_pgrp(task_pgrp(current), SIGTTOU, 1);
+ set_thread_flag(TIF_SIGPENDING);
+ ret = -ERESTARTSYS;
+out:
+ return ret;
+out_unlock:
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+ return ret;
+}
+
+EXPORT_SYMBOL(tty_check_change);
+
+static ssize_t hung_up_tty_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ return 0;
+}
+
+static ssize_t hung_up_tty_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ return -EIO;
+}
+
+/* No kernel lock held - none needed ;) */
+static unsigned int hung_up_tty_poll(struct file *filp, poll_table *wait)
+{
+ return POLLIN | POLLOUT | POLLERR | POLLHUP | POLLRDNORM | POLLWRNORM;
+}
+
+static long hung_up_tty_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return cmd == TIOCSPGRP ? -ENOTTY : -EIO;
+}
+
+static long hung_up_tty_compat_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return cmd == TIOCSPGRP ? -ENOTTY : -EIO;
+}
+
+static const struct file_operations tty_fops = {
+ .llseek = no_llseek,
+ .read = tty_read,
+ .write = tty_write,
+ .poll = tty_poll,
+ .unlocked_ioctl = tty_ioctl,
+ .compat_ioctl = tty_compat_ioctl,
+ .open = tty_open,
+ .release = tty_release,
+ .fasync = tty_fasync,
+};
+
+static const struct file_operations console_fops = {
+ .llseek = no_llseek,
+ .read = tty_read,
+ .write = redirected_tty_write,
+ .poll = tty_poll,
+ .unlocked_ioctl = tty_ioctl,
+ .compat_ioctl = tty_compat_ioctl,
+ .open = tty_open,
+ .release = tty_release,
+ .fasync = tty_fasync,
+};
+
+static const struct file_operations hung_up_tty_fops = {
+ .llseek = no_llseek,
+ .read = hung_up_tty_read,
+ .write = hung_up_tty_write,
+ .poll = hung_up_tty_poll,
+ .unlocked_ioctl = hung_up_tty_ioctl,
+ .compat_ioctl = hung_up_tty_compat_ioctl,
+ .release = tty_release,
+};
+
+static DEFINE_SPINLOCK(redirect_lock);
+static struct file *redirect;
+
+/**
+ * tty_wakeup - request more data
+ * @tty: terminal
+ *
+ * Internal and external helper for wakeups of tty. This function
+ * informs the line discipline if present that the driver is ready
+ * to receive more output data.
+ */
+
+void tty_wakeup(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld;
+
+ if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) {
+ ld = tty_ldisc_ref(tty);
+ if (ld) {
+ if (ld->ops->write_wakeup)
+ ld->ops->write_wakeup(tty);
+ tty_ldisc_deref(ld);
+ }
+ }
+ wake_up_interruptible_poll(&tty->write_wait, POLLOUT);
+}
+
+EXPORT_SYMBOL_GPL(tty_wakeup);
+
+/**
+ * do_tty_hangup - actual handler for hangup events
+ * @work: tty device
+ *
+ * This can be called by the "eventd" kernel thread. That is process
+ * synchronous but doesn't hold any locks, so we need to make sure we
+ * have the appropriate locks for what we're doing.
+ *
+ * The hangup event clears any pending redirections onto the hung up
+ * device. It ensures future writes will error and it does the needed
+ * line discipline hangup and signal delivery. The tty object itself
+ * remains intact.
+ *
+ * Locking:
+ * BKL
+ * redirect lock for undoing redirection
+ * file list lock for manipulating list of ttys
+ * tty_ldisc_lock from called functions
+ * termios_mutex resetting termios data
+ * tasklist_lock to walk task list for hangup event
+ * ->siglock to protect ->signal/->sighand
+ */
+static void do_tty_hangup(struct work_struct *work)
+{
+ struct tty_struct *tty =
+ container_of(work, struct tty_struct, hangup_work);
+ struct file *cons_filp = NULL;
+ struct file *filp, *f = NULL;
+ struct task_struct *p;
+ int closecount = 0, n;
+ unsigned long flags;
+ int refs = 0;
+
+ if (!tty)
+ return;
+
+
+ spin_lock(&redirect_lock);
+ if (redirect && redirect->private_data == tty) {
+ f = redirect;
+ redirect = NULL;
+ }
+ spin_unlock(&redirect_lock);
+
+ /* inuse_filps is protected by the single kernel lock */
+ lock_kernel();
+ check_tty_count(tty, "do_tty_hangup");
+
+ file_list_lock();
+ /* This breaks for file handles being sent over AF_UNIX sockets ? */
+ list_for_each_entry(filp, &tty->tty_files, f_u.fu_list) {
+ if (filp->f_op->write == redirected_tty_write)
+ cons_filp = filp;
+ if (filp->f_op->write != tty_write)
+ continue;
+ closecount++;
+ tty_fasync(-1, filp, 0); /* can't block */
+ filp->f_op = &hung_up_tty_fops;
+ }
+ file_list_unlock();
+
+ tty_ldisc_hangup(tty);
+
+ read_lock(&tasklist_lock);
+ if (tty->session) {
+ do_each_pid_task(tty->session, PIDTYPE_SID, p) {
+ spin_lock_irq(&p->sighand->siglock);
+ if (p->signal->tty == tty) {
+ p->signal->tty = NULL;
+ /* We defer the dereferences outside fo
+ the tasklist lock */
+ refs++;
+ }
+ if (!p->signal->leader) {
+ spin_unlock_irq(&p->sighand->siglock);
+ continue;
+ }
+ __group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p);
+ __group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p);
+ put_pid(p->signal->tty_old_pgrp); /* A noop */
+ spin_lock_irqsave(&tty->ctrl_lock, flags);
+ if (tty->pgrp)
+ p->signal->tty_old_pgrp = get_pid(tty->pgrp);
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+ spin_unlock_irq(&p->sighand->siglock);
+ } while_each_pid_task(tty->session, PIDTYPE_SID, p);
+ }
+ read_unlock(&tasklist_lock);
+
+ spin_lock_irqsave(&tty->ctrl_lock, flags);
+ clear_bit(TTY_THROTTLED, &tty->flags);
+ clear_bit(TTY_PUSH, &tty->flags);
+ clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+ put_pid(tty->session);
+ put_pid(tty->pgrp);
+ tty->session = NULL;
+ tty->pgrp = NULL;
+ tty->ctrl_status = 0;
+ set_bit(TTY_HUPPED, &tty->flags);
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+
+ /* Account for the p->signal references we killed */
+ while (refs--)
+ tty_kref_put(tty);
+
+ /*
+ * If one of the devices matches a console pointer, we
+ * cannot just call hangup() because that will cause
+ * tty->count and state->count to go out of sync.
+ * So we just call close() the right number of times.
+ */
+ if (cons_filp) {
+ if (tty->ops->close)
+ for (n = 0; n < closecount; n++)
+ tty->ops->close(tty, cons_filp);
+ } else if (tty->ops->hangup)
+ (tty->ops->hangup)(tty);
+ /*
+ * We don't want to have driver/ldisc interactions beyond
+ * the ones we did here. The driver layer expects no
+ * calls after ->hangup() from the ldisc side. However we
+ * can't yet guarantee all that.
+ */
+ set_bit(TTY_HUPPED, &tty->flags);
+ tty_ldisc_enable(tty);
+ unlock_kernel();
+ if (f)
+ fput(f);
+}
+
+/**
+ * tty_hangup - trigger a hangup event
+ * @tty: tty to hangup
+ *
+ * A carrier loss (virtual or otherwise) has occurred on this like
+ * schedule a hangup sequence to run after this event.
+ */
+
+void tty_hangup(struct tty_struct *tty)
+{
+#ifdef TTY_DEBUG_HANGUP
+ char buf[64];
+ printk(KERN_DEBUG "%s hangup...\n", tty_name(tty, buf));
+#endif
+ schedule_work(&tty->hangup_work);
+}
+
+EXPORT_SYMBOL(tty_hangup);
+
+/**
+ * tty_vhangup - process vhangup
+ * @tty: tty to hangup
+ *
+ * The user has asked via system call for the terminal to be hung up.
+ * We do this synchronously so that when the syscall returns the process
+ * is complete. That guarantee is necessary for security reasons.
+ */
+
+void tty_vhangup(struct tty_struct *tty)
+{
+#ifdef TTY_DEBUG_HANGUP
+ char buf[64];
+
+ printk(KERN_DEBUG "%s vhangup...\n", tty_name(tty, buf));
+#endif
+ do_tty_hangup(&tty->hangup_work);
+}
+
+EXPORT_SYMBOL(tty_vhangup);
+
+/**
+ * tty_vhangup_self - process vhangup for own ctty
+ *
+ * Perform a vhangup on the current controlling tty
+ */
+
+void tty_vhangup_self(void)
+{
+ struct tty_struct *tty;
+
+ tty = get_current_tty();
+ if (tty) {
+ tty_vhangup(tty);
+ tty_kref_put(tty);
+ }
+}
+
+/**
+ * tty_hung_up_p - was tty hung up
+ * @filp: file pointer of tty
+ *
+ * Return true if the tty has been subject to a vhangup or a carrier
+ * loss
+ */
+
+int tty_hung_up_p(struct file *filp)
+{
+ return (filp->f_op == &hung_up_tty_fops);
+}
+
+EXPORT_SYMBOL(tty_hung_up_p);
+
+static void session_clear_tty(struct pid *session)
+{
+ struct task_struct *p;
+ do_each_pid_task(session, PIDTYPE_SID, p) {
+ proc_clear_tty(p);
+ } while_each_pid_task(session, PIDTYPE_SID, p);
+}
+
+/**
+ * disassociate_ctty - disconnect controlling tty
+ * @on_exit: true if exiting so need to "hang up" the session
+ *
+ * This function is typically called only by the session leader, when
+ * it wants to disassociate itself from its controlling tty.
+ *
+ * It performs the following functions:
+ * (1) Sends a SIGHUP and SIGCONT to the foreground process group
+ * (2) Clears the tty from being controlling the session
+ * (3) Clears the controlling tty for all processes in the
+ * session group.
+ *
+ * The argument on_exit is set to 1 if called when a process is
+ * exiting; it is 0 if called by the ioctl TIOCNOTTY.
+ *
+ * Locking:
+ * BKL is taken for hysterical raisins
+ * tty_mutex is taken to protect tty
+ * ->siglock is taken to protect ->signal/->sighand
+ * tasklist_lock is taken to walk process list for sessions
+ * ->siglock is taken to protect ->signal/->sighand
+ */
+
+void disassociate_ctty(int on_exit)
+{
+ struct tty_struct *tty;
+ struct pid *tty_pgrp = NULL;
+
+ if (!current->signal->leader)
+ return;
+
+ tty = get_current_tty();
+ if (tty) {
+ tty_pgrp = get_pid(tty->pgrp);
+ lock_kernel();
+ if (on_exit && tty->driver->type != TTY_DRIVER_TYPE_PTY)
+ tty_vhangup(tty);
+ unlock_kernel();
+ tty_kref_put(tty);
+ } else if (on_exit) {
+ struct pid *old_pgrp;
+ spin_lock_irq(¤t->sighand->siglock);
+ old_pgrp = current->signal->tty_old_pgrp;
+ current->signal->tty_old_pgrp = NULL;
+ spin_unlock_irq(¤t->sighand->siglock);
+ if (old_pgrp) {
+ kill_pgrp(old_pgrp, SIGHUP, on_exit);
+ kill_pgrp(old_pgrp, SIGCONT, on_exit);
+ put_pid(old_pgrp);
+ }
+ return;
+ }
+ if (tty_pgrp) {
+ kill_pgrp(tty_pgrp, SIGHUP, on_exit);
+ if (!on_exit)
+ kill_pgrp(tty_pgrp, SIGCONT, on_exit);
+ put_pid(tty_pgrp);
+ }
+
+ spin_lock_irq(¤t->sighand->siglock);
+ put_pid(current->signal->tty_old_pgrp);
+ current->signal->tty_old_pgrp = NULL;
+ spin_unlock_irq(¤t->sighand->siglock);
+
+ tty = get_current_tty();
+ if (tty) {
+ unsigned long flags;
+ spin_lock_irqsave(&tty->ctrl_lock, flags);
+ put_pid(tty->session);
+ put_pid(tty->pgrp);
+ tty->session = NULL;
+ tty->pgrp = NULL;
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+ tty_kref_put(tty);
+ } else {
+#ifdef TTY_DEBUG_HANGUP
+ printk(KERN_DEBUG "error attempted to write to tty [0x%p]"
+ " = NULL", tty);
+#endif
+ }
+
+ /* Now clear signal->tty under the lock */
+ read_lock(&tasklist_lock);
+ session_clear_tty(task_session(current));
+ read_unlock(&tasklist_lock);
+}
+
+/**
+ *
+ * no_tty - Ensure the current process does not have a controlling tty
+ */
+void no_tty(void)
+{
+ struct task_struct *tsk = current;
+ lock_kernel();
+ disassociate_ctty(0);
+ unlock_kernel();
+ proc_clear_tty(tsk);
+}
+
+
+/**
+ * stop_tty - propagate flow control
+ * @tty: tty to stop
+ *
+ * Perform flow control to the driver. For PTY/TTY pairs we
+ * must also propagate the TIOCKPKT status. May be called
+ * on an already stopped device and will not re-call the driver
+ * method.
+ *
+ * This functionality is used by both the line disciplines for
+ * halting incoming flow and by the driver. It may therefore be
+ * called from any context, may be under the tty atomic_write_lock
+ * but not always.
+ *
+ * Locking:
+ * Uses the tty control lock internally
+ */
+
+void stop_tty(struct tty_struct *tty)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&tty->ctrl_lock, flags);
+ if (tty->stopped) {
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+ return;
+ }
+ tty->stopped = 1;
+ if (tty->link && tty->link->packet) {
+ tty->ctrl_status &= ~TIOCPKT_START;
+ tty->ctrl_status |= TIOCPKT_STOP;
+ wake_up_interruptible_poll(&tty->link->read_wait, POLLIN);
+ }
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+ if (tty->ops->stop)
+ (tty->ops->stop)(tty);
+}
+
+EXPORT_SYMBOL(stop_tty);
+
+/**
+ * start_tty - propagate flow control
+ * @tty: tty to start
+ *
+ * Start a tty that has been stopped if at all possible. Perform
+ * any necessary wakeups and propagate the TIOCPKT status. If this
+ * is the tty was previous stopped and is being started then the
+ * driver start method is invoked and the line discipline woken.
+ *
+ * Locking:
+ * ctrl_lock
+ */
+
+void start_tty(struct tty_struct *tty)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&tty->ctrl_lock, flags);
+ if (!tty->stopped || tty->flow_stopped) {
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+ return;
+ }
+ tty->stopped = 0;
+ if (tty->link && tty->link->packet) {
+ tty->ctrl_status &= ~TIOCPKT_STOP;
+ tty->ctrl_status |= TIOCPKT_START;
+ wake_up_interruptible_poll(&tty->link->read_wait, POLLIN);
+ }
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+ if (tty->ops->start)
+ (tty->ops->start)(tty);
+ /* If we have a running line discipline it may need kicking */
+ tty_wakeup(tty);
+}
+
+EXPORT_SYMBOL(start_tty);
+
+/**
+ * tty_read - read method for tty device files
+ * @file: pointer to tty file
+ * @buf: user buffer
+ * @count: size of user buffer
+ * @ppos: unused
+ *
+ * Perform the read system call function on this terminal device. Checks
+ * for hung up devices before calling the line discipline method.
+ *
+ * Locking:
+ * Locks the line discipline internally while needed. Multiple
+ * read calls may be outstanding in parallel.
+ */
+
+static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ int i;
+ struct tty_struct *tty;
+ struct inode *inode;
+ struct tty_ldisc *ld;
+
+ tty = (struct tty_struct *)file->private_data;
+ inode = file->f_path.dentry->d_inode;
+ if (tty_paranoia_check(tty, inode, "tty_read"))
+ return -EIO;
+ if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
+ return -EIO;
+
+ /* We want to wait for the line discipline to sort out in this
+ situation */
+ ld = tty_ldisc_ref_wait(tty);
+ if (ld->ops->read)
+ i = (ld->ops->read)(tty, file, buf, count);
+ else
+ i = -EIO;
+ tty_ldisc_deref(ld);
+ if (i > 0)
+ inode->i_atime = current_fs_time(inode->i_sb);
+ return i;
+}
+
+void tty_write_unlock(struct tty_struct *tty)
+{
+ mutex_unlock(&tty->atomic_write_lock);
+ wake_up_interruptible_poll(&tty->write_wait, POLLOUT);
+}
+
+int tty_write_lock(struct tty_struct *tty, int ndelay)
+{
+ if (!mutex_trylock(&tty->atomic_write_lock)) {
+ if (ndelay)
+ return -EAGAIN;
+ if (mutex_lock_interruptible(&tty->atomic_write_lock))
+ return -ERESTARTSYS;
+ }
+ return 0;
+}
+
+/*
+ * Split writes up in sane blocksizes to avoid
+ * denial-of-service type attacks
+ */
+static inline ssize_t do_tty_write(
+ ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
+ struct tty_struct *tty,
+ struct file *file,
+ const char __user *buf,
+ size_t count)
+{
+ ssize_t ret, written = 0;
+ unsigned int chunk;
+
+ ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * We chunk up writes into a temporary buffer. This
+ * simplifies low-level drivers immensely, since they
+ * don't have locking issues and user mode accesses.
+ *
+ * But if TTY_NO_WRITE_SPLIT is set, we should use a
+ * big chunk-size..
+ *
+ * The default chunk-size is 2kB, because the NTTY
+ * layer has problems with bigger chunks. It will
+ * claim to be able to handle more characters than
+ * it actually does.
+ *
+ * FIXME: This can probably go away now except that 64K chunks
+ * are too likely to fail unless switched to vmalloc...
+ */
+ chunk = 2048;
+ if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
+ chunk = 65536;
+ if (count < chunk)
+ chunk = count;
+
+ /* write_buf/write_cnt is protected by the atomic_write_lock mutex */
+ if (tty->write_cnt < chunk) {
+ unsigned char *buf_chunk;
+
+ if (chunk < 1024)
+ chunk = 1024;
+
+ buf_chunk = kmalloc(chunk, GFP_KERNEL);
+ if (!buf_chunk) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ kfree(tty->write_buf);
+ tty->write_cnt = chunk;
+ tty->write_buf = buf_chunk;
+ }
+
+ /* Do the write .. */
+ for (;;) {
+ size_t size = count;
+ if (size > chunk)
+ size = chunk;
+ ret = -EFAULT;
+ if (copy_from_user(tty->write_buf, buf, size))
+ break;
+ ret = write(tty, file, tty->write_buf, size);
+ if (ret <= 0)
+ break;
+ written += ret;
+ buf += ret;
+ count -= ret;
+ if (!count)
+ break;
+ ret = -ERESTARTSYS;
+ if (signal_pending(current))
+ break;
+ cond_resched();
+ }
+ if (written) {
+ struct inode *inode = file->f_path.dentry->d_inode;
+ inode->i_mtime = current_fs_time(inode->i_sb);
+ ret = written;
+ }
+out:
+ tty_write_unlock(tty);
+ return ret;
+}
+
+/**
+ * tty_write_message - write a message to a certain tty, not just the console.
+ * @tty: the destination tty_struct
+ * @msg: the message to write
+ *
+ * This is used for messages that need to be redirected to a specific tty.
+ * We don't put it into the syslog queue right now maybe in the future if
+ * really needed.
+ *
+ * We must still hold the BKL and test the CLOSING flag for the moment.
+ */
+
+void tty_write_message(struct tty_struct *tty, char *msg)
+{
+ if (tty) {
+ mutex_lock(&tty->atomic_write_lock);
+ lock_kernel();
+ if (tty->ops->write && !test_bit(TTY_CLOSING, &tty->flags)) {
+ unlock_kernel();
+ tty->ops->write(tty, msg, strlen(msg));
+ } else
+ unlock_kernel();
+ tty_write_unlock(tty);
+ }
+ return;
+}
+
+
+/**
+ * tty_write - write method for tty device file
+ * @file: tty file pointer
+ * @buf: user data to write
+ * @count: bytes to write
+ * @ppos: unused
+ *
+ * Write data to a tty device via the line discipline.
+ *
+ * Locking:
+ * Locks the line discipline as required
+ * Writes to the tty driver are serialized by the atomic_write_lock
+ * and are then processed in chunks to the device. The line discipline
+ * write method will not be invoked in parallel for each device.
+ */
+
+static ssize_t tty_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct tty_struct *tty;
+ struct inode *inode = file->f_path.dentry->d_inode;
+ ssize_t ret;
+ struct tty_ldisc *ld;
+
+ tty = (struct tty_struct *)file->private_data;
+ if (tty_paranoia_check(tty, inode, "tty_write"))
+ return -EIO;
+ if (!tty || !tty->ops->write ||
+ (test_bit(TTY_IO_ERROR, &tty->flags)))
+ return -EIO;
+ /* Short term debug to catch buggy drivers */
+ if (tty->ops->write_room == NULL)
+ printk(KERN_ERR "tty driver %s lacks a write_room method.\n",
+ tty->driver->name);
+ ld = tty_ldisc_ref_wait(tty);
+ if (!ld->ops->write)
+ ret = -EIO;
+ else
+ ret = do_tty_write(ld->ops->write, tty, file, buf, count);
+ tty_ldisc_deref(ld);
+ return ret;
+}
+
+ssize_t redirected_tty_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct file *p = NULL;
+
+ spin_lock(&redirect_lock);
+ if (redirect) {
+ get_file(redirect);
+ p = redirect;
+ }
+ spin_unlock(&redirect_lock);
+
+ if (p) {
+ ssize_t res;
+ res = vfs_write(p, buf, count, &p->f_pos);
+ fput(p);
+ return res;
+ }
+ return tty_write(file, buf, count, ppos);
+}
+
+static char ptychar[] = "pqrstuvwxyzabcde";
+
+/**
+ * pty_line_name - generate name for a pty
+ * @driver: the tty driver in use
+ * @index: the minor number
+ * @p: output buffer of at least 6 bytes
+ *
+ * Generate a name from a driver reference and write it to the output
+ * buffer.
+ *
+ * Locking: None
+ */
+static void pty_line_name(struct tty_driver *driver, int index, char *p)
+{
+ int i = index + driver->name_base;
+ /* ->name is initialized to "ttyp", but "tty" is expected */
+ sprintf(p, "%s%c%x",
+ driver->subtype == PTY_TYPE_SLAVE ? "tty" : driver->name,
+ ptychar[i >> 4 & 0xf], i & 0xf);
+}
+
+/**
+ * tty_line_name - generate name for a tty
+ * @driver: the tty driver in use
+ * @index: the minor number
+ * @p: output buffer of at least 7 bytes
+ *
+ * Generate a name from a driver reference and write it to the output
+ * buffer.
+ *
+ * Locking: None
+ */
+static void tty_line_name(struct tty_driver *driver, int index, char *p)
+{
+ sprintf(p, "%s%d", driver->name, index + driver->name_base);
+}
+
+/**
+ * tty_driver_lookup_tty() - find an existing tty, if any
+ * @driver: the driver for the tty
+ * @idx: the minor number
+ *
+ * Return the tty, if found or ERR_PTR() otherwise.
+ *
+ * Locking: tty_mutex must be held. If tty is found, the mutex must
+ * be held until the 'fast-open' is also done. Will change once we
+ * have refcounting in the driver and per driver locking
+ */
+static struct tty_struct *tty_driver_lookup_tty(struct tty_driver *driver,
+ struct inode *inode, int idx)
+{
+ struct tty_struct *tty;
+
+ if (driver->ops->lookup)
+ return driver->ops->lookup(driver, inode, idx);
+
+ tty = driver->ttys[idx];
+ return tty;
+}
+
+/**
+ * tty_init_termios - helper for termios setup
+ * @tty: the tty to set up
+ *
+ * Initialise the termios structures for this tty. Thus runs under
+ * the tty_mutex currently so we can be relaxed about ordering.
+ */
+
+int tty_init_termios(struct tty_struct *tty)
+{
+ struct ktermios *tp;
+ int idx = tty->index;
+
+ tp = tty->driver->termios[idx];
+ if (tp == NULL) {
+ tp = kzalloc(sizeof(struct ktermios[2]), GFP_KERNEL);
+ if (tp == NULL)
+ return -ENOMEM;
+ memcpy(tp, &tty->driver->init_termios,
+ sizeof(struct ktermios));
+ tty->driver->termios[idx] = tp;
+ }
+ tty->termios = tp;
+ tty->termios_locked = tp + 1;
+
+ /* Compatibility until drivers always set this */
+ tty->termios->c_ispeed = tty_termios_input_baud_rate(tty->termios);
+ tty->termios->c_ospeed = tty_termios_baud_rate(tty->termios);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tty_init_termios);
+
+/**
+ * tty_driver_install_tty() - install a tty entry in the driver
+ * @driver: the driver for the tty
+ * @tty: the tty
+ *
+ * Install a tty object into the driver tables. The tty->index field
+ * will be set by the time this is called. This method is responsible
+ * for ensuring any need additional structures are allocated and
+ * configured.
+ *
+ * Locking: tty_mutex for now
+ */
+static int tty_driver_install_tty(struct tty_driver *driver,
+ struct tty_struct *tty)
+{
+ int idx = tty->index;
+ int ret;
+
+ if (driver->ops->install) {
+ lock_kernel();
+ ret = driver->ops->install(driver, tty);
+ unlock_kernel();
+ return ret;
+ }
+
+ if (tty_init_termios(tty) == 0) {
+ lock_kernel();
+ tty_driver_kref_get(driver);
+ tty->count++;
+ driver->ttys[idx] = tty;
+ unlock_kernel();
+ return 0;
+ }
+ return -ENOMEM;
+}
+
+/**
+ * tty_driver_remove_tty() - remove a tty from the driver tables
+ * @driver: the driver for the tty
+ * @idx: the minor number
+ *
+ * Remvoe a tty object from the driver tables. The tty->index field
+ * will be set by the time this is called.
+ *
+ * Locking: tty_mutex for now
+ */
+static void tty_driver_remove_tty(struct tty_driver *driver,
+ struct tty_struct *tty)
+{
+ if (driver->ops->remove)
+ driver->ops->remove(driver, tty);
+ else
+ driver->ttys[tty->index] = NULL;
+}
+
+/*
+ * tty_reopen() - fast re-open of an open tty
+ * @tty - the tty to open
+ *
+ * Return 0 on success, -errno on error.
+ *
+ * Locking: tty_mutex must be held from the time the tty was found
+ * till this open completes.
+ */
+static int tty_reopen(struct tty_struct *tty)
+{
+ struct tty_driver *driver = tty->driver;
+
+ if (test_bit(TTY_CLOSING, &tty->flags))
+ return -EIO;
+
+ if (driver->type == TTY_DRIVER_TYPE_PTY &&
+ driver->subtype == PTY_TYPE_MASTER) {
+ /*
+ * special case for PTY masters: only one open permitted,
+ * and the slave side open count is incremented as well.
+ */
+ if (tty->count)
+ return -EIO;
+
+ tty->link->count++;
+ }
+ tty->count++;
+ tty->driver = driver; /* N.B. why do this every time?? */
+
+ mutex_lock(&tty->ldisc_mutex);
+ WARN_ON(!test_bit(TTY_LDISC, &tty->flags));
+ mutex_unlock(&tty->ldisc_mutex);
+
+ return 0;
+}
+
+/**
+ * tty_init_dev - initialise a tty device
+ * @driver: tty driver we are opening a device on
+ * @idx: device index
+ * @ret_tty: returned tty structure
+ * @first_ok: ok to open a new device (used by ptmx)
+ *
+ * Prepare a tty device. This may not be a "new" clean device but
+ * could also be an active device. The pty drivers require special
+ * handling because of this.
+ *
+ * Locking:
+ * The function is called under the tty_mutex, which
+ * protects us from the tty struct or driver itself going away.
+ *
+ * On exit the tty device has the line discipline attached and
+ * a reference count of 1. If a pair was created for pty/tty use
+ * and the other was a pty master then it too has a reference count of 1.
+ *
+ * WSH 06/09/97: Rewritten to remove races and properly clean up after a
+ * failed open. The new code protects the open with a mutex, so it's
+ * really quite straightforward. The mutex locking can probably be
+ * relaxed for the (most common) case of reopening a tty.
+ */
+
+struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,
+ int first_ok)
+{
+ struct tty_struct *tty;
+ int retval;
+
+ lock_kernel();
+ /* Check if pty master is being opened multiple times */
+ if (driver->subtype == PTY_TYPE_MASTER &&
+ (driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok) {
+ unlock_kernel();
+ return ERR_PTR(-EIO);
+ }
+ unlock_kernel();
+
+ /*
+ * First time open is complex, especially for PTY devices.
+ * This code guarantees that either everything succeeds and the
+ * TTY is ready for operation, or else the table slots are vacated
+ * and the allocated memory released. (Except that the termios
+ * and locked termios may be retained.)
+ */
+
+ if (!try_module_get(driver->owner))
+ return ERR_PTR(-ENODEV);
+
+ tty = alloc_tty_struct();
+ if (!tty)
+ goto fail_no_mem;
+ initialize_tty_struct(tty, driver, idx);
+
+ retval = tty_driver_install_tty(driver, tty);
+ if (retval < 0) {
+ free_tty_struct(tty);
+ module_put(driver->owner);
+ return ERR_PTR(retval);
+ }
+
+ /*
+ * Structures all installed ... call the ldisc open routines.
+ * If we fail here just call release_tty to clean up. No need
+ * to decrement the use counts, as release_tty doesn't care.
+ */
+ retval = tty_ldisc_setup(tty, tty->link);
+ if (retval)
+ goto release_mem_out;
+ return tty;
+
+fail_no_mem:
+ module_put(driver->owner);
+ return ERR_PTR(-ENOMEM);
+
+ /* call the tty release_tty routine to clean out this slot */
+release_mem_out:
+ if (printk_ratelimit())
+ printk(KERN_INFO "tty_init_dev: ldisc open failed, "
+ "clearing slot %d\n", idx);
+ lock_kernel();
+ release_tty(tty, idx);
+ unlock_kernel();
+ return ERR_PTR(retval);
+}
+
+void tty_free_termios(struct tty_struct *tty)
+{
+ struct ktermios *tp;
+ int idx = tty->index;
+ /* Kill this flag and push into drivers for locking etc */
+ if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) {
+ /* FIXME: Locking on ->termios array */
+ tp = tty->termios;
+ tty->driver->termios[idx] = NULL;
+ kfree(tp);
+ }
+}
+EXPORT_SYMBOL(tty_free_termios);
+
+void tty_shutdown(struct tty_struct *tty)
+{
+ tty_driver_remove_tty(tty->driver, tty);
+ tty_free_termios(tty);
+}
+EXPORT_SYMBOL(tty_shutdown);
+
+/**
+ * release_one_tty - release tty structure memory
+ * @kref: kref of tty we are obliterating
+ *
+ * Releases memory associated with a tty structure, and clears out the
+ * driver table slots. This function is called when a device is no longer
+ * in use. It also gets called when setup of a device fails.
+ *
+ * Locking:
+ * tty_mutex - sometimes only
+ * takes the file list lock internally when working on the list
+ * of ttys that the driver keeps.
+ *
+ * This method gets called from a work queue so that the driver private
+ * cleanup ops can sleep (needed for USB at least)
+ */
+static void release_one_tty(struct work_struct *work)
+{
+ struct tty_struct *tty =
+ container_of(work, struct tty_struct, hangup_work);
+ struct tty_driver *driver = tty->driver;
+
+ if (tty->ops->cleanup)
+ tty->ops->cleanup(tty);
+
+ tty->magic = 0;
+ tty_driver_kref_put(driver);
+ module_put(driver->owner);
+
+ file_list_lock();
+ list_del_init(&tty->tty_files);
+ file_list_unlock();
+
+ put_pid(tty->pgrp);
+ put_pid(tty->session);
+ free_tty_struct(tty);
+}
+
+static void queue_release_one_tty(struct kref *kref)
+{
+ struct tty_struct *tty = container_of(kref, struct tty_struct, kref);
+
+ if (tty->ops->shutdown)
+ tty->ops->shutdown(tty);
+ else
+ tty_shutdown(tty);
+
+ /* The hangup queue is now free so we can reuse it rather than
+ waste a chunk of memory for each port */
+ INIT_WORK(&tty->hangup_work, release_one_tty);
+ schedule_work(&tty->hangup_work);
+}
+
+/**
+ * tty_kref_put - release a tty kref
+ * @tty: tty device
+ *
+ * Release a reference to a tty device and if need be let the kref
+ * layer destruct the object for us
+ */
+
+void tty_kref_put(struct tty_struct *tty)
+{
+ if (tty)
+ kref_put(&tty->kref, queue_release_one_tty);
+}
+EXPORT_SYMBOL(tty_kref_put);
+
+/**
+ * release_tty - release tty structure memory
+ *
+ * Release both @tty and a possible linked partner (think pty pair),
+ * and decrement the refcount of the backing module.
+ *
+ * Locking:
+ * tty_mutex - sometimes only
+ * takes the file list lock internally when working on the list
+ * of ttys that the driver keeps.
+ * FIXME: should we require tty_mutex is held here ??
+ *
+ */
+static void release_tty(struct tty_struct *tty, int idx)
+{
+ /* This should always be true but check for the moment */
+ WARN_ON(tty->index != idx);
+
+ if (tty->link)
+ tty_kref_put(tty->link);
+ tty_kref_put(tty);
+}
+
+/**
+ * tty_release - vfs callback for close
+ * @inode: inode of tty
+ * @filp: file pointer for handle to tty
+ *
+ * Called the last time each file handle is closed that references
+ * this tty. There may however be several such references.
+ *
+ * Locking:
+ * Takes bkl. See tty_release_dev
+ *
+ * Even releasing the tty structures is a tricky business.. We have
+ * to be very careful that the structures are all released at the
+ * same time, as interrupts might otherwise get the wrong pointers.
+ *
+ * WSH 09/09/97: rewritten to avoid some nasty race conditions that could
+ * lead to double frees or releasing memory still in use.
+ */
+
+int tty_release(struct inode *inode, struct file *filp)
+{
+ struct tty_struct *tty, *o_tty;
+ int pty_master, tty_closing, o_tty_closing, do_sleep;
+ int devpts;
+ int idx;
+ char buf[64];
+
+ tty = (struct tty_struct *)filp->private_data;
+ if (tty_paranoia_check(tty, inode, "tty_release_dev"))
+ return 0;
+
+ lock_kernel();
+ check_tty_count(tty, "tty_release_dev");
+
+ tty_fasync(-1, filp, 0);
+
+ idx = tty->index;
+ pty_master = (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+ tty->driver->subtype == PTY_TYPE_MASTER);
+ devpts = (tty->driver->flags & TTY_DRIVER_DEVPTS_MEM) != 0;
+ o_tty = tty->link;
+
+#ifdef TTY_PARANOIA_CHECK
+ if (idx < 0 || idx >= tty->driver->num) {
+ printk(KERN_DEBUG "tty_release_dev: bad idx when trying to "
+ "free (%s)\n", tty->name);
+ unlock_kernel();
+ return 0;
+ }
+ if (!devpts) {
+ if (tty != tty->driver->ttys[idx]) {
+ unlock_kernel();
+ printk(KERN_DEBUG "tty_release_dev: driver.table[%d] not tty "
+ "for (%s)\n", idx, tty->name);
+ return 0;
+ }
+ if (tty->termios != tty->driver->termios[idx]) {
+ unlock_kernel();
+ printk(KERN_DEBUG "tty_release_dev: driver.termios[%d] not termios "
+ "for (%s)\n",
+ idx, tty->name);
+ return 0;
+ }
+ }
+#endif
+
+#ifdef TTY_DEBUG_HANGUP
+ printk(KERN_DEBUG "tty_release_dev of %s (tty count=%d)...",
+ tty_name(tty, buf), tty->count);
+#endif
+
+#ifdef TTY_PARANOIA_CHECK
+ if (tty->driver->other &&
+ !(tty->driver->flags & TTY_DRIVER_DEVPTS_MEM)) {
+ if (o_tty != tty->driver->other->ttys[idx]) {
+ unlock_kernel();
+ printk(KERN_DEBUG "tty_release_dev: other->table[%d] "
+ "not o_tty for (%s)\n",
+ idx, tty->name);
+ return 0 ;
+ }
+ if (o_tty->termios != tty->driver->other->termios[idx]) {
+ unlock_kernel();
+ printk(KERN_DEBUG "tty_release_dev: other->termios[%d] "
+ "not o_termios for (%s)\n",
+ idx, tty->name);
+ return 0;
+ }
+ if (o_tty->link != tty) {
+ unlock_kernel();
+ printk(KERN_DEBUG "tty_release_dev: bad pty pointers\n");
+ return 0;
+ }
+ }
+#endif
+ if (tty->ops->close)
+ tty->ops->close(tty, filp);
+
+ unlock_kernel();
+ /*
+ * Sanity check: if tty->count is going to zero, there shouldn't be
+ * any waiters on tty->read_wait or tty->write_wait. We test the
+ * wait queues and kick everyone out _before_ actually starting to
+ * close. This ensures that we won't block while releasing the tty
+ * structure.
+ *
+ * The test for the o_tty closing is necessary, since the master and
+ * slave sides may close in any order. If the slave side closes out
+ * first, its count will be one, since the master side holds an open.
+ * Thus this test wouldn't be triggered at the time the slave closes,
+ * so we do it now.
+ *
+ * Note that it's possible for the tty to be opened again while we're
+ * flushing out waiters. By recalculating the closing flags before
+ * each iteration we avoid any problems.
+ */
+ while (1) {
+ /* Guard against races with tty->count changes elsewhere and
+ opens on /dev/tty */
+
+ mutex_lock(&tty_mutex);
+ lock_kernel();
+ tty_closing = tty->count <= 1;
+ o_tty_closing = o_tty &&
+ (o_tty->count <= (pty_master ? 1 : 0));
+ do_sleep = 0;
+
+ if (tty_closing) {
+ if (waitqueue_active(&tty->read_wait)) {
+ wake_up_poll(&tty->read_wait, POLLIN);
+ do_sleep++;
+ }
+ if (waitqueue_active(&tty->write_wait)) {
+ wake_up_poll(&tty->write_wait, POLLOUT);
+ do_sleep++;
+ }
+ }
+ if (o_tty_closing) {
+ if (waitqueue_active(&o_tty->read_wait)) {
+ wake_up_poll(&o_tty->read_wait, POLLIN);
+ do_sleep++;
+ }
+ if (waitqueue_active(&o_tty->write_wait)) {
+ wake_up_poll(&o_tty->write_wait, POLLOUT);
+ do_sleep++;
+ }
+ }
+ if (!do_sleep)
+ break;
+
+ printk(KERN_WARNING "tty_release_dev: %s: read/write wait queue "
+ "active!\n", tty_name(tty, buf));
+ unlock_kernel();
+ mutex_unlock(&tty_mutex);
+ schedule();
+ }
+
+ /*
+ * The closing flags are now consistent with the open counts on
+ * both sides, and we've completed the last operation that could
+ * block, so it's safe to proceed with closing.
+ */
+ if (pty_master) {
+ if (--o_tty->count < 0) {
+ printk(KERN_WARNING "tty_release_dev: bad pty slave count "
+ "(%d) for %s\n",
+ o_tty->count, tty_name(o_tty, buf));
+ o_tty->count = 0;
+ }
+ }
+ if (--tty->count < 0) {
+ printk(KERN_WARNING "tty_release_dev: bad tty->count (%d) for %s\n",
+ tty->count, tty_name(tty, buf));
+ tty->count = 0;
+ }
+
+ /*
+ * We've decremented tty->count, so we need to remove this file
+ * descriptor off the tty->tty_files list; this serves two
+ * purposes:
+ * - check_tty_count sees the correct number of file descriptors
+ * associated with this tty.
+ * - do_tty_hangup no longer sees this file descriptor as
+ * something that needs to be handled for hangups.
+ */
+ file_kill(filp);
+ filp->private_data = NULL;
+
+ /*
+ * Perform some housekeeping before deciding whether to return.
+ *
+ * Set the TTY_CLOSING flag if this was the last open. In the
+ * case of a pty we may have to wait around for the other side
+ * to close, and TTY_CLOSING makes sure we can't be reopened.
+ */
+ if (tty_closing)
+ set_bit(TTY_CLOSING, &tty->flags);
+ if (o_tty_closing)
+ set_bit(TTY_CLOSING, &o_tty->flags);
+
+ /*
+ * If _either_ side is closing, make sure there aren't any
+ * processes that still think tty or o_tty is their controlling
+ * tty.
+ */
+ if (tty_closing || o_tty_closing) {
+ read_lock(&tasklist_lock);
+ session_clear_tty(tty->session);
+ if (o_tty)
+ session_clear_tty(o_tty->session);
+ read_unlock(&tasklist_lock);
+ }
+
+ mutex_unlock(&tty_mutex);
+
+ /* check whether both sides are closing ... */
+ if (!tty_closing || (o_tty && !o_tty_closing)) {
+ unlock_kernel();
+ return 0;
+ }
+
+#ifdef TTY_DEBUG_HANGUP
+ printk(KERN_DEBUG "freeing tty structure...");
+#endif
+ /*
+ * Ask the line discipline code to release its structures
+ */
+ tty_ldisc_release(tty, o_tty);
+ /*
+ * The release_tty function takes care of the details of clearing
+ * the slots and preserving the termios structure.
+ */
+ release_tty(tty, idx);
+
+ /* Make this pty number available for reallocation */
+ if (devpts)
+ devpts_kill_index(inode, idx);
+ unlock_kernel();
+ return 0;
+}
+
+/**
+ * tty_open - open a tty device
+ * @inode: inode of device file
+ * @filp: file pointer to tty
+ *
+ * tty_open and tty_release keep up the tty count that contains the
+ * number of opens done on a tty. We cannot use the inode-count, as
+ * different inodes might point to the same tty.
+ *
+ * Open-counting is needed for pty masters, as well as for keeping
+ * track of serial lines: DTR is dropped when the last close happens.
+ * (This is not done solely through tty->count, now. - Ted 1/27/92)
+ *
+ * The termios state of a pty is reset on first open so that
+ * settings don't persist across reuse.
+ *
+ * Locking: tty_mutex protects tty, get_tty_driver and tty_init_dev work.
+ * tty->count should protect the rest.
+ * ->siglock protects ->signal/->sighand
+ */
+
+static int tty_open(struct inode *inode, struct file *filp)
+{
+ struct tty_struct *tty = NULL;
+ int noctty, retval;
+ struct tty_driver *driver;
+ int index;
+ dev_t device = inode->i_rdev;
+ unsigned saved_flags = filp->f_flags;
+
+ nonseekable_open(inode, filp);
+
+retry_open:
+ noctty = filp->f_flags & O_NOCTTY;
+ index = -1;
+ retval = 0;
+
+ mutex_lock(&tty_mutex);
+ lock_kernel();
+
+ if (device == MKDEV(TTYAUX_MAJOR, 0)) {
+ tty = get_current_tty();
+ if (!tty) {
+ unlock_kernel();
+ mutex_unlock(&tty_mutex);
+ return -ENXIO;
+ }
+ driver = tty_driver_kref_get(tty->driver);
+ index = tty->index;
+ filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
+ /* noctty = 1; */
+ /* FIXME: Should we take a driver reference ? */
+ tty_kref_put(tty);
+ goto got_driver;
+ }
+#ifdef CONFIG_VT
+ if (device == MKDEV(TTY_MAJOR, 0)) {
+ extern struct tty_driver *console_driver;
+ driver = tty_driver_kref_get(console_driver);
+ index = fg_console;
+ noctty = 1;
+ goto got_driver;
+ }
+#endif
+ if (device == MKDEV(TTYAUX_MAJOR, 1)) {
+ struct tty_driver *console_driver = console_device(&index);
+ if (console_driver) {
+ driver = tty_driver_kref_get(console_driver);
+ if (driver) {
+ /* Don't let /dev/console block */
+ filp->f_flags |= O_NONBLOCK;
+ noctty = 1;
+ goto got_driver;
+ }
+ }
+ unlock_kernel();
+ mutex_unlock(&tty_mutex);
+ return -ENODEV;
+ }
+
+ driver = get_tty_driver(device, &index);
+ if (!driver) {
+ unlock_kernel();
+ mutex_unlock(&tty_mutex);
+ return -ENODEV;
+ }
+got_driver:
+ if (!tty) {
+ /* check whether we're reopening an existing tty */
+ tty = tty_driver_lookup_tty(driver, inode, index);
+
+ if (IS_ERR(tty)) {
+ unlock_kernel();
+ mutex_unlock(&tty_mutex);
+ return PTR_ERR(tty);
+ }
+ }
+
+ if (tty) {
+ retval = tty_reopen(tty);
+ if (retval)
+ tty = ERR_PTR(retval);
+ } else
+ tty = tty_init_dev(driver, index, 0);
+
+ mutex_unlock(&tty_mutex);
+ tty_driver_kref_put(driver);
+ if (IS_ERR(tty)) {
+ unlock_kernel();
+ return PTR_ERR(tty);
+ }
+
+ filp->private_data = tty;
+ file_move(filp, &tty->tty_files);
+ check_tty_count(tty, "tty_open");
+ if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+ tty->driver->subtype == PTY_TYPE_MASTER)
+ noctty = 1;
+#ifdef TTY_DEBUG_HANGUP
+ printk(KERN_DEBUG "opening %s...", tty->name);
+#endif
+ if (!retval) {
+ if (tty->ops->open)
+ retval = tty->ops->open(tty, filp);
+ else
+ retval = -ENODEV;
+ }
+ filp->f_flags = saved_flags;
+
+ if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&
+ !capable(CAP_SYS_ADMIN))
+ retval = -EBUSY;
+
+ if (retval) {
+#ifdef TTY_DEBUG_HANGUP
+ printk(KERN_DEBUG "error %d in opening %s...", retval,
+ tty->name);
+#endif
+ tty_release(inode, filp);
+ if (retval != -ERESTARTSYS) {
+ unlock_kernel();
+ return retval;
+ }
+ if (signal_pending(current)) {
+ unlock_kernel();
+ return retval;
+ }
+ schedule();
+ /*
+ * Need to reset f_op in case a hangup happened.
+ */
+ if (filp->f_op == &hung_up_tty_fops)
+ filp->f_op = &tty_fops;
+ unlock_kernel();
+ goto retry_open;
+ }
+ unlock_kernel();
+
+
+ mutex_lock(&tty_mutex);
+ lock_kernel();
+ spin_lock_irq(¤t->sighand->siglock);
+ if (!noctty &&
+ current->signal->leader &&
+ !current->signal->tty &&
+ tty->session == NULL)
+ __proc_set_tty(current, tty);
+ spin_unlock_irq(¤t->sighand->siglock);
+ unlock_kernel();
+ mutex_unlock(&tty_mutex);
+ return 0;
+}
+
+
+
+/**
+ * tty_poll - check tty status
+ * @filp: file being polled
+ * @wait: poll wait structures to update
+ *
+ * Call the line discipline polling method to obtain the poll
+ * status of the device.
+ *
+ * Locking: locks called line discipline but ldisc poll method
+ * may be re-entered freely by other callers.
+ */
+
+static unsigned int tty_poll(struct file *filp, poll_table *wait)
+{
+ struct tty_struct *tty;
+ struct tty_ldisc *ld;
+ int ret = 0;
+
+ tty = (struct tty_struct *)filp->private_data;
+ if (tty_paranoia_check(tty, filp->f_path.dentry->d_inode, "tty_poll"))
+ return 0;
+
+ ld = tty_ldisc_ref_wait(tty);
+ if (ld->ops->poll)
+ ret = (ld->ops->poll)(tty, filp, wait);
+ tty_ldisc_deref(ld);
+ return ret;
+}
+
+static int tty_fasync(int fd, struct file *filp, int on)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+ int retval = 0;
+
+ lock_kernel();
+ tty = (struct tty_struct *)filp->private_data;
+ if (tty_paranoia_check(tty, filp->f_path.dentry->d_inode, "tty_fasync"))
+ goto out;
+
+ retval = fasync_helper(fd, filp, on, &tty->fasync);
+ if (retval <= 0)
+ goto out;
+
+ if (on) {
+ enum pid_type type;
+ struct pid *pid;
+ if (!waitqueue_active(&tty->read_wait))
+ tty->minimum_to_wake = 1;
+ spin_lock_irqsave(&tty->ctrl_lock, flags);
+ if (tty->pgrp) {
+ pid = tty->pgrp;
+ type = PIDTYPE_PGID;
+ } else {
+ pid = task_pid(current);
+ type = PIDTYPE_PID;
+ }
+ get_pid(pid);
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+ retval = __f_setown(filp, pid, type, 0);
+ put_pid(pid);
+ if (retval)
+ goto out;
+ } else {
+ if (!tty->fasync && !waitqueue_active(&tty->read_wait))
+ tty->minimum_to_wake = N_TTY_BUF_SIZE;
+ }
+ retval = 0;
+out:
+ unlock_kernel();
+ return retval;
+}
+
+/**
+ * tiocsti - fake input character
+ * @tty: tty to fake input into
+ * @p: pointer to character
+ *
+ * Fake input to a tty device. Does the necessary locking and
+ * input management.
+ *
+ * FIXME: does not honour flow control ??
+ *
+ * Locking:
+ * Called functions take tty_ldisc_lock
+ * current->signal->tty check is safe without locks
+ *
+ * FIXME: may race normal receive processing
+ */
+
+static int tiocsti(struct tty_struct *tty, char __user *p)
+{
+ char ch, mbz = 0;
+ struct tty_ldisc *ld;
+
+ if ((current->signal->tty != tty) && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (get_user(ch, p))
+ return -EFAULT;
+ tty_audit_tiocsti(tty, ch);
+ ld = tty_ldisc_ref_wait(tty);
+ ld->ops->receive_buf(tty, &ch, &mbz, 1);
+ tty_ldisc_deref(ld);
+ return 0;
+}
+
+/**
+ * tiocgwinsz - implement window query ioctl
+ * @tty; tty
+ * @arg: user buffer for result
+ *
+ * Copies the kernel idea of the window size into the user buffer.
+ *
+ * Locking: tty->termios_mutex is taken to ensure the winsize data
+ * is consistent.
+ */
+
+static int tiocgwinsz(struct tty_struct *tty, struct winsize __user *arg)
+{
+ int err;
+
+ mutex_lock(&tty->termios_mutex);
+ err = copy_to_user(arg, &tty->winsize, sizeof(*arg));
+ mutex_unlock(&tty->termios_mutex);
+
+ return err ? -EFAULT: 0;
+}
+
+/**
+ * tty_do_resize - resize event
+ * @tty: tty being resized
+ * @rows: rows (character)
+ * @cols: cols (character)
+ *
+ * Update the termios variables and send the necessary signals to
+ * peform a terminal resize correctly
+ */
+
+int tty_do_resize(struct tty_struct *tty, struct winsize *ws)
+{
+ struct pid *pgrp;
+ unsigned long flags;
+
+ /* Lock the tty */
+ mutex_lock(&tty->termios_mutex);
+ if (!memcmp(ws, &tty->winsize, sizeof(*ws)))
+ goto done;
+ /* Get the PID values and reference them so we can
+ avoid holding the tty ctrl lock while sending signals */
+ spin_lock_irqsave(&tty->ctrl_lock, flags);
+ pgrp = get_pid(tty->pgrp);
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+
+ if (pgrp)
+ kill_pgrp(pgrp, SIGWINCH, 1);
+ put_pid(pgrp);
+
+ tty->winsize = *ws;
+done:
+ mutex_unlock(&tty->termios_mutex);
+ return 0;
+}
+
+/**
+ * tiocswinsz - implement window size set ioctl
+ * @tty; tty side of tty
+ * @arg: user buffer for result
+ *
+ * Copies the user idea of the window size to the kernel. Traditionally
+ * this is just advisory information but for the Linux console it
+ * actually has driver level meaning and triggers a VC resize.
+ *
+ * Locking:
+ * Driver dependant. The default do_resize method takes the
+ * tty termios mutex and ctrl_lock. The console takes its own lock
+ * then calls into the default method.
+ */
+
+static int tiocswinsz(struct tty_struct *tty, struct winsize __user *arg)
+{
+ struct winsize tmp_ws;
+ if (copy_from_user(&tmp_ws, arg, sizeof(*arg)))
+ return -EFAULT;
+
+ if (tty->ops->resize)
+ return tty->ops->resize(tty, &tmp_ws);
+ else
+ return tty_do_resize(tty, &tmp_ws);
+}
+
+/**
+ * tioccons - allow admin to move logical console
+ * @file: the file to become console
+ *
+ * Allow the adminstrator to move the redirected console device
+ *
+ * Locking: uses redirect_lock to guard the redirect information
+ */
+
+static int tioccons(struct file *file)
+{
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (file->f_op->write == redirected_tty_write) {
+ struct file *f;
+ spin_lock(&redirect_lock);
+ f = redirect;
+ redirect = NULL;
+ spin_unlock(&redirect_lock);
+ if (f)
+ fput(f);
+ return 0;
+ }
+ spin_lock(&redirect_lock);
+ if (redirect) {
+ spin_unlock(&redirect_lock);
+ return -EBUSY;
+ }
+ get_file(file);
+ redirect = file;
+ spin_unlock(&redirect_lock);
+ return 0;
+}
+
+/**
+ * fionbio - non blocking ioctl
+ * @file: file to set blocking value
+ * @p: user parameter
+ *
+ * Historical tty interfaces had a blocking control ioctl before
+ * the generic functionality existed. This piece of history is preserved
+ * in the expected tty API of posix OS's.
+ *
+ * Locking: none, the open file handle ensures it won't go away.
+ */
+
+static int fionbio(struct file *file, int __user *p)
+{
+ int nonblock;
+
+ if (get_user(nonblock, p))
+ return -EFAULT;
+
+ spin_lock(&file->f_lock);
+ if (nonblock)
+ file->f_flags |= O_NONBLOCK;
+ else
+ file->f_flags &= ~O_NONBLOCK;
+ spin_unlock(&file->f_lock);
+ return 0;
+}
+
+/**
+ * tiocsctty - set controlling tty
+ * @tty: tty structure
+ * @arg: user argument
+ *
+ * This ioctl is used to manage job control. It permits a session
+ * leader to set this tty as the controlling tty for the session.
+ *
+ * Locking:
+ * Takes tty_mutex() to protect tty instance
+ * Takes tasklist_lock internally to walk sessions
+ * Takes ->siglock() when updating signal->tty
+ */
+
+static int tiocsctty(struct tty_struct *tty, int arg)
+{
+ int ret = 0;
+ if (current->signal->leader && (task_session(current) == tty->session))
+ return ret;
+
+ mutex_lock(&tty_mutex);
+ /*
+ * The process must be a session leader and
+ * not have a controlling tty already.
+ */
+ if (!current->signal->leader || current->signal->tty) {
+ ret = -EPERM;
+ goto unlock;
+ }
+
+ if (tty->session) {
+ /*
+ * This tty is already the controlling
+ * tty for another session group!
+ */
+ if (arg == 1 && capable(CAP_SYS_ADMIN)) {
+ /*
+ * Steal it away
+ */
+ read_lock(&tasklist_lock);
+ session_clear_tty(tty->session);
+ read_unlock(&tasklist_lock);
+ } else {
+ ret = -EPERM;
+ goto unlock;
+ }
+ }
+ proc_set_tty(current, tty);
+unlock:
+ mutex_unlock(&tty_mutex);
+ return ret;
+}
+
+/**
+ * tty_get_pgrp - return a ref counted pgrp pid
+ * @tty: tty to read
+ *
+ * Returns a refcounted instance of the pid struct for the process
+ * group controlling the tty.
+ */
+
+struct pid *tty_get_pgrp(struct tty_struct *tty)
+{
+ unsigned long flags;
+ struct pid *pgrp;
+
+ spin_lock_irqsave(&tty->ctrl_lock, flags);
+ pgrp = get_pid(tty->pgrp);
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+
+ return pgrp;
+}
+EXPORT_SYMBOL_GPL(tty_get_pgrp);
+
+/**
+ * tiocgpgrp - get process group
+ * @tty: tty passed by user
+ * @real_tty: tty side of the tty pased by the user if a pty else the tty
+ * @p: returned pid
+ *
+ * Obtain the process group of the tty. If there is no process group
+ * return an error.
+ *
+ * Locking: none. Reference to current->signal->tty is safe.
+ */
+
+static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
+{
+ struct pid *pid;
+ int ret;
+ /*
+ * (tty == real_tty) is a cheap way of
+ * testing if the tty is NOT a master pty.
+ */
+ if (tty == real_tty && current->signal->tty != real_tty)
+ return -ENOTTY;
+ pid = tty_get_pgrp(real_tty);
+ ret = put_user(pid_vnr(pid), p);
+ put_pid(pid);
+ return ret;
+}
+
+/**
+ * tiocspgrp - attempt to set process group
+ * @tty: tty passed by user
+ * @real_tty: tty side device matching tty passed by user
+ * @p: pid pointer
+ *
+ * Set the process group of the tty to the session passed. Only
+ * permitted where the tty session is our session.
+ *
+ * Locking: RCU, ctrl lock
+ */
+
+static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
+{
+ struct pid *pgrp;
+ pid_t pgrp_nr;
+ int retval = tty_check_change(real_tty);
+ unsigned long flags;
+
+ if (retval == -EIO)
+ return -ENOTTY;
+ if (retval)
+ return retval;
+ if (!current->signal->tty ||
+ (current->signal->tty != real_tty) ||
+ (real_tty->session != task_session(current)))
+ return -ENOTTY;
+ if (get_user(pgrp_nr, p))
+ return -EFAULT;
+ if (pgrp_nr < 0)
+ return -EINVAL;
+ rcu_read_lock();
+ pgrp = find_vpid(pgrp_nr);
+ retval = -ESRCH;
+ if (!pgrp)
+ goto out_unlock;
+ retval = -EPERM;
+ if (session_of_pgrp(pgrp) != task_session(current))
+ goto out_unlock;
+ retval = 0;
+ spin_lock_irqsave(&tty->ctrl_lock, flags);
+ put_pid(real_tty->pgrp);
+ real_tty->pgrp = get_pid(pgrp);
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+out_unlock:
+ rcu_read_unlock();
+ return retval;
+}
+
+/**
+ * tiocgsid - get session id
+ * @tty: tty passed by user
+ * @real_tty: tty side of the tty pased by the user if a pty else the tty
+ * @p: pointer to returned session id
+ *
+ * Obtain the session id of the tty. If there is no session
+ * return an error.
+ *
+ * Locking: none. Reference to current->signal->tty is safe.
+ */
+
+static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
+{
+ /*
+ * (tty == real_tty) is a cheap way of
+ * testing if the tty is NOT a master pty.
+ */
+ if (tty == real_tty && current->signal->tty != real_tty)
+ return -ENOTTY;
+ if (!real_tty->session)
+ return -ENOTTY;
+ return put_user(pid_vnr(real_tty->session), p);
+}
+
+/**
+ * tiocsetd - set line discipline
+ * @tty: tty device
+ * @p: pointer to user data
+ *
+ * Set the line discipline according to user request.
+ *
+ * Locking: see tty_set_ldisc, this function is just a helper
+ */
+
+static int tiocsetd(struct tty_struct *tty, int __user *p)
+{
+ int ldisc;
+ int ret;
+
+ if (get_user(ldisc, p))
+ return -EFAULT;
+
+ ret = tty_set_ldisc(tty, ldisc);
+
+ return ret;
+}
+
+/**
+ * send_break - performed time break
+ * @tty: device to break on
+ * @duration: timeout in mS
+ *
+ * Perform a timed break on hardware that lacks its own driver level
+ * timed break functionality.
+ *
+ * Locking:
+ * atomic_write_lock serializes
+ *
+ */
+
+static int send_break(struct tty_struct *tty, unsigned int duration)
+{
+ int retval;
+
+ if (tty->ops->break_ctl == NULL)
+ return 0;
+
+ if (tty->driver->flags & TTY_DRIVER_HARDWARE_BREAK)
+ retval = tty->ops->break_ctl(tty, duration);
+ else {
+ /* Do the work ourselves */
+ if (tty_write_lock(tty, 0) < 0)
+ return -EINTR;
+ retval = tty->ops->break_ctl(tty, -1);
+ if (retval)
+ goto out;
+ if (!signal_pending(current))
+ msleep_interruptible(duration);
+ retval = tty->ops->break_ctl(tty, 0);
+out:
+ tty_write_unlock(tty);
+ if (signal_pending(current))
+ retval = -EINTR;
+ }
+ return retval;
+}
+
+/**
+ * tty_tiocmget - get modem status
+ * @tty: tty device
+ * @file: user file pointer
+ * @p: pointer to result
+ *
+ * Obtain the modem status bits from the tty driver if the feature
+ * is supported. Return -EINVAL if it is not available.
+ *
+ * Locking: none (up to the driver)
+ */
+
+static int tty_tiocmget(struct tty_struct *tty, struct file *file, int __user *p)
+{
+ int retval = -EINVAL;
+
+ if (tty->ops->tiocmget) {
+ retval = tty->ops->tiocmget(tty, file);
+
+ if (retval >= 0)
+ retval = put_user(retval, p);
+ }
+ return retval;
+}
+
+/**
+ * tty_tiocmset - set modem status
+ * @tty: tty device
+ * @file: user file pointer
+ * @cmd: command - clear bits, set bits or set all
+ * @p: pointer to desired bits
+ *
+ * Set the modem status bits from the tty driver if the feature
+ * is supported. Return -EINVAL if it is not available.
+ *
+ * Locking: none (up to the driver)
+ */
+
+static int tty_tiocmset(struct tty_struct *tty, struct file *file, unsigned int cmd,
+ unsigned __user *p)
+{
+ int retval;
+ unsigned int set, clear, val;
+
+ if (tty->ops->tiocmset == NULL)
+ return -EINVAL;
+
+ retval = get_user(val, p);
+ if (retval)
+ return retval;
+ set = clear = 0;
+ switch (cmd) {
+ case TIOCMBIS:
+ set = val;
+ break;
+ case TIOCMBIC:
+ clear = val;
+ break;
+ case TIOCMSET:
+ set = val;
+ clear = ~val;
+ break;
+ }
+ set &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP|TIOCM_CD|
+ TIOCM_RI|TIOCM_DSR|TIOCM_CTS;
+ clear &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP|TIOCM_CD|
+ TIOCM_RI|TIOCM_DSR|TIOCM_CTS;
+ return tty->ops->tiocmset(tty, file, set, clear);
+}
+
+struct tty_struct *tty_pair_get_tty(struct tty_struct *tty)
+{
+ if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+ tty->driver->subtype == PTY_TYPE_MASTER)
+ tty = tty->link;
+ return tty;
+}
+EXPORT_SYMBOL(tty_pair_get_tty);
+
+struct tty_struct *tty_pair_get_pty(struct tty_struct *tty)
+{
+ if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+ tty->driver->subtype == PTY_TYPE_MASTER)
+ return tty;
+ return tty->link;
+}
+EXPORT_SYMBOL(tty_pair_get_pty);
+
+/*
+ * Split this up, as gcc can choke on it otherwise..
+ */
+long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct tty_struct *tty, *real_tty;
+ void __user *p = (void __user *)arg;
+ int retval;
+ struct tty_ldisc *ld;
+ struct inode *inode = file->f_dentry->d_inode;
+
+ tty = (struct tty_struct *)file->private_data;
+ if (tty_paranoia_check(tty, inode, "tty_ioctl"))
+ return -EINVAL;
+
+ real_tty = tty_pair_get_tty(tty);
+
+ /*
+ * Factor out some common prep work
+ */
+ switch (cmd) {
+ case TIOCSETD:
+ case TIOCSBRK:
+ case TIOCCBRK:
+ case TCSBRK:
+ case TCSBRKP:
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ if (cmd != TIOCCBRK) {
+ tty_wait_until_sent(tty, 0);
+ if (signal_pending(current))
+ return -EINTR;
+ }
+ break;
+ }
+
+ /*
+ * Now do the stuff.
+ */
+ switch (cmd) {
+ case TIOCSTI:
+ return tiocsti(tty, p);
+ case TIOCGWINSZ:
+ return tiocgwinsz(real_tty, p);
+ case TIOCSWINSZ:
+ return tiocswinsz(real_tty, p);
+ case TIOCCONS:
+ return real_tty != tty ? -EINVAL : tioccons(file);
+ case FIONBIO:
+ return fionbio(file, p);
+ case TIOCEXCL:
+ set_bit(TTY_EXCLUSIVE, &tty->flags);
+ return 0;
+ case TIOCNXCL:
+ clear_bit(TTY_EXCLUSIVE, &tty->flags);
+ return 0;
+ case TIOCNOTTY:
+ if (current->signal->tty != tty)
+ return -ENOTTY;
+ no_tty();
+ return 0;
+ case TIOCSCTTY:
+ return tiocsctty(tty, arg);
+ case TIOCGPGRP:
+ return tiocgpgrp(tty, real_tty, p);
+ case TIOCSPGRP:
+ return tiocspgrp(tty, real_tty, p);
+ case TIOCGSID:
+ return tiocgsid(tty, real_tty, p);
+ case TIOCGETD:
+ return put_user(tty->ldisc->ops->num, (int __user *)p);
+ case TIOCSETD:
+ return tiocsetd(tty, p);
+ /*
+ * Break handling
+ */
+ case TIOCSBRK: /* Turn break on, unconditionally */
+ if (tty->ops->break_ctl)
+ return tty->ops->break_ctl(tty, -1);
+ return 0;
+ case TIOCCBRK: /* Turn break off, unconditionally */
+ if (tty->ops->break_ctl)
+ return tty->ops->break_ctl(tty, 0);
+ return 0;
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+ /* non-zero arg means wait for all output data
+ * to be sent (performed above) but don't send break.
+ * This is used by the tcdrain() termios function.
+ */
+ if (!arg)
+ return send_break(tty, 250);
+ return 0;
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+ return send_break(tty, arg ? arg*100 : 250);
+
+ case TIOCMGET:
+ return tty_tiocmget(tty, file, p);
+ case TIOCMSET:
+ case TIOCMBIC:
+ case TIOCMBIS:
+ return tty_tiocmset(tty, file, cmd, p);
+ case TCFLSH:
+ switch (arg) {
+ case TCIFLUSH:
+ case TCIOFLUSH:
+ /* flush tty buffer and allow ldisc to process ioctl */
+ tty_buffer_flush(tty);
+ break;
+ }
+ break;
+ }
+ if (tty->ops->ioctl) {
+ retval = (tty->ops->ioctl)(tty, file, cmd, arg);
+ if (retval != -ENOIOCTLCMD)
+ return retval;
+ }
+ ld = tty_ldisc_ref_wait(tty);
+ retval = -EINVAL;
+ if (ld->ops->ioctl) {
+ retval = ld->ops->ioctl(tty, file, cmd, arg);
+ if (retval == -ENOIOCTLCMD)
+ retval = -EINVAL;
+ }
+ tty_ldisc_deref(ld);
+ return retval;
+}
+
+#ifdef CONFIG_COMPAT
+static long tty_compat_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct inode *inode = file->f_dentry->d_inode;
+ struct tty_struct *tty = file->private_data;
+ struct tty_ldisc *ld;
+ int retval = -ENOIOCTLCMD;
+
+ if (tty_paranoia_check(tty, inode, "tty_ioctl"))
+ return -EINVAL;
+
+ if (tty->ops->compat_ioctl) {
+ retval = (tty->ops->compat_ioctl)(tty, file, cmd, arg);
+ if (retval != -ENOIOCTLCMD)
+ return retval;
+ }
+
+ ld = tty_ldisc_ref_wait(tty);
+ if (ld->ops->compat_ioctl)
+ retval = ld->ops->compat_ioctl(tty, file, cmd, arg);
+ tty_ldisc_deref(ld);
+
+ return retval;
+}
+#endif
+
+/*
+ * This implements the "Secure Attention Key" --- the idea is to
+ * prevent trojan horses by killing all processes associated with this
+ * tty when the user hits the "Secure Attention Key". Required for
+ * super-paranoid applications --- see the Orange Book for more details.
+ *
+ * This code could be nicer; ideally it should send a HUP, wait a few
+ * seconds, then send a INT, and then a KILL signal. But you then
+ * have to coordinate with the init process, since all processes associated
+ * with the current tty must be dead before the new getty is allowed
+ * to spawn.
+ *
+ * Now, if it would be correct ;-/ The current code has a nasty hole -
+ * it doesn't catch files in flight. We may send the descriptor to ourselves
+ * via AF_UNIX socket, close it and later fetch from socket. FIXME.
+ *
+ * Nasty bug: do_SAK is being called in interrupt context. This can
+ * deadlock. We punt it up to process context. AKPM - 16Mar2001
+ */
+void __do_SAK(struct tty_struct *tty)
+{
+#ifdef TTY_SOFT_SAK
+ tty_hangup(tty);
+#else
+ struct task_struct *g, *p;
+ struct pid *session;
+ int i;
+ struct file *filp;
+ struct fdtable *fdt;
+
+ if (!tty)
+ return;
+ session = tty->session;
+
+ tty_ldisc_flush(tty);
+
+ tty_driver_flush_buffer(tty);
+
+ read_lock(&tasklist_lock);
+ /* Kill the entire session */
+ do_each_pid_task(session, PIDTYPE_SID, p) {
+ printk(KERN_NOTICE "SAK: killed process %d"
+ " (%s): task_session(p)==tty->session\n",
+ task_pid_nr(p), p->comm);
+ send_sig(SIGKILL, p, 1);
+ } while_each_pid_task(session, PIDTYPE_SID, p);
+ /* Now kill any processes that happen to have the
+ * tty open.
+ */
+ do_each_thread(g, p) {
+ if (p->signal->tty == tty) {
+ printk(KERN_NOTICE "SAK: killed process %d"
+ " (%s): task_session(p)==tty->session\n",
+ task_pid_nr(p), p->comm);
+ send_sig(SIGKILL, p, 1);
+ continue;
+ }
+ task_lock(p);
+ if (p->files) {
+ /*
+ * We don't take a ref to the file, so we must
+ * hold ->file_lock instead.
+ */
+ spin_lock(&p->files->file_lock);
+ fdt = files_fdtable(p->files);
+ for (i = 0; i < fdt->max_fds; i++) {
+ filp = fcheck_files(p->files, i);
+ if (!filp)
+ continue;
+ if (filp->f_op->read == tty_read &&
+ filp->private_data == tty) {
+ printk(KERN_NOTICE "SAK: killed process %d"
+ " (%s): fd#%d opened to the tty\n",
+ task_pid_nr(p), p->comm, i);
+ force_sig(SIGKILL, p);
+ break;
+ }
+ }
+ spin_unlock(&p->files->file_lock);
+ }
+ task_unlock(p);
+ } while_each_thread(g, p);
+ read_unlock(&tasklist_lock);
+#endif
+}
+
+static void do_SAK_work(struct work_struct *work)
+{
+ struct tty_struct *tty =
+ container_of(work, struct tty_struct, SAK_work);
+ __do_SAK(tty);
+}
+
+/*
+ * The tq handling here is a little racy - tty->SAK_work may already be queued.
+ * Fortunately we don't need to worry, because if ->SAK_work is already queued,
+ * the values which we write to it will be identical to the values which it
+ * already has. --akpm
+ */
+void do_SAK(struct tty_struct *tty)
+{
+ if (!tty)
+ return;
+ schedule_work(&tty->SAK_work);
+}
+
+EXPORT_SYMBOL(do_SAK);
+
+/**
+ * initialize_tty_struct
+ * @tty: tty to initialize
+ *
+ * This subroutine initializes a tty structure that has been newly
+ * allocated.
+ *
+ * Locking: none - tty in question must not be exposed at this point
+ */
+
+void initialize_tty_struct(struct tty_struct *tty,
+ struct tty_driver *driver, int idx)
+{
+ memset(tty, 0, sizeof(struct tty_struct));
+ kref_init(&tty->kref);
+ tty->magic = TTY_MAGIC;
+ tty_ldisc_init(tty);
+ tty->session = NULL;
+ tty->pgrp = NULL;
+ tty->overrun_time = jiffies;
+ tty->buf.head = tty->buf.tail = NULL;
+ tty_buffer_init(tty);
+ mutex_init(&tty->termios_mutex);
+ mutex_init(&tty->ldisc_mutex);
+ init_waitqueue_head(&tty->write_wait);
+ init_waitqueue_head(&tty->read_wait);
+ INIT_WORK(&tty->hangup_work, do_tty_hangup);
+ mutex_init(&tty->atomic_read_lock);
+ mutex_init(&tty->atomic_write_lock);
+ mutex_init(&tty->output_lock);
+ mutex_init(&tty->echo_lock);
+ spin_lock_init(&tty->read_lock);
+ spin_lock_init(&tty->ctrl_lock);
+ INIT_LIST_HEAD(&tty->tty_files);
+ INIT_WORK(&tty->SAK_work, do_SAK_work);
+
+ tty->driver = driver;
+ tty->ops = driver->ops;
+ tty->index = idx;
+ tty_line_name(driver, idx, tty->name);
+}
+
+/**
+ * tty_put_char - write one character to a tty
+ * @tty: tty
+ * @ch: character
+ *
+ * Write one byte to the tty using the provided put_char method
+ * if present. Returns the number of characters successfully output.
+ *
+ * Note: the specific put_char operation in the driver layer may go
+ * away soon. Don't call it directly, use this method
+ */
+
+int tty_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ if (tty->ops->put_char)
+ return tty->ops->put_char(tty, ch);
+ return tty->ops->write(tty, &ch, 1);
+}
+EXPORT_SYMBOL_GPL(tty_put_char);
+
+struct class *tty_class;
+
+/**
+ * tty_register_device - register a tty device
+ * @driver: the tty driver that describes the tty device
+ * @index: the index in the tty driver for this tty device
+ * @device: a struct device that is associated with this tty device.
+ * This field is optional, if there is no known struct device
+ * for this tty device it can be set to NULL safely.
+ *
+ * Returns a pointer to the struct device for this tty device
+ * (or ERR_PTR(-EFOO) on error).
+ *
+ * This call is required to be made to register an individual tty device
+ * if the tty driver's flags have the TTY_DRIVER_DYNAMIC_DEV bit set. If
+ * that bit is not set, this function should not be called by a tty
+ * driver.
+ *
+ * Locking: ??
+ */
+
+struct device *tty_register_device(struct tty_driver *driver, unsigned index,
+ struct device *device)
+{
+ char name[64];
+ dev_t dev = MKDEV(driver->major, driver->minor_start) + index;
+
+ if (index >= driver->num) {
+ printk(KERN_ERR "Attempt to register invalid tty line number "
+ " (%d).\n", index);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (driver->type == TTY_DRIVER_TYPE_PTY)
+ pty_line_name(driver, index, name);
+ else
+ tty_line_name(driver, index, name);
+
+ return device_create(tty_class, device, dev, NULL, name);
+}
+EXPORT_SYMBOL(tty_register_device);
+
+/**
+ * tty_unregister_device - unregister a tty device
+ * @driver: the tty driver that describes the tty device
+ * @index: the index in the tty driver for this tty device
+ *
+ * If a tty device is registered with a call to tty_register_device() then
+ * this function must be called when the tty device is gone.
+ *
+ * Locking: ??
+ */
+
+void tty_unregister_device(struct tty_driver *driver, unsigned index)
+{
+ device_destroy(tty_class,
+ MKDEV(driver->major, driver->minor_start) + index);
+}
+EXPORT_SYMBOL(tty_unregister_device);
+
+struct tty_driver *alloc_tty_driver(int lines)
+{
+ struct tty_driver *driver;
+
+ driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
+ if (driver) {
+ kref_init(&driver->kref);
+ driver->magic = TTY_DRIVER_MAGIC;
+ driver->num = lines;
+ /* later we'll move allocation of tables here */
+ }
+ return driver;
+}
+EXPORT_SYMBOL(alloc_tty_driver);
+
+static void destruct_tty_driver(struct kref *kref)
+{
+ struct tty_driver *driver = container_of(kref, struct tty_driver, kref);
+ int i;
+ struct ktermios *tp;
+ void *p;
+
+ if (driver->flags & TTY_DRIVER_INSTALLED) {
+ /*
+ * Free the termios and termios_locked structures because
+ * we don't want to get memory leaks when modular tty
+ * drivers are removed from the kernel.
+ */
+ for (i = 0; i < driver->num; i++) {
+ tp = driver->termios[i];
+ if (tp) {
+ driver->termios[i] = NULL;
+ kfree(tp);
+ }
+ if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV))
+ tty_unregister_device(driver, i);
+ }
+ p = driver->ttys;
+ proc_tty_unregister_driver(driver);
+ driver->ttys = NULL;
+ driver->termios = NULL;
+ kfree(p);
+ cdev_del(&driver->cdev);
+ }
+ kfree(driver);
+}
+
+void tty_driver_kref_put(struct tty_driver *driver)
+{
+ kref_put(&driver->kref, destruct_tty_driver);
+}
+EXPORT_SYMBOL(tty_driver_kref_put);
+
+void tty_set_operations(struct tty_driver *driver,
+ const struct tty_operations *op)
+{
+ driver->ops = op;
+};
+EXPORT_SYMBOL(tty_set_operations);
+
+void put_tty_driver(struct tty_driver *d)
+{
+ tty_driver_kref_put(d);
+}
+EXPORT_SYMBOL(put_tty_driver);
+
+/*
+ * Called by a tty driver to register itself.
+ */
+int tty_register_driver(struct tty_driver *driver)
+{
+ int error;
+ int i;
+ dev_t dev;
+ void **p = NULL;
+
+ if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
+ p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ }
+
+ if (!driver->major) {
+ error = alloc_chrdev_region(&dev, driver->minor_start,
+ driver->num, driver->name);
+ if (!error) {
+ driver->major = MAJOR(dev);
+ driver->minor_start = MINOR(dev);
+ }
+ } else {
+ dev = MKDEV(driver->major, driver->minor_start);
+ error = register_chrdev_region(dev, driver->num, driver->name);
+ }
+ if (error < 0) {
+ kfree(p);
+ return error;
+ }
+
+ if (p) {
+ driver->ttys = (struct tty_struct **)p;
+ driver->termios = (struct ktermios **)(p + driver->num);
+ } else {
+ driver->ttys = NULL;
+ driver->termios = NULL;
+ }
+
+ cdev_init(&driver->cdev, &tty_fops);
+ driver->cdev.owner = driver->owner;
+ error = cdev_add(&driver->cdev, dev, driver->num);
+ if (error) {
+ unregister_chrdev_region(dev, driver->num);
+ driver->ttys = NULL;
+ driver->termios = NULL;
+ kfree(p);
+ return error;
+ }
+
+ mutex_lock(&tty_mutex);
+ list_add(&driver->tty_drivers, &tty_drivers);
+ mutex_unlock(&tty_mutex);
+
+ if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
+ for (i = 0; i < driver->num; i++)
+ tty_register_device(driver, i, NULL);
+ }
+ proc_tty_register_driver(driver);
+ driver->flags |= TTY_DRIVER_INSTALLED;
+ return 0;
+}
+
+EXPORT_SYMBOL(tty_register_driver);
+
+/*
+ * Called by a tty driver to unregister itself.
+ */
+int tty_unregister_driver(struct tty_driver *driver)
+{
+#if 0
+ /* FIXME */
+ if (driver->refcount)
+ return -EBUSY;
+#endif
+ unregister_chrdev_region(MKDEV(driver->major, driver->minor_start),
+ driver->num);
+ mutex_lock(&tty_mutex);
+ list_del(&driver->tty_drivers);
+ mutex_unlock(&tty_mutex);
+ return 0;
+}
+
+EXPORT_SYMBOL(tty_unregister_driver);
+
+dev_t tty_devnum(struct tty_struct *tty)
+{
+ return MKDEV(tty->driver->major, tty->driver->minor_start) + tty->index;
+}
+EXPORT_SYMBOL(tty_devnum);
+
+void proc_clear_tty(struct task_struct *p)
+{
+ unsigned long flags;
+ struct tty_struct *tty;
+ spin_lock_irqsave(&p->sighand->siglock, flags);
+ tty = p->signal->tty;
+ p->signal->tty = NULL;
+ spin_unlock_irqrestore(&p->sighand->siglock, flags);
+ tty_kref_put(tty);
+}
+
+/* Called under the sighand lock */
+
+static void __proc_set_tty(struct task_struct *tsk, struct tty_struct *tty)
+{
+ if (tty) {
+ unsigned long flags;
+ /* We should not have a session or pgrp to put here but.... */
+ spin_lock_irqsave(&tty->ctrl_lock, flags);
+ put_pid(tty->session);
+ put_pid(tty->pgrp);
+ tty->pgrp = get_pid(task_pgrp(tsk));
+ spin_unlock_irqrestore(&tty->ctrl_lock, flags);
+ tty->session = get_pid(task_session(tsk));
+ if (tsk->signal->tty) {
+ printk(KERN_DEBUG "tty not NULL!!\n");
+ tty_kref_put(tsk->signal->tty);
+ }
+ }
+ put_pid(tsk->signal->tty_old_pgrp);
+ tsk->signal->tty = tty_kref_get(tty);
+ tsk->signal->tty_old_pgrp = NULL;
+}
+
+static void proc_set_tty(struct task_struct *tsk, struct tty_struct *tty)
+{
+ spin_lock_irq(&tsk->sighand->siglock);
+ __proc_set_tty(tsk, tty);
+ spin_unlock_irq(&tsk->sighand->siglock);
+}
+
+struct tty_struct *get_current_tty(void)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+
+ spin_lock_irqsave(¤t->sighand->siglock, flags);
+ tty = tty_kref_get(current->signal->tty);
+ spin_unlock_irqrestore(¤t->sighand->siglock, flags);
+ return tty;
+}
+EXPORT_SYMBOL_GPL(get_current_tty);
+
+void tty_default_fops(struct file_operations *fops)
+{
+ *fops = tty_fops;
+}
+
+/*
+ * Initialize the console device. This is called *early*, so
+ * we can't necessarily depend on lots of kernel help here.
+ * Just do some early initializations, and do the complex setup
+ * later.
+ */
+void __init console_init(void)
+{
+ initcall_t *call;
+
+ /* Setup the default TTY line discipline. */
+ tty_ldisc_begin();
+
+ /*
+ * set up the console device so that later boot sequences can
+ * inform about problems etc..
+ */
+ call = __con_initcall_start;
+ while (call < __con_initcall_end) {
+ (*call)();
+ call++;
+ }
+}
+
+static char *tty_devnode(struct device *dev, mode_t *mode)
+{
+ if (!mode)
+ return NULL;
+ if (dev->devt == MKDEV(TTYAUX_MAJOR, 0) ||
+ dev->devt == MKDEV(TTYAUX_MAJOR, 2))
+ *mode = 0666;
+ return NULL;
+}
+
+static int __init tty_class_init(void)
+{
+ tty_class = class_create(THIS_MODULE, "tty");
+ if (IS_ERR(tty_class))
+ return PTR_ERR(tty_class);
+ tty_class->devnode = tty_devnode;
+ return 0;
+}
+
+postcore_initcall(tty_class_init);
+
+/* 3/2004 jmc: why do these devices exist? */
+
+static struct cdev tty_cdev, console_cdev;
+
+/*
+ * Ok, now we can initialize the rest of the tty devices and can count
+ * on memory allocations, interrupts etc..
+ */
+int __init tty_init(void)
+{
+ cdev_init(&tty_cdev, &tty_fops);
+ if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
+ register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
+ panic("Couldn't register /dev/tty driver\n");
+ device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL,
+ "tty");
+
+ cdev_init(&console_cdev, &console_fops);
+ if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
+ register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
+ panic("Couldn't register /dev/console driver\n");
+ device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL,
+ "console");
+
+#ifdef CONFIG_VT
+ vty_init(&console_fops);
+#endif
+ return 0;
+}
+