summaryrefslogtreecommitdiff
path: root/drivers/misc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc')
-rw-r--r--drivers/misc/Kconfig18
-rw-r--r--drivers/misc/Makefile4
-rw-r--r--drivers/misc/mxs-perfmon.c395
-rw-r--r--drivers/misc/mxs-persistent.c271
-rw-r--r--drivers/misc/pmem.c1340
-rw-r--r--drivers/misc/regs-perfmon.h189
-rw-r--r--drivers/misc/uid_stat.c153
7 files changed, 2370 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 26386a92f5aa..c13aa6fe558a 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -353,6 +353,24 @@ config VMWARE_BALLOON
To compile this driver as a module, choose M here: the
module will be called vmware_balloon.
+config MXS_PERSISTENT
+ tristate "MX23/MX28 persistent bit"
+ depends on ARCH_MXS
+ default y
+
+config MXS_PERFMON
+ tristate "i.MX Performance Monitor"
+ depends on ARCH_MX28 || ARCH_MX50
+ default y
+
+config ANDROID_PMEM
+ bool "Android pmem allocator"
+ default n
+
+config UID_STAT
+ bool "UID based statistics tracking exported to /proc/uid_stat"
+ default n
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 6ed06a19474a..281eb2e73e1f 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -28,6 +28,10 @@ obj-$(CONFIG_DS1682) += ds1682.o
obj-$(CONFIG_TI_DAC7512) += ti_dac7512.o
obj-$(CONFIG_C2PORT) += c2port/
obj-$(CONFIG_IWMC3200TOP) += iwmc3200top/
+obj-$(CONFIG_MXS_PERSISTENT) += mxs-persistent.o
+obj-$(CONFIG_MXS_PERFMON) += mxs-perfmon.o
obj-y += eeprom/
obj-y += cb710/
obj-$(CONFIG_VMWARE_BALLOON) += vmware_balloon.o
+obj-$(CONFIG_ANDROID_PMEM) += pmem.o
+obj-$(CONFIG_UID_STAT) += uid_stat.o
diff --git a/drivers/misc/mxs-perfmon.c b/drivers/misc/mxs-perfmon.c
new file mode 100644
index 000000000000..51228ec3bf89
--- /dev/null
+++ b/drivers/misc/mxs-perfmon.c
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. 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 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/sysdev.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include <linux/io.h>
+#include <mach/hardware.h>
+#include <asm/irq.h>
+#include <linux/fsl_devices.h>
+#include <mach/system.h>
+#include "regs-perfmon.h"
+
+#define MONITOR "Monitor"
+
+struct mxs_perfmon_cmd_config {
+ int field;
+ int val;
+ const char *cmd;
+};
+
+static struct mxs_perfmon_cmd_config
+common_perfmon_cmd_config[] = {
+ {.val = 1, .cmd = "start", .field = BM_PERFMON_CTRL_RUN },
+ {.val = 0, .cmd = "stop", .field = BM_PERFMON_CTRL_RUN },
+ {.val = 1, .cmd = "fetch", .field = BM_PERFMON_CTRL_SNAP },
+ {.val = 1, .cmd = "clear", .field = BM_PERFMON_CTRL_CLR },
+ {.val = 1, .cmd = "read", .field = BM_PERFMON_CTRL_READ_EN },
+ {.val = 0, .cmd = "write", .field = BM_PERFMON_CTRL_READ_EN }
+};
+
+static struct mxs_perfmon_bit_config
+common_perfmon_bit_config[] = {
+ {.reg = HW_PERFMON_CTRL, .name = MONITOR,
+ .field = ~0 },
+ {.reg = HW_PERFMON_CTRL, .name = "Trap-En",
+ .field = BM_PERFMON_CTRL_TRAP_ENABLE },
+ {.reg = HW_PERFMON_CTRL, .name = "Trap-In-Range",
+ .field = BM_PERFMON_CTRL_TRAP_IN_RANGE },
+ {.reg = HW_PERFMON_CTRL, .name = "Latency-En",
+ .field = BM_PERFMON_CTRL_LATENCY_ENABLE },
+ {.reg = HW_PERFMON_CTRL, .name = "Trap-IRQ",
+ .field = BM_PERFMON_CTRL_TRAP_IRQ },
+ {.reg = HW_PERFMON_CTRL, .name = "Latency-IRQ",
+ .field = BM_PERFMON_CTRL_LATENCY_IRQ },
+ {.reg = HW_PERFMON_TRAP_ADDR_LOW, .name = "Trap-Low",
+ .field = BM_PERFMON_TRAP_ADDR_LOW_ADDR },
+ {.reg = HW_PERFMON_TRAP_ADDR_HIGH, .name = "Trap-High",
+ .field = BM_PERFMON_TRAP_ADDR_HIGH_ADDR },
+ {.reg = HW_PERFMON_LAT_THRESHOLD, .name = "Latency-Threshold",
+ .field = BM_PERFMON_LAT_THRESHOLD_VALUE },
+ {.reg = HW_PERFMON_ACTIVE_CYCLE, .name = "Active-Cycle",
+ .field = BM_PERFMON_ACTIVE_CYCLE_COUNT },
+ {.reg = HW_PERFMON_TRANSFER_COUNT, .name = "Transfer-count",
+ .field = BM_PERFMON_TRANSFER_COUNT_VALUE },
+ {.reg = HW_PERFMON_TOTAL_LATENCY, .name = "Latency-count",
+ .field = BM_PERFMON_TOTAL_LATENCY_COUNT },
+ {.reg = HW_PERFMON_DATA_COUNT, .name = "Data-count",
+ .field = BM_PERFMON_DATA_COUNT_COUNT },
+ {.reg = HW_PERFMON_MAX_LATENCY, .name = "ABurst",
+ .field = BM_PERFMON_MAX_LATENCY_ABURST },
+ {.reg = HW_PERFMON_MAX_LATENCY, .name = "ALen",
+ .field = BM_PERFMON_MAX_LATENCY_ALEN },
+ {.reg = HW_PERFMON_MAX_LATENCY, .name = "ASize",
+ .field = BM_PERFMON_MAX_LATENCY_ASIZE },
+ {.reg = HW_PERFMON_MAX_LATENCY, .name = "TAGID",
+ .field = BM_PERFMON_MAX_LATENCY_TAGID },
+ {.reg = HW_PERFMON_MAX_LATENCY, .name = "Max-Count",
+ .field = BM_PERFMON_MAX_LATENCY_COUNT }
+};
+
+static struct mxs_platform_perfmon_data common_perfmon_data = {
+ .bit_config_tab = common_perfmon_bit_config,
+ .bit_config_cnt = ARRAY_SIZE(common_perfmon_bit_config),
+};
+
+struct mxs_perfmon_data {
+ struct device *dev;
+ struct mxs_platform_perfmon_data *pdata;
+ struct mxs_platform_perfmon_data *pdata_common;
+ int count;
+ struct attribute_group attr_group;
+ unsigned int base;
+ unsigned int initial;
+ /* attribute ** follow */
+ /* device_attribute follow */
+};
+
+#define pd_attribute_ptr(x) \
+ ((struct attribute **)((x) + 1))
+#define pd_device_attribute_ptr(x) \
+ ((struct device_attribute *)(pd_attribute_ptr(x) + (x)->count + 1))
+
+static inline u32 perfmon_reg_read(struct mxs_perfmon_data *pdata,
+ int reg)
+{
+ return __raw_readl(pdata->base + reg);
+}
+
+static inline void perfmon_reg_write(struct mxs_perfmon_data *pdata,
+ u32 val, int reg)
+{
+ __raw_writel(val, pdata->base + reg);
+}
+
+static int get_offset_form_field(int field)
+{
+ int offset = 0;
+
+ if (!field)
+ return offset;
+
+ while (!(field & 0x1)) {
+ field >>= 1;
+ offset++;
+ }
+
+ return offset;
+}
+
+static ssize_t
+perfmon_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mxs_perfmon_data *pd = platform_get_drvdata(pdev);
+ struct device_attribute *devattr = pd_device_attribute_ptr(pd);
+ struct mxs_perfmon_bit_config *pb;
+ int idx;
+ u32 val;
+ ssize_t result = 0;
+
+ idx = attr - devattr;
+ if ((unsigned int)idx >= pd->count)
+ return -EINVAL;
+
+ if (idx < pd->pdata->bit_config_cnt) {
+ pb = &pd->pdata->bit_config_tab[idx];
+ pb->reg = HW_PERFMON_MASTER_EN;
+ } else
+ pb = &pd->pdata_common->bit_config_tab \
+ [idx - pd->pdata->bit_config_cnt];
+
+ if (!pd->initial) {
+ mxs_reset_block((void *)pd->base, true);
+ pd->initial = true;
+ }
+
+ if (!memcmp(pb->name, MONITOR, sizeof(MONITOR))) {
+ /* cat monitor command, we return monitor status */
+ val = perfmon_reg_read(pd, pb->reg);
+
+ if (val & BV_PERFMON_CTRL_RUN__RUN)
+ result += sprintf(buf, "Run mode\r\n");
+ else
+ result += sprintf(buf, "Stop mode\r\n");
+
+ if (val & BM_PERFMON_CTRL_READ_EN)
+ result += \
+ sprintf(buf + result, "PM Read Activities\r\n");
+ else
+ result += \
+ sprintf(buf + result, "PM Write Activities\r\n");
+
+ return result;
+ }
+
+ /* read value and shift */
+ val = perfmon_reg_read(pd, pb->reg);
+ val &= pb->field;
+ val >>= get_offset_form_field(pb->field);
+
+ return sprintf(buf, "0x%x\n", val);
+}
+
+static ssize_t
+perfmon_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mxs_perfmon_data *pd = platform_get_drvdata(pdev);
+ struct device_attribute *devattr = pd_device_attribute_ptr(pd);
+ struct mxs_perfmon_bit_config *pb;
+ int idx, r;
+ unsigned long val, newval;
+
+ idx = attr - devattr;
+ if ((unsigned int)idx >= pd->count)
+ return -EINVAL;
+
+ if (!buf)
+ return -EINVAL;
+
+ if (idx < pd->pdata->bit_config_cnt) {
+ pb = &pd->pdata->bit_config_tab[idx];
+ pb->reg = HW_PERFMON_MASTER_EN;
+ } else
+ pb = &pd->pdata_common->bit_config_tab \
+ [idx - pd->pdata->bit_config_cnt];
+
+ if (!pd->initial) {
+ mxs_reset_block((void *)pd->base, true);
+ pd->initial = true;
+ }
+
+ if (!memcmp(pb->name, MONITOR, sizeof(MONITOR))) {
+ /* it's a cmd */
+ int scan, size;
+ const struct mxs_perfmon_cmd_config *pcfg;
+
+ size = ARRAY_SIZE(common_perfmon_cmd_config);
+ for (scan = 0; scan < size; scan++) {
+ pcfg = &common_perfmon_cmd_config[scan];
+ if (!memcmp(buf, pcfg->cmd, strlen(pcfg->cmd))) {
+ val = perfmon_reg_read(pd, HW_PERFMON_CTRL);
+ val &= ~pcfg->field;
+ val |= \
+ pcfg->val << get_offset_form_field(pcfg->field);
+ perfmon_reg_write(pd, val, HW_PERFMON_CTRL);
+
+ return count;
+ }
+ }
+ if (scan == ARRAY_SIZE(common_perfmon_cmd_config))
+ return -EINVAL;
+ }
+ /* get value to write */
+ if (buf && (count >= 2) && buf[0] == '0' && buf[1] == 'x')
+ r = strict_strtoul(buf, 16, &val);
+ else
+ r = strict_strtoul(buf, 10, &val);
+
+ if (r != 0)
+ return r;
+
+ /* verify it fits */
+ if ((unsigned int)val > (pb->field >> get_offset_form_field(pb->field)))
+ return -EINVAL;
+
+ newval = perfmon_reg_read(pd, pb->reg);
+ newval &= ~pb->field;
+ newval |= val << get_offset_form_field(pb->field);
+ perfmon_reg_write(pd, newval, pb->reg);
+
+ return count;
+}
+
+
+static int __devinit mxs_perfmon_probe(struct platform_device *pdev)
+{
+ struct mxs_perfmon_data *pd;
+ struct mxs_platform_perfmon_data *pdata;
+ struct mxs_platform_perfmon_data *pdata_common;
+ struct resource *res;
+ struct mxs_perfmon_bit_config *pb;
+ struct attribute **attr;
+ struct device_attribute *devattr;
+ int i, cnt, size;
+ int err;
+
+ pdata = pdev->dev.platform_data;
+ if (pdata == NULL)
+ return -ENODEV;
+
+ pdata_common = &common_perfmon_data;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL)
+ return -ENODEV;
+
+ cnt = pdata->bit_config_cnt + pdata_common->bit_config_cnt;
+ size = sizeof(*pd) +
+ (cnt + 1) * sizeof(struct atrribute *) +
+ cnt * sizeof(struct device_attribute);
+ pd = kzalloc(size, GFP_KERNEL);
+ if (pd == NULL)
+ return -ENOMEM;
+ pd->dev = &pdev->dev;
+ pd->pdata = pdata;
+ pd->pdata_common = pdata_common;
+ pd->base = (unsigned int)ioremap(res->start, res->end - res->start);
+ pd->initial = false;
+
+ platform_set_drvdata(pdev, pd);
+ pd->count = cnt;
+ attr = pd_attribute_ptr(pd);
+ devattr = pd_device_attribute_ptr(pd);
+
+ /* build the attributes structures */
+ pd->attr_group.attrs = attr;
+ pb = pdata->bit_config_tab;
+ for (i = 0; i < pdata->bit_config_cnt; i++) {
+ devattr[i].attr.name = pb[i].name;
+ devattr[i].attr.mode = S_IWUSR | S_IRUGO;
+ devattr[i].show = perfmon_show;
+ devattr[i].store = perfmon_store;
+ attr[i] = &devattr[i].attr;
+ }
+ pb = pdata_common->bit_config_tab;
+ for (i = 0; i < pdata_common->bit_config_cnt; i++) {
+ devattr[i + pdata->bit_config_cnt].attr.name = pb[i].name;
+ devattr[i + pdata->bit_config_cnt].attr.mode = \
+ S_IWUSR | S_IRUGO;
+ devattr[i + pdata->bit_config_cnt].show = perfmon_show;
+ devattr[i + pdata->bit_config_cnt].store = perfmon_store;
+ attr[i + pdata->bit_config_cnt] = \
+ &devattr[i + pdata->bit_config_cnt].attr;
+ }
+
+ err = sysfs_create_group(&pdev->dev.kobj, &pd->attr_group);
+ if (err != 0) {
+ platform_set_drvdata(pdev, NULL);
+ kfree(pd);
+ return err;
+ }
+
+ return 0;
+}
+
+static int __devexit mxs_perfmon_remove(struct platform_device *pdev)
+{
+ struct mxs_perfmon_data *pd;
+
+ pd = platform_get_drvdata(pdev);
+ sysfs_remove_group(&pdev->dev.kobj, &pd->attr_group);
+ platform_set_drvdata(pdev, NULL);
+ kfree(pd);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int
+mxs_perfmon_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+static int mxs_perfmon_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+#else
+#define mxs_perfmon_suspend NULL
+#define mxs_perfmon_resume NULL
+#endif
+
+static struct platform_driver mxs_perfmon_driver = {
+ .probe = mxs_perfmon_probe,
+ .remove = __exit_p(mxs_perfmon_remove),
+ .suspend = mxs_perfmon_suspend,
+ .resume = mxs_perfmon_resume,
+ .driver = {
+ .name = "mxs-perfmon",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mxs_perfmon_init(void)
+{
+ return platform_driver_register(&mxs_perfmon_driver);
+}
+
+static void __exit mxs_perfmon_exit(void)
+{
+ platform_driver_unregister(&mxs_perfmon_driver);
+}
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Performance Monitor user-access driver");
+MODULE_LICENSE("GPL");
+
+module_init(mxs_perfmon_init);
+module_exit(mxs_perfmon_exit);
diff --git a/drivers/misc/mxs-persistent.c b/drivers/misc/mxs-persistent.c
new file mode 100644
index 000000000000..6acb006489d0
--- /dev/null
+++ b/drivers/misc/mxs-persistent.c
@@ -0,0 +1,271 @@
+/*
+ * Freescale STMP378X Persistent bits manipulation driver
+ *
+ * Author: Pantelis Antoniou <pantelis@embeddedalley.com>
+ *
+ * Copyright 2008-2010 Freescale Semiconductor, Inc.
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/sysdev.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include <mach/hardware.h>
+#include <asm/irq.h>
+#include <mach/device.h>
+
+#include <mach/regs-rtc.h>
+
+struct mxs_persistent_data {
+ struct device *dev;
+ struct mxs_platform_persistent_data *pdata;
+ int count;
+ struct attribute_group attr_group;
+ unsigned int base;
+ /* attribute ** follow */
+ /* device_attribute follow */
+};
+
+#define pd_attribute_ptr(x) \
+ ((struct attribute **)((x) + 1))
+#define pd_device_attribute_ptr(x) \
+ ((struct device_attribute *)(pd_attribute_ptr(x) + (x)->count + 1))
+
+static inline u32 persistent_reg_read(struct mxs_persistent_data *pdata,
+ int reg)
+{
+ u32 msk;
+
+ /* wait for stable value */
+ msk = BF_RTC_STAT_STALE_REGS((0x1 << reg));
+ while (__raw_readl(pdata->base + HW_RTC_STAT) & msk)
+ cpu_relax();
+
+ return __raw_readl(pdata->base + 0x60 + (reg * 0x10));
+}
+
+static inline void persistent_reg_wait_settle(struct mxs_persistent_data *pdata
+ , int reg)
+{
+ u32 msk;
+
+ /* wait until the change is propagated */
+ msk = BF_RTC_STAT_NEW_REGS((0x1 << reg));
+ while (__raw_readl(pdata->base + HW_RTC_STAT) & msk)
+ cpu_relax();
+}
+
+static inline void persistent_reg_write(struct mxs_persistent_data *pdata,
+ u32 val, int reg)
+{
+ __raw_writel(val, pdata->base + 0x60 + (reg * 0x10));
+ persistent_reg_wait_settle(pdata, reg);
+}
+
+static inline void persistent_reg_set(struct mxs_persistent_data *pdata,
+ u32 val, int reg)
+{
+ __raw_writel(val, pdata->base + 0x60 + (reg * 0x10) + 0x4);
+ persistent_reg_wait_settle(pdata, reg);
+}
+
+static inline void persistent_reg_clr(struct mxs_persistent_data *pdata,
+ u32 val, int reg)
+{
+ __raw_writel(val, pdata->base + 0x60 + (reg * 0x10) + 0x8);
+ persistent_reg_wait_settle(pdata, reg);
+}
+
+static inline void persistent_reg_tog(struct mxs_persistent_data *pdata,
+ u32 val, int reg)
+{
+ __raw_writel(val, pdata->base + 0x60 + (reg * 0x10) + 0xc);
+ persistent_reg_wait_settle(pdata, reg);
+}
+
+static ssize_t
+persistent_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mxs_persistent_data *pd = platform_get_drvdata(pdev);
+ struct device_attribute *devattr = pd_device_attribute_ptr(pd);
+ const struct mxs_persistent_bit_config *pb;
+ int idx;
+ u32 val;
+
+ idx = attr - devattr;
+ if ((unsigned int)idx >= pd->count)
+ return -EINVAL;
+
+ pb = &pd->pdata->bit_config_tab[idx];
+
+ /* read value and shift */
+ val = persistent_reg_read(pd, pb->reg);
+ val >>= pb->start;
+ val &= (1 << pb->width) - 1;
+
+ return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t
+persistent_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mxs_persistent_data *pd = platform_get_drvdata(pdev);
+ struct device_attribute *devattr = pd_device_attribute_ptr(pd);
+ const struct mxs_persistent_bit_config *pb;
+ int idx, r;
+ unsigned long val, msk;
+
+ idx = attr - devattr;
+ if ((unsigned int)idx >= pd->count)
+ return -EINVAL;
+
+ pb = &pd->pdata->bit_config_tab[idx];
+
+ /* get value to write */
+ r = strict_strtoul(buf, 10, &val);
+ if (r != 0)
+ return r;
+
+ /* verify it fits */
+ if ((unsigned int)val > (1 << pb->width) - 1)
+ return -EINVAL;
+
+ /* lockless update, first clear the area */
+ msk = ((1 << pb->width) - 1) << pb->start;
+ persistent_reg_clr(pd, msk, pb->reg);
+
+ /* shift into position */
+ val <<= pb->start;
+ persistent_reg_set(pd, val, pb->reg);
+
+ return count;
+}
+
+
+static int __devinit mxs_persistent_probe(struct platform_device *pdev)
+{
+ struct mxs_persistent_data *pd;
+ struct mxs_platform_persistent_data *pdata;
+ struct resource *res;
+ const struct mxs_persistent_bit_config *pb;
+ struct attribute **attr;
+ struct device_attribute *devattr;
+ int i, cnt, size;
+ int err;
+
+ pdata = pdev->dev.platform_data;
+ if (pdata == NULL)
+ return -ENODEV;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL)
+ return -ENODEV;
+
+ cnt = pdata->bit_config_cnt;
+ size = sizeof(*pd) +
+ (cnt + 1) * sizeof(struct atrribute *) +
+ cnt * sizeof(struct device_attribute);
+ pd = kzalloc(size, GFP_KERNEL);
+ if (pd == NULL)
+ return -ENOMEM;
+ pd->dev = &pdev->dev;
+ pd->pdata = pdata;
+ pd->base = (unsigned int)IO_ADDRESS(res->start);
+
+ platform_set_drvdata(pdev, pd);
+ pd->count = cnt;
+ attr = pd_attribute_ptr(pd);
+ devattr = pd_device_attribute_ptr(pd);
+
+ /* build the attributes structures */
+ pd->attr_group.attrs = attr;
+ pb = pdata->bit_config_tab;
+ for (i = 0; i < cnt; i++) {
+ devattr[i].attr.name = pb[i].name;
+ devattr[i].attr.mode = S_IWUSR | S_IRUGO;
+ devattr[i].show = persistent_show;
+ devattr[i].store = persistent_store;
+ attr[i] = &devattr[i].attr;
+ }
+
+ err = sysfs_create_group(&pdev->dev.kobj, &pd->attr_group);
+ if (err != 0) {
+ platform_set_drvdata(pdev, NULL);
+ kfree(pd);
+ return err;
+ }
+
+ return 0;
+}
+
+static int __devexit mxs_persistent_remove(struct platform_device *pdev)
+{
+ struct mxs_persistent_data *pd;
+
+ pd = platform_get_drvdata(pdev);
+ sysfs_remove_group(&pdev->dev.kobj, &pd->attr_group);
+ platform_set_drvdata(pdev, NULL);
+ kfree(pd);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int
+mxs_persistent_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+static int mxs_persistent_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+#else
+#define mxs_persistent_suspend NULL
+#define mxs_persistent_resume NULL
+#endif
+
+static struct platform_driver mxs_persistent_driver = {
+ .probe = mxs_persistent_probe,
+ .remove = __exit_p(mxs_persistent_remove),
+ .suspend = mxs_persistent_suspend,
+ .resume = mxs_persistent_resume,
+ .driver = {
+ .name = "mxs-persistent",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mxs_persistent_init(void)
+{
+ return platform_driver_register(&mxs_persistent_driver);
+}
+
+static void __exit mxs_persistent_exit(void)
+{
+ platform_driver_unregister(&mxs_persistent_driver);
+}
+
+MODULE_AUTHOR("Pantelis Antoniou <pantelis@embeddedalley.com>");
+MODULE_DESCRIPTION("Persistent bits user-access driver");
+MODULE_LICENSE("GPL");
+
+module_init(mxs_persistent_init);
+module_exit(mxs_persistent_exit);
diff --git a/drivers/misc/pmem.c b/drivers/misc/pmem.c
new file mode 100644
index 000000000000..0e1651dfe324
--- /dev/null
+++ b/drivers/misc/pmem.c
@@ -0,0 +1,1340 @@
+/* drivers/android/pmem.c
+ *
+ * Copyright (C) 2007 Google, Inc.
+ * Copyright (C) 2010 Freescale Semiconductor, 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/debugfs.h>
+#include <linux/android_pmem.h>
+#include <linux/mempolicy.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/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_sem should be taken before pmem_data->sem if both are
+ * needed */
+ struct semaphore data_list_sem;
+ 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;
+
+
+ down(&pmem[id].data_list_sem);
+ /* 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);
+ up(&pmem[id].data_list_sem);
+
+
+ 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 != &pmem[id].dev &&
+ 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);
+
+ down(&pmem[id].data_list_sem);
+ list_add(&data->list, &pmem[id].data_list);
+ up(&pmem[id].data_list_sem);
+ 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 phys_mem_access_prot(struct file *file, pgprot_t vma_prot)
+{
+ int id = get_id(file);
+
+#ifdef pgprot_writecombine
+ if (pmem[id].cached == 0 || file->f_flags & O_SYNC)
+ return pgprot_writecombine(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 = phys_mem_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);
+ 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");
+
+ down(&pmem[id].data_list_sem);
+ 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);
+ }
+ up(&pmem[id].data_list_sem);
+
+ 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
+
+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);
+ init_MUTEX(&pmem[id].data_list_sem);
+ 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/regs-perfmon.h b/drivers/misc/regs-perfmon.h
new file mode 100644
index 000000000000..2f12885b3baf
--- /dev/null
+++ b/drivers/misc/regs-perfmon.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. 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 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * This file is created by xml file. Don't Edit it.
+ *
+ * Xml Revision: 1.36
+ * Template revision: 26195
+ */
+
+#ifndef __ARCH_ARM___PERFMON_H
+#define __ARCH_ARM___PERFMON_H
+
+
+#define HW_PERFMON_CTRL (0x00000000)
+#define HW_PERFMON_CTRL_SET (0x00000004)
+#define HW_PERFMON_CTRL_CLR (0x00000008)
+#define HW_PERFMON_CTRL_TOG (0x0000000c)
+
+#define BM_PERFMON_CTRL_SFTRST 0x80000000
+#define BV_PERFMON_CTRL_SFTRST__RUN 0x0
+#define BV_PERFMON_CTRL_SFTRST__RESET 0x1
+#define BM_PERFMON_CTRL_CLKGATE 0x40000000
+#define BV_PERFMON_CTRL_CLKGATE__RUN 0x0
+#define BV_PERFMON_CTRL_CLKGATE__NO_CLKS 0x1
+#define BP_PERFMON_CTRL_RSVD2 24
+#define BM_PERFMON_CTRL_RSVD2 0x3F000000
+#define BF_PERFMON_CTRL_RSVD2(v) \
+ (((v) << 24) & BM_PERFMON_CTRL_RSVD2)
+#define BP_PERFMON_CTRL_IRQ_MID 16
+#define BM_PERFMON_CTRL_IRQ_MID 0x00FF0000
+#define BF_PERFMON_CTRL_IRQ_MID(v) \
+ (((v) << 16) & BM_PERFMON_CTRL_IRQ_MID)
+#define BP_PERFMON_CTRL_RSVD1 13
+#define BM_PERFMON_CTRL_RSVD1 0x0000E000
+#define BF_PERFMON_CTRL_RSVD1(v) \
+ (((v) << 13) & BM_PERFMON_CTRL_RSVD1)
+#define BM_PERFMON_CTRL_BUS_ERR_IRQ 0x00001000
+#define BM_PERFMON_CTRL_LATENCY_IRQ 0x00000800
+#define BM_PERFMON_CTRL_TRAP_IRQ 0x00000400
+#define BM_PERFMON_CTRL_BUS_ERR_IRQ_EN 0x00000200
+#define BM_PERFMON_CTRL_LATENCY_IRQ_EN 0x00000100
+#define BM_PERFMON_CTRL_TRAP_IRQ_EN 0x00000080
+#define BM_PERFMON_CTRL_LATENCY_ENABLE 0x00000040
+#define BM_PERFMON_CTRL_TRAP_IN_RANGE 0x00000020
+#define BM_PERFMON_CTRL_TRAP_ENABLE 0x00000010
+#define BM_PERFMON_CTRL_READ_EN 0x00000008
+#define BM_PERFMON_CTRL_CLR 0x00000004
+#define BM_PERFMON_CTRL_SNAP 0x00000002
+#define BM_PERFMON_CTRL_RUN 0x00000001
+#define BV_PERFMON_CTRL_RUN__HALT 0x0
+#define BV_PERFMON_CTRL_RUN__RUN 0x1
+
+#define HW_PERFMON_MASTER_EN (0x00000010)
+
+#define BP_PERFMON_MASTER_EN_RSVD0 16
+#define BM_PERFMON_MASTER_EN_RSVD0 0xFFFF0000
+#define BF_PERFMON_MASTER_EN_RSVD0(v) \
+ (((v) << 16) & BM_PERFMON_MASTER_EN_RSVD0)
+#define BM_PERFMON_MASTER_EN_MID15 0x00008000
+#define BM_PERFMON_MASTER_EN_MID14 0x00004000
+#define BM_PERFMON_MASTER_EN_MID13 0x00002000
+#define BM_PERFMON_MASTER_EN_MID12 0x00001000
+#define BM_PERFMON_MASTER_EN_MID11 0x00000800
+#define BM_PERFMON_MASTER_EN_MID10 0x00000400
+#define BM_PERFMON_MASTER_EN_MID9 0x00000200
+#define BM_PERFMON_MASTER_EN_MID8 0x00000100
+#define BM_PERFMON_MASTER_EN_MID7 0x00000080
+#define BM_PERFMON_MASTER_EN_MID6 0x00000040
+#define BM_PERFMON_MASTER_EN_MID5 0x00000020
+#define BM_PERFMON_MASTER_EN_MID4 0x00000010
+#define BM_PERFMON_MASTER_EN_MID3 0x00000008
+#define BM_PERFMON_MASTER_EN_MID2 0x00000004
+#define BM_PERFMON_MASTER_EN_MID1 0x00000002
+#define BM_PERFMON_MASTER_EN_MID0 0x00000001
+
+#define HW_PERFMON_TRAP_ADDR_LOW (0x00000020)
+
+#define BP_PERFMON_TRAP_ADDR_LOW_ADDR 0
+#define BM_PERFMON_TRAP_ADDR_LOW_ADDR 0xFFFFFFFF
+#define BF_PERFMON_TRAP_ADDR_LOW_ADDR(v) (v)
+
+#define HW_PERFMON_TRAP_ADDR_HIGH (0x00000030)
+
+#define BP_PERFMON_TRAP_ADDR_HIGH_ADDR 0
+#define BM_PERFMON_TRAP_ADDR_HIGH_ADDR 0xFFFFFFFF
+#define BF_PERFMON_TRAP_ADDR_HIGH_ADDR(v) (v)
+
+#define HW_PERFMON_LAT_THRESHOLD (0x00000040)
+
+#define BP_PERFMON_LAT_THRESHOLD_RSVD0 12
+#define BM_PERFMON_LAT_THRESHOLD_RSVD0 0xFFFFF000
+#define BF_PERFMON_LAT_THRESHOLD_RSVD0(v) \
+ (((v) << 12) & BM_PERFMON_LAT_THRESHOLD_RSVD0)
+#define BP_PERFMON_LAT_THRESHOLD_VALUE 0
+#define BM_PERFMON_LAT_THRESHOLD_VALUE 0x00000FFF
+#define BF_PERFMON_LAT_THRESHOLD_VALUE(v) \
+ (((v) << 0) & BM_PERFMON_LAT_THRESHOLD_VALUE)
+
+#define HW_PERFMON_ACTIVE_CYCLE (0x00000050)
+
+#define BP_PERFMON_ACTIVE_CYCLE_COUNT 0
+#define BM_PERFMON_ACTIVE_CYCLE_COUNT 0xFFFFFFFF
+#define BF_PERFMON_ACTIVE_CYCLE_COUNT(v) (v)
+
+#define HW_PERFMON_TRANSFER_COUNT (0x00000060)
+
+#define BP_PERFMON_TRANSFER_COUNT_VALUE 0
+#define BM_PERFMON_TRANSFER_COUNT_VALUE 0xFFFFFFFF
+#define BF_PERFMON_TRANSFER_COUNT_VALUE(v) (v)
+
+#define HW_PERFMON_TOTAL_LATENCY (0x00000070)
+
+#define BP_PERFMON_TOTAL_LATENCY_COUNT 0
+#define BM_PERFMON_TOTAL_LATENCY_COUNT 0xFFFFFFFF
+#define BF_PERFMON_TOTAL_LATENCY_COUNT(v) (v)
+
+#define HW_PERFMON_DATA_COUNT (0x00000080)
+
+#define BP_PERFMON_DATA_COUNT_COUNT 0
+#define BM_PERFMON_DATA_COUNT_COUNT 0xFFFFFFFF
+#define BF_PERFMON_DATA_COUNT_COUNT(v) (v)
+
+#define HW_PERFMON_MAX_LATENCY (0x00000090)
+
+#define BP_PERFMON_MAX_LATENCY_ABURST 30
+#define BM_PERFMON_MAX_LATENCY_ABURST 0xC0000000
+#define BF_PERFMON_MAX_LATENCY_ABURST(v) \
+ (((v) << 30) & BM_PERFMON_MAX_LATENCY_ABURST)
+#define BP_PERFMON_MAX_LATENCY_ALEN 26
+#define BM_PERFMON_MAX_LATENCY_ALEN 0x3C000000
+#define BF_PERFMON_MAX_LATENCY_ALEN(v) \
+ (((v) << 26) & BM_PERFMON_MAX_LATENCY_ALEN)
+#define BP_PERFMON_MAX_LATENCY_ASIZE 23
+#define BM_PERFMON_MAX_LATENCY_ASIZE 0x03800000
+#define BF_PERFMON_MAX_LATENCY_ASIZE(v) \
+ (((v) << 23) & BM_PERFMON_MAX_LATENCY_ASIZE)
+#define BP_PERFMON_MAX_LATENCY_TAGID 15
+#define BM_PERFMON_MAX_LATENCY_TAGID 0x007F8000
+#define BF_PERFMON_MAX_LATENCY_TAGID(v) \
+ (((v) << 15) & BM_PERFMON_MAX_LATENCY_TAGID)
+#define BP_PERFMON_MAX_LATENCY_RSVD0 12
+#define BM_PERFMON_MAX_LATENCY_RSVD0 0x00007000
+#define BF_PERFMON_MAX_LATENCY_RSVD0(v) \
+ (((v) << 12) & BM_PERFMON_MAX_LATENCY_RSVD0)
+#define BP_PERFMON_MAX_LATENCY_COUNT 0
+#define BM_PERFMON_MAX_LATENCY_COUNT 0x00000FFF
+#define BF_PERFMON_MAX_LATENCY_COUNT(v) \
+ (((v) << 0) & BM_PERFMON_MAX_LATENCY_COUNT)
+
+#define HW_PERFMON_DEBUG (0x000000a0)
+
+#define BP_PERFMON_DEBUG_RSVD 2
+#define BM_PERFMON_DEBUG_RSVD 0xFFFFFFFC
+#define BF_PERFMON_DEBUG_RSVD(v) \
+ (((v) << 2) & BM_PERFMON_DEBUG_RSVD)
+#define BM_PERFMON_DEBUG_TOTAL_CYCLE_CLR_EN 0x00000002
+#define BM_PERFMON_DEBUG_ERR_MID 0x00000001
+
+#define HW_PERFMON_VERSION (0x000000b0)
+
+#define BP_PERFMON_VERSION_MAJOR 24
+#define BM_PERFMON_VERSION_MAJOR 0xFF000000
+#define BF_PERFMON_VERSION_MAJOR(v) \
+ (((v) << 24) & BM_PERFMON_VERSION_MAJOR)
+#define BP_PERFMON_VERSION_MINOR 16
+#define BM_PERFMON_VERSION_MINOR 0x00FF0000
+#define BF_PERFMON_VERSION_MINOR(v) \
+ (((v) << 16) & BM_PERFMON_VERSION_MINOR)
+#define BP_PERFMON_VERSION_STEP 0
+#define BM_PERFMON_VERSION_STEP 0x0000FFFF
+#define BF_PERFMON_VERSION_STEP(v) \
+ (((v) << 0) & BM_PERFMON_VERSION_STEP)
+#endif /* __ARCH_ARM___PERFMON_H */
diff --git a/drivers/misc/uid_stat.c b/drivers/misc/uid_stat.c
new file mode 100644
index 000000000000..e6760b56083c
--- /dev/null
+++ b/drivers/misc/uid_stat.c
@@ -0,0 +1,153 @@
+/* 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>
+
+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;
+ 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;
+ 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);