summaryrefslogtreecommitdiff
path: root/drivers/misc
diff options
context:
space:
mode:
authorDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:51:56 -0800
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:51:56 -0800
commit68529c0f36074be88ee937634fb9433e69c96388 (patch)
tree00c842c0eff6a2b946e1c11a1bdf2d632535e0a7 /drivers/misc
parentf9a10833c7a12ce1244685eba88848bb88bbd43f (diff)
parent3ac6e2e8106ac304c56b9435c907b2b3bda27a09 (diff)
Merge branch 'korg-android-tegra-3.1' into after-upstream-android
Conflicts: arch/arm/mach-tegra/Kconfig arch/arm/mach-tegra/board-ventana.c drivers/misc/Kconfig drivers/video/tegra/dc/hdmi.c Signed-off-by: Dan Willemsen <dwillemsen@nvidia.com>
Diffstat (limited to 'drivers/misc')
-rw-r--r--drivers/misc/Kconfig39
-rw-r--r--drivers/misc/Makefile5
-rw-r--r--drivers/misc/akm8975.c732
-rw-r--r--drivers/misc/apanic.c606
-rw-r--r--drivers/misc/pmem.c1345
-rw-r--r--drivers/misc/uid_stat.c156
-rw-r--r--drivers/misc/wl127x-rfkill.c121
7 files changed, 3004 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index f0459ceb7360..36c368160fcb 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -61,6 +61,10 @@ config AD525X_DPOT_SPI
To compile this driver as a module, choose M here: the
module will be called ad525x_dpot-spi.
+config ANDROID_PMEM
+ bool "Android pmem allocator"
+ default y
+
config ATMEL_PWM
tristate "Atmel AT32/AT91 PWM support"
depends on AVR32 || ARCH_AT91SAM9263 || ARCH_AT91SAM9RL || ARCH_AT91CAP9
@@ -392,6 +396,14 @@ config HMC6352
This driver provides support for the Honeywell HMC6352 compass,
providing configuration and heading data via sysfs.
+config SENSORS_AK8975
+ tristate "AK8975 compass support"
+ default n
+ depends on I2C
+ help
+ If you say yes here you get support for Asahi Kasei's
+ orientation sensor AK8975.
+
config SENSORS_NCT1008
tristate "ON Semiconductor Temperature Sensor"
default n
@@ -443,6 +455,10 @@ config TI_DAC7512
This driver can also be built as a module. If so, the module
will be called ti_dac7512.
+config UID_STAT
+ bool "UID based statistics tracking exported to /proc/uid_stat"
+ default n
+
config VMWARE_BALLOON
tristate "VMware Balloon Driver"
depends on X86
@@ -508,6 +524,29 @@ config USB_SWITCH_FSA9480
stereo and mono audio, video, microphone and UART data to use
a common connector port.
+config WL127X_RFKILL
+ tristate "Bluetooth power control driver for TI wl127x"
+ depends on RFKILL
+ default n
+ ---help---
+ Creates an rfkill entry in sysfs for power control of Bluetooth
+ TI wl127x chips.
+
+config APANIC
+ bool "Android kernel panic diagnostics driver"
+ default n
+ ---help---
+ Driver which handles kernel panics and attempts to write
+ critical debugging data to flash.
+
+config APANIC_PLABEL
+ string "Android panic dump flash partition label"
+ depends on APANIC
+ default "kpanic"
+ ---help---
+ If your platform uses a different flash partition label for storing
+ crashdumps, enter it here.
+
config BCM4329_RFKILL
bool "Enable BCM4329 RFKILL driver"
default n
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 4fb433f392e5..d2ffa2f98dec 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_PHANTOM) += phantom.o
obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o
obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o
obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o
+obj-$(CONFIG_ANDROID_PMEM) += pmem.o
obj-$(CONFIG_SGI_IOC4) += ioc4.o
obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o
obj-$(CONFIG_KGDB_TESTS) += kgdbts.o
@@ -34,6 +35,7 @@ obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o
obj-$(CONFIG_EP93XX_PWM) += ep93xx_pwm.o
obj-$(CONFIG_DS1682) += ds1682.o
obj-$(CONFIG_TI_DAC7512) += ti_dac7512.o
+obj-$(CONFIG_UID_STAT) += uid_stat.o
obj-$(CONFIG_C2PORT) += c2port/
obj-$(CONFIG_IWMC3200TOP) += iwmc3200top/
obj-$(CONFIG_HMC6352) += hmc6352.o
@@ -48,6 +50,9 @@ obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o
obj-y += lis3lv02d/
obj-y += carma/
obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o
+obj-$(CONFIG_WL127X_RFKILL) += wl127x-rfkill.o
+obj-$(CONFIG_APANIC) += apanic.o
+obj-$(CONFIG_SENSORS_AK8975) += akm8975.o
obj-$(CONFIG_SENSORS_NCT1008) += nct1008.o
obj-$(CONFIG_BCM4329_RFKILL) += bcm4329_rfkill.o
obj-$(CONFIG_MPU_SENSORS_MPU3050) += mpu3050/
diff --git a/drivers/misc/akm8975.c b/drivers/misc/akm8975.c
new file mode 100644
index 000000000000..830d2897afd6
--- /dev/null
+++ b/drivers/misc/akm8975.c
@@ -0,0 +1,732 @@
+/* drivers/misc/akm8975.c - akm8975 compass driver
+ *
+ * Copyright (C) 2007-2008 HTC Corporation.
+ * Author: Hou-Kun Chen <houkun.chen@gmail.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/*
+ * Revised by AKM 2009/04/02
+ * Revised by Motorola 2010/05/27
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/miscdevice.h>
+#include <linux/gpio.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/freezer.h>
+#include <linux/akm8975.h>
+#include <linux/earlysuspend.h>
+
+#define AK8975DRV_CALL_DBG 0
+#if AK8975DRV_CALL_DBG
+#define FUNCDBG(msg) pr_err("%s:%s\n", __func__, msg);
+#else
+#define FUNCDBG(msg)
+#endif
+
+#define AK8975DRV_DATA_DBG 0
+#define MAX_FAILURE_COUNT 10
+
+struct akm8975_data {
+ struct i2c_client *this_client;
+ struct akm8975_platform_data *pdata;
+ struct input_dev *input_dev;
+ struct work_struct work;
+ struct mutex flags_lock;
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ struct early_suspend early_suspend;
+#endif
+};
+
+/*
+* Because misc devices can not carry a pointer from driver register to
+* open, we keep this global. This limits the driver to a single instance.
+*/
+struct akm8975_data *akmd_data;
+
+static DECLARE_WAIT_QUEUE_HEAD(open_wq);
+
+static atomic_t open_flag;
+
+static short m_flag;
+static short a_flag;
+static short t_flag;
+static short mv_flag;
+
+static short akmd_delay;
+
+static ssize_t akm8975_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ return sprintf(buf, "%u\n", i2c_smbus_read_byte_data(client,
+ AK8975_REG_CNTL));
+}
+static ssize_t akm8975_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ unsigned long val;
+ strict_strtoul(buf, 10, &val);
+ if (val > 0xff)
+ return -EINVAL;
+ i2c_smbus_write_byte_data(client, AK8975_REG_CNTL, val);
+ return count;
+}
+static DEVICE_ATTR(akm_ms1, S_IWUSR | S_IRUGO, akm8975_show, akm8975_store);
+
+static int akm8975_i2c_rxdata(struct akm8975_data *akm, char *buf, int length)
+{
+ struct i2c_msg msgs[] = {
+ {
+ .addr = akm->this_client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = buf,
+ },
+ {
+ .addr = akm->this_client->addr,
+ .flags = I2C_M_RD,
+ .len = length,
+ .buf = buf,
+ },
+ };
+
+ FUNCDBG("called");
+
+ if (i2c_transfer(akm->this_client->adapter, msgs, 2) < 0) {
+ pr_err("akm8975_i2c_rxdata: transfer error\n");
+ return EIO;
+ } else
+ return 0;
+}
+
+static int akm8975_i2c_txdata(struct akm8975_data *akm, char *buf, int length)
+{
+ struct i2c_msg msgs[] = {
+ {
+ .addr = akm->this_client->addr,
+ .flags = 0,
+ .len = length,
+ .buf = buf,
+ },
+ };
+
+ FUNCDBG("called");
+
+ if (i2c_transfer(akm->this_client->adapter, msgs, 1) < 0) {
+ pr_err("akm8975_i2c_txdata: transfer error\n");
+ return -EIO;
+ } else
+ return 0;
+}
+
+static void akm8975_ecs_report_value(struct akm8975_data *akm, short *rbuf)
+{
+ struct akm8975_data *data = i2c_get_clientdata(akm->this_client);
+
+ FUNCDBG("called");
+
+#if AK8975DRV_DATA_DBG
+ pr_info("akm8975_ecs_report_value: yaw = %d, pitch = %d, roll = %d\n",
+ rbuf[0], rbuf[1], rbuf[2]);
+ pr_info("tmp = %d, m_stat= %d, g_stat=%d\n", rbuf[3], rbuf[4], rbuf[5]);
+ pr_info("Acceleration: x = %d LSB, y = %d LSB, z = %d LSB\n",
+ rbuf[6], rbuf[7], rbuf[8]);
+ pr_info("Magnetic: x = %d LSB, y = %d LSB, z = %d LSB\n\n",
+ rbuf[9], rbuf[10], rbuf[11]);
+#endif
+ mutex_lock(&akm->flags_lock);
+ /* Report magnetic sensor information */
+ if (m_flag) {
+ input_report_abs(data->input_dev, ABS_RX, rbuf[0]);
+ input_report_abs(data->input_dev, ABS_RY, rbuf[1]);
+ input_report_abs(data->input_dev, ABS_RZ, rbuf[2]);
+ input_report_abs(data->input_dev, ABS_RUDDER, rbuf[4]);
+ }
+
+ /* Report acceleration sensor information */
+ if (a_flag) {
+ input_report_abs(data->input_dev, ABS_X, rbuf[6]);
+ input_report_abs(data->input_dev, ABS_Y, rbuf[7]);
+ input_report_abs(data->input_dev, ABS_Z, rbuf[8]);
+ input_report_abs(data->input_dev, ABS_WHEEL, rbuf[5]);
+ }
+
+ /* Report temperature information */
+ if (t_flag)
+ input_report_abs(data->input_dev, ABS_THROTTLE, rbuf[3]);
+
+ if (mv_flag) {
+ input_report_abs(data->input_dev, ABS_HAT0X, rbuf[9]);
+ input_report_abs(data->input_dev, ABS_HAT0Y, rbuf[10]);
+ input_report_abs(data->input_dev, ABS_BRAKE, rbuf[11]);
+ }
+ mutex_unlock(&akm->flags_lock);
+
+ input_sync(data->input_dev);
+}
+
+static void akm8975_ecs_close_done(struct akm8975_data *akm)
+{
+ FUNCDBG("called");
+ mutex_lock(&akm->flags_lock);
+ m_flag = 1;
+ a_flag = 1;
+ t_flag = 1;
+ mv_flag = 1;
+ mutex_unlock(&akm->flags_lock);
+}
+
+static int akm_aot_open(struct inode *inode, struct file *file)
+{
+ int ret = -1;
+
+ FUNCDBG("called");
+ if (atomic_cmpxchg(&open_flag, 0, 1) == 0) {
+ wake_up(&open_wq);
+ ret = 0;
+ }
+
+ ret = nonseekable_open(inode, file);
+ if (ret)
+ return ret;
+
+ file->private_data = akmd_data;
+
+ return ret;
+}
+
+static int akm_aot_release(struct inode *inode, struct file *file)
+{
+ FUNCDBG("called");
+ atomic_set(&open_flag, 0);
+ wake_up(&open_wq);
+ return 0;
+}
+
+static int akm_aot_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *) arg;
+ short flag;
+ struct akm8975_data *akm = file->private_data;
+
+ FUNCDBG("called");
+
+ switch (cmd) {
+ case ECS_IOCTL_APP_SET_MFLAG:
+ case ECS_IOCTL_APP_SET_AFLAG:
+ case ECS_IOCTL_APP_SET_MVFLAG:
+ if (copy_from_user(&flag, argp, sizeof(flag)))
+ return -EFAULT;
+ if (flag < 0 || flag > 1)
+ return -EINVAL;
+ break;
+ case ECS_IOCTL_APP_SET_DELAY:
+ if (copy_from_user(&flag, argp, sizeof(flag)))
+ return -EFAULT;
+ break;
+ default:
+ break;
+ }
+
+ mutex_lock(&akm->flags_lock);
+ switch (cmd) {
+ case ECS_IOCTL_APP_SET_MFLAG:
+ m_flag = flag;
+ break;
+ case ECS_IOCTL_APP_GET_MFLAG:
+ flag = m_flag;
+ break;
+ case ECS_IOCTL_APP_SET_AFLAG:
+ a_flag = flag;
+ break;
+ case ECS_IOCTL_APP_GET_AFLAG:
+ flag = a_flag;
+ break;
+ case ECS_IOCTL_APP_SET_MVFLAG:
+ mv_flag = flag;
+ break;
+ case ECS_IOCTL_APP_GET_MVFLAG:
+ flag = mv_flag;
+ break;
+ case ECS_IOCTL_APP_SET_DELAY:
+ akmd_delay = flag;
+ break;
+ case ECS_IOCTL_APP_GET_DELAY:
+ flag = akmd_delay;
+ break;
+ default:
+ return -ENOTTY;
+ }
+ mutex_unlock(&akm->flags_lock);
+
+ switch (cmd) {
+ case ECS_IOCTL_APP_GET_MFLAG:
+ case ECS_IOCTL_APP_GET_AFLAG:
+ case ECS_IOCTL_APP_GET_MVFLAG:
+ case ECS_IOCTL_APP_GET_DELAY:
+ if (copy_to_user(argp, &flag, sizeof(flag)))
+ return -EFAULT;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int akmd_open(struct inode *inode, struct file *file)
+{
+ int err = 0;
+
+ FUNCDBG("called");
+ err = nonseekable_open(inode, file);
+ if (err)
+ return err;
+
+ file->private_data = akmd_data;
+ return 0;
+}
+
+static int akmd_release(struct inode *inode, struct file *file)
+{
+ struct akm8975_data *akm = file->private_data;
+
+ FUNCDBG("called");
+ akm8975_ecs_close_done(akm);
+ return 0;
+}
+
+static int akmd_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *) arg;
+
+ char rwbuf[16];
+ int ret = -1;
+ int status;
+ short value[12];
+ short delay;
+ struct akm8975_data *akm = file->private_data;
+
+ FUNCDBG("called");
+
+ switch (cmd) {
+ case ECS_IOCTL_READ:
+ case ECS_IOCTL_WRITE:
+ if (copy_from_user(&rwbuf, argp, sizeof(rwbuf)))
+ return -EFAULT;
+ break;
+
+ case ECS_IOCTL_SET_YPR:
+ if (copy_from_user(&value, argp, sizeof(value)))
+ return -EFAULT;
+ break;
+
+ default:
+ break;
+ }
+
+ switch (cmd) {
+ case ECS_IOCTL_READ:
+ if (rwbuf[0] < 1)
+ return -EINVAL;
+
+ ret = akm8975_i2c_rxdata(akm, &rwbuf[1], rwbuf[0]);
+ if (ret < 0)
+ return ret;
+ break;
+
+ case ECS_IOCTL_WRITE:
+ if (rwbuf[0] < 2)
+ return -EINVAL;
+
+ ret = akm8975_i2c_txdata(akm, &rwbuf[1], rwbuf[0]);
+ if (ret < 0)
+ return ret;
+ break;
+ case ECS_IOCTL_SET_YPR:
+ akm8975_ecs_report_value(akm, value);
+ break;
+
+ case ECS_IOCTL_GET_OPEN_STATUS:
+ wait_event_interruptible(open_wq,
+ (atomic_read(&open_flag) != 0));
+ status = atomic_read(&open_flag);
+ break;
+ case ECS_IOCTL_GET_CLOSE_STATUS:
+ wait_event_interruptible(open_wq,
+ (atomic_read(&open_flag) == 0));
+ status = atomic_read(&open_flag);
+ break;
+
+ case ECS_IOCTL_GET_DELAY:
+ delay = akmd_delay;
+ break;
+
+ default:
+ FUNCDBG("Unknown cmd\n");
+ return -ENOTTY;
+ }
+
+ switch (cmd) {
+ case ECS_IOCTL_READ:
+ if (copy_to_user(argp, &rwbuf, sizeof(rwbuf)))
+ return -EFAULT;
+ break;
+ case ECS_IOCTL_GET_OPEN_STATUS:
+ case ECS_IOCTL_GET_CLOSE_STATUS:
+ if (copy_to_user(argp, &status, sizeof(status)))
+ return -EFAULT;
+ break;
+ case ECS_IOCTL_GET_DELAY:
+ if (copy_to_user(argp, &delay, sizeof(delay)))
+ return -EFAULT;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* needed to clear the int. pin */
+static void akm_work_func(struct work_struct *work)
+{
+ struct akm8975_data *akm =
+ container_of(work, struct akm8975_data, work);
+
+ FUNCDBG("called");
+ enable_irq(akm->this_client->irq);
+}
+
+static irqreturn_t akm8975_interrupt(int irq, void *dev_id)
+{
+ struct akm8975_data *akm = dev_id;
+ FUNCDBG("called");
+
+ disable_irq_nosync(akm->this_client->irq);
+ schedule_work(&akm->work);
+ return IRQ_HANDLED;
+}
+
+static int akm8975_power_off(struct akm8975_data *akm)
+{
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ if (akm->pdata->power_off)
+ akm->pdata->power_off();
+
+ return 0;
+}
+
+static int akm8975_power_on(struct akm8975_data *akm)
+{
+ int err;
+
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ if (akm->pdata->power_on) {
+ err = akm->pdata->power_on();
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int akm8975_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct akm8975_data *akm = i2c_get_clientdata(client);
+
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ /* TO DO: might need more work after power mgmt
+ is enabled */
+ return akm8975_power_off(akm);
+}
+
+static int akm8975_resume(struct i2c_client *client)
+{
+ struct akm8975_data *akm = i2c_get_clientdata(client);
+
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ /* TO DO: might need more work after power mgmt
+ is enabled */
+ return akm8975_power_on(akm);
+}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void akm8975_early_suspend(struct early_suspend *handler)
+{
+ struct akm8975_data *akm;
+ akm = container_of(handler, struct akm8975_data, early_suspend);
+
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ akm8975_suspend(akm->this_client, PMSG_SUSPEND);
+}
+
+static void akm8975_early_resume(struct early_suspend *handler)
+{
+ struct akm8975_data *akm;
+ akm = container_of(handler, struct akm8975_data, early_suspend);
+
+#if AK8975DRV_CALL_DBG
+ pr_info("%s\n", __func__);
+#endif
+ akm8975_resume(akm->this_client);
+}
+#endif
+
+
+static int akm8975_init_client(struct i2c_client *client)
+{
+ struct akm8975_data *data;
+ int ret;
+
+ data = i2c_get_clientdata(client);
+
+ ret = request_irq(client->irq, akm8975_interrupt, IRQF_TRIGGER_RISING,
+ "akm8975", data);
+
+ if (ret < 0) {
+ pr_err("akm8975_init_client: request irq failed\n");
+ goto err;
+ }
+
+ init_waitqueue_head(&open_wq);
+
+ mutex_lock(&data->flags_lock);
+ m_flag = 1;
+ a_flag = 1;
+ t_flag = 1;
+ mv_flag = 1;
+ mutex_unlock(&data->flags_lock);
+
+ return 0;
+err:
+ return ret;
+}
+
+static const struct file_operations akmd_fops = {
+ .owner = THIS_MODULE,
+ .open = akmd_open,
+ .release = akmd_release,
+ .ioctl = akmd_ioctl,
+};
+
+static const struct file_operations akm_aot_fops = {
+ .owner = THIS_MODULE,
+ .open = akm_aot_open,
+ .release = akm_aot_release,
+ .ioctl = akm_aot_ioctl,
+};
+
+static struct miscdevice akm_aot_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "akm8975_aot",
+ .fops = &akm_aot_fops,
+};
+
+static struct miscdevice akmd_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "akm8975_dev",
+ .fops = &akmd_fops,
+};
+
+int akm8975_probe(struct i2c_client *client,
+ const struct i2c_device_id *devid)
+{
+ struct akm8975_data *akm;
+ int err;
+ FUNCDBG("called");
+
+ if (client->dev.platform_data == NULL) {
+ dev_err(&client->dev, "platform data is NULL. exiting.\n");
+ err = -ENODEV;
+ goto exit_platform_data_null;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "platform data is NULL. exiting.\n");
+ err = -ENODEV;
+ goto exit_check_functionality_failed;
+ }
+
+ akm = kzalloc(sizeof(struct akm8975_data), GFP_KERNEL);
+ if (!akm) {
+ dev_err(&client->dev,
+ "failed to allocate memory for module data\n");
+ err = -ENOMEM;
+ goto exit_alloc_data_failed;
+ }
+
+ akm->pdata = client->dev.platform_data;
+
+ mutex_init(&akm->flags_lock);
+ INIT_WORK(&akm->work, akm_work_func);
+ i2c_set_clientdata(client, akm);
+
+ err = akm8975_power_on(akm);
+ if (err < 0)
+ goto exit_power_on_failed;
+
+ akm8975_init_client(client);
+ akm->this_client = client;
+ akmd_data = akm;
+
+ akm->input_dev = input_allocate_device();
+ if (!akm->input_dev) {
+ err = -ENOMEM;
+ dev_err(&akm->this_client->dev,
+ "input device allocate failed\n");
+ goto exit_input_dev_alloc_failed;
+ }
+
+ set_bit(EV_ABS, akm->input_dev->evbit);
+
+ /* yaw */
+ input_set_abs_params(akm->input_dev, ABS_RX, 0, 23040, 0, 0);
+ /* pitch */
+ input_set_abs_params(akm->input_dev, ABS_RY, -11520, 11520, 0, 0);
+ /* roll */
+ input_set_abs_params(akm->input_dev, ABS_RZ, -5760, 5760, 0, 0);
+ /* x-axis acceleration */
+ input_set_abs_params(akm->input_dev, ABS_X, -5760, 5760, 0, 0);
+ /* y-axis acceleration */
+ input_set_abs_params(akm->input_dev, ABS_Y, -5760, 5760, 0, 0);
+ /* z-axis acceleration */
+ input_set_abs_params(akm->input_dev, ABS_Z, -5760, 5760, 0, 0);
+ /* temparature */
+ input_set_abs_params(akm->input_dev, ABS_THROTTLE, -30, 85, 0, 0);
+ /* status of magnetic sensor */
+ input_set_abs_params(akm->input_dev, ABS_RUDDER, 0, 3, 0, 0);
+ /* status of acceleration sensor */
+ input_set_abs_params(akm->input_dev, ABS_WHEEL, 0, 3, 0, 0);
+ /* x-axis of raw magnetic vector */
+ input_set_abs_params(akm->input_dev, ABS_HAT0X, -20480, 20479, 0, 0);
+ /* y-axis of raw magnetic vector */
+ input_set_abs_params(akm->input_dev, ABS_HAT0Y, -20480, 20479, 0, 0);
+ /* z-axis of raw magnetic vector */
+ input_set_abs_params(akm->input_dev, ABS_BRAKE, -20480, 20479, 0, 0);
+
+ akm->input_dev->name = "compass";
+
+ err = input_register_device(akm->input_dev);
+ if (err) {
+ pr_err("akm8975_probe: Unable to register input device: %s\n",
+ akm->input_dev->name);
+ goto exit_input_register_device_failed;
+ }
+
+ err = misc_register(&akmd_device);
+ if (err) {
+ pr_err("akm8975_probe: akmd_device register failed\n");
+ goto exit_misc_device_register_failed;
+ }
+
+ err = misc_register(&akm_aot_device);
+ if (err) {
+ pr_err("akm8975_probe: akm_aot_device register failed\n");
+ goto exit_misc_device_register_failed;
+ }
+
+ err = device_create_file(&client->dev, &dev_attr_akm_ms1);
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ akm->early_suspend.suspend = akm8975_early_suspend;
+ akm->early_suspend.resume = akm8975_early_resume;
+ register_early_suspend(&akm->early_suspend);
+#endif
+ return 0;
+
+exit_misc_device_register_failed:
+exit_input_register_device_failed:
+ input_free_device(akm->input_dev);
+exit_input_dev_alloc_failed:
+ akm8975_power_off(akm);
+exit_power_on_failed:
+ kfree(akm);
+exit_alloc_data_failed:
+exit_check_functionality_failed:
+exit_platform_data_null:
+ return err;
+}
+
+static int __devexit akm8975_remove(struct i2c_client *client)
+{
+ struct akm8975_data *akm = i2c_get_clientdata(client);
+ FUNCDBG("called");
+ free_irq(client->irq, NULL);
+ input_unregister_device(akm->input_dev);
+ misc_deregister(&akmd_device);
+ misc_deregister(&akm_aot_device);
+ akm8975_power_off(akm);
+ kfree(akm);
+ return 0;
+}
+
+static const struct i2c_device_id akm8975_id[] = {
+ { "akm8975", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, akm8975_id);
+
+static struct i2c_driver akm8975_driver = {
+ .probe = akm8975_probe,
+ .remove = akm8975_remove,
+#ifndef CONFIG_HAS_EARLYSUSPEND
+ .resume = akm8975_resume,
+ .suspend = akm8975_suspend,
+#endif
+ .id_table = akm8975_id,
+ .driver = {
+ .name = "akm8975",
+ },
+};
+
+static int __init akm8975_init(void)
+{
+ pr_info("AK8975 compass driver: init\n");
+ FUNCDBG("AK8975 compass driver: init\n");
+ return i2c_add_driver(&akm8975_driver);
+}
+
+static void __exit akm8975_exit(void)
+{
+ FUNCDBG("AK8975 compass driver: exit\n");
+ i2c_del_driver(&akm8975_driver);
+}
+
+module_init(akm8975_init);
+module_exit(akm8975_exit);
+
+MODULE_AUTHOR("Hou-Kun Chen <hk_chen@htc.com>");
+MODULE_DESCRIPTION("AK8975 compass driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/apanic.c b/drivers/misc/apanic.c
new file mode 100644
index 000000000000..ca875f89da7a
--- /dev/null
+++ b/drivers/misc/apanic.c
@@ -0,0 +1,606 @@
+/* drivers/misc/apanic.c
+ *
+ * Copyright (C) 2009 Google, Inc.
+ * Author: San Mehat <san@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/wakelock.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/mtd/mtd.h>
+#include <linux/notifier.h>
+#include <linux/mtd/mtd.h>
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/preempt.h>
+
+extern void ram_console_enable_console(int);
+
+struct panic_header {
+ u32 magic;
+#define PANIC_MAGIC 0xdeadf00d
+
+ u32 version;
+#define PHDR_VERSION 0x01
+
+ u32 console_offset;
+ u32 console_length;
+
+ u32 threads_offset;
+ u32 threads_length;
+};
+
+struct apanic_data {
+ struct mtd_info *mtd;
+ struct panic_header curr;
+ void *bounce;
+ struct proc_dir_entry *apanic_console;
+ struct proc_dir_entry *apanic_threads;
+};
+
+static struct apanic_data drv_ctx;
+static struct work_struct proc_removal_work;
+static DEFINE_MUTEX(drv_mutex);
+
+static unsigned int *apanic_bbt;
+static unsigned int apanic_erase_blocks;
+static unsigned int apanic_good_blocks;
+
+static void set_bb(unsigned int block, unsigned int *bbt)
+{
+ unsigned int flag = 1;
+
+ BUG_ON(block >= apanic_erase_blocks);
+
+ flag = flag << (block%32);
+ apanic_bbt[block/32] |= flag;
+ apanic_good_blocks--;
+}
+
+static unsigned int get_bb(unsigned int block, unsigned int *bbt)
+{
+ unsigned int flag;
+
+ BUG_ON(block >= apanic_erase_blocks);
+
+ flag = 1 << (block%32);
+ return apanic_bbt[block/32] & flag;
+}
+
+static void alloc_bbt(struct mtd_info *mtd, unsigned int *bbt)
+{
+ int bbt_size;
+ apanic_erase_blocks = (mtd->size)>>(mtd->erasesize_shift);
+ bbt_size = (apanic_erase_blocks+32)/32;
+
+ apanic_bbt = kmalloc(bbt_size*4, GFP_KERNEL);
+ memset(apanic_bbt, 0, bbt_size*4);
+ apanic_good_blocks = apanic_erase_blocks;
+}
+static void scan_bbt(struct mtd_info *mtd, unsigned int *bbt)
+{
+ int i;
+
+ for (i = 0; i < apanic_erase_blocks; i++) {
+ if (mtd->block_isbad(mtd, i*mtd->erasesize))
+ set_bb(i, apanic_bbt);
+ }
+}
+
+#define APANIC_INVALID_OFFSET 0xFFFFFFFF
+
+static unsigned int phy_offset(struct mtd_info *mtd, unsigned int offset)
+{
+ unsigned int logic_block = offset>>(mtd->erasesize_shift);
+ unsigned int phy_block;
+ unsigned good_block = 0;
+
+ for (phy_block = 0; phy_block < apanic_erase_blocks; phy_block++) {
+ if (!get_bb(phy_block, apanic_bbt))
+ good_block++;
+ if (good_block == (logic_block + 1))
+ break;
+ }
+
+ if (good_block != (logic_block + 1))
+ return APANIC_INVALID_OFFSET;
+
+ return offset + ((phy_block-logic_block)<<mtd->erasesize_shift);
+}
+
+static void apanic_erase_callback(struct erase_info *done)
+{
+ wait_queue_head_t *wait_q = (wait_queue_head_t *) done->priv;
+ wake_up(wait_q);
+}
+
+static int apanic_proc_read(char *buffer, char **start, off_t offset,
+ int count, int *peof, void *dat)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ size_t file_length;
+ off_t file_offset;
+ unsigned int page_no;
+ off_t page_offset;
+ int rc;
+ size_t len;
+
+ if (!count)
+ return 0;
+
+ mutex_lock(&drv_mutex);
+
+ switch ((int) dat) {
+ case 1: /* apanic_console */
+ file_length = ctx->curr.console_length;
+ file_offset = ctx->curr.console_offset;
+ break;
+ case 2: /* apanic_threads */
+ file_length = ctx->curr.threads_length;
+ file_offset = ctx->curr.threads_offset;
+ break;
+ default:
+ pr_err("Bad dat (%d)\n", (int) dat);
+ mutex_unlock(&drv_mutex);
+ return -EINVAL;
+ }
+
+ if ((offset + count) > file_length) {
+ mutex_unlock(&drv_mutex);
+ return 0;
+ }
+
+ /* We only support reading a maximum of a flash page */
+ if (count > ctx->mtd->writesize)
+ count = ctx->mtd->writesize;
+
+ page_no = (file_offset + offset) / ctx->mtd->writesize;
+ page_offset = (file_offset + offset) % ctx->mtd->writesize;
+
+
+ if (phy_offset(ctx->mtd, (page_no * ctx->mtd->writesize))
+ == APANIC_INVALID_OFFSET) {
+ pr_err("apanic: reading an invalid address\n");
+ mutex_unlock(&drv_mutex);
+ return -EINVAL;
+ }
+ rc = ctx->mtd->read(ctx->mtd,
+ phy_offset(ctx->mtd, (page_no * ctx->mtd->writesize)),
+ ctx->mtd->writesize,
+ &len, ctx->bounce);
+
+ if (page_offset)
+ count -= page_offset;
+ memcpy(buffer, ctx->bounce + page_offset, count);
+
+ *start = count;
+
+ if ((offset + count) == file_length)
+ *peof = 1;
+
+ mutex_unlock(&drv_mutex);
+ return count;
+}
+
+static void mtd_panic_erase(void)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ struct erase_info erase;
+ DECLARE_WAITQUEUE(wait, current);
+ wait_queue_head_t wait_q;
+ int rc, i;
+
+ init_waitqueue_head(&wait_q);
+ erase.mtd = ctx->mtd;
+ erase.callback = apanic_erase_callback;
+ erase.len = ctx->mtd->erasesize;
+ erase.priv = (u_long)&wait_q;
+ for (i = 0; i < ctx->mtd->size; i += ctx->mtd->erasesize) {
+ erase.addr = i;
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&wait_q, &wait);
+
+ if (get_bb(erase.addr>>ctx->mtd->erasesize_shift, apanic_bbt)) {
+ printk(KERN_WARNING
+ "apanic: Skipping erase of bad "
+ "block @%llx\n", erase.addr);
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&wait_q, &wait);
+ continue;
+ }
+
+ rc = ctx->mtd->erase(ctx->mtd, &erase);
+ if (rc) {
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&wait_q, &wait);
+ printk(KERN_ERR
+ "apanic: Erase of 0x%llx, 0x%llx failed\n",
+ (unsigned long long) erase.addr,
+ (unsigned long long) erase.len);
+ if (rc == -EIO) {
+ if (ctx->mtd->block_markbad(ctx->mtd,
+ erase.addr)) {
+ printk(KERN_ERR
+ "apanic: Err marking blk bad\n");
+ goto out;
+ }
+ printk(KERN_INFO
+ "apanic: Marked a bad block"
+ " @%llx\n", erase.addr);
+ set_bb(erase.addr>>ctx->mtd->erasesize_shift,
+ apanic_bbt);
+ continue;
+ }
+ goto out;
+ }
+ schedule();
+ remove_wait_queue(&wait_q, &wait);
+ }
+ printk(KERN_DEBUG "apanic: %s partition erased\n",
+ CONFIG_APANIC_PLABEL);
+out:
+ return;
+}
+
+static void apanic_remove_proc_work(struct work_struct *work)
+{
+ struct apanic_data *ctx = &drv_ctx;
+
+ mutex_lock(&drv_mutex);
+ mtd_panic_erase();
+ memset(&ctx->curr, 0, sizeof(struct panic_header));
+ if (ctx->apanic_console) {
+ remove_proc_entry("apanic_console", NULL);
+ ctx->apanic_console = NULL;
+ }
+ if (ctx->apanic_threads) {
+ remove_proc_entry("apanic_threads", NULL);
+ ctx->apanic_threads = NULL;
+ }
+ mutex_unlock(&drv_mutex);
+}
+
+static int apanic_proc_write(struct file *file, const char __user *buffer,
+ unsigned long count, void *data)
+{
+ schedule_work(&proc_removal_work);
+ return count;
+}
+
+static void mtd_panic_notify_add(struct mtd_info *mtd)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ struct panic_header *hdr = ctx->bounce;
+ size_t len;
+ int rc;
+ int proc_entry_created = 0;
+
+ if (strcmp(mtd->name, CONFIG_APANIC_PLABEL))
+ return;
+
+ ctx->mtd = mtd;
+
+ alloc_bbt(mtd, apanic_bbt);
+ scan_bbt(mtd, apanic_bbt);
+
+ if (apanic_good_blocks == 0) {
+ printk(KERN_ERR "apanic: no any good blocks?!\n");
+ goto out_err;
+ }
+
+ rc = mtd->read(mtd, phy_offset(mtd, 0), mtd->writesize,
+ &len, ctx->bounce);
+ if (rc && rc == -EBADMSG) {
+ printk(KERN_WARNING
+ "apanic: Bad ECC on block 0 (ignored)\n");
+ } else if (rc && rc != -EUCLEAN) {
+ printk(KERN_ERR "apanic: Error reading block 0 (%d)\n", rc);
+ goto out_err;
+ }
+
+ if (len != mtd->writesize) {
+ printk(KERN_ERR "apanic: Bad read size (%d)\n", rc);
+ goto out_err;
+ }
+
+ printk(KERN_INFO "apanic: Bound to mtd partition '%s'\n", mtd->name);
+
+ if (hdr->magic != PANIC_MAGIC) {
+ printk(KERN_INFO "apanic: No panic data available\n");
+ mtd_panic_erase();
+ return;
+ }
+
+ if (hdr->version != PHDR_VERSION) {
+ printk(KERN_INFO "apanic: Version mismatch (%d != %d)\n",
+ hdr->version, PHDR_VERSION);
+ mtd_panic_erase();
+ return;
+ }
+
+ memcpy(&ctx->curr, hdr, sizeof(struct panic_header));
+
+ printk(KERN_INFO "apanic: c(%u, %u) t(%u, %u)\n",
+ hdr->console_offset, hdr->console_length,
+ hdr->threads_offset, hdr->threads_length);
+
+ if (hdr->console_length) {
+ ctx->apanic_console = create_proc_entry("apanic_console",
+ S_IFREG | S_IRUGO, NULL);
+ if (!ctx->apanic_console)
+ printk(KERN_ERR "%s: failed creating procfile\n",
+ __func__);
+ else {
+ ctx->apanic_console->read_proc = apanic_proc_read;
+ ctx->apanic_console->write_proc = apanic_proc_write;
+ ctx->apanic_console->size = hdr->console_length;
+ ctx->apanic_console->data = (void *) 1;
+ proc_entry_created = 1;
+ }
+ }
+
+ if (hdr->threads_length) {
+ ctx->apanic_threads = create_proc_entry("apanic_threads",
+ S_IFREG | S_IRUGO, NULL);
+ if (!ctx->apanic_threads)
+ printk(KERN_ERR "%s: failed creating procfile\n",
+ __func__);
+ else {
+ ctx->apanic_threads->read_proc = apanic_proc_read;
+ ctx->apanic_threads->write_proc = apanic_proc_write;
+ ctx->apanic_threads->size = hdr->threads_length;
+ ctx->apanic_threads->data = (void *) 2;
+ proc_entry_created = 1;
+ }
+ }
+
+ if (!proc_entry_created)
+ mtd_panic_erase();
+
+ return;
+out_err:
+ ctx->mtd = NULL;
+}
+
+static void mtd_panic_notify_remove(struct mtd_info *mtd)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ if (mtd == ctx->mtd) {
+ ctx->mtd = NULL;
+ printk(KERN_INFO "apanic: Unbound from %s\n", mtd->name);
+ }
+}
+
+static struct mtd_notifier mtd_panic_notifier = {
+ .add = mtd_panic_notify_add,
+ .remove = mtd_panic_notify_remove,
+};
+
+static int in_panic = 0;
+
+static int apanic_writeflashpage(struct mtd_info *mtd, loff_t to,
+ const u_char *buf)
+{
+ int rc;
+ size_t wlen;
+ int panic = in_interrupt() | in_atomic();
+
+ if (panic && !mtd->panic_write) {
+ printk(KERN_EMERG "%s: No panic_write available\n", __func__);
+ return 0;
+ } else if (!panic && !mtd->write) {
+ printk(KERN_EMERG "%s: No write available\n", __func__);
+ return 0;
+ }
+
+ to = phy_offset(mtd, to);
+ if (to == APANIC_INVALID_OFFSET) {
+ printk(KERN_EMERG "apanic: write to invalid address\n");
+ return 0;
+ }
+
+ if (panic)
+ rc = mtd->panic_write(mtd, to, mtd->writesize, &wlen, buf);
+ else
+ rc = mtd->write(mtd, to, mtd->writesize, &wlen, buf);
+
+ if (rc) {
+ printk(KERN_EMERG
+ "%s: Error writing data to flash (%d)\n",
+ __func__, rc);
+ return rc;
+ }
+
+ return wlen;
+}
+
+extern int log_buf_copy(char *dest, int idx, int len);
+extern void log_buf_clear(void);
+
+/*
+ * Writes the contents of the console to the specified offset in flash.
+ * Returns number of bytes written
+ */
+static int apanic_write_console(struct mtd_info *mtd, unsigned int off)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ int saved_oip;
+ int idx = 0;
+ int rc, rc2;
+ unsigned int last_chunk = 0;
+
+ while (!last_chunk) {
+ saved_oip = oops_in_progress;
+ oops_in_progress = 1;
+ rc = log_buf_copy(ctx->bounce, idx, mtd->writesize);
+ if (rc < 0)
+ break;
+
+ if (rc != mtd->writesize)
+ last_chunk = rc;
+
+ oops_in_progress = saved_oip;
+ if (rc <= 0)
+ break;
+ if (rc != mtd->writesize)
+ memset(ctx->bounce + rc, 0, mtd->writesize - rc);
+
+ rc2 = apanic_writeflashpage(mtd, off, ctx->bounce);
+ if (rc2 <= 0) {
+ printk(KERN_EMERG
+ "apanic: Flash write failed (%d)\n", rc2);
+ return idx;
+ }
+ if (!last_chunk)
+ idx += rc2;
+ else
+ idx += last_chunk;
+ off += rc2;
+ }
+ return idx;
+}
+
+static int apanic(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ struct apanic_data *ctx = &drv_ctx;
+ struct panic_header *hdr = (struct panic_header *) ctx->bounce;
+ int console_offset = 0;
+ int console_len = 0;
+ int threads_offset = 0;
+ int threads_len = 0;
+ int rc;
+
+ if (in_panic)
+ return NOTIFY_DONE;
+ in_panic = 1;
+#ifdef CONFIG_PREEMPT
+ /* Ensure that cond_resched() won't try to preempt anybody */
+ add_preempt_count(PREEMPT_ACTIVE);
+#endif
+ touch_softlockup_watchdog();
+
+ if (!ctx->mtd)
+ goto out;
+
+ if (ctx->curr.magic) {
+ printk(KERN_EMERG "Crash partition in use!\n");
+ goto out;
+ }
+ console_offset = ctx->mtd->writesize;
+
+ /*
+ * Write out the console
+ */
+ console_len = apanic_write_console(ctx->mtd, console_offset);
+ if (console_len < 0) {
+ printk(KERN_EMERG "Error writing console to panic log! (%d)\n",
+ console_len);
+ console_len = 0;
+ }
+
+ /*
+ * Write out all threads
+ */
+ threads_offset = ALIGN(console_offset + console_len,
+ ctx->mtd->writesize);
+ if (!threads_offset)
+ threads_offset = ctx->mtd->writesize;
+
+ ram_console_enable_console(0);
+
+ log_buf_clear();
+ show_state_filter(0);
+ threads_len = apanic_write_console(ctx->mtd, threads_offset);
+ if (threads_len < 0) {
+ printk(KERN_EMERG "Error writing threads to panic log! (%d)\n",
+ threads_len);
+ threads_len = 0;
+ }
+
+ /*
+ * Finally write the panic header
+ */
+ memset(ctx->bounce, 0, PAGE_SIZE);
+ hdr->magic = PANIC_MAGIC;
+ hdr->version = PHDR_VERSION;
+
+ hdr->console_offset = console_offset;
+ hdr->console_length = console_len;
+
+ hdr->threads_offset = threads_offset;
+ hdr->threads_length = threads_len;
+
+ rc = apanic_writeflashpage(ctx->mtd, 0, ctx->bounce);
+ if (rc <= 0) {
+ printk(KERN_EMERG "apanic: Header write failed (%d)\n",
+ rc);
+ goto out;
+ }
+
+ printk(KERN_EMERG "apanic: Panic dump sucessfully written to flash\n");
+
+ out:
+#ifdef CONFIG_PREEMPT
+ sub_preempt_count(PREEMPT_ACTIVE);
+#endif
+ in_panic = 0;
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block panic_blk = {
+ .notifier_call = apanic,
+};
+
+static int panic_dbg_get(void *data, u64 *val)
+{
+ apanic(NULL, 0, NULL);
+ return 0;
+}
+
+static int panic_dbg_set(void *data, u64 val)
+{
+ BUG();
+ return -1;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(panic_dbg_fops, panic_dbg_get, panic_dbg_set, "%llu\n");
+
+int __init apanic_init(void)
+{
+ register_mtd_user(&mtd_panic_notifier);
+ atomic_notifier_chain_register(&panic_notifier_list, &panic_blk);
+ debugfs_create_file("apanic", 0644, NULL, NULL, &panic_dbg_fops);
+ memset(&drv_ctx, 0, sizeof(drv_ctx));
+ drv_ctx.bounce = (void *) __get_free_page(GFP_KERNEL);
+ INIT_WORK(&proc_removal_work, apanic_remove_proc_work);
+ printk(KERN_INFO "Android kernel panic handler initialized (bind=%s)\n",
+ CONFIG_APANIC_PLABEL);
+ return 0;
+}
+
+module_init(apanic_init);
diff --git a/drivers/misc/pmem.c b/drivers/misc/pmem.c
new file mode 100644
index 000000000000..abb73c143164
--- /dev/null
+++ b/drivers/misc/pmem.c
@@ -0,0 +1,1345 @@
+/* drivers/android/pmem.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/mm.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/debugfs.h>
+#include <linux/android_pmem.h>
+#include <linux/mempolicy.h>
+#include <linux/sched.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/cacheflush.h>
+
+#define PMEM_MAX_DEVICES 10
+#define PMEM_MAX_ORDER 128
+#define PMEM_MIN_ALLOC PAGE_SIZE
+
+#define PMEM_DEBUG 1
+
+/* indicates that a refernce to this file has been taken via get_pmem_file,
+ * the file should not be released until put_pmem_file is called */
+#define PMEM_FLAGS_BUSY 0x1
+/* indicates that this is a suballocation of a larger master range */
+#define PMEM_FLAGS_CONNECTED 0x1 << 1
+/* indicates this is a master and not a sub allocation and that it is mmaped */
+#define PMEM_FLAGS_MASTERMAP 0x1 << 2
+/* submap and unsubmap flags indicate:
+ * 00: subregion has never been mmaped
+ * 10: subregion has been mmaped, reference to the mm was taken
+ * 11: subretion has ben released, refernece to the mm still held
+ * 01: subretion has been released, reference to the mm has been released
+ */
+#define PMEM_FLAGS_SUBMAP 0x1 << 3
+#define PMEM_FLAGS_UNSUBMAP 0x1 << 4
+
+
+struct pmem_data {
+ /* in alloc mode: an index into the bitmap
+ * in no_alloc mode: the size of the allocation */
+ int index;
+ /* see flags above for descriptions */
+ unsigned int flags;
+ /* protects this data field, if the mm_mmap sem will be held at the
+ * same time as this sem, the mm sem must be taken first (as this is
+ * the order for vma_open and vma_close ops */
+ struct rw_semaphore sem;
+ /* info about the mmaping process */
+ struct vm_area_struct *vma;
+ /* task struct of the mapping process */
+ struct task_struct *task;
+ /* process id of teh mapping process */
+ pid_t pid;
+ /* file descriptor of the master */
+ int master_fd;
+ /* file struct of the master */
+ struct file *master_file;
+ /* a list of currently available regions if this is a suballocation */
+ struct list_head region_list;
+ /* a linked list of data so we can access them for debugging */
+ struct list_head list;
+#if PMEM_DEBUG
+ int ref;
+#endif
+};
+
+struct pmem_bits {
+ unsigned allocated:1; /* 1 if allocated, 0 if free */
+ unsigned order:7; /* size of the region in pmem space */
+};
+
+struct pmem_region_node {
+ struct pmem_region region;
+ struct list_head list;
+};
+
+#define PMEM_DEBUG_MSGS 0
+#if PMEM_DEBUG_MSGS
+#define DLOG(fmt,args...) \
+ do { printk(KERN_INFO "[%s:%s:%d] "fmt, __FILE__, __func__, __LINE__, \
+ ##args); } \
+ while (0)
+#else
+#define DLOG(x...) do {} while (0)
+#endif
+
+struct pmem_info {
+ struct miscdevice dev;
+ /* physical start address of the remaped pmem space */
+ unsigned long base;
+ /* vitual start address of the remaped pmem space */
+ unsigned char __iomem *vbase;
+ /* total size of the pmem space */
+ unsigned long size;
+ /* number of entries in the pmem space */
+ unsigned long num_entries;
+ /* pfn of the garbage page in memory */
+ unsigned long garbage_pfn;
+ /* index of the garbage page in the pmem space */
+ int garbage_index;
+ /* the bitmap for the region indicating which entries are allocated
+ * and which are free */
+ struct pmem_bits *bitmap;
+ /* indicates the region should not be managed with an allocator */
+ unsigned no_allocator;
+ /* indicates maps of this region should be cached, if a mix of
+ * cached and uncached is desired, set this and open the device with
+ * O_SYNC to get an uncached region */
+ unsigned cached;
+ unsigned buffered;
+ /* in no_allocator mode the first mapper gets the whole space and sets
+ * this flag */
+ unsigned allocated;
+ /* for debugging, creates a list of pmem file structs, the
+ * data_list_lock should be taken before pmem_data->sem if both are
+ * needed */
+ struct mutex data_list_lock;
+ struct list_head data_list;
+ /* pmem_sem protects the bitmap array
+ * a write lock should be held when modifying entries in bitmap
+ * a read lock should be held when reading data from bits or
+ * dereferencing a pointer into bitmap
+ *
+ * pmem_data->sem protects the pmem data of a particular file
+ * Many of the function that require the pmem_data->sem have a non-
+ * locking version for when the caller is already holding that sem.
+ *
+ * IF YOU TAKE BOTH LOCKS TAKE THEM IN THIS ORDER:
+ * down(pmem_data->sem) => down(bitmap_sem)
+ */
+ struct rw_semaphore bitmap_sem;
+
+ long (*ioctl)(struct file *, unsigned int, unsigned long);
+ int (*release)(struct inode *, struct file *);
+};
+
+static struct pmem_info pmem[PMEM_MAX_DEVICES];
+static int id_count;
+
+#define PMEM_IS_FREE(id, index) !(pmem[id].bitmap[index].allocated)
+#define PMEM_ORDER(id, index) pmem[id].bitmap[index].order
+#define PMEM_BUDDY_INDEX(id, index) (index ^ (1 << PMEM_ORDER(id, index)))
+#define PMEM_NEXT_INDEX(id, index) (index + (1 << PMEM_ORDER(id, index)))
+#define PMEM_OFFSET(index) (index * PMEM_MIN_ALLOC)
+#define PMEM_START_ADDR(id, index) (PMEM_OFFSET(index) + pmem[id].base)
+#define PMEM_LEN(id, index) ((1 << PMEM_ORDER(id, index)) * PMEM_MIN_ALLOC)
+#define PMEM_END_ADDR(id, index) (PMEM_START_ADDR(id, index) + \
+ PMEM_LEN(id, index))
+#define PMEM_START_VADDR(id, index) (PMEM_OFFSET(id, index) + pmem[id].vbase)
+#define PMEM_END_VADDR(id, index) (PMEM_START_VADDR(id, index) + \
+ PMEM_LEN(id, index))
+#define PMEM_REVOKED(data) (data->flags & PMEM_FLAGS_REVOKED)
+#define PMEM_IS_PAGE_ALIGNED(addr) (!((addr) & (~PAGE_MASK)))
+#define PMEM_IS_SUBMAP(data) ((data->flags & PMEM_FLAGS_SUBMAP) && \
+ (!(data->flags & PMEM_FLAGS_UNSUBMAP)))
+
+static int pmem_release(struct inode *, struct file *);
+static int pmem_mmap(struct file *, struct vm_area_struct *);
+static int pmem_open(struct inode *, struct file *);
+static long pmem_ioctl(struct file *, unsigned int, unsigned long);
+
+struct file_operations pmem_fops = {
+ .release = pmem_release,
+ .mmap = pmem_mmap,
+ .open = pmem_open,
+ .unlocked_ioctl = pmem_ioctl,
+};
+
+static int get_id(struct file *file)
+{
+ return MINOR(file->f_dentry->d_inode->i_rdev);
+}
+
+int is_pmem_file(struct file *file)
+{
+ int id;
+
+ if (unlikely(!file || !file->f_dentry || !file->f_dentry->d_inode))
+ return 0;
+ id = get_id(file);
+ if (unlikely(id >= PMEM_MAX_DEVICES))
+ return 0;
+ if (unlikely(file->f_dentry->d_inode->i_rdev !=
+ MKDEV(MISC_MAJOR, pmem[id].dev.minor)))
+ return 0;
+ return 1;
+}
+
+static int has_allocation(struct file *file)
+{
+ struct pmem_data *data;
+ /* check is_pmem_file first if not accessed via pmem_file_ops */
+
+ if (unlikely(!file->private_data))
+ return 0;
+ data = (struct pmem_data *)file->private_data;
+ if (unlikely(data->index < 0))
+ return 0;
+ return 1;
+}
+
+static int is_master_owner(struct file *file)
+{
+ struct file *master_file;
+ struct pmem_data *data;
+ int put_needed, ret = 0;
+
+ if (!is_pmem_file(file) || !has_allocation(file))
+ return 0;
+ data = (struct pmem_data *)file->private_data;
+ if (PMEM_FLAGS_MASTERMAP & data->flags)
+ return 1;
+ master_file = fget_light(data->master_fd, &put_needed);
+ if (master_file && data->master_file == master_file)
+ ret = 1;
+ fput_light(master_file, put_needed);
+ return ret;
+}
+
+static int pmem_free(int id, int index)
+{
+ /* caller should hold the write lock on pmem_sem! */
+ int buddy, curr = index;
+ DLOG("index %d\n", index);
+
+ if (pmem[id].no_allocator) {
+ pmem[id].allocated = 0;
+ return 0;
+ }
+ /* clean up the bitmap, merging any buddies */
+ pmem[id].bitmap[curr].allocated = 0;
+ /* find a slots buddy Buddy# = Slot# ^ (1 << order)
+ * if the buddy is also free merge them
+ * repeat until the buddy is not free or end of the bitmap is reached
+ */
+ do {
+ buddy = PMEM_BUDDY_INDEX(id, curr);
+ if (PMEM_IS_FREE(id, buddy) &&
+ PMEM_ORDER(id, buddy) == PMEM_ORDER(id, curr)) {
+ PMEM_ORDER(id, buddy)++;
+ PMEM_ORDER(id, curr)++;
+ curr = min(buddy, curr);
+ } else {
+ break;
+ }
+ } while (curr < pmem[id].num_entries);
+
+ return 0;
+}
+
+static void pmem_revoke(struct file *file, struct pmem_data *data);
+
+static int pmem_release(struct inode *inode, struct file *file)
+{
+ struct pmem_data *data = (struct pmem_data *)file->private_data;
+ struct pmem_region_node *region_node;
+ struct list_head *elt, *elt2;
+ int id = get_id(file), ret = 0;
+
+
+ mutex_lock(&pmem[id].data_list_lock);
+ /* if this file is a master, revoke all the memory in the connected
+ * files */
+ if (PMEM_FLAGS_MASTERMAP & data->flags) {
+ struct pmem_data *sub_data;
+ list_for_each(elt, &pmem[id].data_list) {
+ sub_data = list_entry(elt, struct pmem_data, list);
+ down_read(&sub_data->sem);
+ if (PMEM_IS_SUBMAP(sub_data) &&
+ file == sub_data->master_file) {
+ up_read(&sub_data->sem);
+ pmem_revoke(file, sub_data);
+ } else
+ up_read(&sub_data->sem);
+ }
+ }
+ list_del(&data->list);
+ mutex_unlock(&pmem[id].data_list_lock);
+
+
+ down_write(&data->sem);
+
+ /* if its not a conencted file and it has an allocation, free it */
+ if (!(PMEM_FLAGS_CONNECTED & data->flags) && has_allocation(file)) {
+ down_write(&pmem[id].bitmap_sem);
+ ret = pmem_free(id, data->index);
+ up_write(&pmem[id].bitmap_sem);
+ }
+
+ /* if this file is a submap (mapped, connected file), downref the
+ * task struct */
+ if (PMEM_FLAGS_SUBMAP & data->flags)
+ if (data->task) {
+ put_task_struct(data->task);
+ data->task = NULL;
+ }
+
+ file->private_data = NULL;
+
+ list_for_each_safe(elt, elt2, &data->region_list) {
+ region_node = list_entry(elt, struct pmem_region_node, list);
+ list_del(elt);
+ kfree(region_node);
+ }
+ BUG_ON(!list_empty(&data->region_list));
+
+ up_write(&data->sem);
+ kfree(data);
+ if (pmem[id].release)
+ ret = pmem[id].release(inode, file);
+
+ return ret;
+}
+
+static int pmem_open(struct inode *inode, struct file *file)
+{
+ struct pmem_data *data;
+ int id = get_id(file);
+ int ret = 0;
+
+ DLOG("current %u file %p(%d)\n", current->pid, file, file_count(file));
+ /* setup file->private_data to indicate its unmapped */
+ /* you can only open a pmem device one time */
+ if (file->private_data != NULL)
+ return -1;
+ data = kmalloc(sizeof(struct pmem_data), GFP_KERNEL);
+ if (!data) {
+ printk("pmem: unable to allocate memory for pmem metadata.");
+ return -1;
+ }
+ data->flags = 0;
+ data->index = -1;
+ data->task = NULL;
+ data->vma = NULL;
+ data->pid = 0;
+ data->master_file = NULL;
+#if PMEM_DEBUG
+ data->ref = 0;
+#endif
+ INIT_LIST_HEAD(&data->region_list);
+ init_rwsem(&data->sem);
+
+ file->private_data = data;
+ INIT_LIST_HEAD(&data->list);
+
+ mutex_lock(&pmem[id].data_list_lock);
+ list_add(&data->list, &pmem[id].data_list);
+ mutex_unlock(&pmem[id].data_list_lock);
+ return ret;
+}
+
+static unsigned long pmem_order(unsigned long len)
+{
+ int i;
+
+ len = (len + PMEM_MIN_ALLOC - 1)/PMEM_MIN_ALLOC;
+ len--;
+ for (i = 0; i < sizeof(len)*8; i++)
+ if (len >> i == 0)
+ break;
+ return i;
+}
+
+static int pmem_allocate(int id, unsigned long len)
+{
+ /* caller should hold the write lock on pmem_sem! */
+ /* return the corresponding pdata[] entry */
+ int curr = 0;
+ int end = pmem[id].num_entries;
+ int best_fit = -1;
+ unsigned long order = pmem_order(len);
+
+ if (pmem[id].no_allocator) {
+ DLOG("no allocator");
+ if ((len > pmem[id].size) || pmem[id].allocated)
+ return -1;
+ pmem[id].allocated = 1;
+ return len;
+ }
+
+ if (order > PMEM_MAX_ORDER)
+ return -1;
+ DLOG("order %lx\n", order);
+
+ /* look through the bitmap:
+ * if you find a free slot of the correct order use it
+ * otherwise, use the best fit (smallest with size > order) slot
+ */
+ while (curr < end) {
+ if (PMEM_IS_FREE(id, curr)) {
+ if (PMEM_ORDER(id, curr) == (unsigned char)order) {
+ /* set the not free bit and clear others */
+ best_fit = curr;
+ break;
+ }
+ if (PMEM_ORDER(id, curr) > (unsigned char)order &&
+ (best_fit < 0 ||
+ PMEM_ORDER(id, curr) < PMEM_ORDER(id, best_fit)))
+ best_fit = curr;
+ }
+ curr = PMEM_NEXT_INDEX(id, curr);
+ }
+
+ /* if best_fit < 0, there are no suitable slots,
+ * return an error
+ */
+ if (best_fit < 0) {
+ printk("pmem: no space left to allocate!\n");
+ return -1;
+ }
+
+ /* now partition the best fit:
+ * split the slot into 2 buddies of order - 1
+ * repeat until the slot is of the correct order
+ */
+ while (PMEM_ORDER(id, best_fit) > (unsigned char)order) {
+ int buddy;
+ PMEM_ORDER(id, best_fit) -= 1;
+ buddy = PMEM_BUDDY_INDEX(id, best_fit);
+ PMEM_ORDER(id, buddy) = PMEM_ORDER(id, best_fit);
+ }
+ pmem[id].bitmap[best_fit].allocated = 1;
+ return best_fit;
+}
+
+static pgprot_t pmem_access_prot(struct file *file, pgprot_t vma_prot)
+{
+ int id = get_id(file);
+#ifdef pgprot_noncached
+ if (pmem[id].cached == 0 || file->f_flags & O_SYNC)
+ return pgprot_noncached(vma_prot);
+#endif
+#ifdef pgprot_ext_buffered
+ else if (pmem[id].buffered)
+ return pgprot_ext_buffered(vma_prot);
+#endif
+ return vma_prot;
+}
+
+static unsigned long pmem_start_addr(int id, struct pmem_data *data)
+{
+ if (pmem[id].no_allocator)
+ return PMEM_START_ADDR(id, 0);
+ else
+ return PMEM_START_ADDR(id, data->index);
+
+}
+
+static void *pmem_start_vaddr(int id, struct pmem_data *data)
+{
+ return pmem_start_addr(id, data) - pmem[id].base + pmem[id].vbase;
+}
+
+static unsigned long pmem_len(int id, struct pmem_data *data)
+{
+ if (pmem[id].no_allocator)
+ return data->index;
+ else
+ return PMEM_LEN(id, data->index);
+}
+
+static int pmem_map_garbage(int id, struct vm_area_struct *vma,
+ struct pmem_data *data, unsigned long offset,
+ unsigned long len)
+{
+ int i, garbage_pages = len >> PAGE_SHIFT;
+
+ vma->vm_flags |= VM_IO | VM_RESERVED | VM_PFNMAP | VM_SHARED | VM_WRITE;
+ for (i = 0; i < garbage_pages; i++) {
+ if (vm_insert_pfn(vma, vma->vm_start + offset + (i * PAGE_SIZE),
+ pmem[id].garbage_pfn))
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+static int pmem_unmap_pfn_range(int id, struct vm_area_struct *vma,
+ struct pmem_data *data, unsigned long offset,
+ unsigned long len)
+{
+ int garbage_pages;
+ DLOG("unmap offset %lx len %lx\n", offset, len);
+
+ BUG_ON(!PMEM_IS_PAGE_ALIGNED(len));
+
+ garbage_pages = len >> PAGE_SHIFT;
+ zap_page_range(vma, vma->vm_start + offset, len, NULL);
+ pmem_map_garbage(id, vma, data, offset, len);
+ return 0;
+}
+
+static int pmem_map_pfn_range(int id, struct vm_area_struct *vma,
+ struct pmem_data *data, unsigned long offset,
+ unsigned long len)
+{
+ DLOG("map offset %lx len %lx\n", offset, len);
+ BUG_ON(!PMEM_IS_PAGE_ALIGNED(vma->vm_start));
+ BUG_ON(!PMEM_IS_PAGE_ALIGNED(vma->vm_end));
+ BUG_ON(!PMEM_IS_PAGE_ALIGNED(len));
+ BUG_ON(!PMEM_IS_PAGE_ALIGNED(offset));
+
+ if (io_remap_pfn_range(vma, vma->vm_start + offset,
+ (pmem_start_addr(id, data) + offset) >> PAGE_SHIFT,
+ len, vma->vm_page_prot)) {
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+static int pmem_remap_pfn_range(int id, struct vm_area_struct *vma,
+ struct pmem_data *data, unsigned long offset,
+ unsigned long len)
+{
+ /* hold the mm semp for the vma you are modifying when you call this */
+ BUG_ON(!vma);
+ zap_page_range(vma, vma->vm_start + offset, len, NULL);
+ return pmem_map_pfn_range(id, vma, data, offset, len);
+}
+
+static void pmem_vma_open(struct vm_area_struct *vma)
+{
+ struct file *file = vma->vm_file;
+ struct pmem_data *data = file->private_data;
+ int id = get_id(file);
+ /* this should never be called as we don't support copying pmem
+ * ranges via fork */
+ BUG_ON(!has_allocation(file));
+ down_write(&data->sem);
+ /* remap the garbage pages, forkers don't get access to the data */
+ pmem_unmap_pfn_range(id, vma, data, 0, vma->vm_start - vma->vm_end);
+ up_write(&data->sem);
+}
+
+static void pmem_vma_close(struct vm_area_struct *vma)
+{
+ struct file *file = vma->vm_file;
+ struct pmem_data *data = file->private_data;
+
+ DLOG("current %u ppid %u file %p count %d\n", current->pid,
+ current->parent->pid, file, file_count(file));
+ if (unlikely(!is_pmem_file(file) || !has_allocation(file))) {
+ printk(KERN_WARNING "pmem: something is very wrong, you are "
+ "closing a vm backing an allocation that doesn't "
+ "exist!\n");
+ return;
+ }
+ down_write(&data->sem);
+ if (data->vma == vma) {
+ data->vma = NULL;
+ if ((data->flags & PMEM_FLAGS_CONNECTED) &&
+ (data->flags & PMEM_FLAGS_SUBMAP))
+ data->flags |= PMEM_FLAGS_UNSUBMAP;
+ }
+ /* the kernel is going to free this vma now anyway */
+ up_write(&data->sem);
+}
+
+static struct vm_operations_struct vm_ops = {
+ .open = pmem_vma_open,
+ .close = pmem_vma_close,
+};
+
+static int pmem_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct pmem_data *data;
+ int index;
+ unsigned long vma_size = vma->vm_end - vma->vm_start;
+ int ret = 0, id = get_id(file);
+
+ if (vma->vm_pgoff || !PMEM_IS_PAGE_ALIGNED(vma_size)) {
+#if PMEM_DEBUG
+ printk(KERN_ERR "pmem: mmaps must be at offset zero, aligned"
+ " and a multiple of pages_size.\n");
+#endif
+ return -EINVAL;
+ }
+
+ data = (struct pmem_data *)file->private_data;
+ down_write(&data->sem);
+ /* check this file isn't already mmaped, for submaps check this file
+ * has never been mmaped */
+ if ((data->flags & PMEM_FLAGS_SUBMAP) ||
+ (data->flags & PMEM_FLAGS_UNSUBMAP)) {
+#if PMEM_DEBUG
+ printk(KERN_ERR "pmem: you can only mmap a pmem file once, "
+ "this file is already mmaped. %x\n", data->flags);
+#endif
+ ret = -EINVAL;
+ goto error;
+ }
+ /* if file->private_data == unalloced, alloc*/
+ if (data && data->index == -1) {
+ down_write(&pmem[id].bitmap_sem);
+ index = pmem_allocate(id, vma->vm_end - vma->vm_start);
+ up_write(&pmem[id].bitmap_sem);
+ data->index = index;
+ }
+ /* either no space was available or an error occured */
+ if (!has_allocation(file)) {
+ ret = -EINVAL;
+ printk("pmem: could not find allocation for map.\n");
+ goto error;
+ }
+
+ if (pmem_len(id, data) < vma_size) {
+#if PMEM_DEBUG
+ printk(KERN_WARNING "pmem: mmap size [%lu] does not match"
+ "size of backing region [%lu].\n", vma_size,
+ pmem_len(id, data));
+#endif
+ ret = -EINVAL;
+ goto error;
+ }
+
+ vma->vm_pgoff = pmem_start_addr(id, data) >> PAGE_SHIFT;
+ vma->vm_page_prot = pmem_access_prot(file, vma->vm_page_prot);
+
+ if (data->flags & PMEM_FLAGS_CONNECTED) {
+ struct pmem_region_node *region_node;
+ struct list_head *elt;
+ if (pmem_map_garbage(id, vma, data, 0, vma_size)) {
+ printk("pmem: mmap failed in kernel!\n");
+ ret = -EAGAIN;
+ goto error;
+ }
+ list_for_each(elt, &data->region_list) {
+ region_node = list_entry(elt, struct pmem_region_node,
+ list);
+ DLOG("remapping file: %p %lx %lx\n", file,
+ region_node->region.offset,
+ region_node->region.len);
+ if (pmem_remap_pfn_range(id, vma, data,
+ region_node->region.offset,
+ region_node->region.len)) {
+ ret = -EAGAIN;
+ goto error;
+ }
+ }
+ data->flags |= PMEM_FLAGS_SUBMAP;
+ get_task_struct(current->group_leader);
+ data->task = current->group_leader;
+ data->vma = vma;
+#if PMEM_DEBUG
+ data->pid = current->pid;
+#endif
+ DLOG("submmapped file %p vma %p pid %u\n", file, vma,
+ current->pid);
+ } else {
+ if (pmem_map_pfn_range(id, vma, data, 0, vma_size)) {
+ printk(KERN_INFO "pmem: mmap failed in kernel!\n");
+ ret = -EAGAIN;
+ goto error;
+ }
+ data->flags |= PMEM_FLAGS_MASTERMAP;
+ data->pid = current->pid;
+ }
+ vma->vm_ops = &vm_ops;
+error:
+ up_write(&data->sem);
+ return ret;
+}
+
+/* the following are the api for accessing pmem regions by other drivers
+ * from inside the kernel */
+int get_pmem_user_addr(struct file *file, unsigned long *start,
+ unsigned long *len)
+{
+ struct pmem_data *data;
+ if (!is_pmem_file(file) || !has_allocation(file)) {
+#if PMEM_DEBUG
+ printk(KERN_INFO "pmem: requested pmem data from invalid"
+ "file.\n");
+#endif
+ return -1;
+ }
+ data = (struct pmem_data *)file->private_data;
+ down_read(&data->sem);
+ if (data->vma) {
+ *start = data->vma->vm_start;
+ *len = data->vma->vm_end - data->vma->vm_start;
+ } else {
+ *start = 0;
+ *len = 0;
+ }
+ up_read(&data->sem);
+ return 0;
+}
+
+int get_pmem_addr(struct file *file, unsigned long *start,
+ unsigned long *vstart, unsigned long *len)
+{
+ struct pmem_data *data;
+ int id;
+
+ if (!is_pmem_file(file) || !has_allocation(file)) {
+ return -1;
+ }
+
+ data = (struct pmem_data *)file->private_data;
+ if (data->index == -1) {
+#if PMEM_DEBUG
+ printk(KERN_INFO "pmem: requested pmem data from file with no "
+ "allocation.\n");
+ return -1;
+#endif
+ }
+ id = get_id(file);
+
+ down_read(&data->sem);
+ *start = pmem_start_addr(id, data);
+ *len = pmem_len(id, data);
+ *vstart = (unsigned long)pmem_start_vaddr(id, data);
+ up_read(&data->sem);
+#if PMEM_DEBUG
+ down_write(&data->sem);
+ data->ref++;
+ up_write(&data->sem);
+#endif
+ return 0;
+}
+
+int get_pmem_file(int fd, unsigned long *start, unsigned long *vstart,
+ unsigned long *len, struct file **filp)
+{
+ struct file *file;
+
+ file = fget(fd);
+ if (unlikely(file == NULL)) {
+ printk(KERN_INFO "pmem: requested data from file descriptor "
+ "that doesn't exist.");
+ return -1;
+ }
+
+ if (get_pmem_addr(file, start, vstart, len))
+ goto end;
+
+ if (filp)
+ *filp = file;
+ return 0;
+end:
+ fput(file);
+ return -1;
+}
+
+void put_pmem_file(struct file *file)
+{
+ struct pmem_data *data;
+ int id;
+
+ if (!is_pmem_file(file))
+ return;
+ id = get_id(file);
+ data = (struct pmem_data *)file->private_data;
+#if PMEM_DEBUG
+ down_write(&data->sem);
+ if (data->ref == 0) {
+ printk("pmem: pmem_put > pmem_get %s (pid %d)\n",
+ pmem[id].dev.name, data->pid);
+ BUG();
+ }
+ data->ref--;
+ up_write(&data->sem);
+#endif
+ fput(file);
+}
+
+void flush_pmem_file(struct file *file, unsigned long offset, unsigned long len)
+{
+ struct pmem_data *data;
+ int id;
+ void *vaddr;
+ struct pmem_region_node *region_node;
+ struct list_head *elt;
+ void *flush_start, *flush_end;
+
+ if (!is_pmem_file(file) || !has_allocation(file)) {
+ return;
+ }
+
+ id = get_id(file);
+ data = (struct pmem_data *)file->private_data;
+ if (!pmem[id].cached || file->f_flags & O_SYNC)
+ return;
+
+ down_read(&data->sem);
+ vaddr = pmem_start_vaddr(id, data);
+ /* if this isn't a submmapped file, flush the whole thing */
+ if (unlikely(!(data->flags & PMEM_FLAGS_CONNECTED))) {
+ dmac_flush_range(vaddr, vaddr + pmem_len(id, data));
+ goto end;
+ }
+ /* otherwise, flush the region of the file we are drawing */
+ list_for_each(elt, &data->region_list) {
+ region_node = list_entry(elt, struct pmem_region_node, list);
+ if ((offset >= region_node->region.offset) &&
+ ((offset + len) <= (region_node->region.offset +
+ region_node->region.len))) {
+ flush_start = vaddr + region_node->region.offset;
+ flush_end = flush_start + region_node->region.len;
+ dmac_flush_range(flush_start, flush_end);
+ break;
+ }
+ }
+end:
+ up_read(&data->sem);
+}
+
+static int pmem_connect(unsigned long connect, struct file *file)
+{
+ struct pmem_data *data = (struct pmem_data *)file->private_data;
+ struct pmem_data *src_data;
+ struct file *src_file;
+ int ret = 0, put_needed;
+
+ down_write(&data->sem);
+ /* retrieve the src file and check it is a pmem file with an alloc */
+ src_file = fget_light(connect, &put_needed);
+ DLOG("connect %p to %p\n", file, src_file);
+ if (!src_file) {
+ printk("pmem: src file not found!\n");
+ ret = -EINVAL;
+ goto err_no_file;
+ }
+ if (unlikely(!is_pmem_file(src_file) || !has_allocation(src_file))) {
+ printk(KERN_INFO "pmem: src file is not a pmem file or has no "
+ "alloc!\n");
+ ret = -EINVAL;
+ goto err_bad_file;
+ }
+ src_data = (struct pmem_data *)src_file->private_data;
+
+ if (has_allocation(file) && (data->index != src_data->index)) {
+ printk("pmem: file is already mapped but doesn't match this"
+ " src_file!\n");
+ ret = -EINVAL;
+ goto err_bad_file;
+ }
+ data->index = src_data->index;
+ data->flags |= PMEM_FLAGS_CONNECTED;
+ data->master_fd = connect;
+ data->master_file = src_file;
+
+err_bad_file:
+ fput_light(src_file, put_needed);
+err_no_file:
+ up_write(&data->sem);
+ return ret;
+}
+
+static void pmem_unlock_data_and_mm(struct pmem_data *data,
+ struct mm_struct *mm)
+{
+ up_write(&data->sem);
+ if (mm != NULL) {
+ up_write(&mm->mmap_sem);
+ mmput(mm);
+ }
+}
+
+static int pmem_lock_data_and_mm(struct file *file, struct pmem_data *data,
+ struct mm_struct **locked_mm)
+{
+ int ret = 0;
+ struct mm_struct *mm = NULL;
+ *locked_mm = NULL;
+lock_mm:
+ down_read(&data->sem);
+ if (PMEM_IS_SUBMAP(data)) {
+ mm = get_task_mm(data->task);
+ if (!mm) {
+#if PMEM_DEBUG
+ printk("pmem: can't remap task is gone!\n");
+#endif
+ up_read(&data->sem);
+ return -1;
+ }
+ }
+ up_read(&data->sem);
+
+ if (mm)
+ down_write(&mm->mmap_sem);
+
+ down_write(&data->sem);
+ /* check that the file didn't get mmaped before we could take the
+ * data sem, this should be safe b/c you can only submap each file
+ * once */
+ if (PMEM_IS_SUBMAP(data) && !mm) {
+ pmem_unlock_data_and_mm(data, mm);
+ up_write(&data->sem);
+ goto lock_mm;
+ }
+ /* now check that vma.mm is still there, it could have been
+ * deleted by vma_close before we could get the data->sem */
+ if ((data->flags & PMEM_FLAGS_UNSUBMAP) && (mm != NULL)) {
+ /* might as well release this */
+ if (data->flags & PMEM_FLAGS_SUBMAP) {
+ put_task_struct(data->task);
+ data->task = NULL;
+ /* lower the submap flag to show the mm is gone */
+ data->flags &= ~(PMEM_FLAGS_SUBMAP);
+ }
+ pmem_unlock_data_and_mm(data, mm);
+ return -1;
+ }
+ *locked_mm = mm;
+ return ret;
+}
+
+int pmem_remap(struct pmem_region *region, struct file *file,
+ unsigned operation)
+{
+ int ret;
+ struct pmem_region_node *region_node;
+ struct mm_struct *mm = NULL;
+ struct list_head *elt, *elt2;
+ int id = get_id(file);
+ struct pmem_data *data = (struct pmem_data *)file->private_data;
+
+ /* pmem region must be aligned on a page boundry */
+ if (unlikely(!PMEM_IS_PAGE_ALIGNED(region->offset) ||
+ !PMEM_IS_PAGE_ALIGNED(region->len))) {
+#if PMEM_DEBUG
+ printk("pmem: request for unaligned pmem suballocation "
+ "%lx %lx\n", region->offset, region->len);
+#endif
+ return -EINVAL;
+ }
+
+ /* if userspace requests a region of len 0, there's nothing to do */
+ if (region->len == 0)
+ return 0;
+
+ /* lock the mm and data */
+ ret = pmem_lock_data_and_mm(file, data, &mm);
+ if (ret)
+ return 0;
+
+ /* only the owner of the master file can remap the client fds
+ * that back in it */
+ if (!is_master_owner(file)) {
+#if PMEM_DEBUG
+ printk("pmem: remap requested from non-master process\n");
+#endif
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* check that the requested range is within the src allocation */
+ if (unlikely((region->offset > pmem_len(id, data)) ||
+ (region->len > pmem_len(id, data)) ||
+ (region->offset + region->len > pmem_len(id, data)))) {
+#if PMEM_DEBUG
+ printk(KERN_INFO "pmem: suballoc doesn't fit in src_file!\n");
+#endif
+ ret = -EINVAL;
+ goto err;
+ }
+
+ if (operation == PMEM_MAP) {
+ region_node = kmalloc(sizeof(struct pmem_region_node),
+ GFP_KERNEL);
+ if (!region_node) {
+ ret = -ENOMEM;
+#if PMEM_DEBUG
+ printk(KERN_INFO "No space to allocate metadata!");
+#endif
+ goto err;
+ }
+ region_node->region = *region;
+ list_add(&region_node->list, &data->region_list);
+ } else if (operation == PMEM_UNMAP) {
+ int found = 0;
+ list_for_each_safe(elt, elt2, &data->region_list) {
+ region_node = list_entry(elt, struct pmem_region_node,
+ list);
+ if (region->len == 0 ||
+ (region_node->region.offset == region->offset &&
+ region_node->region.len == region->len)) {
+ list_del(elt);
+ kfree(region_node);
+ found = 1;
+ }
+ }
+ if (!found) {
+#if PMEM_DEBUG
+ printk("pmem: Unmap region does not map any mapped "
+ "region!");
+#endif
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ if (data->vma && PMEM_IS_SUBMAP(data)) {
+ if (operation == PMEM_MAP)
+ ret = pmem_remap_pfn_range(id, data->vma, data,
+ region->offset, region->len);
+ else if (operation == PMEM_UNMAP)
+ ret = pmem_unmap_pfn_range(id, data->vma, data,
+ region->offset, region->len);
+ }
+
+err:
+ pmem_unlock_data_and_mm(data, mm);
+ return ret;
+}
+
+static void pmem_revoke(struct file *file, struct pmem_data *data)
+{
+ struct pmem_region_node *region_node;
+ struct list_head *elt, *elt2;
+ struct mm_struct *mm = NULL;
+ int id = get_id(file);
+ int ret = 0;
+
+ data->master_file = NULL;
+ ret = pmem_lock_data_and_mm(file, data, &mm);
+ /* if lock_data_and_mm fails either the task that mapped the fd, or
+ * the vma that mapped it have already gone away, nothing more
+ * needs to be done */
+ if (ret)
+ return;
+ /* unmap everything */
+ /* delete the regions and region list nothing is mapped any more */
+ if (data->vma)
+ list_for_each_safe(elt, elt2, &data->region_list) {
+ region_node = list_entry(elt, struct pmem_region_node,
+ list);
+ pmem_unmap_pfn_range(id, data->vma, data,
+ region_node->region.offset,
+ region_node->region.len);
+ list_del(elt);
+ kfree(region_node);
+ }
+ /* delete the master file */
+ pmem_unlock_data_and_mm(data, mm);
+}
+
+static void pmem_get_size(struct pmem_region *region, struct file *file)
+{
+ struct pmem_data *data = (struct pmem_data *)file->private_data;
+ int id = get_id(file);
+
+ if (!has_allocation(file)) {
+ region->offset = 0;
+ region->len = 0;
+ return;
+ } else {
+ region->offset = pmem_start_addr(id, data);
+ region->len = pmem_len(id, data);
+ }
+ DLOG("offset %lx len %lx\n", region->offset, region->len);
+}
+
+
+static long pmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct pmem_data *data;
+ int id = get_id(file);
+
+ switch (cmd) {
+ case PMEM_GET_PHYS:
+ {
+ struct pmem_region region;
+ DLOG("get_phys\n");
+ if (!has_allocation(file)) {
+ region.offset = 0;
+ region.len = 0;
+ } else {
+ data = (struct pmem_data *)file->private_data;
+ region.offset = pmem_start_addr(id, data);
+ region.len = pmem_len(id, data);
+ }
+ printk(KERN_INFO "pmem: request for physical address of pmem region "
+ "from process %d.\n", current->pid);
+ if (copy_to_user((void __user *)arg, &region,
+ sizeof(struct pmem_region)))
+ return -EFAULT;
+ break;
+ }
+ case PMEM_MAP:
+ {
+ struct pmem_region region;
+ if (copy_from_user(&region, (void __user *)arg,
+ sizeof(struct pmem_region)))
+ return -EFAULT;
+ data = (struct pmem_data *)file->private_data;
+ return pmem_remap(&region, file, PMEM_MAP);
+ }
+ break;
+ case PMEM_UNMAP:
+ {
+ struct pmem_region region;
+ if (copy_from_user(&region, (void __user *)arg,
+ sizeof(struct pmem_region)))
+ return -EFAULT;
+ data = (struct pmem_data *)file->private_data;
+ return pmem_remap(&region, file, PMEM_UNMAP);
+ break;
+ }
+ case PMEM_GET_SIZE:
+ {
+ struct pmem_region region;
+ DLOG("get_size\n");
+ pmem_get_size(&region, file);
+ if (copy_to_user((void __user *)arg, &region,
+ sizeof(struct pmem_region)))
+ return -EFAULT;
+ break;
+ }
+ case PMEM_GET_TOTAL_SIZE:
+ {
+ struct pmem_region region;
+ DLOG("get total size\n");
+ region.offset = 0;
+ get_id(file);
+ region.len = pmem[id].size;
+ if (copy_to_user((void __user *)arg, &region,
+ sizeof(struct pmem_region)))
+ return -EFAULT;
+ break;
+ }
+ case PMEM_ALLOCATE:
+ {
+ if (has_allocation(file))
+ return -EINVAL;
+ data = (struct pmem_data *)file->private_data;
+ data->index = pmem_allocate(id, arg);
+ break;
+ }
+ case PMEM_CONNECT:
+ DLOG("connect\n");
+ return pmem_connect(arg, file);
+ break;
+ case PMEM_CACHE_FLUSH:
+ {
+ struct pmem_region region;
+ DLOG("flush\n");
+ if (copy_from_user(&region, (void __user *)arg,
+ sizeof(struct pmem_region)))
+ return -EFAULT;
+ flush_pmem_file(file, region.offset, region.len);
+ break;
+ }
+ default:
+ if (pmem[id].ioctl)
+ return pmem[id].ioctl(file, cmd, arg);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+#if PMEM_DEBUG
+static ssize_t debug_open(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+ return 0;
+}
+
+static ssize_t debug_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct list_head *elt, *elt2;
+ struct pmem_data *data;
+ struct pmem_region_node *region_node;
+ int id = (int)file->private_data;
+ const int debug_bufmax = 4096;
+ static char buffer[4096];
+ int n = 0;
+
+ DLOG("debug open\n");
+ n = scnprintf(buffer, debug_bufmax,
+ "pid #: mapped regions (offset, len) (offset,len)...\n");
+
+ mutex_lock(&pmem[id].data_list_lock);
+ list_for_each(elt, &pmem[id].data_list) {
+ data = list_entry(elt, struct pmem_data, list);
+ down_read(&data->sem);
+ n += scnprintf(buffer + n, debug_bufmax - n, "pid %u:",
+ data->pid);
+ list_for_each(elt2, &data->region_list) {
+ region_node = list_entry(elt2, struct pmem_region_node,
+ list);
+ n += scnprintf(buffer + n, debug_bufmax - n,
+ "(%lx,%lx) ",
+ region_node->region.offset,
+ region_node->region.len);
+ }
+ n += scnprintf(buffer + n, debug_bufmax - n, "\n");
+ up_read(&data->sem);
+ }
+ mutex_unlock(&pmem[id].data_list_lock);
+
+ n++;
+ buffer[n] = 0;
+ return simple_read_from_buffer(buf, count, ppos, buffer, n);
+}
+
+static struct file_operations debug_fops = {
+ .read = debug_read,
+ .open = debug_open,
+};
+#endif
+
+#if 0
+static struct miscdevice pmem_dev = {
+ .name = "pmem",
+ .fops = &pmem_fops,
+};
+#endif
+
+int pmem_setup(struct android_pmem_platform_data *pdata,
+ long (*ioctl)(struct file *, unsigned int, unsigned long),
+ int (*release)(struct inode *, struct file *))
+{
+ int err = 0;
+ int i, index = 0;
+ int id = id_count;
+ id_count++;
+
+ pmem[id].no_allocator = pdata->no_allocator;
+ pmem[id].cached = pdata->cached;
+ pmem[id].buffered = pdata->buffered;
+ pmem[id].base = pdata->start;
+ pmem[id].size = pdata->size;
+ pmem[id].ioctl = ioctl;
+ pmem[id].release = release;
+ init_rwsem(&pmem[id].bitmap_sem);
+ mutex_init(&pmem[id].data_list_lock);
+ INIT_LIST_HEAD(&pmem[id].data_list);
+ pmem[id].dev.name = pdata->name;
+ pmem[id].dev.minor = id;
+ pmem[id].dev.fops = &pmem_fops;
+ printk(KERN_INFO "%s: %d init\n", pdata->name, pdata->cached);
+
+ err = misc_register(&pmem[id].dev);
+ if (err) {
+ printk(KERN_ALERT "Unable to register pmem driver!\n");
+ goto err_cant_register_device;
+ }
+ pmem[id].num_entries = pmem[id].size / PMEM_MIN_ALLOC;
+
+ pmem[id].bitmap = kmalloc(pmem[id].num_entries *
+ sizeof(struct pmem_bits), GFP_KERNEL);
+ if (!pmem[id].bitmap)
+ goto err_no_mem_for_metadata;
+
+ memset(pmem[id].bitmap, 0, sizeof(struct pmem_bits) *
+ pmem[id].num_entries);
+
+ for (i = sizeof(pmem[id].num_entries) * 8 - 1; i >= 0; i--) {
+ if ((pmem[id].num_entries) & 1<<i) {
+ PMEM_ORDER(id, index) = i;
+ index = PMEM_NEXT_INDEX(id, index);
+ }
+ }
+
+ if (pmem[id].cached)
+ pmem[id].vbase = ioremap_cached(pmem[id].base,
+ pmem[id].size);
+#ifdef ioremap_ext_buffered
+ else if (pmem[id].buffered)
+ pmem[id].vbase = ioremap_ext_buffered(pmem[id].base,
+ pmem[id].size);
+#endif
+ else
+ pmem[id].vbase = ioremap(pmem[id].base, pmem[id].size);
+
+ if (pmem[id].vbase == 0)
+ goto error_cant_remap;
+
+ pmem[id].garbage_pfn = page_to_pfn(alloc_page(GFP_KERNEL));
+ if (pmem[id].no_allocator)
+ pmem[id].allocated = 0;
+
+#if PMEM_DEBUG
+ debugfs_create_file(pdata->name, S_IFREG | S_IRUGO, NULL, (void *)id,
+ &debug_fops);
+#endif
+ return 0;
+error_cant_remap:
+ kfree(pmem[id].bitmap);
+err_no_mem_for_metadata:
+ misc_deregister(&pmem[id].dev);
+err_cant_register_device:
+ return -1;
+}
+
+static int pmem_probe(struct platform_device *pdev)
+{
+ struct android_pmem_platform_data *pdata;
+
+ if (!pdev || !pdev->dev.platform_data) {
+ printk(KERN_ALERT "Unable to probe pmem!\n");
+ return -1;
+ }
+ pdata = pdev->dev.platform_data;
+ return pmem_setup(pdata, NULL, NULL);
+}
+
+
+static int pmem_remove(struct platform_device *pdev)
+{
+ int id = pdev->id;
+ __free_page(pfn_to_page(pmem[id].garbage_pfn));
+ misc_deregister(&pmem[id].dev);
+ return 0;
+}
+
+static struct platform_driver pmem_driver = {
+ .probe = pmem_probe,
+ .remove = pmem_remove,
+ .driver = { .name = "android_pmem" }
+};
+
+
+static int __init pmem_init(void)
+{
+ return platform_driver_register(&pmem_driver);
+}
+
+static void __exit pmem_exit(void)
+{
+ platform_driver_unregister(&pmem_driver);
+}
+
+module_init(pmem_init);
+module_exit(pmem_exit);
+
diff --git a/drivers/misc/uid_stat.c b/drivers/misc/uid_stat.c
new file mode 100644
index 000000000000..2141124a6c12
--- /dev/null
+++ b/drivers/misc/uid_stat.c
@@ -0,0 +1,156 @@
+/* drivers/misc/uid_stat.c
+ *
+ * Copyright (C) 2008 - 2009 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 <asm/atomic.h>
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/stat.h>
+#include <linux/uid_stat.h>
+#include <net/activity_stats.h>
+
+static DEFINE_SPINLOCK(uid_lock);
+static LIST_HEAD(uid_list);
+static struct proc_dir_entry *parent;
+
+struct uid_stat {
+ struct list_head link;
+ uid_t uid;
+ atomic_t tcp_rcv;
+ atomic_t tcp_snd;
+};
+
+static struct uid_stat *find_uid_stat(uid_t uid) {
+ unsigned long flags;
+ struct uid_stat *entry;
+
+ spin_lock_irqsave(&uid_lock, flags);
+ list_for_each_entry(entry, &uid_list, link) {
+ if (entry->uid == uid) {
+ spin_unlock_irqrestore(&uid_lock, flags);
+ return entry;
+ }
+ }
+ spin_unlock_irqrestore(&uid_lock, flags);
+ return NULL;
+}
+
+static int tcp_snd_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int len;
+ unsigned int bytes;
+ char *p = page;
+ struct uid_stat *uid_entry = (struct uid_stat *) data;
+ if (!data)
+ return 0;
+
+ bytes = (unsigned int) (atomic_read(&uid_entry->tcp_snd) + INT_MIN);
+ p += sprintf(p, "%u\n", bytes);
+ len = (p - page) - off;
+ *eof = (len <= count) ? 1 : 0;
+ *start = page + off;
+ return len;
+}
+
+static int tcp_rcv_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int len;
+ unsigned int bytes;
+ char *p = page;
+ struct uid_stat *uid_entry = (struct uid_stat *) data;
+ if (!data)
+ return 0;
+
+ bytes = (unsigned int) (atomic_read(&uid_entry->tcp_rcv) + INT_MIN);
+ p += sprintf(p, "%u\n", bytes);
+ len = (p - page) - off;
+ *eof = (len <= count) ? 1 : 0;
+ *start = page + off;
+ return len;
+}
+
+/* Create a new entry for tracking the specified uid. */
+static struct uid_stat *create_stat(uid_t uid) {
+ unsigned long flags;
+ char uid_s[32];
+ struct uid_stat *new_uid;
+ struct proc_dir_entry *entry;
+
+ /* Create the uid stat struct and append it to the list. */
+ if ((new_uid = kmalloc(sizeof(struct uid_stat), GFP_KERNEL)) == NULL)
+ return NULL;
+
+ new_uid->uid = uid;
+ /* Counters start at INT_MIN, so we can track 4GB of network traffic. */
+ atomic_set(&new_uid->tcp_rcv, INT_MIN);
+ atomic_set(&new_uid->tcp_snd, INT_MIN);
+
+ spin_lock_irqsave(&uid_lock, flags);
+ list_add_tail(&new_uid->link, &uid_list);
+ spin_unlock_irqrestore(&uid_lock, flags);
+
+ sprintf(uid_s, "%d", uid);
+ entry = proc_mkdir(uid_s, parent);
+
+ /* Keep reference to uid_stat so we know what uid to read stats from. */
+ create_proc_read_entry("tcp_snd", S_IRUGO, entry , tcp_snd_read_proc,
+ (void *) new_uid);
+
+ create_proc_read_entry("tcp_rcv", S_IRUGO, entry, tcp_rcv_read_proc,
+ (void *) new_uid);
+
+ return new_uid;
+}
+
+int uid_stat_tcp_snd(uid_t uid, int size) {
+ struct uid_stat *entry;
+ activity_stats_update();
+ if ((entry = find_uid_stat(uid)) == NULL &&
+ ((entry = create_stat(uid)) == NULL)) {
+ return -1;
+ }
+ atomic_add(size, &entry->tcp_snd);
+ return 0;
+}
+
+int uid_stat_tcp_rcv(uid_t uid, int size) {
+ struct uid_stat *entry;
+ activity_stats_update();
+ if ((entry = find_uid_stat(uid)) == NULL &&
+ ((entry = create_stat(uid)) == NULL)) {
+ return -1;
+ }
+ atomic_add(size, &entry->tcp_rcv);
+ return 0;
+}
+
+static int __init uid_stat_init(void)
+{
+ parent = proc_mkdir("uid_stat", NULL);
+ if (!parent) {
+ pr_err("uid_stat: failed to create proc entry\n");
+ return -1;
+ }
+ return 0;
+}
+
+__initcall(uid_stat_init);
diff --git a/drivers/misc/wl127x-rfkill.c b/drivers/misc/wl127x-rfkill.c
new file mode 100644
index 000000000000..f5b95152948b
--- /dev/null
+++ b/drivers/misc/wl127x-rfkill.c
@@ -0,0 +1,121 @@
+/*
+ * Bluetooth TI wl127x rfkill power control via GPIO
+ *
+ * Copyright (C) 2009 Motorola, Inc.
+ * Copyright (C) 2008 Texas Instruments
+ * Initial code: Pavan Savoy <pavan.savoy@gmail.com> (wl127x_power.c)
+ *
+ * This program 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 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.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/gpio.h>
+#include <linux/rfkill.h>
+#include <linux/platform_device.h>
+#include <linux/wl127x-rfkill.h>
+
+static int wl127x_rfkill_set_power(void *data, enum rfkill_state state)
+{
+ int nshutdown_gpio = (int) data;
+
+ switch (state) {
+ case RFKILL_STATE_UNBLOCKED:
+ gpio_set_value(nshutdown_gpio, 1);
+ break;
+ case RFKILL_STATE_SOFT_BLOCKED:
+ gpio_set_value(nshutdown_gpio, 0);
+ break;
+ default:
+ printk(KERN_ERR "invalid bluetooth rfkill state %d\n", state);
+ }
+ return 0;
+}
+
+static int wl127x_rfkill_probe(struct platform_device *pdev)
+{
+ int rc = 0;
+ struct wl127x_rfkill_platform_data *pdata = pdev->dev.platform_data;
+ enum rfkill_state default_state = RFKILL_STATE_SOFT_BLOCKED; /* off */
+
+ rc = gpio_request(pdata->nshutdown_gpio, "wl127x_nshutdown_gpio");
+ if (unlikely(rc))
+ return rc;
+
+ rc = gpio_direction_output(pdata->nshutdown_gpio, 0);
+ if (unlikely(rc))
+ return rc;
+
+ rfkill_set_default(RFKILL_TYPE_BLUETOOTH, default_state);
+ wl127x_rfkill_set_power(NULL, default_state);
+
+ pdata->rfkill = rfkill_allocate(&pdev->dev, RFKILL_TYPE_BLUETOOTH);
+ if (unlikely(!pdata->rfkill))
+ return -ENOMEM;
+
+ pdata->rfkill->name = "wl127x";
+ pdata->rfkill->state = default_state;
+ /* userspace cannot take exclusive control */
+ pdata->rfkill->user_claim_unsupported = 1;
+ pdata->rfkill->user_claim = 0;
+ pdata->rfkill->data = (void *) pdata->nshutdown_gpio;
+ pdata->rfkill->toggle_radio = wl127x_rfkill_set_power;
+
+ rc = rfkill_register(pdata->rfkill);
+
+ if (unlikely(rc))
+ rfkill_free(pdata->rfkill);
+
+ return 0;
+}
+
+static int wl127x_rfkill_remove(struct platform_device *pdev)
+{
+ struct wl127x_rfkill_platform_data *pdata = pdev->dev.platform_data;
+
+ rfkill_unregister(pdata->rfkill);
+ rfkill_free(pdata->rfkill);
+ gpio_free(pdata->nshutdown_gpio);
+
+ return 0;
+}
+
+static struct platform_driver wl127x_rfkill_platform_driver = {
+ .probe = wl127x_rfkill_probe,
+ .remove = wl127x_rfkill_remove,
+ .driver = {
+ .name = "wl127x-rfkill",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init wl127x_rfkill_init(void)
+{
+ return platform_driver_register(&wl127x_rfkill_platform_driver);
+}
+
+static void __exit wl127x_rfkill_exit(void)
+{
+ platform_driver_unregister(&wl127x_rfkill_platform_driver);
+}
+
+module_init(wl127x_rfkill_init);
+module_exit(wl127x_rfkill_exit);
+
+MODULE_ALIAS("platform:wl127x");
+MODULE_DESCRIPTION("wl127x-rfkill");
+MODULE_AUTHOR("Motorola");
+MODULE_LICENSE("GPL");