From 78b7280cce23293f7570ad52c1ffe1485c6d9669 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 11 Mar 2011 17:57:23 +0000 Subject: KEYS: Improve /proc/keys Improve /proc/keys by: (1) Don't attempt to summarise the payload of a negated key. It won't have one. To this end, a helper function - key_is_instantiated() has been added that allows the caller to find out whether the key is positively instantiated (as opposed to being uninstantiated or negatively instantiated). (2) Do show keys that are negative, expired or revoked rather than hiding them. This requires an override flag (no_state_check) to be passed to search_my_process_keyrings() and keyring_search_aux() to suppress this check. Without this, keys that are possessed by the caller, but only grant permissions to the caller if possessed are skipped as the possession check fails. Keys that are visible due to user, group or other checks are visible with or without this patch. Signed-off-by: David Howells Signed-off-by: James Morris --- include/linux/key.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'include/linux') diff --git a/include/linux/key.h b/include/linux/key.h index b2bb01719561..ef19b99aff98 100644 --- a/include/linux/key.h +++ b/include/linux/key.h @@ -276,6 +276,19 @@ static inline key_serial_t key_serial(struct key *key) return key ? key->serial : 0; } +/** + * key_is_instantiated - Determine if a key has been positively instantiated + * @key: The key to check. + * + * Return true if the specified key has been positively instantiated, false + * otherwise. + */ +static inline bool key_is_instantiated(const struct key *key) +{ + return test_bit(KEY_FLAG_INSTANTIATED, &key->flags) && + !test_bit(KEY_FLAG_NEGATIVE, &key->flags); +} + #define rcu_dereference_key(KEY) \ (rcu_dereference_protected((KEY)->payload.rcudata, \ rwsem_is_locked(&((struct key *)(KEY))->sem))) -- cgit v1.2.3 From 17f60a7da150fdd0cfb9756f86a262daa72c835f Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Fri, 1 Apr 2011 17:07:50 -0400 Subject: capabilites: allow the application of capability limits to usermode helpers There is no way to limit the capabilities of usermodehelpers. This problem reared its head recently when someone complained that any user with cap_net_admin was able to load arbitrary kernel modules, even though the user didn't have cap_sys_module. The reason is because the actual load is done by a usermode helper and those always have the full cap set. This patch addes new sysctls which allow us to bound the permissions of usermode helpers. /proc/sys/kernel/usermodehelper/bset /proc/sys/kernel/usermodehelper/inheritable You must have CAP_SYS_MODULE and CAP_SETPCAP to change these (changes are &= ONLY). When the kernel launches a usermodehelper it will do so with these as the bset and pI. -v2: make globals static create spinlock to protect globals -v3: require both CAP_SETPCAP and CAP_SYS_MODULE -v4: fix the typo s/CAP_SET_PCAP/CAP_SETPCAP/ because I didn't commit Signed-off-by: Eric Paris No-objection-from: Serge E. Hallyn Acked-by: David Howells Acked-by: Serge E. Hallyn Acked-by: Andrew G. Morgan Signed-off-by: James Morris --- include/linux/kmod.h | 3 ++ kernel/kmod.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++ kernel/sysctl.c | 6 ++++ 3 files changed, 109 insertions(+) (limited to 'include/linux') diff --git a/include/linux/kmod.h b/include/linux/kmod.h index 6efd7a78de6a..79bb98d71858 100644 --- a/include/linux/kmod.h +++ b/include/linux/kmod.h @@ -24,6 +24,7 @@ #include #include #include +#include #define KMOD_PATH_LEN 256 @@ -109,6 +110,8 @@ call_usermodehelper(char *path, char **argv, char **envp, enum umh_wait wait) NULL, NULL, NULL); } +extern struct ctl_table usermodehelper_table[]; + extern void usermodehelper_init(void); extern int usermodehelper_disable(void); diff --git a/kernel/kmod.c b/kernel/kmod.c index 9cd0591c96a2..06fdea2819b6 100644 --- a/kernel/kmod.c +++ b/kernel/kmod.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,13 @@ extern int max_threads; static struct workqueue_struct *khelper_wq; +#define CAP_BSET (void *)1 +#define CAP_PI (void *)2 + +static kernel_cap_t usermodehelper_bset = CAP_FULL_SET; +static kernel_cap_t usermodehelper_inheritable = CAP_FULL_SET; +static DEFINE_SPINLOCK(umh_sysctl_lock); + #ifdef CONFIG_MODULES /* @@ -132,6 +140,7 @@ EXPORT_SYMBOL(__request_module); static int ____call_usermodehelper(void *data) { struct subprocess_info *sub_info = data; + struct cred *new; int retval; spin_lock_irq(¤t->sighand->siglock); @@ -153,6 +162,19 @@ static int ____call_usermodehelper(void *data) goto fail; } + retval = -ENOMEM; + new = prepare_kernel_cred(current); + if (!new) + goto fail; + + spin_lock(&umh_sysctl_lock); + new->cap_bset = cap_intersect(usermodehelper_bset, new->cap_bset); + new->cap_inheritable = cap_intersect(usermodehelper_inheritable, + new->cap_inheritable); + spin_unlock(&umh_sysctl_lock); + + commit_creds(new); + retval = kernel_execve(sub_info->path, (const char *const *)sub_info->argv, (const char *const *)sub_info->envp); @@ -418,6 +440,84 @@ unlock: } EXPORT_SYMBOL(call_usermodehelper_exec); +static int proc_cap_handler(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + struct ctl_table t; + unsigned long cap_array[_KERNEL_CAPABILITY_U32S]; + kernel_cap_t new_cap; + int err, i; + + if (write && (!capable(CAP_SETPCAP) || + !capable(CAP_SYS_MODULE))) + return -EPERM; + + /* + * convert from the global kernel_cap_t to the ulong array to print to + * userspace if this is a read. + */ + spin_lock(&umh_sysctl_lock); + for (i = 0; i < _KERNEL_CAPABILITY_U32S; i++) { + if (table->data == CAP_BSET) + cap_array[i] = usermodehelper_bset.cap[i]; + else if (table->data == CAP_PI) + cap_array[i] = usermodehelper_inheritable.cap[i]; + else + BUG(); + } + spin_unlock(&umh_sysctl_lock); + + t = *table; + t.data = &cap_array; + + /* + * actually read or write and array of ulongs from userspace. Remember + * these are least significant 32 bits first + */ + err = proc_doulongvec_minmax(&t, write, buffer, lenp, ppos); + if (err < 0) + return err; + + /* + * convert from the sysctl array of ulongs to the kernel_cap_t + * internal representation + */ + for (i = 0; i < _KERNEL_CAPABILITY_U32S; i++) + new_cap.cap[i] = cap_array[i]; + + /* + * Drop everything not in the new_cap (but don't add things) + */ + spin_lock(&umh_sysctl_lock); + if (write) { + if (table->data == CAP_BSET) + usermodehelper_bset = cap_intersect(usermodehelper_bset, new_cap); + if (table->data == CAP_PI) + usermodehelper_inheritable = cap_intersect(usermodehelper_inheritable, new_cap); + } + spin_unlock(&umh_sysctl_lock); + + return 0; +} + +struct ctl_table usermodehelper_table[] = { + { + .procname = "bset", + .data = CAP_BSET, + .maxlen = _KERNEL_CAPABILITY_U32S * sizeof(unsigned long), + .mode = 0600, + .proc_handler = proc_cap_handler, + }, + { + .procname = "inheritable", + .data = CAP_PI, + .maxlen = _KERNEL_CAPABILITY_U32S * sizeof(unsigned long), + .mode = 0600, + .proc_handler = proc_cap_handler, + }, + { } +}; + void __init usermodehelper_init(void) { khelper_wq = create_singlethread_workqueue("khelper"); diff --git a/kernel/sysctl.c b/kernel/sysctl.c index c0bb32414b17..965134bed6cd 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -56,6 +56,7 @@ #include #include #include +#include #include #include @@ -615,6 +616,11 @@ static struct ctl_table kern_table[] = { .mode = 0555, .child = random_table, }, + { + .procname = "usermodehelper", + .mode = 0555, + .child = usermodehelper_table, + }, { .procname = "overflowuid", .data = &overflowuid, -- cgit v1.2.3 From ffa8e59df047d57e812a04f7d6baf6a25c652c0c Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Fri, 1 Apr 2011 17:08:34 -0400 Subject: capabilities: do not drop CAP_SETPCAP from the initial task In olden' days of yore CAP_SETPCAP had special meaning for the init task. We actually have code to make sure that CAP_SETPCAP wasn't in pE of things using the init_cred. But CAP_SETPCAP isn't so special any more and we don't have a reason to special case dropping it for init or kthreads.... Signed-off-by: Eric Paris Acked-by: Andrew G. Morgan Signed-off-by: James Morris --- include/linux/capability.h | 6 ++++-- kernel/capability.c | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/include/linux/capability.h b/include/linux/capability.h index 16ee8b49a200..11d562863e49 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -412,7 +412,6 @@ extern const kernel_cap_t __cap_init_eff_set; # define CAP_EMPTY_SET ((kernel_cap_t){{ 0, 0 }}) # define CAP_FULL_SET ((kernel_cap_t){{ ~0, ~0 }}) -# define CAP_INIT_EFF_SET ((kernel_cap_t){{ ~CAP_TO_MASK(CAP_SETPCAP), ~0 }}) # define CAP_FS_SET ((kernel_cap_t){{ CAP_FS_MASK_B0 \ | CAP_TO_MASK(CAP_LINUX_IMMUTABLE), \ CAP_FS_MASK_B1 } }) @@ -423,10 +422,10 @@ extern const kernel_cap_t __cap_init_eff_set; #endif /* _KERNEL_CAPABILITY_U32S != 2 */ #define CAP_INIT_INH_SET CAP_EMPTY_SET +#define CAP_INIT_EFF_SET CAP_FULL_SET # define cap_clear(c) do { (c) = __cap_empty_set; } while (0) # define cap_set_full(c) do { (c) = __cap_full_set; } while (0) -# define cap_set_init_eff(c) do { (c) = __cap_init_eff_set; } while (0) #define cap_raise(c, flag) ((c).cap[CAP_TO_INDEX(flag)] |= CAP_TO_MASK(flag)) #define cap_lower(c, flag) ((c).cap[CAP_TO_INDEX(flag)] &= ~CAP_TO_MASK(flag)) @@ -547,6 +546,9 @@ extern bool capable(int cap); extern bool ns_capable(struct user_namespace *ns, int cap); extern bool task_ns_capable(struct task_struct *t, int cap); +extern const kernel_cap_t __cap_empty_set; +extern const kernel_cap_t __cap_full_set; + /** * nsown_capable - Check superior capability to one's own user_ns * @cap: The capability in question diff --git a/kernel/capability.c b/kernel/capability.c index bf0c734d0c12..2a374d512ead 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -23,11 +23,9 @@ const kernel_cap_t __cap_empty_set = CAP_EMPTY_SET; const kernel_cap_t __cap_full_set = CAP_FULL_SET; -const kernel_cap_t __cap_init_eff_set = CAP_INIT_EFF_SET; EXPORT_SYMBOL(__cap_empty_set); EXPORT_SYMBOL(__cap_full_set); -EXPORT_SYMBOL(__cap_init_eff_set); int file_caps_enabled = 1; -- cgit v1.2.3 From 5163b583a036b103c3cec7171d6731c125773ed6 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Fri, 1 Apr 2011 17:08:39 -0400 Subject: capabilities: delete unused cap_set_full unused code. Clean it up. Signed-off-by: Eric Paris Acked-by: David Howells Acked-by: Andrew G. Morgan Signed-off-by: James Morris --- include/linux/capability.h | 2 -- kernel/capability.c | 2 -- 2 files changed, 4 deletions(-) (limited to 'include/linux') diff --git a/include/linux/capability.h b/include/linux/capability.h index 11d562863e49..8d0da30dad23 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -425,7 +425,6 @@ extern const kernel_cap_t __cap_init_eff_set; #define CAP_INIT_EFF_SET CAP_FULL_SET # define cap_clear(c) do { (c) = __cap_empty_set; } while (0) -# define cap_set_full(c) do { (c) = __cap_full_set; } while (0) #define cap_raise(c, flag) ((c).cap[CAP_TO_INDEX(flag)] |= CAP_TO_MASK(flag)) #define cap_lower(c, flag) ((c).cap[CAP_TO_INDEX(flag)] &= ~CAP_TO_MASK(flag)) @@ -547,7 +546,6 @@ extern bool ns_capable(struct user_namespace *ns, int cap); extern bool task_ns_capable(struct task_struct *t, int cap); extern const kernel_cap_t __cap_empty_set; -extern const kernel_cap_t __cap_full_set; /** * nsown_capable - Check superior capability to one's own user_ns diff --git a/kernel/capability.c b/kernel/capability.c index 2a374d512ead..14ea4210a530 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -22,10 +22,8 @@ */ const kernel_cap_t __cap_empty_set = CAP_EMPTY_SET; -const kernel_cap_t __cap_full_set = CAP_FULL_SET; EXPORT_SYMBOL(__cap_empty_set); -EXPORT_SYMBOL(__cap_full_set); int file_caps_enabled = 1; -- cgit v1.2.3 From a3232d2fa2e3cbab3e76d91cdae5890fee8a4034 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Fri, 1 Apr 2011 17:08:45 -0400 Subject: capabilities: delete all CAP_INIT macros The CAP_INIT macros of INH, BSET, and EFF made sense at one point in time, but now days they aren't helping. Just open code the logic in the init_cred. Signed-off-by: Eric Paris Acked-by: David Howells Signed-off-by: James Morris --- include/linux/capability.h | 3 --- include/linux/init_task.h | 7 ------- kernel/cred.c | 6 +++--- 3 files changed, 3 insertions(+), 13 deletions(-) (limited to 'include/linux') diff --git a/include/linux/capability.h b/include/linux/capability.h index 8d0da30dad23..04fed72809de 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -421,9 +421,6 @@ extern const kernel_cap_t __cap_init_eff_set; #endif /* _KERNEL_CAPABILITY_U32S != 2 */ -#define CAP_INIT_INH_SET CAP_EMPTY_SET -#define CAP_INIT_EFF_SET CAP_FULL_SET - # define cap_clear(c) do { (c) = __cap_empty_set; } while (0) #define cap_raise(c, flag) ((c).cap[CAP_TO_INDEX(flag)] |= CAP_TO_MASK(flag)) diff --git a/include/linux/init_task.h b/include/linux/init_task.h index caa151fbebb7..1f277204de34 100644 --- a/include/linux/init_task.h +++ b/include/linux/init_task.h @@ -83,13 +83,6 @@ extern struct group_info init_groups; #define INIT_IDS #endif -/* - * Because of the reduced scope of CAP_SETPCAP when filesystem - * capabilities are in effect, it is safe to allow CAP_SETPCAP to - * be available in the default configuration. - */ -# define CAP_INIT_BSET CAP_FULL_SET - #ifdef CONFIG_RCU_BOOST #define INIT_TASK_RCU_BOOST() \ .rcu_boost_mutex = NULL, diff --git a/kernel/cred.c b/kernel/cred.c index 5557b55048df..b982f0863ae9 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -49,10 +49,10 @@ struct cred init_cred = { .magic = CRED_MAGIC, #endif .securebits = SECUREBITS_DEFAULT, - .cap_inheritable = CAP_INIT_INH_SET, + .cap_inheritable = CAP_EMPTY_SET, .cap_permitted = CAP_FULL_SET, - .cap_effective = CAP_INIT_EFF_SET, - .cap_bset = CAP_INIT_BSET, + .cap_effective = CAP_FULL_SET, + .cap_bset = CAP_FULL_SET, .user = INIT_USER, .group_info = &init_groups, #ifdef CONFIG_KEYS -- cgit v1.2.3 From 92a47674f57b4a84a43ce93b0dfdb596c0543749 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Mon, 11 Apr 2011 23:34:37 -0700 Subject: Input: gpio_keys - add support for EV_ABS With this patch you can setup a group of GPIOs representing a specific position on an EV_ABS axis. Signed-off-by: Alexander Stein Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/gpio_keys.c | 9 +++++++-- include/linux/gpio_keys.h | 7 ++++--- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'include/linux') diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index eb3006361ee4..73e58a96ab99 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -324,7 +324,12 @@ static void gpio_keys_report_event(struct gpio_button_data *bdata) unsigned int type = button->type ?: EV_KEY; int state = (gpio_get_value_cansleep(button->gpio) ? 1 : 0) ^ button->active_low; - input_event(input, type, button->code, !!state); + if (type == EV_ABS) { + if (state) + input_event(input, type, button->code, button->value); + } else { + input_event(input, type, button->code, !!state); + } input_sync(input); } @@ -363,7 +368,7 @@ static int __devinit gpio_keys_setup_key(struct platform_device *pdev, struct gpio_button_data *bdata, struct gpio_keys_button *button) { - char *desc = button->desc ? button->desc : "gpio_keys"; + const char *desc = button->desc ? button->desc : "gpio_keys"; struct device *dev = &pdev->dev; unsigned long irqflags; int irq, error; diff --git a/include/linux/gpio_keys.h b/include/linux/gpio_keys.h index dd1a56fbe924..3204edfe6b19 100644 --- a/include/linux/gpio_keys.h +++ b/include/linux/gpio_keys.h @@ -3,14 +3,15 @@ struct gpio_keys_button { /* Configuration parameters */ - int code; /* input event code (KEY_*, SW_*) */ + unsigned int code; /* input event code (KEY_*, SW_*) */ int gpio; int active_low; - char *desc; - int type; /* input event type (EV_KEY, EV_SW) */ + const char *desc; + unsigned int type; /* input event type (EV_KEY, EV_SW, EV_ABS) */ int wakeup; /* configure the button as a wake-up source */ int debounce_interval; /* debounce ticks interval in msecs */ bool can_disable; + int value; /* axis value for EV_ABS */ }; struct gpio_keys_platform_data { -- cgit v1.2.3 From 467112777c462a592c27338eeea5d1a320e82b5f Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Mon, 11 Apr 2011 23:34:48 -0700 Subject: Input: gpio-keys - add support for setting device name This patch allows to set a device name which helps distinguishing several gpio-keys devices. Signed-off-by: Alexander Stein Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/gpio_keys.c | 2 +- include/linux/gpio_keys.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index 73e58a96ab99..6e6145b9a4c1 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -473,7 +473,7 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) platform_set_drvdata(pdev, ddata); input_set_drvdata(input, ddata); - input->name = pdev->name; + input->name = pdata->name ? : pdev->name; input->phys = "gpio-keys/input0"; input->dev.parent = &pdev->dev; input->open = gpio_keys_open; diff --git a/include/linux/gpio_keys.h b/include/linux/gpio_keys.h index 3204edfe6b19..b5ca4b2c08ec 100644 --- a/include/linux/gpio_keys.h +++ b/include/linux/gpio_keys.h @@ -22,6 +22,7 @@ struct gpio_keys_platform_data { unsigned int rep:1; /* enable input subsystem auto repeat */ int (*enable)(struct device *dev); void (*disable)(struct device *dev); + const char *name; /* input device name */ }; #endif -- cgit v1.2.3 From feddbb34ebd75e9b6bf573b852079e327a88c07a Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Mon, 28 Mar 2011 10:12:25 +0300 Subject: UBI: fix minor stylistic issues Fix checkpatch.pl errors and warnings: * space before tab * line over 80 characters * include linux/ioctl.h instead of asm/ioctl.h Signed-off-by: Artem Bityutskiy --- drivers/mtd/ubi/cdev.c | 4 ++-- drivers/mtd/ubi/debug.c | 18 +++++++++--------- drivers/mtd/ubi/io.c | 4 ++-- drivers/mtd/ubi/scan.c | 2 +- drivers/mtd/ubi/ubi.h | 4 ++-- drivers/mtd/ubi/wl.c | 3 ++- include/linux/mtd/ubi.h | 4 ++-- include/mtd/ubi-user.h | 6 +++--- 8 files changed, 23 insertions(+), 22 deletions(-) (limited to 'include/linux') diff --git a/drivers/mtd/ubi/cdev.c b/drivers/mtd/ubi/cdev.c index 4119cb857c97..191f3bb3c41a 100644 --- a/drivers/mtd/ubi/cdev.c +++ b/drivers/mtd/ubi/cdev.c @@ -115,7 +115,7 @@ static int vol_cdev_open(struct inode *inode, struct file *file) mode = UBI_READONLY; dbg_gen("open device %d, volume %d, mode %d", - ubi_num, vol_id, mode); + ubi_num, vol_id, mode); desc = ubi_open_volume(ubi_num, vol_id, mode); if (IS_ERR(desc)) @@ -158,7 +158,7 @@ static loff_t vol_cdev_llseek(struct file *file, loff_t offset, int origin) loff_t new_offset; if (vol->updating) { - /* Update is in progress, seeking is prohibited */ + /* Update is in progress, seeking is prohibited */ dbg_err("updating"); return -EBUSY; } diff --git a/drivers/mtd/ubi/debug.c b/drivers/mtd/ubi/debug.c index d4d07e5f138f..0cd5beabe9c9 100644 --- a/drivers/mtd/ubi/debug.c +++ b/drivers/mtd/ubi/debug.c @@ -75,15 +75,15 @@ void ubi_dbg_dump_vid_hdr(const struct ubi_vid_hdr *vid_hdr) { printk(KERN_DEBUG "Volume identifier header dump:\n"); printk(KERN_DEBUG "\tmagic %08x\n", be32_to_cpu(vid_hdr->magic)); - printk(KERN_DEBUG "\tversion %d\n", (int)vid_hdr->version); - printk(KERN_DEBUG "\tvol_type %d\n", (int)vid_hdr->vol_type); - printk(KERN_DEBUG "\tcopy_flag %d\n", (int)vid_hdr->copy_flag); - printk(KERN_DEBUG "\tcompat %d\n", (int)vid_hdr->compat); - printk(KERN_DEBUG "\tvol_id %d\n", be32_to_cpu(vid_hdr->vol_id)); - printk(KERN_DEBUG "\tlnum %d\n", be32_to_cpu(vid_hdr->lnum)); - printk(KERN_DEBUG "\tdata_size %d\n", be32_to_cpu(vid_hdr->data_size)); - printk(KERN_DEBUG "\tused_ebs %d\n", be32_to_cpu(vid_hdr->used_ebs)); - printk(KERN_DEBUG "\tdata_pad %d\n", be32_to_cpu(vid_hdr->data_pad)); + printk(KERN_DEBUG "\tversion %d\n", (int)vid_hdr->version); + printk(KERN_DEBUG "\tvol_type %d\n", (int)vid_hdr->vol_type); + printk(KERN_DEBUG "\tcopy_flag %d\n", (int)vid_hdr->copy_flag); + printk(KERN_DEBUG "\tcompat %d\n", (int)vid_hdr->compat); + printk(KERN_DEBUG "\tvol_id %d\n", be32_to_cpu(vid_hdr->vol_id)); + printk(KERN_DEBUG "\tlnum %d\n", be32_to_cpu(vid_hdr->lnum)); + printk(KERN_DEBUG "\tdata_size %d\n", be32_to_cpu(vid_hdr->data_size)); + printk(KERN_DEBUG "\tused_ebs %d\n", be32_to_cpu(vid_hdr->used_ebs)); + printk(KERN_DEBUG "\tdata_pad %d\n", be32_to_cpu(vid_hdr->data_pad)); printk(KERN_DEBUG "\tsqnum %llu\n", (unsigned long long)be64_to_cpu(vid_hdr->sqnum)); printk(KERN_DEBUG "\thdr_crc %08x\n", be32_to_cpu(vid_hdr->hdr_crc)); diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index e347cc4388ed..d58ceb1ca8fd 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -189,8 +189,8 @@ retry: } if (retries++ < UBI_IO_RETRIES) { - dbg_io("error %d%s while reading %d bytes from PEB %d:%d," - " read only %zd bytes, retry", + dbg_io("error %d%s while reading %d bytes from PEB " + "%d:%d, read only %zd bytes, retry", err, errstr, len, pnum, offset, read); yield(); goto retry; diff --git a/drivers/mtd/ubi/scan.c b/drivers/mtd/ubi/scan.c index d2d12ab7def4..2135a53732ff 100644 --- a/drivers/mtd/ubi/scan.c +++ b/drivers/mtd/ubi/scan.c @@ -1103,7 +1103,7 @@ static int check_what_we_have(struct ubi_device *ubi, struct ubi_scan_info *si) * otherwise, only print a warning. */ if (si->corr_peb_count >= max_corr) { - ubi_err("too many corrupted PEBs, refusing this device"); + ubi_err("too many corrupted PEBs, refusing"); return -EINVAL; } } diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index f1be8b79663c..c6c22295898e 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -341,8 +341,8 @@ struct ubi_wl_entry; * protected from the wear-leveling worker) * @pq_head: protection queue head * @wl_lock: protects the @used, @free, @pq, @pq_head, @lookuptbl, @move_from, - * @move_to, @move_to_put @erase_pending, @wl_scheduled, @works, - * @erroneous, and @erroneous_peb_count fields + * @move_to, @move_to_put @erase_pending, @wl_scheduled, @works, + * @erroneous, and @erroneous_peb_count fields * @move_mutex: serializes eraseblock moves * @work_sem: synchronizes the WL worker with use tasks * @wl_scheduled: non-zero if the wear-leveling was scheduled diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index b4cf57db2556..ff2c4956eeff 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -1570,7 +1570,8 @@ void ubi_wl_close(struct ubi_device *ubi) * @ec: the erase counter to check * * This function returns zero if the erase counter of physical eraseblock @pnum - * is equivalent to @ec, and a negative error code if not or if an error occurred. + * is equivalent to @ec, and a negative error code if not or if an error + * occurred. */ static int paranoid_check_ec(struct ubi_device *ubi, int pnum, int ec) { diff --git a/include/linux/mtd/ubi.h b/include/linux/mtd/ubi.h index 84854edf4436..15da0e99f48a 100644 --- a/include/linux/mtd/ubi.h +++ b/include/linux/mtd/ubi.h @@ -21,7 +21,7 @@ #ifndef __LINUX_UBI_H__ #define __LINUX_UBI_H__ -#include +#include #include #include @@ -87,7 +87,7 @@ enum { * physical eraseblock size and on how much bytes UBI headers consume. But * because of the volume alignment (@alignment), the usable size of logical * eraseblocks if a volume may be less. The following equation is true: - * @usable_leb_size = LEB size - (LEB size mod @alignment), + * @usable_leb_size = LEB size - (LEB size mod @alignment), * where LEB size is the logical eraseblock size defined by the UBI device. * * The alignment is multiple to the minimal flash input/output unit size or %1 diff --git a/include/mtd/ubi-user.h b/include/mtd/ubi-user.h index a3903423c005..3c4109777aff 100644 --- a/include/mtd/ubi-user.h +++ b/include/mtd/ubi-user.h @@ -406,9 +406,9 @@ struct ubi_map_req { * @value: value to set */ struct ubi_set_vol_prop_req { - __u8 property; - __u8 padding[7]; - __u64 value; + __u8 property; + __u8 padding[7]; + __u64 value; } __packed; #endif /* __UBI_USER_H__ */ -- cgit v1.2.3 From 1c9904297451f558191e211a48d8838b4bf792b0 Mon Sep 17 00:00:00 2001 From: Andi Kleen Date: Thu, 21 Apr 2011 17:23:19 -0700 Subject: SECURITY: Move exec_permission RCU checks into security modules Right now all RCU walks fall back to reference walk when CONFIG_SECURITY is enabled, even though just the standard capability module is active. This is because security_inode_exec_permission unconditionally fails RCU walks. Move this decision to the low level security module. This requires passing the RCU flags down the security hook. This way at least the capability module and a few easy cases in selinux/smack work with RCU walks with CONFIG_SECURITY=y Signed-off-by: Andi Kleen Signed-off-by: Eric Paris --- include/linux/security.h | 2 +- security/capability.c | 2 +- security/security.c | 6 ++---- security/selinux/hooks.c | 6 +++++- security/smack/smack_lsm.c | 6 +++++- 5 files changed, 14 insertions(+), 8 deletions(-) (limited to 'include/linux') diff --git a/include/linux/security.h b/include/linux/security.h index 84a202ac3de9..2f99ecd0fb2a 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1454,7 +1454,7 @@ struct security_operations { struct inode *new_dir, struct dentry *new_dentry); int (*inode_readlink) (struct dentry *dentry); int (*inode_follow_link) (struct dentry *dentry, struct nameidata *nd); - int (*inode_permission) (struct inode *inode, int mask); + int (*inode_permission) (struct inode *inode, int mask, unsigned flags); int (*inode_setattr) (struct dentry *dentry, struct iattr *attr); int (*inode_getattr) (struct vfsmount *mnt, struct dentry *dentry); int (*inode_setxattr) (struct dentry *dentry, const char *name, diff --git a/security/capability.c b/security/capability.c index ab3d807accc3..56bb1605fd79 100644 --- a/security/capability.c +++ b/security/capability.c @@ -181,7 +181,7 @@ static int cap_inode_follow_link(struct dentry *dentry, return 0; } -static int cap_inode_permission(struct inode *inode, int mask) +static int cap_inode_permission(struct inode *inode, int mask, unsigned flags) { return 0; } diff --git a/security/security.c b/security/security.c index 47b8a447118f..7e34f98bf433 100644 --- a/security/security.c +++ b/security/security.c @@ -514,16 +514,14 @@ int security_inode_permission(struct inode *inode, int mask) { if (unlikely(IS_PRIVATE(inode))) return 0; - return security_ops->inode_permission(inode, mask); + return security_ops->inode_permission(inode, mask, 0); } int security_inode_exec_permission(struct inode *inode, unsigned int flags) { if (unlikely(IS_PRIVATE(inode))) return 0; - if (flags) - return -ECHILD; - return security_ops->inode_permission(inode, MAY_EXEC); + return security_ops->inode_permission(inode, MAY_EXEC, flags); } int security_inode_setattr(struct dentry *dentry, struct iattr *attr) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 7a630a8a5cef..9a220be17a3f 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2635,7 +2635,7 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *na return dentry_has_perm(cred, NULL, dentry, FILE__READ); } -static int selinux_inode_permission(struct inode *inode, int mask) +static int selinux_inode_permission(struct inode *inode, int mask, unsigned flags) { const struct cred *cred = current_cred(); struct common_audit_data ad; @@ -2649,6 +2649,10 @@ static int selinux_inode_permission(struct inode *inode, int mask) if (!mask) return 0; + /* May be droppable after audit */ + if (flags & IPERM_FLAG_RCU) + return -ECHILD; + COMMON_AUDIT_DATA_INIT(&ad, FS); ad.u.fs.inode = inode; diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 23c7a6d0c80c..42fcb47747a3 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -686,7 +686,7 @@ static int smack_inode_rename(struct inode *old_inode, * * Returns 0 if access is permitted, -EACCES otherwise */ -static int smack_inode_permission(struct inode *inode, int mask) +static int smack_inode_permission(struct inode *inode, int mask, unsigned flags) { struct smk_audit_info ad; @@ -696,6 +696,10 @@ static int smack_inode_permission(struct inode *inode, int mask) */ if (mask == 0) return 0; + + /* May be droppable after audit */ + if (flags & IPERM_FLAG_RCU) + return -ECHILD; smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); smk_ad_setfield_u_fs_inode(&ad, inode); return smk_curacc(smk_of_inode(inode), mask, &ad); -- cgit v1.2.3 From f48b7399840b453e7282b523f535561fe9638a2d Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Mon, 25 Apr 2011 12:54:27 -0400 Subject: LSM: split LSM_AUDIT_DATA_FS into _PATH and _INODE The lsm common audit code has wacky contortions making sure which pieces of information are set based on if it was given a path, dentry, or inode. Split this into path and inode to get rid of some of the code complexity. Signed-off-by: Eric Paris Acked-by: Casey Schaufler --- include/linux/lsm_audit.h | 9 ++++----- security/lsm_audit.c | 50 ++++++++++++++++++++++++++-------------------- security/selinux/avc.c | 2 +- security/selinux/hooks.c | 50 +++++++++++++++++++++++----------------------- security/smack/smack.h | 8 ++++---- security/smack/smack_lsm.c | 32 ++++++++++++++--------------- 6 files changed, 78 insertions(+), 73 deletions(-) (limited to 'include/linux') diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 112a55033352..bbaceab83a65 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -27,7 +27,7 @@ /* Auxiliary data to use in generating the audit record. */ struct common_audit_data { char type; -#define LSM_AUDIT_DATA_FS 1 +#define LSM_AUDIT_DATA_PATH 1 #define LSM_AUDIT_DATA_NET 2 #define LSM_AUDIT_DATA_CAP 3 #define LSM_AUDIT_DATA_IPC 4 @@ -35,12 +35,11 @@ struct common_audit_data { #define LSM_AUDIT_DATA_KEY 6 #define LSM_AUDIT_DATA_NONE 7 #define LSM_AUDIT_DATA_KMOD 8 +#define LSM_AUDIT_DATA_INODE 9 struct task_struct *tsk; union { - struct { - struct path path; - struct inode *inode; - } fs; + struct path path; + struct inode *inode; struct { int netif; struct sock *sk; diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 908aa712816a..2e846052cbf4 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -210,7 +210,6 @@ static inline void print_ipv4_addr(struct audit_buffer *ab, __be32 addr, static void dump_common_audit_data(struct audit_buffer *ab, struct common_audit_data *a) { - struct inode *inode = NULL; struct task_struct *tsk = current; if (a->tsk) @@ -229,33 +228,40 @@ static void dump_common_audit_data(struct audit_buffer *ab, case LSM_AUDIT_DATA_CAP: audit_log_format(ab, " capability=%d ", a->u.cap); break; - case LSM_AUDIT_DATA_FS: - if (a->u.fs.path.dentry) { - struct dentry *dentry = a->u.fs.path.dentry; - if (a->u.fs.path.mnt) { - audit_log_d_path(ab, "path=", &a->u.fs.path); - } else { - audit_log_format(ab, " name="); - audit_log_untrustedstring(ab, - dentry->d_name.name); - } - inode = dentry->d_inode; - } else if (a->u.fs.inode) { - struct dentry *dentry; - inode = a->u.fs.inode; - dentry = d_find_alias(inode); - if (dentry) { - audit_log_format(ab, " name="); - audit_log_untrustedstring(ab, - dentry->d_name.name); - dput(dentry); - } + case LSM_AUDIT_DATA_PATH: { + struct dentry *dentry = a->u.path.dentry; + struct inode *inode; + + if (a->u.path.mnt) { + audit_log_d_path(ab, "path=", &a->u.path); + } else { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, + dentry->d_name.name); } + inode = dentry->d_inode; if (inode) audit_log_format(ab, " dev=%s ino=%lu", inode->i_sb->s_id, inode->i_ino); break; + } + case LSM_AUDIT_DATA_INODE: { + struct dentry *dentry; + struct inode *inode; + + inode = a->u.inode; + dentry = d_find_alias(inode); + if (dentry) { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, + dentry->d_name.name); + dput(dentry); + } + audit_log_format(ab, " dev=%s ino=%lu", inode->i_sb->s_id, + inode->i_ino); + break; + } case LSM_AUDIT_DATA_TASK: tsk = a->u.tsk; if (tsk && tsk->pid) { diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 1d027e29ce8d..ce742f1778e1 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -531,7 +531,7 @@ int avc_audit(u32 ssid, u32 tsid, * during retry. However this is logically just as if the operation * happened a little later. */ - if ((a->type == LSM_AUDIT_DATA_FS) && + if ((a->type == LSM_AUDIT_DATA_INODE) && (flags & IPERM_FLAG_RCU)) return -ECHILD; diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index ed5f29aa0a38..ad664d3056eb 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1488,8 +1488,8 @@ static int inode_has_perm(const struct cred *cred, if (!adp) { adp = &ad; - COMMON_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.inode = inode; + COMMON_AUDIT_DATA_INIT(&ad, INODE); + ad.u.inode = inode; } return avc_has_perm_flags(sid, isec->sid, isec->sclass, perms, adp, flags); @@ -1506,9 +1506,9 @@ static inline int dentry_has_perm(const struct cred *cred, struct inode *inode = dentry->d_inode; struct common_audit_data ad; - COMMON_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.path.mnt = mnt; - ad.u.fs.path.dentry = dentry; + COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.u.path.mnt = mnt; + ad.u.path.dentry = dentry; return inode_has_perm(cred, inode, av, &ad, 0); } @@ -1530,8 +1530,8 @@ static int file_has_perm(const struct cred *cred, u32 sid = cred_sid(cred); int rc; - COMMON_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.path = file->f_path; + COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.u.path = file->f_path; if (sid != fsec->sid) { rc = avc_has_perm(sid, fsec->sid, @@ -1569,8 +1569,8 @@ static int may_create(struct inode *dir, sid = tsec->sid; newsid = tsec->create_sid; - COMMON_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.path.dentry = dentry; + COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.u.path.dentry = dentry; rc = avc_has_perm(sid, dsec->sid, SECCLASS_DIR, DIR__ADD_NAME | DIR__SEARCH, @@ -1621,8 +1621,8 @@ static int may_link(struct inode *dir, dsec = dir->i_security; isec = dentry->d_inode->i_security; - COMMON_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.path.dentry = dentry; + COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.u.path.dentry = dentry; av = DIR__SEARCH; av |= (kind ? DIR__REMOVE_NAME : DIR__ADD_NAME); @@ -1667,9 +1667,9 @@ static inline int may_rename(struct inode *old_dir, old_is_dir = S_ISDIR(old_dentry->d_inode->i_mode); new_dsec = new_dir->i_security; - COMMON_AUDIT_DATA_INIT(&ad, FS); + COMMON_AUDIT_DATA_INIT(&ad, PATH); - ad.u.fs.path.dentry = old_dentry; + ad.u.path.dentry = old_dentry; rc = avc_has_perm(sid, old_dsec->sid, SECCLASS_DIR, DIR__REMOVE_NAME | DIR__SEARCH, &ad); if (rc) @@ -1685,7 +1685,7 @@ static inline int may_rename(struct inode *old_dir, return rc; } - ad.u.fs.path.dentry = new_dentry; + ad.u.path.dentry = new_dentry; av = DIR__ADD_NAME | DIR__SEARCH; if (new_dentry->d_inode) av |= DIR__REMOVE_NAME; @@ -1991,8 +1991,8 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) return rc; } - COMMON_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.path = bprm->file->f_path; + COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.u.path = bprm->file->f_path; if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) new_tsec->sid = old_tsec->sid; @@ -2120,7 +2120,7 @@ static inline void flush_unauthorized_files(const struct cred *cred, /* Revalidate access to inherited open files. */ - COMMON_AUDIT_DATA_INIT(&ad, FS); + COMMON_AUDIT_DATA_INIT(&ad, INODE); spin_lock(&files->file_lock); for (;;) { @@ -2468,8 +2468,8 @@ static int selinux_sb_kern_mount(struct super_block *sb, int flags, void *data) if (flags & MS_KERNMOUNT) return 0; - COMMON_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.path.dentry = sb->s_root; + COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.u.path.dentry = sb->s_root; return superblock_has_perm(cred, sb, FILESYSTEM__MOUNT, &ad); } @@ -2478,8 +2478,8 @@ static int selinux_sb_statfs(struct dentry *dentry) const struct cred *cred = current_cred(); struct common_audit_data ad; - COMMON_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.path.dentry = dentry->d_sb->s_root; + COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.u.path.dentry = dentry->d_sb->s_root; return superblock_has_perm(cred, dentry->d_sb, FILESYSTEM__GETATTR, &ad); } @@ -2653,8 +2653,8 @@ static int selinux_inode_permission(struct inode *inode, int mask, unsigned flag if (!mask) return 0; - COMMON_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.inode = inode; + COMMON_AUDIT_DATA_INIT(&ad, INODE); + ad.u.inode = inode; if (from_access) ad.selinux_audit_data.auditdeny |= FILE__AUDIT_ACCESS; @@ -2732,8 +2732,8 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, if (!is_owner_or_cap(inode)) return -EPERM; - COMMON_AUDIT_DATA_INIT(&ad, FS); - ad.u.fs.path.dentry = dentry; + COMMON_AUDIT_DATA_INIT(&ad, PATH); + ad.u.path.dentry = dentry; rc = avc_has_perm(sid, isec->sid, isec->sclass, FILE__RELABELFROM, &ad); diff --git a/security/smack/smack.h b/security/smack/smack.h index b449cfdad21c..a16925c0e91a 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -316,22 +316,22 @@ static inline void smk_ad_setfield_u_tsk(struct smk_audit_info *a, static inline void smk_ad_setfield_u_fs_path_dentry(struct smk_audit_info *a, struct dentry *d) { - a->a.u.fs.path.dentry = d; + a->a.u.path.dentry = d; } static inline void smk_ad_setfield_u_fs_path_mnt(struct smk_audit_info *a, struct vfsmount *m) { - a->a.u.fs.path.mnt = m; + a->a.u.path.mnt = m; } static inline void smk_ad_setfield_u_fs_inode(struct smk_audit_info *a, struct inode *i) { - a->a.u.fs.inode = i; + a->a.u.inode = i; } static inline void smk_ad_setfield_u_fs_path(struct smk_audit_info *a, struct path p) { - a->a.u.fs.path = p; + a->a.u.path = p; } static inline void smk_ad_setfield_u_net_sk(struct smk_audit_info *a, struct sock *sk) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 42fcb47747a3..eeb393fbf925 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -383,7 +383,7 @@ static int smack_sb_statfs(struct dentry *dentry) int rc; struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); rc = smk_curacc(sbp->smk_floor, MAY_READ, &ad); @@ -407,7 +407,7 @@ static int smack_sb_mount(char *dev_name, struct path *path, struct superblock_smack *sbp = path->mnt->mnt_sb->s_security; struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path(&ad, *path); return smk_curacc(sbp->smk_floor, MAY_WRITE, &ad); @@ -426,7 +426,7 @@ static int smack_sb_umount(struct vfsmount *mnt, int flags) struct superblock_smack *sbp; struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, mnt->mnt_root); smk_ad_setfield_u_fs_path_mnt(&ad, mnt); @@ -563,7 +563,7 @@ static int smack_inode_link(struct dentry *old_dentry, struct inode *dir, struct smk_audit_info ad; int rc; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, old_dentry); isp = smk_of_inode(old_dentry->d_inode); @@ -592,7 +592,7 @@ static int smack_inode_unlink(struct inode *dir, struct dentry *dentry) struct smk_audit_info ad; int rc; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); /* @@ -623,7 +623,7 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry) struct smk_audit_info ad; int rc; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); /* @@ -663,7 +663,7 @@ static int smack_inode_rename(struct inode *old_inode, char *isp; struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, old_dentry); isp = smk_of_inode(old_dentry->d_inode); @@ -700,7 +700,7 @@ static int smack_inode_permission(struct inode *inode, int mask, unsigned flags) /* May be droppable after audit */ if (flags & IPERM_FLAG_RCU) return -ECHILD; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_INODE); smk_ad_setfield_u_fs_inode(&ad, inode); return smk_curacc(smk_of_inode(inode), mask, &ad); } @@ -720,7 +720,7 @@ static int smack_inode_setattr(struct dentry *dentry, struct iattr *iattr) */ if (iattr->ia_valid & ATTR_FORCE) return 0; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); return smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); @@ -737,7 +737,7 @@ static int smack_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) { struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); smk_ad_setfield_u_fs_path_mnt(&ad, mnt); return smk_curacc(smk_of_inode(dentry->d_inode), MAY_READ, &ad); @@ -784,7 +784,7 @@ static int smack_inode_setxattr(struct dentry *dentry, const char *name, } else rc = cap_inode_setxattr(dentry, name, value, size, flags); - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); if (rc == 0) @@ -845,7 +845,7 @@ static int smack_inode_getxattr(struct dentry *dentry, const char *name) { struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); return smk_curacc(smk_of_inode(dentry->d_inode), MAY_READ, &ad); @@ -877,7 +877,7 @@ static int smack_inode_removexattr(struct dentry *dentry, const char *name) } else rc = cap_inode_removexattr(dentry, name); - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); if (rc == 0) rc = smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); @@ -1047,7 +1047,7 @@ static int smack_file_ioctl(struct file *file, unsigned int cmd, int rc = 0; struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path(&ad, file->f_path); if (_IOC_DIR(cmd) & _IOC_WRITE) @@ -1070,7 +1070,7 @@ static int smack_file_lock(struct file *file, unsigned int cmd) { struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path_dentry(&ad, file->f_path.dentry); return smk_curacc(file->f_security, MAY_WRITE, &ad); } @@ -1089,7 +1089,7 @@ static int smack_file_fcntl(struct file *file, unsigned int cmd, struct smk_audit_info ad; int rc; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_FS); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path(&ad, file->f_path); switch (cmd) { -- cgit v1.2.3 From a269434d2fb48a4d66c1d7bf821b7874b59c5b41 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Mon, 25 Apr 2011 13:10:27 -0400 Subject: LSM: separate LSM_AUDIT_DATA_DENTRY from LSM_AUDIT_DATA_PATH This patch separates and audit message that only contains a dentry from one that contains a full path. This allows us to make it harder to misuse the interfaces or for the interfaces to be implemented wrong. Signed-off-by: Eric Paris Acked-by: Casey Schaufler --- include/linux/lsm_audit.h | 2 ++ security/lsm_audit.c | 25 ++++++++++++++++--------- security/selinux/hooks.c | 26 +++++++++++++------------- security/smack/smack.h | 7 +------ security/smack/smack_lsm.c | 34 ++++++++++++++++++++-------------- 5 files changed, 52 insertions(+), 42 deletions(-) (limited to 'include/linux') diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index bbaceab83a65..88e78dedc2e8 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -36,9 +36,11 @@ struct common_audit_data { #define LSM_AUDIT_DATA_NONE 7 #define LSM_AUDIT_DATA_KMOD 8 #define LSM_AUDIT_DATA_INODE 9 +#define LSM_AUDIT_DATA_DENTRY 10 struct task_struct *tsk; union { struct path path; + struct dentry *dentry; struct inode *inode; struct { int netif; diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 2e846052cbf4..893af8a2fa1e 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -229,17 +229,24 @@ static void dump_common_audit_data(struct audit_buffer *ab, audit_log_format(ab, " capability=%d ", a->u.cap); break; case LSM_AUDIT_DATA_PATH: { - struct dentry *dentry = a->u.path.dentry; struct inode *inode; - if (a->u.path.mnt) { - audit_log_d_path(ab, "path=", &a->u.path); - } else { - audit_log_format(ab, " name="); - audit_log_untrustedstring(ab, - dentry->d_name.name); - } - inode = dentry->d_inode; + audit_log_d_path(ab, "path=", &a->u.path); + + inode = a->u.path.dentry->d_inode; + if (inode) + audit_log_format(ab, " dev=%s ino=%lu", + inode->i_sb->s_id, + inode->i_ino); + break; + } + case LSM_AUDIT_DATA_DENTRY: { + struct inode *inode; + + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, a->u.dentry->d_name.name); + + inode = a->u.dentry->d_inode; if (inode) audit_log_format(ab, " dev=%s ino=%lu", inode->i_sb->s_id, diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index ad664d3056eb..9e8078a42a94 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1569,8 +1569,8 @@ static int may_create(struct inode *dir, sid = tsec->sid; newsid = tsec->create_sid; - COMMON_AUDIT_DATA_INIT(&ad, PATH); - ad.u.path.dentry = dentry; + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.u.dentry = dentry; rc = avc_has_perm(sid, dsec->sid, SECCLASS_DIR, DIR__ADD_NAME | DIR__SEARCH, @@ -1621,8 +1621,8 @@ static int may_link(struct inode *dir, dsec = dir->i_security; isec = dentry->d_inode->i_security; - COMMON_AUDIT_DATA_INIT(&ad, PATH); - ad.u.path.dentry = dentry; + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.u.dentry = dentry; av = DIR__SEARCH; av |= (kind ? DIR__REMOVE_NAME : DIR__ADD_NAME); @@ -1667,9 +1667,9 @@ static inline int may_rename(struct inode *old_dir, old_is_dir = S_ISDIR(old_dentry->d_inode->i_mode); new_dsec = new_dir->i_security; - COMMON_AUDIT_DATA_INIT(&ad, PATH); + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); - ad.u.path.dentry = old_dentry; + ad.u.dentry = old_dentry; rc = avc_has_perm(sid, old_dsec->sid, SECCLASS_DIR, DIR__REMOVE_NAME | DIR__SEARCH, &ad); if (rc) @@ -1685,7 +1685,7 @@ static inline int may_rename(struct inode *old_dir, return rc; } - ad.u.path.dentry = new_dentry; + ad.u.dentry = new_dentry; av = DIR__ADD_NAME | DIR__SEARCH; if (new_dentry->d_inode) av |= DIR__REMOVE_NAME; @@ -2468,8 +2468,8 @@ static int selinux_sb_kern_mount(struct super_block *sb, int flags, void *data) if (flags & MS_KERNMOUNT) return 0; - COMMON_AUDIT_DATA_INIT(&ad, PATH); - ad.u.path.dentry = sb->s_root; + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.u.dentry = sb->s_root; return superblock_has_perm(cred, sb, FILESYSTEM__MOUNT, &ad); } @@ -2478,8 +2478,8 @@ static int selinux_sb_statfs(struct dentry *dentry) const struct cred *cred = current_cred(); struct common_audit_data ad; - COMMON_AUDIT_DATA_INIT(&ad, PATH); - ad.u.path.dentry = dentry->d_sb->s_root; + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.u.dentry = dentry->d_sb->s_root; return superblock_has_perm(cred, dentry->d_sb, FILESYSTEM__GETATTR, &ad); } @@ -2732,8 +2732,8 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, if (!is_owner_or_cap(inode)) return -EPERM; - COMMON_AUDIT_DATA_INIT(&ad, PATH); - ad.u.path.dentry = dentry; + COMMON_AUDIT_DATA_INIT(&ad, DENTRY); + ad.u.dentry = dentry; rc = avc_has_perm(sid, isec->sid, isec->sclass, FILE__RELABELFROM, &ad); diff --git a/security/smack/smack.h b/security/smack/smack.h index a16925c0e91a..2b6c6a516123 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -316,12 +316,7 @@ static inline void smk_ad_setfield_u_tsk(struct smk_audit_info *a, static inline void smk_ad_setfield_u_fs_path_dentry(struct smk_audit_info *a, struct dentry *d) { - a->a.u.path.dentry = d; -} -static inline void smk_ad_setfield_u_fs_path_mnt(struct smk_audit_info *a, - struct vfsmount *m) -{ - a->a.u.path.mnt = m; + a->a.u.dentry = d; } static inline void smk_ad_setfield_u_fs_inode(struct smk_audit_info *a, struct inode *i) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index eeb393fbf925..a3bdd1383928 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -383,7 +383,7 @@ static int smack_sb_statfs(struct dentry *dentry) int rc; struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); rc = smk_curacc(sbp->smk_floor, MAY_READ, &ad); @@ -425,10 +425,13 @@ static int smack_sb_umount(struct vfsmount *mnt, int flags) { struct superblock_smack *sbp; struct smk_audit_info ad; + struct path path; + + path.dentry = mnt->mnt_root; + path.mnt = mnt; smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); - smk_ad_setfield_u_fs_path_dentry(&ad, mnt->mnt_root); - smk_ad_setfield_u_fs_path_mnt(&ad, mnt); + smk_ad_setfield_u_fs_path(&ad, path); sbp = mnt->mnt_sb->s_security; return smk_curacc(sbp->smk_floor, MAY_WRITE, &ad); @@ -563,7 +566,7 @@ static int smack_inode_link(struct dentry *old_dentry, struct inode *dir, struct smk_audit_info ad; int rc; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, old_dentry); isp = smk_of_inode(old_dentry->d_inode); @@ -592,7 +595,7 @@ static int smack_inode_unlink(struct inode *dir, struct dentry *dentry) struct smk_audit_info ad; int rc; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); /* @@ -623,7 +626,7 @@ static int smack_inode_rmdir(struct inode *dir, struct dentry *dentry) struct smk_audit_info ad; int rc; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); /* @@ -663,7 +666,7 @@ static int smack_inode_rename(struct inode *old_inode, char *isp; struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, old_dentry); isp = smk_of_inode(old_dentry->d_inode); @@ -720,7 +723,7 @@ static int smack_inode_setattr(struct dentry *dentry, struct iattr *iattr) */ if (iattr->ia_valid & ATTR_FORCE) return 0; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); return smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); @@ -736,10 +739,13 @@ static int smack_inode_setattr(struct dentry *dentry, struct iattr *iattr) static int smack_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) { struct smk_audit_info ad; + struct path path; + + path.dentry = dentry; + path.mnt = mnt; smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); - smk_ad_setfield_u_fs_path_dentry(&ad, dentry); - smk_ad_setfield_u_fs_path_mnt(&ad, mnt); + smk_ad_setfield_u_fs_path(&ad, path); return smk_curacc(smk_of_inode(dentry->d_inode), MAY_READ, &ad); } @@ -784,7 +790,7 @@ static int smack_inode_setxattr(struct dentry *dentry, const char *name, } else rc = cap_inode_setxattr(dentry, name, value, size, flags); - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); if (rc == 0) @@ -845,7 +851,7 @@ static int smack_inode_getxattr(struct dentry *dentry, const char *name) { struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); return smk_curacc(smk_of_inode(dentry->d_inode), MAY_READ, &ad); @@ -877,7 +883,7 @@ static int smack_inode_removexattr(struct dentry *dentry, const char *name) } else rc = cap_inode_removexattr(dentry, name); - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, dentry); if (rc == 0) rc = smk_curacc(smk_of_inode(dentry->d_inode), MAY_WRITE, &ad); @@ -1070,7 +1076,7 @@ static int smack_file_lock(struct file *file, unsigned int cmd) { struct smk_audit_info ad; - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_DENTRY); smk_ad_setfield_u_fs_path_dentry(&ad, file->f_path.dentry); return smk_curacc(file->f_security, MAY_WRITE, &ad); } -- cgit v1.2.3 From 5a3ea8782c63d3501cb764c176f153c0d9a400e1 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Thu, 28 Apr 2011 15:55:52 -0400 Subject: flex_array: flex_array_prealloc takes a number of elements, not an end Change flex_array_prealloc to take the number of elements for which space should be allocated instead of the last (inclusive) element. Users and documentation are updated accordingly. flex_arrays got introduced before they had users. When folks started using it, they ended up needing a different API than was coded up originally. This swaps over to the API that folks apparently need. Based-on-patch-by: Steffen Klassert Signed-off-by: Eric Paris Tested-by: Chris Richards Acked-by: Dave Hansen Cc: stable@kernel.org [2.6.38+] --- Documentation/flexible-arrays.txt | 4 ++-- include/linux/flex_array.h | 2 +- lib/flex_array.c | 13 ++++++++----- security/selinux/ss/policydb.c | 6 +++--- 4 files changed, 14 insertions(+), 11 deletions(-) (limited to 'include/linux') diff --git a/Documentation/flexible-arrays.txt b/Documentation/flexible-arrays.txt index cb8a3a00cc92..df904aec9904 100644 --- a/Documentation/flexible-arrays.txt +++ b/Documentation/flexible-arrays.txt @@ -66,10 +66,10 @@ trick is to ensure that any needed memory allocations are done before entering atomic context, using: int flex_array_prealloc(struct flex_array *array, unsigned int start, - unsigned int end, gfp_t flags); + unsigned int nr_elements, gfp_t flags); This function will ensure that memory for the elements indexed in the range -defined by start and end has been allocated. Thereafter, a +defined by start and nr_elements has been allocated. Thereafter, a flex_array_put() call on an element in that range is guaranteed not to block. diff --git a/include/linux/flex_array.h b/include/linux/flex_array.h index 70e4efabe0fb..ebeb2f3ad068 100644 --- a/include/linux/flex_array.h +++ b/include/linux/flex_array.h @@ -61,7 +61,7 @@ struct flex_array { struct flex_array *flex_array_alloc(int element_size, unsigned int total, gfp_t flags); int flex_array_prealloc(struct flex_array *fa, unsigned int start, - unsigned int end, gfp_t flags); + unsigned int nr_elements, gfp_t flags); void flex_array_free(struct flex_array *fa); void flex_array_free_parts(struct flex_array *fa); int flex_array_put(struct flex_array *fa, unsigned int element_nr, void *src, diff --git a/lib/flex_array.c b/lib/flex_array.c index c0ea40ba2082..0c33b24498ba 100644 --- a/lib/flex_array.c +++ b/lib/flex_array.c @@ -232,10 +232,10 @@ EXPORT_SYMBOL(flex_array_clear); /** * flex_array_prealloc - guarantee that array space exists - * @fa: the flex array for which to preallocate parts - * @start: index of first array element for which space is allocated - * @end: index of last (inclusive) element for which space is allocated - * @flags: page allocation flags + * @fa: the flex array for which to preallocate parts + * @start: index of first array element for which space is allocated + * @nr_elements: number of elements for which space is allocated + * @flags: page allocation flags * * This will guarantee that no future calls to flex_array_put() * will allocate memory. It can be used if you are expecting to @@ -245,13 +245,16 @@ EXPORT_SYMBOL(flex_array_clear); * Locking must be provided by the caller. */ int flex_array_prealloc(struct flex_array *fa, unsigned int start, - unsigned int end, gfp_t flags) + unsigned int nr_elements, gfp_t flags) { int start_part; int end_part; int part_nr; + unsigned int end; struct flex_array_part *part; + end = start + nr_elements - 1; + if (start >= fa->total_nr_elements || end >= fa->total_nr_elements) return -ENOSPC; if (elements_fit_in_base(fa)) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 549120c56edd..102e9ec1b77a 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -545,7 +545,7 @@ static int policydb_index(struct policydb *p) goto out; rc = flex_array_prealloc(p->type_val_to_struct_array, 0, - p->p_types.nprim - 1, GFP_KERNEL | __GFP_ZERO); + p->p_types.nprim, GFP_KERNEL | __GFP_ZERO); if (rc) goto out; @@ -562,7 +562,7 @@ static int policydb_index(struct policydb *p) goto out; rc = flex_array_prealloc(p->sym_val_to_name[i], - 0, p->symtab[i].nprim - 1, + 0, p->symtab[i].nprim, GFP_KERNEL | __GFP_ZERO); if (rc) goto out; @@ -2439,7 +2439,7 @@ int policydb_read(struct policydb *p, void *fp) goto bad; /* preallocate so we don't have to worry about the put ever failing */ - rc = flex_array_prealloc(p->type_attr_map_array, 0, p->p_types.nprim - 1, + rc = flex_array_prealloc(p->type_attr_map_array, 0, p->p_types.nprim, GFP_KERNEL | __GFP_ZERO); if (rc) goto bad; -- cgit v1.2.3 From 99b38b4acc0d7dbbab443273577cff60080fcfad Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Mon, 6 Dec 2010 12:43:33 +1000 Subject: platform/x86: add MXM WMI driver. MXM is a laptop graphics card form-factor + interface specification, this adds an initial stub driver to talk to the MXM WMI interface. The only method used is the MUX switching method needed to do switchable graphics on the nvidia chipsets. Signed-off-by: Dave Airlie Acked-by: Matthew Garrett --- drivers/platform/x86/Kconfig | 7 ++++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/mxm-wmi.c | 85 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mxm-wmi.h | 32 ++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 drivers/platform/x86/mxm-wmi.c create mode 100644 include/linux/mxm-wmi.h (limited to 'include/linux') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 0485e394712a..94914572dd7f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -753,4 +753,11 @@ config SAMSUNG_LAPTOP To compile this driver as a module, choose M here: the module will be called samsung-laptop. +config MXM_WMI + tristate "WMI support for MXM Laptop Graphics" + depends on WMI + ---help--- + MXM is a standard for laptop graphics cards, the WMI interface + is required for switchable nvidia graphics machines + endif # X86_PLATFORM_DEVICES diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 029e8861d086..a7ab3bc7b3a1 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -42,3 +42,4 @@ obj-$(CONFIG_XO15_EBOOK) += xo15-ebook.o obj-$(CONFIG_IBM_RTL) += ibm_rtl.o obj-$(CONFIG_SAMSUNG_LAPTOP) += samsung-laptop.o obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o +obj-$(CONFIG_MXM_WMI) += mxm-wmi.o diff --git a/drivers/platform/x86/mxm-wmi.c b/drivers/platform/x86/mxm-wmi.c new file mode 100644 index 000000000000..12b6f341e72b --- /dev/null +++ b/drivers/platform/x86/mxm-wmi.c @@ -0,0 +1,85 @@ +/* + * MXM WMI driver + * + * Copyright(C) 2010 Red Hat. + * + * 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 +#include +#include +#include +#include + +MODULE_AUTHOR("Dave Airlie"); +MODULE_DESCRIPTION("MXM WMI Driver"); +MODULE_LICENSE("GPL"); + +#define MXM_WMMX_GUID "F6CB5C3C-9CAE-4EBD-B577-931EA32A2CC0" + +MODULE_ALIAS("wmi:"MXM_WMMX_GUID); + +#define MXM_WMMX_FUNC_MXDS 0x5344584D /* "MXDS" */ + +struct mxds_args { + u32 func; + u32 args; + u32 xarg; +}; + +int mxm_wmi_call_mxds(int adapter) +{ + struct mxds_args args = { + .func = MXM_WMMX_FUNC_MXDS, + .args = 0, + .xarg = 1, + }; + struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + printk("calling mux switch %d\n", adapter); + + status = wmi_evaluate_method(MXM_WMMX_GUID, 0x1, adapter, &input, + &output); + + if (ACPI_FAILURE(status)) + return status; + + printk("mux switched %d\n", status); + return 0; + +} +EXPORT_SYMBOL_GPL(mxm_wmi_call_mxds); + +bool mxm_wmi_supported(void) +{ + bool guid_valid; + guid_valid = wmi_has_guid(MXM_WMMX_GUID); + return guid_valid; +} +EXPORT_SYMBOL_GPL(mxm_wmi_supported); + +static int __init mxm_wmi_init(void) +{ + return 0; +} + +static void __exit mxm_wmi_exit(void) +{ +} + +module_init(mxm_wmi_init); +module_exit(mxm_wmi_exit); diff --git a/include/linux/mxm-wmi.h b/include/linux/mxm-wmi.h new file mode 100644 index 000000000000..51359c0718bf --- /dev/null +++ b/include/linux/mxm-wmi.h @@ -0,0 +1,32 @@ +/* + * MXM WMI driver + * + * Copyright(C) 2010 Red Hat. + * + * 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 + */ + +#ifndef MXM_WMI_H +#define MXM_WMI_H + +/* discrete adapters */ +#define MXM_MXDS_ADAPTER_0 0x0 +#define MXM_MXDS_ADAPTER_1 0x0 +/* integrated adapter */ +#define MXM_MXDS_ADAPTER_IGD 0x10 +int mxm_wmi_call_mxds(int adapter); +bool mxm_wmi_supported(void); + +#endif -- cgit v1.2.3 From 3448a19da479b6bd1e28e2a2be9fa16c6a6feb39 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Tue, 1 Jun 2010 15:32:24 +1000 Subject: vgaarb: use bridges to control VGA routing where possible. So in a lot of modern systems, a GPU will always be below a parent bridge that won't share with any other GPUs. This means VGA arbitration on those GPUs can be controlled by using the bridge routing instead of io/mem decodes. The problem is locating which GPUs share which upstream bridges. This patch attempts to identify all the GPUs which can be controlled via bridges, and ones that can't. This patch endeavours to work out the bridge sharing semantics. When disabling GPUs via a bridge, it doesn't do irq callbacks or touch the io/mem decodes for the gpu. Signed-off-by: Dave Airlie --- drivers/gpu/vga/vgaarb.c | 113 +++++++++++++++++++++++++++++++++++++++++------ drivers/pci/pci.c | 25 ++++++----- include/linux/pci.h | 7 ++- 3 files changed, 118 insertions(+), 27 deletions(-) (limited to 'include/linux') diff --git a/drivers/gpu/vga/vgaarb.c b/drivers/gpu/vga/vgaarb.c index be8d4cb5861c..8a1021f2e319 100644 --- a/drivers/gpu/vga/vgaarb.c +++ b/drivers/gpu/vga/vgaarb.c @@ -61,7 +61,7 @@ struct vga_device { unsigned int mem_lock_cnt; /* legacy MEM lock count */ unsigned int io_norm_cnt; /* normal IO count */ unsigned int mem_norm_cnt; /* normal MEM count */ - + bool bridge_has_one_vga; /* allow IRQ enable/disable hook */ void *cookie; void (*irq_set_state)(void *cookie, bool enable); @@ -165,6 +165,8 @@ static struct vga_device *__vga_tryget(struct vga_device *vgadev, unsigned int wants, legacy_wants, match; struct vga_device *conflict; unsigned int pci_bits; + u32 flags = 0; + /* Account for "normal" resources to lock. If we decode the legacy, * counterpart, we need to request it as well */ @@ -237,16 +239,23 @@ static struct vga_device *__vga_tryget(struct vga_device *vgadev, /* looks like he doesn't have a lock, we can steal * them from him */ - vga_irq_set_state(conflict, false); + flags = 0; pci_bits = 0; - if (lwants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) - pci_bits |= PCI_COMMAND_MEMORY; - if (lwants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) - pci_bits |= PCI_COMMAND_IO; - pci_set_vga_state(conflict->pdev, false, pci_bits, - change_bridge); + if (!conflict->bridge_has_one_vga) { + vga_irq_set_state(conflict, false); + flags |= PCI_VGA_STATE_CHANGE_DECODES; + if (lwants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) + pci_bits |= PCI_COMMAND_MEMORY; + if (lwants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) + pci_bits |= PCI_COMMAND_IO; + } + + if (change_bridge) + flags |= PCI_VGA_STATE_CHANGE_BRIDGE; + + pci_set_vga_state(conflict->pdev, false, pci_bits, flags); conflict->owns &= ~lwants; /* If he also owned non-legacy, that is no longer the case */ if (lwants & VGA_RSRC_LEGACY_MEM) @@ -261,14 +270,24 @@ enable_them: * also have in "decodes". We can lock resources we don't decode but * not own them. */ + flags = 0; pci_bits = 0; - if (wants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) - pci_bits |= PCI_COMMAND_MEMORY; - if (wants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) - pci_bits |= PCI_COMMAND_IO; - pci_set_vga_state(vgadev->pdev, true, pci_bits, !!(wants & VGA_RSRC_LEGACY_MASK)); - vga_irq_set_state(vgadev, true); + if (!vgadev->bridge_has_one_vga) { + flags |= PCI_VGA_STATE_CHANGE_DECODES; + if (wants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) + pci_bits |= PCI_COMMAND_MEMORY; + if (wants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) + pci_bits |= PCI_COMMAND_IO; + } + if (!!(wants & VGA_RSRC_LEGACY_MASK)) + flags |= PCI_VGA_STATE_CHANGE_BRIDGE; + + pci_set_vga_state(vgadev->pdev, true, pci_bits, flags); + + if (!vgadev->bridge_has_one_vga) { + vga_irq_set_state(vgadev, true); + } vgadev->owns |= (wants & vgadev->decodes); lock_them: vgadev->locks |= (rsrc & VGA_RSRC_LEGACY_MASK); @@ -421,6 +440,62 @@ bail: } EXPORT_SYMBOL(vga_put); +/* Rules for using a bridge to control a VGA descendant decoding: + if a bridge has only one VGA descendant then it can be used + to control the VGA routing for that device. + It should always use the bridge closest to the device to control it. + If a bridge has a direct VGA descendant, but also have a sub-bridge + VGA descendant then we cannot use that bridge to control the direct VGA descendant. + So for every device we register, we need to iterate all its parent bridges + so we can invalidate any devices using them properly. +*/ +static void vga_arbiter_check_bridge_sharing(struct vga_device *vgadev) +{ + struct vga_device *same_bridge_vgadev; + struct pci_bus *new_bus, *bus; + struct pci_dev *new_bridge, *bridge; + + vgadev->bridge_has_one_vga = true; + + if (list_empty(&vga_list)) + return; + + /* okay iterate the new devices bridge hierarachy */ + new_bus = vgadev->pdev->bus; + while (new_bus) { + new_bridge = new_bus->self; + + if (new_bridge) { + /* go through list of devices already registered */ + list_for_each_entry(same_bridge_vgadev, &vga_list, list) { + bus = same_bridge_vgadev->pdev->bus; + bridge = bus->self; + + /* see if the share a bridge with this device */ + if (new_bridge == bridge) { + /* if their direct parent bridge is the same + as any bridge of this device then it can't be used + for that device */ + same_bridge_vgadev->bridge_has_one_vga = false; + } + + /* now iterate the previous devices bridge hierarchy */ + /* if the new devices parent bridge is in the other devices + hierarchy then we can't use it to control this device */ + while (bus) { + bridge = bus->self; + if (bridge) { + if (bridge == vgadev->pdev->bus->self) + vgadev->bridge_has_one_vga = false; + } + bus = bus->parent; + } + } + } + new_bus = new_bus->parent; + } +} + /* * Currently, we assume that the "initial" setup of the system is * not sane, that is we come up with conflicting devices and let @@ -500,6 +575,8 @@ static bool vga_arbiter_add_pci_device(struct pci_dev *pdev) vga_default = pci_dev_get(pdev); #endif + vga_arbiter_check_bridge_sharing(vgadev); + /* Add to the list */ list_add(&vgadev->list, &vga_list); vga_count++; @@ -1222,6 +1299,7 @@ static int __init vga_arb_device_init(void) { int rc; struct pci_dev *pdev; + struct vga_device *vgadev; rc = misc_register(&vga_arb_device); if (rc < 0) @@ -1238,6 +1316,13 @@ static int __init vga_arb_device_init(void) vga_arbiter_add_pci_device(pdev); pr_info("vgaarb: loaded\n"); + + list_for_each_entry(vgadev, &vga_list, list) { + if (vgadev->bridge_has_one_vga) + pr_info("vgaarb: bridge control possible %s\n", pci_name(vgadev->pdev)); + else + pr_info("vgaarb: no bridge control possible %s\n", pci_name(vgadev->pdev)); + } return rc; } subsys_initcall(vga_arb_device_init); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 2472e7177b4b..a339237f4f96 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2875,31 +2875,34 @@ static int pci_set_vga_state_arch(struct pci_dev *dev, bool decode, * @dev: the PCI device * @decode: true = enable decoding, false = disable decoding * @command_bits: PCI_COMMAND_IO and/or PCI_COMMAND_MEMORY - * @change_bridge: traverse ancestors and change bridges + * @change_bridge_flags: traverse ancestors and change bridges + * CHANGE_BRIDGE_ONLY / CHANGE_BRIDGE */ int pci_set_vga_state(struct pci_dev *dev, bool decode, - unsigned int command_bits, bool change_bridge) + unsigned int command_bits, u32 flags) { struct pci_bus *bus; struct pci_dev *bridge; u16 cmd; int rc; - WARN_ON(command_bits & ~(PCI_COMMAND_IO|PCI_COMMAND_MEMORY)); + WARN_ON((flags & PCI_VGA_STATE_CHANGE_DECODES) & (command_bits & ~(PCI_COMMAND_IO|PCI_COMMAND_MEMORY))); /* ARCH specific VGA enables */ - rc = pci_set_vga_state_arch(dev, decode, command_bits, change_bridge); + rc = pci_set_vga_state_arch(dev, decode, command_bits, flags); if (rc) return rc; - pci_read_config_word(dev, PCI_COMMAND, &cmd); - if (decode == true) - cmd |= command_bits; - else - cmd &= ~command_bits; - pci_write_config_word(dev, PCI_COMMAND, cmd); + if (flags & PCI_VGA_STATE_CHANGE_DECODES) { + pci_read_config_word(dev, PCI_COMMAND, &cmd); + if (decode == true) + cmd |= command_bits; + else + cmd &= ~command_bits; + pci_write_config_word(dev, PCI_COMMAND, cmd); + } - if (change_bridge == false) + if (!(flags & PCI_VGA_STATE_CHANGE_BRIDGE)) return 0; bus = dev->bus; diff --git a/include/linux/pci.h b/include/linux/pci.h index 96f70d7e058d..f2e57b2e6a81 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -915,8 +915,11 @@ int pci_cfg_space_size_ext(struct pci_dev *dev); int pci_cfg_space_size(struct pci_dev *dev); unsigned char pci_bus_max_busnr(struct pci_bus *bus); +#define PCI_VGA_STATE_CHANGE_BRIDGE (1 << 0) +#define PCI_VGA_STATE_CHANGE_DECODES (1 << 1) + int pci_set_vga_state(struct pci_dev *pdev, bool decode, - unsigned int command_bits, bool change_bridge); + unsigned int command_bits, u32 flags); /* kmem_cache style wrapper around pci_alloc_consistent() */ #include @@ -1061,7 +1064,7 @@ static inline int pci_proc_domain(struct pci_bus *bus) /* some architectures require additional setup to direct VGA traffic */ typedef int (*arch_set_vga_state_t)(struct pci_dev *pdev, bool decode, - unsigned int command_bits, bool change_bridge); + unsigned int command_bits, u32 flags); extern void pci_register_set_vga_state(arch_set_vga_state_t func); #else /* CONFIG_PCI is not enabled */ -- cgit v1.2.3 From 000703f44c77b152cd966eaf06f4ab043274ff46 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Mon, 9 May 2011 11:40:25 +1000 Subject: mxm/wmi: add MXMX interface entry point. The MXMX method appears to be a mutex of some sort. Signed-off-by: Dave Airlie --- drivers/gpu/drm/nouveau/nouveau_acpi.c | 1 + drivers/platform/x86/mxm-wmi.c | 28 +++++++++++++++++++++++++++- include/linux/mxm-wmi.h | 1 + 3 files changed, 29 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c index e0a885b72e0e..f0d459bb46e4 100644 --- a/drivers/gpu/drm/nouveau/nouveau_acpi.c +++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c @@ -150,6 +150,7 @@ static int nouveau_dsm(acpi_handle handle, int func, int arg, uint32_t *result) static int nouveau_dsm_switch_mux(acpi_handle handle, int mux_id) { + mxm_wmi_call_mxmx(mux_id == NOUVEAU_DSM_LED_STAMINA ? MXM_MXDS_ADAPTER_IGD : MXM_MXDS_ADAPTER_0); mxm_wmi_call_mxds(mux_id == NOUVEAU_DSM_LED_STAMINA ? MXM_MXDS_ADAPTER_IGD : MXM_MXDS_ADAPTER_0); return nouveau_dsm(handle, NOUVEAU_DSM_LED, mux_id, NULL); } diff --git a/drivers/platform/x86/mxm-wmi.c b/drivers/platform/x86/mxm-wmi.c index 12b6f341e72b..0aea63b3729a 100644 --- a/drivers/platform/x86/mxm-wmi.c +++ b/drivers/platform/x86/mxm-wmi.c @@ -32,6 +32,7 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("wmi:"MXM_WMMX_GUID); #define MXM_WMMX_FUNC_MXDS 0x5344584D /* "MXDS" */ +#define MXM_WMMX_FUNC_MXMX 0x53445344 /* "MXMX" */ struct mxds_args { u32 func; @@ -51,7 +52,7 @@ int mxm_wmi_call_mxds(int adapter) acpi_status status; printk("calling mux switch %d\n", adapter); - + status = wmi_evaluate_method(MXM_WMMX_GUID, 0x1, adapter, &input, &output); @@ -64,6 +65,31 @@ int mxm_wmi_call_mxds(int adapter) } EXPORT_SYMBOL_GPL(mxm_wmi_call_mxds); +int mxm_wmi_call_mxmx(int adapter) +{ + struct mxds_args args = { + .func = MXM_WMMX_FUNC_MXMX, + .args = 0, + .xarg = 1, + }; + struct acpi_buffer input = { (acpi_size)sizeof(args), &args }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + printk("calling mux switch %d\n", adapter); + + status = wmi_evaluate_method(MXM_WMMX_GUID, 0x1, adapter, &input, + &output); + + if (ACPI_FAILURE(status)) + return status; + + printk("mux mutex set switched %d\n", status); + return 0; + +} +EXPORT_SYMBOL_GPL(mxm_wmi_call_mxmx); + bool mxm_wmi_supported(void) { bool guid_valid; diff --git a/include/linux/mxm-wmi.h b/include/linux/mxm-wmi.h index 51359c0718bf..617a2950523c 100644 --- a/include/linux/mxm-wmi.h +++ b/include/linux/mxm-wmi.h @@ -27,6 +27,7 @@ /* integrated adapter */ #define MXM_MXDS_ADAPTER_IGD 0x10 int mxm_wmi_call_mxds(int adapter); +int mxm_wmi_call_mxmx(int adapter); bool mxm_wmi_supported(void); #endif -- cgit v1.2.3 From 81f8115305f821335cf9e16110bf0806f7b93283 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Thu, 28 Apr 2011 13:25:36 +0900 Subject: i2c: i2c-sh_mobile bus speed platform data V2 Add support to the i2c-sh_mobile driver for setting the I2C bus speed using platform data. Signed-off-by: Magnus Damm Signed-off-by: Ben Dooks --- drivers/i2c/busses/i2c-sh_mobile.c | 13 +++++++++++-- include/linux/i2c/i2c-sh_mobile.h | 10 ++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 include/linux/i2c/i2c-sh_mobile.h (limited to 'include/linux') diff --git a/drivers/i2c/busses/i2c-sh_mobile.c b/drivers/i2c/busses/i2c-sh_mobile.c index 5a1d37d0b62f..d917dd1cc091 100644 --- a/drivers/i2c/busses/i2c-sh_mobile.c +++ b/drivers/i2c/busses/i2c-sh_mobile.c @@ -32,6 +32,7 @@ #include #include #include +#include /* Transmit operation: */ /* */ @@ -117,7 +118,7 @@ struct sh_mobile_i2c_data { struct device *dev; void __iomem *reg; struct i2c_adapter adap; - + unsigned long bus_speed; struct clk *clk; u_int8_t icic; u_int8_t iccl; @@ -205,7 +206,7 @@ static void activate_ch(struct sh_mobile_i2c_data *pd) * We also round off the result. */ num = i2c_clk * 5; - denom = NORMAL_SPEED * 9; + denom = pd->bus_speed * 9; tmp = num * 10 / denom; if (tmp % 10 >= 5) pd->iccl = (u_int8_t)((num/denom) + 1); @@ -574,6 +575,7 @@ static int sh_mobile_i2c_hook_irqs(struct platform_device *dev, int hook) static int sh_mobile_i2c_probe(struct platform_device *dev) { + struct i2c_sh_mobile_platform_data *pdata = dev->dev.platform_data; struct sh_mobile_i2c_data *pd; struct i2c_adapter *adap; struct resource *res; @@ -618,6 +620,11 @@ static int sh_mobile_i2c_probe(struct platform_device *dev) goto err_irq; } + /* Use platformd data bus speed or NORMAL_SPEED */ + pd->bus_speed = NORMAL_SPEED; + if (pdata && pdata->bus_speed) + pd->bus_speed = pdata->bus_speed; + /* The IIC blocks on SH-Mobile ARM processors * come with two new bits in ICIC. */ @@ -658,6 +665,8 @@ static int sh_mobile_i2c_probe(struct platform_device *dev) goto err_all; } + dev_info(&dev->dev, "I2C adapter %d with bus speed %lu Hz\n", + adap->nr, pd->bus_speed); return 0; err_all: diff --git a/include/linux/i2c/i2c-sh_mobile.h b/include/linux/i2c/i2c-sh_mobile.h new file mode 100644 index 000000000000..beda7081aead --- /dev/null +++ b/include/linux/i2c/i2c-sh_mobile.h @@ -0,0 +1,10 @@ +#ifndef __I2C_SH_MOBILE_H__ +#define __I2C_SH_MOBILE_H__ + +#include + +struct i2c_sh_mobile_platform_data { + unsigned long bus_speed; +}; + +#endif /* __I2C_SH_MOBILE_H__ */ -- cgit v1.2.3 From e70bdd41bd0ead91b4a43e9d656ac1569d7c8779 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 11 May 2011 16:35:30 -0700 Subject: Input: rotary-encoder - add support for half-period encoders Add support for encoders that have two detents per input signal period. Signed-off-by: Johan Hovold Acked-by: Daniel Mack Signed-off-by: Dmitry Torokhov --- Documentation/input/rotary-encoder.txt | 13 +++++++++++ drivers/input/misc/rotary_encoder.c | 42 +++++++++++++++++++++++++++++++--- include/linux/rotary_encoder.h | 1 + 3 files changed, 53 insertions(+), 3 deletions(-) (limited to 'include/linux') diff --git a/Documentation/input/rotary-encoder.txt b/Documentation/input/rotary-encoder.txt index 943e8f6f2b15..92e68bce13a4 100644 --- a/Documentation/input/rotary-encoder.txt +++ b/Documentation/input/rotary-encoder.txt @@ -9,6 +9,9 @@ peripherals with two wires. The outputs are phase-shifted by 90 degrees and by triggering on falling and rising edges, the turn direction can be determined. +Some encoders have both outputs low in stable states, whereas others also have +a stable state with both outputs high (half-period mode). + The phase diagram of these two outputs look like this: _____ _____ _____ @@ -26,6 +29,8 @@ The phase diagram of these two outputs look like this: |<-------->| one step + |<-->| + one step (half-period mode) For more information, please see http://en.wikipedia.org/wiki/Rotary_encoder @@ -34,6 +39,13 @@ For more information, please see 1. Events / state machine ------------------------- +In half-period mode, state a) and c) above are used to determine the +rotational direction based on the last stable state. Events are reported in +states b) and d) given that the new stable state is different from the last +(i.e. the rotation was not reversed half-way). + +Otherwise, the following apply: + a) Rising edge on channel A, channel B in low state This state is used to recognize a clockwise turn @@ -96,6 +108,7 @@ static struct rotary_encoder_platform_data my_rotary_encoder_info = { .gpio_b = GPIO_ROTARY_B, .inverted_a = 0, .inverted_b = 0, + .half_period = false, }; static struct platform_device rotary_encoder_device = { diff --git a/drivers/input/misc/rotary_encoder.c b/drivers/input/misc/rotary_encoder.c index 077b80bdca69..2c8b84dd9dac 100644 --- a/drivers/input/misc/rotary_encoder.c +++ b/drivers/input/misc/rotary_encoder.c @@ -2,6 +2,7 @@ * rotary_encoder.c * * (c) 2009 Daniel Mack + * Copyright (C) 2011 Johan Hovold * * state machine code inspired by code from Tim Ruetz * @@ -38,6 +39,8 @@ struct rotary_encoder { bool armed; unsigned char dir; /* 0 - clockwise, 1 - CCW */ + + char last_stable; }; static int rotary_encoder_get_state(struct rotary_encoder_platform_data *pdata) @@ -112,11 +115,37 @@ static irqreturn_t rotary_encoder_irq(int irq, void *dev_id) return IRQ_HANDLED; } +static irqreturn_t rotary_encoder_half_period_irq(int irq, void *dev_id) +{ + struct rotary_encoder *encoder = dev_id; + int state; + + state = rotary_encoder_get_state(encoder->pdata); + + switch (state) { + case 0x00: + case 0x03: + if (state != encoder->last_stable) { + rotary_encoder_report_event(encoder); + encoder->last_stable = state; + } + break; + + case 0x01: + case 0x02: + encoder->dir = (encoder->last_stable + state) & 0x01; + break; + } + + return IRQ_HANDLED; +} + static int __devinit rotary_encoder_probe(struct platform_device *pdev) { struct rotary_encoder_platform_data *pdata = pdev->dev.platform_data; struct rotary_encoder *encoder; struct input_dev *input; + irq_handler_t handler; int err; if (!pdata) { @@ -187,7 +216,14 @@ static int __devinit rotary_encoder_probe(struct platform_device *pdev) } /* request the IRQs */ - err = request_irq(encoder->irq_a, &rotary_encoder_irq, + if (pdata->half_period) { + handler = &rotary_encoder_half_period_irq; + encoder->last_stable = rotary_encoder_get_state(pdata); + } else { + handler = &rotary_encoder_irq; + } + + err = request_irq(encoder->irq_a, handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DRV_NAME, encoder); if (err) { @@ -196,7 +232,7 @@ static int __devinit rotary_encoder_probe(struct platform_device *pdev) goto exit_free_gpio_b; } - err = request_irq(encoder->irq_b, &rotary_encoder_irq, + err = request_irq(encoder->irq_b, handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DRV_NAME, encoder); if (err) { @@ -264,5 +300,5 @@ module_exit(rotary_encoder_exit); MODULE_ALIAS("platform:" DRV_NAME); MODULE_DESCRIPTION("GPIO rotary encoder driver"); -MODULE_AUTHOR("Daniel Mack "); +MODULE_AUTHOR("Daniel Mack , Johan Hovold"); MODULE_LICENSE("GPL v2"); diff --git a/include/linux/rotary_encoder.h b/include/linux/rotary_encoder.h index 215278b8df2a..3f594dce5716 100644 --- a/include/linux/rotary_encoder.h +++ b/include/linux/rotary_encoder.h @@ -10,6 +10,7 @@ struct rotary_encoder_platform_data { unsigned int inverted_b; bool relative_axis; bool rollover; + bool half_period; }; #endif /* __ROTARY_ENCODER_H__ */ -- cgit v1.2.3 From ebcaaad9d5e8fd284b8dcf25a24999420f0f56d8 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Wed, 11 May 2011 16:24:08 -0700 Subject: Input: ads7846 - add possibility to use external vref on ads7846 Just set vref_mv in your platform config to use external vref. Otherwise the internal one is used. Signed-off-by: Alexander Stein Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/ads7846.c | 19 +++++++++++-------- include/linux/spi/ads7846.h | 3 ++- 2 files changed, 13 insertions(+), 9 deletions(-) (limited to 'include/linux') diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c index c24946f51256..e4ee43e159d2 100644 --- a/drivers/input/touchscreen/ads7846.c +++ b/drivers/input/touchscreen/ads7846.c @@ -109,6 +109,7 @@ struct ads7846 { u16 pressure_max; bool swap_xy; + bool use_internal; struct ads7846_packet *packet; @@ -300,7 +301,6 @@ static int ads7846_read12_ser(struct device *dev, unsigned command) struct ads7846 *ts = dev_get_drvdata(dev); struct ser_req *req; int status; - int use_internal; req = kzalloc(sizeof *req, GFP_KERNEL); if (!req) @@ -308,11 +308,8 @@ static int ads7846_read12_ser(struct device *dev, unsigned command) spi_message_init(&req->msg); - /* FIXME boards with ads7846 might use external vref instead ... */ - use_internal = (ts->model == 7846); - /* maybe turn on internal vREF, and let it settle */ - if (use_internal) { + if (ts->use_internal) { req->ref_on = REF_ON; req->xfer[0].tx_buf = &req->ref_on; req->xfer[0].len = 1; @@ -324,8 +321,14 @@ static int ads7846_read12_ser(struct device *dev, unsigned command) /* for 1uF, settle for 800 usec; no cap, 100 usec. */ req->xfer[1].delay_usecs = ts->vref_delay_usecs; spi_message_add_tail(&req->xfer[1], &req->msg); + + /* Enable reference voltage */ + command |= ADS_PD10_REF_ON; } + /* Enable ADC in every case */ + command |= ADS_PD10_ADC_ON; + /* take sample */ req->command = (u8) command; req->xfer[2].tx_buf = &req->command; @@ -409,7 +412,7 @@ name ## _show(struct device *dev, struct device_attribute *attr, char *buf) \ { \ struct ads7846 *ts = dev_get_drvdata(dev); \ ssize_t v = ads7846_read12_ser(dev, \ - READ_12BIT_SER(var) | ADS_PD10_ALL_ON); \ + READ_12BIT_SER(var)); \ if (v < 0) \ return v; \ return sprintf(buf, "%u\n", adjust(ts, v)); \ @@ -502,6 +505,7 @@ static int ads784x_hwmon_register(struct spi_device *spi, struct ads7846 *ts) if (!ts->vref_mv) { dev_dbg(&spi->dev, "assuming 2.5V internal vREF\n"); ts->vref_mv = 2500; + ts->use_internal = true; } break; case 7845: @@ -1333,8 +1337,7 @@ static int __devinit ads7846_probe(struct spi_device *spi) if (ts->model == 7845) ads7845_read12_ser(&spi->dev, PWRDOWN); else - (void) ads7846_read12_ser(&spi->dev, - READ_12BIT_SER(vaux) | ADS_PD10_ALL_ON); + (void) ads7846_read12_ser(&spi->dev, READ_12BIT_SER(vaux)); err = sysfs_create_group(&spi->dev.kobj, &ads784x_attr_group); if (err) diff --git a/include/linux/spi/ads7846.h b/include/linux/spi/ads7846.h index 92bd0839d5b4..c64de9dd7631 100644 --- a/include/linux/spi/ads7846.h +++ b/include/linux/spi/ads7846.h @@ -14,7 +14,8 @@ enum ads7846_filter { struct ads7846_platform_data { u16 model; /* 7843, 7845, 7846, 7873. */ u16 vref_delay_usecs; /* 0 for external vref; etc */ - u16 vref_mv; /* external vref value, milliVolts */ + u16 vref_mv; /* external vref value, milliVolts + * ads7846: if 0, use internal vref */ bool keep_vref_on; /* set to keep vref on for differential * measurements as well */ bool swap_xy; /* swap x and y axes */ -- cgit v1.2.3 From 36682b81de32090f5c9fac165e71ab8939aab8c0 Mon Sep 17 00:00:00 2001 From: Zhang Jiejing Date: Wed, 11 May 2011 14:32:01 -0700 Subject: Input: add driver FSL MPR121 capacitive touch sensor This patch adds basic support for Freescale MPR121 capacitive touch sensor. It's an i2c controller with up to 12 capacitance sensing inputs. Product information (data sheet, application notes) can be found here: http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=MPR121 Signed-off-by: Zhang Jiejing Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/Kconfig | 12 ++ drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/mpr121_touchkey.c | 339 +++++++++++++++++++++++++++++++ include/linux/i2c/mpr121_touchkey.h | 20 ++ 4 files changed, 372 insertions(+) create mode 100644 drivers/input/keyboard/mpr121_touchkey.c create mode 100644 include/linux/i2c/mpr121_touchkey.h (limited to 'include/linux') diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index b16bed038f72..dec090ac7d31 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -325,6 +325,18 @@ config KEYBOARD_MCS To compile this driver as a module, choose M here: the module will be called mcs_touchkey. +config KEYBOARD_MPR121 + tristate "Freescale MPR121 Touchkey" + depends on I2C + help + Say Y here if you have Freescale MPR121 touchkey controller + chip in your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mpr121_touchkey. + config KEYBOARD_IMX tristate "IMX keypad support" depends on ARCH_MXC diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 878e6c20deb0..b45361009f2e 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o obj-$(CONFIG_KEYBOARD_MATRIX) += matrix_keypad.o obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o obj-$(CONFIG_KEYBOARD_MCS) += mcs_touchkey.o +obj-$(CONFIG_KEYBOARD_MPR121) += mpr121_touchkey.o obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o obj-$(CONFIG_KEYBOARD_NOMADIK) += nomadik-ske-keypad.o obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o diff --git a/drivers/input/keyboard/mpr121_touchkey.c b/drivers/input/keyboard/mpr121_touchkey.c new file mode 100644 index 000000000000..0a9e81194888 --- /dev/null +++ b/drivers/input/keyboard/mpr121_touchkey.c @@ -0,0 +1,339 @@ +/* + * Touchkey driver for Freescale MPR121 Controllor + * + * Copyright (C) 2011 Freescale Semiconductor, Inc. + * Author: Zhang Jiejing + * + * Based on mcs_touchkey.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register definitions */ +#define ELE_TOUCH_STATUS_0_ADDR 0x0 +#define ELE_TOUCH_STATUS_1_ADDR 0X1 +#define MHD_RISING_ADDR 0x2b +#define NHD_RISING_ADDR 0x2c +#define NCL_RISING_ADDR 0x2d +#define FDL_RISING_ADDR 0x2e +#define MHD_FALLING_ADDR 0x2f +#define NHD_FALLING_ADDR 0x30 +#define NCL_FALLING_ADDR 0x31 +#define FDL_FALLING_ADDR 0x32 +#define ELE0_TOUCH_THRESHOLD_ADDR 0x41 +#define ELE0_RELEASE_THRESHOLD_ADDR 0x42 +#define AFE_CONF_ADDR 0x5c +#define FILTER_CONF_ADDR 0x5d + +/* + * ELECTRODE_CONF_ADDR: This register configures the number of + * enabled capacitance sensing inputs and its run/suspend mode. + */ +#define ELECTRODE_CONF_ADDR 0x5e +#define AUTO_CONFIG_CTRL_ADDR 0x7b +#define AUTO_CONFIG_USL_ADDR 0x7d +#define AUTO_CONFIG_LSL_ADDR 0x7e +#define AUTO_CONFIG_TL_ADDR 0x7f + +/* Threshold of touch/release trigger */ +#define TOUCH_THRESHOLD 0x0f +#define RELEASE_THRESHOLD 0x0a +/* Masks for touch and release triggers */ +#define TOUCH_STATUS_MASK 0xfff +/* MPR121 has 12 keys */ +#define MPR121_MAX_KEY_COUNT 12 + +struct mpr121_touchkey { + struct i2c_client *client; + struct input_dev *input_dev; + unsigned int key_val; + unsigned int statusbits; + unsigned int keycount; + u16 keycodes[MPR121_MAX_KEY_COUNT]; +}; + +struct mpr121_init_register { + int addr; + u8 val; +}; + +static const struct mpr121_init_register init_reg_table[] __devinitconst = { + { MHD_RISING_ADDR, 0x1 }, + { NHD_RISING_ADDR, 0x1 }, + { MHD_FALLING_ADDR, 0x1 }, + { NHD_FALLING_ADDR, 0x1 }, + { NCL_FALLING_ADDR, 0xff }, + { FDL_FALLING_ADDR, 0x02 }, + { FILTER_CONF_ADDR, 0x04 }, + { AFE_CONF_ADDR, 0x0b }, + { AUTO_CONFIG_CTRL_ADDR, 0x0b }, +}; + +static irqreturn_t mpr_touchkey_interrupt(int irq, void *dev_id) +{ + struct mpr121_touchkey *mpr121 = dev_id; + struct i2c_client *client = mpr121->client; + struct input_dev *input = mpr121->input_dev; + unsigned int key_num, key_val, pressed; + int reg; + + reg = i2c_smbus_read_byte_data(client, ELE_TOUCH_STATUS_1_ADDR); + if (reg < 0) { + dev_err(&client->dev, "i2c read error [%d]\n", reg); + goto out; + } + + reg <<= 8; + reg |= i2c_smbus_read_byte_data(client, ELE_TOUCH_STATUS_0_ADDR); + if (reg < 0) { + dev_err(&client->dev, "i2c read error [%d]\n", reg); + goto out; + } + + reg &= TOUCH_STATUS_MASK; + /* use old press bit to figure out which bit changed */ + key_num = ffs(reg ^ mpr121->statusbits) - 1; + pressed = reg & (1 << key_num); + mpr121->statusbits = reg; + + key_val = mpr121->keycodes[key_num]; + + input_event(input, EV_MSC, MSC_SCAN, key_num); + input_report_key(input, key_val, pressed); + input_sync(input); + + dev_dbg(&client->dev, "key %d %d %s\n", key_num, key_val, + pressed ? "pressed" : "released"); + +out: + return IRQ_HANDLED; +} + +static int __devinit mpr121_phys_init(const struct mpr121_platform_data *pdata, + struct mpr121_touchkey *mpr121, + struct i2c_client *client) +{ + const struct mpr121_init_register *reg; + unsigned char usl, lsl, tl; + int i, t, vdd, ret; + + /* Set up touch/release threshold for ele0-ele11 */ + for (i = 0; i <= MPR121_MAX_KEY_COUNT; i++) { + t = ELE0_TOUCH_THRESHOLD_ADDR + (i * 2); + ret = i2c_smbus_write_byte_data(client, t, TOUCH_THRESHOLD); + if (ret < 0) + goto err_i2c_write; + ret = i2c_smbus_write_byte_data(client, t + 1, + RELEASE_THRESHOLD); + if (ret < 0) + goto err_i2c_write; + } + + /* Set up init register */ + for (i = 0; i < ARRAY_SIZE(init_reg_table); i++) { + reg = &init_reg_table[i]; + ret = i2c_smbus_write_byte_data(client, reg->addr, reg->val); + if (ret < 0) + goto err_i2c_write; + } + + + /* + * Capacitance on sensing input varies and needs to be compensated. + * The internal MPR121-auto-configuration can do this if it's + * registers are set properly (based on pdata->vdd_uv). + */ + vdd = pdata->vdd_uv / 1000; + usl = ((vdd - 700) * 256) / vdd; + lsl = (usl * 65) / 100; + tl = (usl * 90) / 100; + ret = i2c_smbus_write_byte_data(client, AUTO_CONFIG_USL_ADDR, usl); + ret |= i2c_smbus_write_byte_data(client, AUTO_CONFIG_LSL_ADDR, lsl); + ret |= i2c_smbus_write_byte_data(client, AUTO_CONFIG_TL_ADDR, tl); + ret |= i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, + mpr121->keycount); + if (ret != 0) + goto err_i2c_write; + + dev_dbg(&client->dev, "set up with %x keys.\n", mpr121->keycount); + + return 0; + +err_i2c_write: + dev_err(&client->dev, "i2c write error: %d\n", ret); + return ret; +} + +static int __devinit mpr_touchkey_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct mpr121_platform_data *pdata = client->dev.platform_data; + struct mpr121_touchkey *mpr121; + struct input_dev *input_dev; + int error; + int i; + + if (!pdata) { + dev_err(&client->dev, "no platform data defined\n"); + return -EINVAL; + } + + if (!pdata->keymap || !pdata->keymap_size) { + dev_err(&client->dev, "missing keymap data\n"); + return -EINVAL; + } + + if (pdata->keymap_size > MPR121_MAX_KEY_COUNT) { + dev_err(&client->dev, "too many keys defined\n"); + return -EINVAL; + } + + if (!client->irq) { + dev_err(&client->dev, "irq number should not be zero\n"); + return -EINVAL; + } + + mpr121 = kzalloc(sizeof(struct mpr121_touchkey), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!mpr121 || !input_dev) { + dev_err(&client->dev, "Failed to allocate memory\n"); + error = -ENOMEM; + goto err_free_mem; + } + + mpr121->client = client; + mpr121->input_dev = input_dev; + mpr121->keycount = pdata->keymap_size; + + input_dev->name = "Freescale MPR121 Touchkey"; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + + input_dev->keycode = mpr121->keycodes; + input_dev->keycodesize = sizeof(mpr121->keycodes[0]); + input_dev->keycodemax = mpr121->keycount; + + for (i = 0; i < pdata->keymap_size; i++) { + input_set_capability(input_dev, EV_KEY, pdata->keymap[i]); + mpr121->keycodes[i] = pdata->keymap[i]; + } + + error = mpr121_phys_init(pdata, mpr121, client); + if (error) { + dev_err(&client->dev, "Failed to init register\n"); + goto err_free_mem; + } + + error = request_threaded_irq(client->irq, NULL, + mpr_touchkey_interrupt, + IRQF_TRIGGER_FALLING, + client->dev.driver->name, mpr121); + if (error) { + dev_err(&client->dev, "Failed to register interrupt\n"); + goto err_free_mem; + } + + error = input_register_device(input_dev); + if (error) + goto err_free_irq; + + i2c_set_clientdata(client, mpr121); + device_init_wakeup(&client->dev, pdata->wakeup); + + return 0; + +err_free_irq: + free_irq(client->irq, mpr121); +err_free_mem: + input_free_device(input_dev); + kfree(mpr121); + return error; +} + +static int __devexit mpr_touchkey_remove(struct i2c_client *client) +{ + struct mpr121_touchkey *mpr121 = i2c_get_clientdata(client); + + free_irq(client->irq, mpr121); + input_unregister_device(mpr121->input_dev); + kfree(mpr121); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mpr_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + if (device_may_wakeup(&client->dev)) + enable_irq_wake(client->irq); + + i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, 0x00); + + return 0; +} + +static int mpr_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mpr121_touchkey *mpr121 = i2c_get_clientdata(client); + + if (device_may_wakeup(&client->dev)) + disable_irq_wake(client->irq); + + i2c_smbus_write_byte_data(client, ELECTRODE_CONF_ADDR, + mpr121->keycount); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(mpr121_touchkey_pm_ops, mpr_suspend, mpr_resume); + +static const struct i2c_device_id mpr121_id[] = { + { "mpr121_touchkey", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpr121_id); + +static struct i2c_driver mpr_touchkey_driver = { + .driver = { + .name = "mpr121", + .owner = THIS_MODULE, + .pm = &mpr121_touchkey_pm_ops, + }, + .id_table = mpr121_id, + .probe = mpr_touchkey_probe, + .remove = __devexit_p(mpr_touchkey_remove), +}; + +static int __init mpr_touchkey_init(void) +{ + return i2c_add_driver(&mpr_touchkey_driver); +} +module_init(mpr_touchkey_init); + +static void __exit mpr_touchkey_exit(void) +{ + i2c_del_driver(&mpr_touchkey_driver); +} +module_exit(mpr_touchkey_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Zhang Jiejing "); +MODULE_DESCRIPTION("Touch Key driver for Freescale MPR121 Chip"); diff --git a/include/linux/i2c/mpr121_touchkey.h b/include/linux/i2c/mpr121_touchkey.h new file mode 100644 index 000000000000..f0bcc38bbb97 --- /dev/null +++ b/include/linux/i2c/mpr121_touchkey.h @@ -0,0 +1,20 @@ +/* Header file for Freescale MPR121 Capacitive Touch Sensor */ + +#ifndef _MPR121_TOUCHKEY_H +#define _MPR121_TOUCHKEY_H + +/** + * struct mpr121_platform_data - platform data for mpr121 sensor + * @keymap: pointer to array of KEY_* values representing keymap + * @keymap_size: size of the keymap + * @wakeup: configure the button as a wake-up source + * @vdd_uv: VDD voltage in uV + */ +struct mpr121_platform_data { + const unsigned short *keymap; + unsigned int keymap_size; + bool wakeup; + int vdd_uv; +}; + +#endif /* _MPR121_TOUCHKEY_H */ -- cgit v1.2.3 From 3532cb0ca2774b05e3c660f536ba3d1b38061fc9 Mon Sep 17 00:00:00 2001 From: Michael Hennerich Date: Mon, 16 May 2011 23:17:34 -0700 Subject: Input: ad714x - allow platform code to specify irqflags Add option to specify irqflags in platfrom data. Also update copyright notice. Signed-off-by: Michael Hennerich Tested-by: Jean-Francois Dagenais Signed-off-by: Dmitry Torokhov --- drivers/input/misc/ad714x.c | 4 +++- include/linux/input/ad714x.h | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/drivers/input/misc/ad714x.c b/drivers/input/misc/ad714x.c index 0f2db45f730c..5f683ec2999e 100644 --- a/drivers/input/misc/ad714x.c +++ b/drivers/input/misc/ad714x.c @@ -1257,7 +1257,9 @@ struct ad714x_chip *ad714x_probe(struct device *dev, u16 bus_type, int irq, } error = request_threaded_irq(ad714x->irq, NULL, ad714x_interrupt_thread, - IRQF_TRIGGER_FALLING, "ad714x_captouch", ad714x); + plat_data->irqflags ? + plat_data->irqflags : IRQF_TRIGGER_FALLING, + "ad714x_captouch", ad714x); if (error) { dev_err(dev, "can't allocate irq %d\n", ad714x->irq); goto err_unreg_dev; diff --git a/include/linux/input/ad714x.h b/include/linux/input/ad714x.h index 0cbe5e81482e..d388d857bf14 100644 --- a/include/linux/input/ad714x.h +++ b/include/linux/input/ad714x.h @@ -6,7 +6,7 @@ * The platform_data for the device's "struct device" holds this * information. * - * Copyright 2009 Analog Devices Inc. + * Copyright 2009-2011 Analog Devices Inc. * * Licensed under the GPL-2 or later. */ @@ -58,6 +58,7 @@ struct ad714x_platform_data { struct ad714x_button_plat *button; unsigned short stage_cfg_reg[STAGE_NUM][STAGE_CFGREG_NUM]; unsigned short sys_cfg_reg[SYS_CFGREG_NUM]; + unsigned long irqflags; }; #endif -- cgit v1.2.3 From 84005eb5ea2939d448047fcf6924b3b9b6ed974d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 17 May 2011 09:31:01 -0700 Subject: Input: tsc2007 - add max_rt parameter to platform data Finger touch events or very quick stylus events on low-quality panels can cause the tsc2007 to read bogus values. Looking at oscilloscope snapshots, this seems to be caused by the touch event disappearing during the measurements. These bogus values result in misclicks, where the X and Y values deviate from the real position. Most of these misclicks can be filtered out by setting a low enough threshold for the maximum resistance (which is loosely the inverse of the pressure) allowed to consider a set of values valid. Since this behaviour is largely dependent on the type and quality of the panel, this commit introduces the max_rt parameter. The default value is kept at MAX_12BIT. Signed-off-by: Thierry Reding Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/tsc2007.c | 4 +++- include/linux/i2c/tsc2007.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007.c index df4ae354969d..8c48a91a6783 100644 --- a/drivers/input/touchscreen/tsc2007.c +++ b/drivers/input/touchscreen/tsc2007.c @@ -75,6 +75,7 @@ struct tsc2007 { u16 model; u16 x_plate_ohms; + u16 max_rt; bool pendown; int irq; @@ -185,7 +186,7 @@ static void tsc2007_work(struct work_struct *work) tsc2007_read_values(ts, &tc); rt = tsc2007_calculate_pressure(ts, &tc); - if (rt > MAX_12BIT) { + if (rt > ts->max_rt) { /* * Sample found inconsistent by debouncing or pressure is * beyond the maximum. Don't report it to user space, @@ -294,6 +295,7 @@ static int __devinit tsc2007_probe(struct i2c_client *client, ts->model = pdata->model; ts->x_plate_ohms = pdata->x_plate_ohms; + ts->max_rt = pdata->max_rt ? : MAX_12BIT; ts->get_pendown_state = pdata->get_pendown_state; ts->clear_penirq = pdata->clear_penirq; diff --git a/include/linux/i2c/tsc2007.h b/include/linux/i2c/tsc2007.h index c6361fbb7bf9..d95f11347314 100644 --- a/include/linux/i2c/tsc2007.h +++ b/include/linux/i2c/tsc2007.h @@ -6,6 +6,7 @@ struct tsc2007_platform_data { u16 model; /* 2007. */ u16 x_plate_ohms; + u16 max_rt; /* max. resistance above which samples are ignored */ int (*get_pendown_state)(void); void (*clear_penirq)(void); /* If needed, clear 2nd level -- cgit v1.2.3 From 2d137c7ed25f9754f52495af570897ad993e4fe7 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 17 May 2011 09:31:33 -0700 Subject: Input: tsc2007 - add poll_delay parameter to platform data Depending on the quality of the touch panel, the time for the X-, X+, Y- and Y+ inputs to settle may vary. The poll_delay parameter can be used to override the default of 1 millisecond. Cc: Dmitry Torokhov Cc: Kwangwoo Lee Signed-off-by: Thierry Reding Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/tsc2007.c | 5 +++-- include/linux/i2c/tsc2007.h | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007.c index 8c48a91a6783..faa82ddb014b 100644 --- a/drivers/input/touchscreen/tsc2007.c +++ b/drivers/input/touchscreen/tsc2007.c @@ -27,7 +27,6 @@ #include #include -#define TS_POLL_DELAY 1 /* ms delay between samples */ #define TS_POLL_PERIOD 1 /* ms delay between samples */ #define TSC2007_MEASURE_TEMP0 (0x0 << 4) @@ -76,6 +75,7 @@ struct tsc2007 { u16 model; u16 x_plate_ohms; u16 max_rt; + unsigned long poll_delay; bool pendown; int irq; @@ -242,7 +242,7 @@ static irqreturn_t tsc2007_irq(int irq, void *handle) if (!ts->get_pendown_state || likely(ts->get_pendown_state())) { disable_irq_nosync(ts->irq); schedule_delayed_work(&ts->work, - msecs_to_jiffies(TS_POLL_DELAY)); + msecs_to_jiffies(ts->poll_delay)); } if (ts->clear_penirq) @@ -296,6 +296,7 @@ static int __devinit tsc2007_probe(struct i2c_client *client, ts->model = pdata->model; ts->x_plate_ohms = pdata->x_plate_ohms; ts->max_rt = pdata->max_rt ? : MAX_12BIT; + ts->poll_delay = pdata->poll_delay ? : 1; ts->get_pendown_state = pdata->get_pendown_state; ts->clear_penirq = pdata->clear_penirq; diff --git a/include/linux/i2c/tsc2007.h b/include/linux/i2c/tsc2007.h index d95f11347314..342b130a0a98 100644 --- a/include/linux/i2c/tsc2007.h +++ b/include/linux/i2c/tsc2007.h @@ -7,6 +7,8 @@ struct tsc2007_platform_data { u16 model; /* 2007. */ u16 x_plate_ohms; u16 max_rt; /* max. resistance above which samples are ignored */ + unsigned long poll_delay; /* delay (in ms) after pen-down event + before polling starts */ int (*get_pendown_state)(void); void (*clear_penirq)(void); /* If needed, clear 2nd level -- cgit v1.2.3 From 1af38eac8cdb70dbad3086b4f1bf7296eb1b09b1 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 17 May 2011 09:32:02 -0700 Subject: Input: tsc2007 - add poll_period parameter to platform data This new parameter allows the polling frequency to be configured while keeping the default of once every millisecond. Signed-off-by: Thierry Reding Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/tsc2007.c | 6 +++--- include/linux/i2c/tsc2007.h | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'include/linux') diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007.c index faa82ddb014b..87c40c79f71e 100644 --- a/drivers/input/touchscreen/tsc2007.c +++ b/drivers/input/touchscreen/tsc2007.c @@ -27,8 +27,6 @@ #include #include -#define TS_POLL_PERIOD 1 /* ms delay between samples */ - #define TSC2007_MEASURE_TEMP0 (0x0 << 4) #define TSC2007_MEASURE_AUX (0x2 << 4) #define TSC2007_MEASURE_TEMP1 (0x4 << 4) @@ -76,6 +74,7 @@ struct tsc2007 { u16 x_plate_ohms; u16 max_rt; unsigned long poll_delay; + unsigned long poll_period; bool pendown; int irq; @@ -230,7 +229,7 @@ static void tsc2007_work(struct work_struct *work) out: if (ts->pendown || debounced) schedule_delayed_work(&ts->work, - msecs_to_jiffies(TS_POLL_PERIOD)); + msecs_to_jiffies(ts->poll_period)); else enable_irq(ts->irq); } @@ -297,6 +296,7 @@ static int __devinit tsc2007_probe(struct i2c_client *client, ts->x_plate_ohms = pdata->x_plate_ohms; ts->max_rt = pdata->max_rt ? : MAX_12BIT; ts->poll_delay = pdata->poll_delay ? : 1; + ts->poll_period = pdata->poll_period ? : 1; ts->get_pendown_state = pdata->get_pendown_state; ts->clear_penirq = pdata->clear_penirq; diff --git a/include/linux/i2c/tsc2007.h b/include/linux/i2c/tsc2007.h index 342b130a0a98..a51dac081aca 100644 --- a/include/linux/i2c/tsc2007.h +++ b/include/linux/i2c/tsc2007.h @@ -9,6 +9,7 @@ struct tsc2007_platform_data { u16 max_rt; /* max. resistance above which samples are ignored */ unsigned long poll_delay; /* delay (in ms) after pen-down event before polling starts */ + unsigned long poll_period; /* time (in ms) between samples */ int (*get_pendown_state)(void); void (*clear_penirq)(void); /* If needed, clear 2nd level -- cgit v1.2.3 From 891e376b1e864519bf7e49fa741f473078318530 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 17 May 2011 09:32:29 -0700 Subject: Input: tsc2007 - add X, Y and Z fuzz factors to platform data These new platform-specific values can be used to set the fuzz parameter passed to the input_set_abs_params() function for the ABS_X, ABS_Y and ABS_PRESSURE axes. Signed-off-by: Thierry Reding Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/tsc2007.c | 7 ++++--- include/linux/i2c/tsc2007.h | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'include/linux') diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007.c index 87c40c79f71e..fadc11545b1e 100644 --- a/drivers/input/touchscreen/tsc2007.c +++ b/drivers/input/touchscreen/tsc2007.c @@ -310,9 +310,10 @@ static int __devinit tsc2007_probe(struct i2c_client *client, input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); - input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, 0, 0); - input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, 0, 0); - input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, 0, 0); + input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, pdata->fuzzx, 0); + input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, pdata->fuzzy, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, + pdata->fuzzz, 0); if (pdata->init_platform_hw) pdata->init_platform_hw(); diff --git a/include/linux/i2c/tsc2007.h b/include/linux/i2c/tsc2007.h index a51dac081aca..591427a63b06 100644 --- a/include/linux/i2c/tsc2007.h +++ b/include/linux/i2c/tsc2007.h @@ -10,6 +10,9 @@ struct tsc2007_platform_data { unsigned long poll_delay; /* delay (in ms) after pen-down event before polling starts */ unsigned long poll_period; /* time (in ms) between samples */ + int fuzzx; /* fuzz factor for X, Y and pressure axes */ + int fuzzy; + int fuzzz; int (*get_pendown_state)(void); void (*clear_penirq)(void); /* If needed, clear 2nd level -- cgit v1.2.3 From 9d2e173644bb5c42ff1b280fbdda3f195a7cf1f7 Mon Sep 17 00:00:00 2001 From: Michael Hennerich Date: Thu, 19 May 2011 00:59:32 -0700 Subject: Input: ADP5589 - new driver for I2C Keypad Decoder and I/O Expander From http://www.analog.com/ADP5589: The ADP5589 is an I/O port expander and keypad matrix decoder designed for QWERTY type phones that require a large keypad matrix and expanded I/O lines. Signed-off-by: Michael Hennerich Signed-off-by: Mike Frysinger Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/Kconfig | 10 + drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/adp5589-keys.c | 771 ++++++++++++++++++++++++++++++++++ include/linux/input/adp5589.h | 213 ++++++++++ 4 files changed, 995 insertions(+) create mode 100644 drivers/input/keyboard/adp5589-keys.c create mode 100644 include/linux/input/adp5589.h (limited to 'include/linux') diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index dec090ac7d31..69badb4e06aa 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -32,6 +32,16 @@ config KEYBOARD_ADP5588 To compile this driver as a module, choose M here: the module will be called adp5588-keys. +config KEYBOARD_ADP5589 + tristate "ADP5589 I2C QWERTY Keypad and IO Expander" + depends on I2C + help + Say Y here if you want to use a ADP5589 attached to your + system I2C bus. + + To compile this driver as a module, choose M here: the + module will be called adp5589-keys. + config KEYBOARD_AMIGA tristate "Amiga keyboard" depends on AMIGA diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index b45361009f2e..c49cf8e04cd7 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_KEYBOARD_ADP5520) += adp5520-keys.o obj-$(CONFIG_KEYBOARD_ADP5588) += adp5588-keys.o +obj-$(CONFIG_KEYBOARD_ADP5589) += adp5589-keys.o obj-$(CONFIG_KEYBOARD_AMIGA) += amikbd.o obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o diff --git a/drivers/input/keyboard/adp5589-keys.c b/drivers/input/keyboard/adp5589-keys.c new file mode 100644 index 000000000000..631598663aab --- /dev/null +++ b/drivers/input/keyboard/adp5589-keys.c @@ -0,0 +1,771 @@ +/* + * Description: keypad driver for ADP5589 + * I2C QWERTY Keypad and IO Expander + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * Copyright (C) 2010-2011 Analog Devices Inc. + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* GENERAL_CFG Register */ +#define OSC_EN (1 << 7) +#define CORE_CLK(x) (((x) & 0x3) << 5) +#define LCK_TRK_LOGIC (1 << 4) +#define LCK_TRK_GPI (1 << 3) +#define INT_CFG (1 << 1) +#define RST_CFG (1 << 0) + +/* INT_EN Register */ +#define LOGIC2_IEN (1 << 5) +#define LOGIC1_IEN (1 << 4) +#define LOCK_IEN (1 << 3) +#define OVRFLOW_IEN (1 << 2) +#define GPI_IEN (1 << 1) +#define EVENT_IEN (1 << 0) + +/* Interrupt Status Register */ +#define LOGIC2_INT (1 << 5) +#define LOGIC1_INT (1 << 4) +#define LOCK_INT (1 << 3) +#define OVRFLOW_INT (1 << 2) +#define GPI_INT (1 << 1) +#define EVENT_INT (1 << 0) + +/* STATUS Register */ + +#define LOGIC2_STAT (1 << 7) +#define LOGIC1_STAT (1 << 6) +#define LOCK_STAT (1 << 5) +#define KEC 0xF + +/* PIN_CONFIG_D Register */ +#define C4_EXTEND_CFG (1 << 6) /* RESET2 */ +#define R4_EXTEND_CFG (1 << 5) /* RESET1 */ + +/* LOCK_CFG */ +#define LOCK_EN (1 << 0) + +#define PTIME_MASK 0x3 +#define LTIME_MASK 0x3 + +/* Key Event Register xy */ +#define KEY_EV_PRESSED (1 << 7) +#define KEY_EV_MASK (0x7F) + +#define KEYP_MAX_EVENT 16 + +#define MAXGPIO 19 +#define ADP_BANK(offs) ((offs) >> 3) +#define ADP_BIT(offs) (1u << ((offs) & 0x7)) + +struct adp5589_kpad { + struct i2c_client *client; + struct input_dev *input; + unsigned short keycode[ADP5589_KEYMAPSIZE]; + const struct adp5589_gpi_map *gpimap; + unsigned short gpimapsize; + unsigned extend_cfg; +#ifdef CONFIG_GPIOLIB + unsigned char gpiomap[MAXGPIO]; + bool export_gpio; + struct gpio_chip gc; + struct mutex gpio_lock; /* Protect cached dir, dat_out */ + u8 dat_out[3]; + u8 dir[3]; +#endif +}; + +static int adp5589_read(struct i2c_client *client, u8 reg) +{ + int ret = i2c_smbus_read_byte_data(client, reg); + + if (ret < 0) + dev_err(&client->dev, "Read Error\n"); + + return ret; +} + +static int adp5589_write(struct i2c_client *client, u8 reg, u8 val) +{ + return i2c_smbus_write_byte_data(client, reg, val); +} + +#ifdef CONFIG_GPIOLIB +static int adp5589_gpio_get_value(struct gpio_chip *chip, unsigned off) +{ + struct adp5589_kpad *kpad = container_of(chip, struct adp5589_kpad, gc); + unsigned int bank = ADP_BANK(kpad->gpiomap[off]); + unsigned int bit = ADP_BIT(kpad->gpiomap[off]); + + return !!(adp5589_read(kpad->client, ADP5589_GPI_STATUS_A + bank) & + bit); +} + +static void adp5589_gpio_set_value(struct gpio_chip *chip, + unsigned off, int val) +{ + struct adp5589_kpad *kpad = container_of(chip, struct adp5589_kpad, gc); + unsigned int bank = ADP_BANK(kpad->gpiomap[off]); + unsigned int bit = ADP_BIT(kpad->gpiomap[off]); + + mutex_lock(&kpad->gpio_lock); + + if (val) + kpad->dat_out[bank] |= bit; + else + kpad->dat_out[bank] &= ~bit; + + adp5589_write(kpad->client, ADP5589_GPO_DATA_OUT_A + bank, + kpad->dat_out[bank]); + + mutex_unlock(&kpad->gpio_lock); +} + +static int adp5589_gpio_direction_input(struct gpio_chip *chip, unsigned off) +{ + struct adp5589_kpad *kpad = container_of(chip, struct adp5589_kpad, gc); + unsigned int bank = ADP_BANK(kpad->gpiomap[off]); + unsigned int bit = ADP_BIT(kpad->gpiomap[off]); + int ret; + + mutex_lock(&kpad->gpio_lock); + + kpad->dir[bank] &= ~bit; + ret = adp5589_write(kpad->client, ADP5589_GPIO_DIRECTION_A + bank, + kpad->dir[bank]); + + mutex_unlock(&kpad->gpio_lock); + + return ret; +} + +static int adp5589_gpio_direction_output(struct gpio_chip *chip, + unsigned off, int val) +{ + struct adp5589_kpad *kpad = container_of(chip, struct adp5589_kpad, gc); + unsigned int bank = ADP_BANK(kpad->gpiomap[off]); + unsigned int bit = ADP_BIT(kpad->gpiomap[off]); + int ret; + + mutex_lock(&kpad->gpio_lock); + + kpad->dir[bank] |= bit; + + if (val) + kpad->dat_out[bank] |= bit; + else + kpad->dat_out[bank] &= ~bit; + + ret = adp5589_write(kpad->client, ADP5589_GPO_DATA_OUT_A + bank, + kpad->dat_out[bank]); + ret |= adp5589_write(kpad->client, ADP5589_GPIO_DIRECTION_A + bank, + kpad->dir[bank]); + + mutex_unlock(&kpad->gpio_lock); + + return ret; +} + +static int __devinit adp5589_build_gpiomap(struct adp5589_kpad *kpad, + const struct adp5589_kpad_platform_data *pdata) +{ + bool pin_used[MAXGPIO]; + int n_unused = 0; + int i; + + memset(pin_used, false, sizeof(pin_used)); + + for (i = 0; i < MAXGPIO; i++) + if (pdata->keypad_en_mask & (1 << i)) + pin_used[i] = true; + + for (i = 0; i < kpad->gpimapsize; i++) + pin_used[kpad->gpimap[i].pin - ADP5589_GPI_PIN_BASE] = true; + + if (kpad->extend_cfg & R4_EXTEND_CFG) + pin_used[4] = true; + + if (kpad->extend_cfg & C4_EXTEND_CFG) + pin_used[12] = true; + + for (i = 0; i < MAXGPIO; i++) + if (!pin_used[i]) + kpad->gpiomap[n_unused++] = i; + + return n_unused; +} + +static int __devinit adp5589_gpio_add(struct adp5589_kpad *kpad) +{ + struct device *dev = &kpad->client->dev; + const struct adp5589_kpad_platform_data *pdata = dev->platform_data; + const struct adp5589_gpio_platform_data *gpio_data = pdata->gpio_data; + int i, error; + + if (!gpio_data) + return 0; + + kpad->gc.ngpio = adp5589_build_gpiomap(kpad, pdata); + if (kpad->gc.ngpio == 0) { + dev_info(dev, "No unused gpios left to export\n"); + return 0; + } + + kpad->export_gpio = true; + + kpad->gc.direction_input = adp5589_gpio_direction_input; + kpad->gc.direction_output = adp5589_gpio_direction_output; + kpad->gc.get = adp5589_gpio_get_value; + kpad->gc.set = adp5589_gpio_set_value; + kpad->gc.can_sleep = 1; + + kpad->gc.base = gpio_data->gpio_start; + kpad->gc.label = kpad->client->name; + kpad->gc.owner = THIS_MODULE; + + mutex_init(&kpad->gpio_lock); + + error = gpiochip_add(&kpad->gc); + if (error) { + dev_err(dev, "gpiochip_add failed, err: %d\n", error); + return error; + } + + for (i = 0; i <= ADP_BANK(MAXGPIO); i++) { + kpad->dat_out[i] = adp5589_read(kpad->client, + ADP5589_GPO_DATA_OUT_A + i); + kpad->dir[i] = adp5589_read(kpad->client, + ADP5589_GPIO_DIRECTION_A + i); + } + + if (gpio_data->setup) { + error = gpio_data->setup(kpad->client, + kpad->gc.base, kpad->gc.ngpio, + gpio_data->context); + if (error) + dev_warn(dev, "setup failed, %d\n", error); + } + + return 0; +} + +static void __devexit adp5589_gpio_remove(struct adp5589_kpad *kpad) +{ + struct device *dev = &kpad->client->dev; + const struct adp5589_kpad_platform_data *pdata = dev->platform_data; + const struct adp5589_gpio_platform_data *gpio_data = pdata->gpio_data; + int error; + + if (!kpad->export_gpio) + return; + + if (gpio_data->teardown) { + error = gpio_data->teardown(kpad->client, + kpad->gc.base, kpad->gc.ngpio, + gpio_data->context); + if (error) + dev_warn(dev, "teardown failed %d\n", error); + } + + error = gpiochip_remove(&kpad->gc); + if (error) + dev_warn(dev, "gpiochip_remove failed %d\n", error); +} +#else +static inline int adp5589_gpio_add(struct adp5589_kpad *kpad) +{ + return 0; +} + +static inline void adp5589_gpio_remove(struct adp5589_kpad *kpad) +{ +} +#endif + +static void adp5589_report_switches(struct adp5589_kpad *kpad, + int key, int key_val) +{ + int i; + + for (i = 0; i < kpad->gpimapsize; i++) { + if (key_val == kpad->gpimap[i].pin) { + input_report_switch(kpad->input, + kpad->gpimap[i].sw_evt, + key & KEY_EV_PRESSED); + break; + } + } +} + +static void adp5589_report_events(struct adp5589_kpad *kpad, int ev_cnt) +{ + int i; + + for (i = 0; i < ev_cnt; i++) { + int key = adp5589_read(kpad->client, ADP5589_FIFO_1 + i); + int key_val = key & KEY_EV_MASK; + + if (key_val >= ADP5589_GPI_PIN_BASE && + key_val <= ADP5589_GPI_PIN_END) { + adp5589_report_switches(kpad, key, key_val); + } else { + input_report_key(kpad->input, + kpad->keycode[key_val - 1], + key & KEY_EV_PRESSED); + } + } +} + +static irqreturn_t adp5589_irq(int irq, void *handle) +{ + struct adp5589_kpad *kpad = handle; + struct i2c_client *client = kpad->client; + int status, ev_cnt; + + status = adp5589_read(client, ADP5589_INT_STATUS); + + if (status & OVRFLOW_INT) /* Unlikely and should never happen */ + dev_err(&client->dev, "Event Overflow Error\n"); + + if (status & EVENT_INT) { + ev_cnt = adp5589_read(client, ADP5589_STATUS) & KEC; + if (ev_cnt) { + adp5589_report_events(kpad, ev_cnt); + input_sync(kpad->input); + } + } + + adp5589_write(client, ADP5589_INT_STATUS, status); /* Status is W1C */ + + return IRQ_HANDLED; +} + +static int __devinit adp5589_get_evcode(struct adp5589_kpad *kpad, unsigned short key) +{ + int i; + + for (i = 0; i < ADP5589_KEYMAPSIZE; i++) + if (key == kpad->keycode[i]) + return (i + 1) | KEY_EV_PRESSED; + + dev_err(&kpad->client->dev, "RESET/UNLOCK key not in keycode map\n"); + + return -EINVAL; +} + +static int __devinit adp5589_setup(struct adp5589_kpad *kpad) +{ + struct i2c_client *client = kpad->client; + const struct adp5589_kpad_platform_data *pdata = + client->dev.platform_data; + int i, ret; + unsigned char evt_mode1 = 0, evt_mode2 = 0, evt_mode3 = 0; + unsigned char pull_mask = 0; + + ret = adp5589_write(client, ADP5589_PIN_CONFIG_A, + pdata->keypad_en_mask & 0xFF); + ret |= adp5589_write(client, ADP5589_PIN_CONFIG_B, + (pdata->keypad_en_mask >> 8) & 0xFF); + ret |= adp5589_write(client, ADP5589_PIN_CONFIG_C, + (pdata->keypad_en_mask >> 16) & 0xFF); + + if (pdata->en_keylock) { + ret |= adp5589_write(client, ADP5589_UNLOCK1, + pdata->unlock_key1); + ret |= adp5589_write(client, ADP5589_UNLOCK2, + pdata->unlock_key2); + ret |= adp5589_write(client, ADP5589_UNLOCK_TIMERS, + pdata->unlock_timer & LTIME_MASK); + ret |= adp5589_write(client, ADP5589_LOCK_CFG, LOCK_EN); + } + + for (i = 0; i < KEYP_MAX_EVENT; i++) + ret |= adp5589_read(client, ADP5589_FIFO_1 + i); + + for (i = 0; i < pdata->gpimapsize; i++) { + unsigned short pin = pdata->gpimap[i].pin; + + if (pin <= ADP5589_GPI_PIN_ROW_END) { + evt_mode1 |= (1 << (pin - ADP5589_GPI_PIN_ROW_BASE)); + } else { + evt_mode2 |= + ((1 << (pin - ADP5589_GPI_PIN_COL_BASE)) & 0xFF); + evt_mode3 |= + ((1 << (pin - ADP5589_GPI_PIN_COL_BASE)) >> 8); + } + } + + if (pdata->gpimapsize) { + ret |= adp5589_write(client, ADP5589_GPI_EVENT_EN_A, evt_mode1); + ret |= adp5589_write(client, ADP5589_GPI_EVENT_EN_B, evt_mode2); + ret |= adp5589_write(client, ADP5589_GPI_EVENT_EN_C, evt_mode3); + } + + if (pdata->pull_dis_mask & pdata->pullup_en_100k & + pdata->pullup_en_300k & pdata->pulldown_en_300k) + dev_warn(&client->dev, "Conflicting pull resistor config\n"); + + for (i = 0; i < MAXGPIO; i++) { + unsigned val = 0; + + if (pdata->pullup_en_300k & (1 << i)) + val = 0; + else if (pdata->pulldown_en_300k & (1 << i)) + val = 1; + else if (pdata->pullup_en_100k & (1 << i)) + val = 2; + else if (pdata->pull_dis_mask & (1 << i)) + val = 3; + + pull_mask |= val << (2 * (i & 0x3)); + + if ((i & 0x3) == 0x3 || i == MAXGPIO - 1) { + ret |= adp5589_write(client, + ADP5589_RPULL_CONFIG_A + (i >> 2), + pull_mask); + pull_mask = 0; + } + } + + if (pdata->reset1_key_1 && pdata->reset1_key_2 && pdata->reset1_key_3) { + ret |= adp5589_write(client, ADP5589_RESET1_EVENT_A, + adp5589_get_evcode(kpad, + pdata->reset1_key_1)); + ret |= adp5589_write(client, ADP5589_RESET1_EVENT_B, + adp5589_get_evcode(kpad, + pdata->reset1_key_2)); + ret |= adp5589_write(client, ADP5589_RESET1_EVENT_C, + adp5589_get_evcode(kpad, + pdata->reset1_key_3)); + kpad->extend_cfg |= R4_EXTEND_CFG; + } + + if (pdata->reset2_key_1 && pdata->reset2_key_2) { + ret |= adp5589_write(client, ADP5589_RESET2_EVENT_A, + adp5589_get_evcode(kpad, + pdata->reset2_key_1)); + ret |= adp5589_write(client, ADP5589_RESET2_EVENT_B, + adp5589_get_evcode(kpad, + pdata->reset2_key_2)); + kpad->extend_cfg |= C4_EXTEND_CFG; + } + + if (kpad->extend_cfg) { + ret |= adp5589_write(client, ADP5589_RESET_CFG, + pdata->reset_cfg); + ret |= adp5589_write(client, ADP5589_PIN_CONFIG_D, + kpad->extend_cfg); + } + + for (i = 0; i <= ADP_BANK(MAXGPIO); i++) + ret |= adp5589_write(client, ADP5589_DEBOUNCE_DIS_A + i, + pdata->debounce_dis_mask >> (i * 8)); + + ret |= adp5589_write(client, ADP5589_POLL_PTIME_CFG, + pdata->scan_cycle_time & PTIME_MASK); + ret |= adp5589_write(client, ADP5589_INT_STATUS, LOGIC2_INT | + LOGIC1_INT | OVRFLOW_INT | LOCK_INT | + GPI_INT | EVENT_INT); /* Status is W1C */ + + ret |= adp5589_write(client, ADP5589_GENERAL_CFG, + INT_CFG | OSC_EN | CORE_CLK(3)); + ret |= adp5589_write(client, ADP5589_INT_EN, + OVRFLOW_IEN | GPI_IEN | EVENT_IEN); + + if (ret < 0) { + dev_err(&client->dev, "Write Error\n"); + return ret; + } + + return 0; +} + +static void __devinit adp5589_report_switch_state(struct adp5589_kpad *kpad) +{ + int gpi_stat1 = adp5589_read(kpad->client, ADP5589_GPI_STATUS_A); + int gpi_stat2 = adp5589_read(kpad->client, ADP5589_GPI_STATUS_B); + int gpi_stat3 = adp5589_read(kpad->client, ADP5589_GPI_STATUS_C); + int gpi_stat_tmp, pin_loc; + int i; + + for (i = 0; i < kpad->gpimapsize; i++) { + unsigned short pin = kpad->gpimap[i].pin; + + if (pin <= ADP5589_GPI_PIN_ROW_END) { + gpi_stat_tmp = gpi_stat1; + pin_loc = pin - ADP5589_GPI_PIN_ROW_BASE; + } else if ((pin - ADP5589_GPI_PIN_COL_BASE) < 8) { + gpi_stat_tmp = gpi_stat2; + pin_loc = pin - ADP5589_GPI_PIN_COL_BASE; + } else { + gpi_stat_tmp = gpi_stat3; + pin_loc = pin - ADP5589_GPI_PIN_COL_BASE - 8; + } + + if (gpi_stat_tmp < 0) { + dev_err(&kpad->client->dev, + "Can't read GPIO_DAT_STAT switch" + " %d default to OFF\n", pin); + gpi_stat_tmp = 0; + } + + input_report_switch(kpad->input, + kpad->gpimap[i].sw_evt, + !(gpi_stat_tmp & (1 << pin_loc))); + } + + input_sync(kpad->input); +} + +static int __devinit adp5589_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adp5589_kpad *kpad; + const struct adp5589_kpad_platform_data *pdata; + struct input_dev *input; + unsigned int revid; + int ret, i; + int error; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, "SMBUS Byte Data not Supported\n"); + return -EIO; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->dev, "no platform data?\n"); + return -EINVAL; + } + + if (!((pdata->keypad_en_mask & 0xFF) && + (pdata->keypad_en_mask >> 8)) || !pdata->keymap) { + dev_err(&client->dev, "no rows, cols or keymap from pdata\n"); + return -EINVAL; + } + + if (pdata->keymapsize != ADP5589_KEYMAPSIZE) { + dev_err(&client->dev, "invalid keymapsize\n"); + return -EINVAL; + } + + if (!pdata->gpimap && pdata->gpimapsize) { + dev_err(&client->dev, "invalid gpimap from pdata\n"); + return -EINVAL; + } + + if (pdata->gpimapsize > ADP5589_GPIMAPSIZE_MAX) { + dev_err(&client->dev, "invalid gpimapsize\n"); + return -EINVAL; + } + + for (i = 0; i < pdata->gpimapsize; i++) { + unsigned short pin = pdata->gpimap[i].pin; + + if (pin < ADP5589_GPI_PIN_BASE || pin > ADP5589_GPI_PIN_END) { + dev_err(&client->dev, "invalid gpi pin data\n"); + return -EINVAL; + } + + if ((1 << (pin - ADP5589_GPI_PIN_ROW_BASE)) & + pdata->keypad_en_mask) { + dev_err(&client->dev, "invalid gpi row/col data\n"); + return -EINVAL; + } + } + + if (!client->irq) { + dev_err(&client->dev, "no IRQ?\n"); + return -EINVAL; + } + + kpad = kzalloc(sizeof(*kpad), GFP_KERNEL); + input = input_allocate_device(); + if (!kpad || !input) { + error = -ENOMEM; + goto err_free_mem; + } + + kpad->client = client; + kpad->input = input; + + ret = adp5589_read(client, ADP5589_ID); + if (ret < 0) { + error = ret; + goto err_free_mem; + } + + revid = (u8) ret & ADP5589_DEVICE_ID_MASK; + + input->name = client->name; + input->phys = "adp5589-keys/input0"; + input->dev.parent = &client->dev; + + input_set_drvdata(input, kpad); + + input->id.bustype = BUS_I2C; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = revid; + + input->keycodesize = sizeof(kpad->keycode[0]); + input->keycodemax = pdata->keymapsize; + input->keycode = kpad->keycode; + + memcpy(kpad->keycode, pdata->keymap, + pdata->keymapsize * input->keycodesize); + + kpad->gpimap = pdata->gpimap; + kpad->gpimapsize = pdata->gpimapsize; + + /* setup input device */ + __set_bit(EV_KEY, input->evbit); + + if (pdata->repeat) + __set_bit(EV_REP, input->evbit); + + for (i = 0; i < input->keycodemax; i++) + __set_bit(kpad->keycode[i] & KEY_MAX, input->keybit); + __clear_bit(KEY_RESERVED, input->keybit); + + if (kpad->gpimapsize) + __set_bit(EV_SW, input->evbit); + for (i = 0; i < kpad->gpimapsize; i++) + __set_bit(kpad->gpimap[i].sw_evt, input->swbit); + + error = input_register_device(input); + if (error) { + dev_err(&client->dev, "unable to register input device\n"); + goto err_free_mem; + } + + error = request_threaded_irq(client->irq, NULL, adp5589_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + client->dev.driver->name, kpad); + if (error) { + dev_err(&client->dev, "irq %d busy?\n", client->irq); + goto err_unreg_dev; + } + + error = adp5589_setup(kpad); + if (error) + goto err_free_irq; + + if (kpad->gpimapsize) + adp5589_report_switch_state(kpad); + + error = adp5589_gpio_add(kpad); + if (error) + goto err_free_irq; + + device_init_wakeup(&client->dev, 1); + i2c_set_clientdata(client, kpad); + + dev_info(&client->dev, "Rev.%d keypad, irq %d\n", revid, client->irq); + return 0; + +err_free_irq: + free_irq(client->irq, kpad); +err_unreg_dev: + input_unregister_device(input); + input = NULL; +err_free_mem: + input_free_device(input); + kfree(kpad); + + return error; +} + +static int __devexit adp5589_remove(struct i2c_client *client) +{ + struct adp5589_kpad *kpad = i2c_get_clientdata(client); + + adp5589_write(client, ADP5589_GENERAL_CFG, 0); + free_irq(client->irq, kpad); + input_unregister_device(kpad->input); + adp5589_gpio_remove(kpad); + kfree(kpad); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int adp5589_suspend(struct device *dev) +{ + struct adp5589_kpad *kpad = dev_get_drvdata(dev); + struct i2c_client *client = kpad->client; + + disable_irq(client->irq); + + if (device_may_wakeup(&client->dev)) + enable_irq_wake(client->irq); + + return 0; +} + +static int adp5589_resume(struct device *dev) +{ + struct adp5589_kpad *kpad = dev_get_drvdata(dev); + struct i2c_client *client = kpad->client; + + if (device_may_wakeup(&client->dev)) + disable_irq_wake(client->irq); + + enable_irq(client->irq); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(adp5589_dev_pm_ops, adp5589_suspend, adp5589_resume); + +static const struct i2c_device_id adp5589_id[] = { + {"adp5589-keys", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, adp5589_id); + +static struct i2c_driver adp5589_driver = { + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .pm = &adp5589_dev_pm_ops, + }, + .probe = adp5589_probe, + .remove = __devexit_p(adp5589_remove), + .id_table = adp5589_id, +}; + +static int __init adp5589_init(void) +{ + return i2c_add_driver(&adp5589_driver); +} +module_init(adp5589_init); + +static void __exit adp5589_exit(void) +{ + i2c_del_driver(&adp5589_driver); +} +module_exit(adp5589_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("ADP5589 Keypad driver"); diff --git a/include/linux/input/adp5589.h b/include/linux/input/adp5589.h new file mode 100644 index 000000000000..ef792ecfaabf --- /dev/null +++ b/include/linux/input/adp5589.h @@ -0,0 +1,213 @@ +/* + * Analog Devices ADP5589 I/O Expander and QWERTY Keypad Controller + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef _ADP5589_H +#define _ADP5589_H + +#define ADP5589_ID 0x00 +#define ADP5589_INT_STATUS 0x01 +#define ADP5589_STATUS 0x02 +#define ADP5589_FIFO_1 0x03 +#define ADP5589_FIFO_2 0x04 +#define ADP5589_FIFO_3 0x05 +#define ADP5589_FIFO_4 0x06 +#define ADP5589_FIFO_5 0x07 +#define ADP5589_FIFO_6 0x08 +#define ADP5589_FIFO_7 0x09 +#define ADP5589_FIFO_8 0x0A +#define ADP5589_FIFO_9 0x0B +#define ADP5589_FIFO_10 0x0C +#define ADP5589_FIFO_11 0x0D +#define ADP5589_FIFO_12 0x0E +#define ADP5589_FIFO_13 0x0F +#define ADP5589_FIFO_14 0x10 +#define ADP5589_FIFO_15 0x11 +#define ADP5589_FIFO_16 0x12 +#define ADP5589_GPI_INT_STAT_A 0x13 +#define ADP5589_GPI_INT_STAT_B 0x14 +#define ADP5589_GPI_INT_STAT_C 0x15 +#define ADP5589_GPI_STATUS_A 0x16 +#define ADP5589_GPI_STATUS_B 0x17 +#define ADP5589_GPI_STATUS_C 0x18 +#define ADP5589_RPULL_CONFIG_A 0x19 +#define ADP5589_RPULL_CONFIG_B 0x1A +#define ADP5589_RPULL_CONFIG_C 0x1B +#define ADP5589_RPULL_CONFIG_D 0x1C +#define ADP5589_RPULL_CONFIG_E 0x1D +#define ADP5589_GPI_INT_LEVEL_A 0x1E +#define ADP5589_GPI_INT_LEVEL_B 0x1F +#define ADP5589_GPI_INT_LEVEL_C 0x20 +#define ADP5589_GPI_EVENT_EN_A 0x21 +#define ADP5589_GPI_EVENT_EN_B 0x22 +#define ADP5589_GPI_EVENT_EN_C 0x23 +#define ADP5589_GPI_INTERRUPT_EN_A 0x24 +#define ADP5589_GPI_INTERRUPT_EN_B 0x25 +#define ADP5589_GPI_INTERRUPT_EN_C 0x26 +#define ADP5589_DEBOUNCE_DIS_A 0x27 +#define ADP5589_DEBOUNCE_DIS_B 0x28 +#define ADP5589_DEBOUNCE_DIS_C 0x29 +#define ADP5589_GPO_DATA_OUT_A 0x2A +#define ADP5589_GPO_DATA_OUT_B 0x2B +#define ADP5589_GPO_DATA_OUT_C 0x2C +#define ADP5589_GPO_OUT_MODE_A 0x2D +#define ADP5589_GPO_OUT_MODE_B 0x2E +#define ADP5589_GPO_OUT_MODE_C 0x2F +#define ADP5589_GPIO_DIRECTION_A 0x30 +#define ADP5589_GPIO_DIRECTION_B 0x31 +#define ADP5589_GPIO_DIRECTION_C 0x32 +#define ADP5589_UNLOCK1 0x33 +#define ADP5589_UNLOCK2 0x34 +#define ADP5589_EXT_LOCK_EVENT 0x35 +#define ADP5589_UNLOCK_TIMERS 0x36 +#define ADP5589_LOCK_CFG 0x37 +#define ADP5589_RESET1_EVENT_A 0x38 +#define ADP5589_RESET1_EVENT_B 0x39 +#define ADP5589_RESET1_EVENT_C 0x3A +#define ADP5589_RESET2_EVENT_A 0x3B +#define ADP5589_RESET2_EVENT_B 0x3C +#define ADP5589_RESET_CFG 0x3D +#define ADP5589_PWM_OFFT_LOW 0x3E +#define ADP5589_PWM_OFFT_HIGH 0x3F +#define ADP5589_PWM_ONT_LOW 0x40 +#define ADP5589_PWM_ONT_HIGH 0x41 +#define ADP5589_PWM_CFG 0x42 +#define ADP5589_CLOCK_DIV_CFG 0x43 +#define ADP5589_LOGIC_1_CFG 0x44 +#define ADP5589_LOGIC_2_CFG 0x45 +#define ADP5589_LOGIC_FF_CFG 0x46 +#define ADP5589_LOGIC_INT_EVENT_EN 0x47 +#define ADP5589_POLL_PTIME_CFG 0x48 +#define ADP5589_PIN_CONFIG_A 0x49 +#define ADP5589_PIN_CONFIG_B 0x4A +#define ADP5589_PIN_CONFIG_C 0x4B +#define ADP5589_PIN_CONFIG_D 0x4C +#define ADP5589_GENERAL_CFG 0x4D +#define ADP5589_INT_EN 0x4E + +#define ADP5589_DEVICE_ID_MASK 0xF + +/* Put one of these structures in i2c_board_info platform_data */ + +#define ADP5589_KEYMAPSIZE 88 + +#define ADP5589_GPI_PIN_ROW0 97 +#define ADP5589_GPI_PIN_ROW1 98 +#define ADP5589_GPI_PIN_ROW2 99 +#define ADP5589_GPI_PIN_ROW3 100 +#define ADP5589_GPI_PIN_ROW4 101 +#define ADP5589_GPI_PIN_ROW5 102 +#define ADP5589_GPI_PIN_ROW6 103 +#define ADP5589_GPI_PIN_ROW7 104 +#define ADP5589_GPI_PIN_COL0 105 +#define ADP5589_GPI_PIN_COL1 106 +#define ADP5589_GPI_PIN_COL2 107 +#define ADP5589_GPI_PIN_COL3 108 +#define ADP5589_GPI_PIN_COL4 109 +#define ADP5589_GPI_PIN_COL5 110 +#define ADP5589_GPI_PIN_COL6 111 +#define ADP5589_GPI_PIN_COL7 112 +#define ADP5589_GPI_PIN_COL8 113 +#define ADP5589_GPI_PIN_COL9 114 +#define ADP5589_GPI_PIN_COL10 115 +#define GPI_LOGIC1 116 +#define GPI_LOGIC2 117 + +#define ADP5589_GPI_PIN_ROW_BASE ADP5589_GPI_PIN_ROW0 +#define ADP5589_GPI_PIN_ROW_END ADP5589_GPI_PIN_ROW7 +#define ADP5589_GPI_PIN_COL_BASE ADP5589_GPI_PIN_COL0 +#define ADP5589_GPI_PIN_COL_END ADP5589_GPI_PIN_COL10 + +#define ADP5589_GPI_PIN_BASE ADP5589_GPI_PIN_ROW_BASE +#define ADP5589_GPI_PIN_END ADP5589_GPI_PIN_COL_END + +#define ADP5589_GPIMAPSIZE_MAX (ADP5589_GPI_PIN_END - ADP5589_GPI_PIN_BASE + 1) + +struct adp5589_gpi_map { + unsigned short pin; + unsigned short sw_evt; +}; + +/* scan_cycle_time */ +#define ADP5589_SCAN_CYCLE_10ms 0 +#define ADP5589_SCAN_CYCLE_20ms 1 +#define ADP5589_SCAN_CYCLE_30ms 2 +#define ADP5589_SCAN_CYCLE_40ms 3 + +/* RESET_CFG */ +#define RESET_PULSE_WIDTH_500us 0 +#define RESET_PULSE_WIDTH_1ms 1 +#define RESET_PULSE_WIDTH_2ms 2 +#define RESET_PULSE_WIDTH_10ms 3 + +#define RESET_TRIG_TIME_0ms (0 << 2) +#define RESET_TRIG_TIME_1000ms (1 << 2) +#define RESET_TRIG_TIME_1500ms (2 << 2) +#define RESET_TRIG_TIME_2000ms (3 << 2) +#define RESET_TRIG_TIME_2500ms (4 << 2) +#define RESET_TRIG_TIME_3000ms (5 << 2) +#define RESET_TRIG_TIME_3500ms (6 << 2) +#define RESET_TRIG_TIME_4000ms (7 << 2) + +#define RESET_PASSTHRU_EN (1 << 5) +#define RESET1_POL_HIGH (1 << 6) +#define RESET1_POL_LOW (0 << 6) +#define RESET2_POL_HIGH (1 << 7) +#define RESET2_POL_LOW (0 << 7) + +/* Mask Bits: + * C C C C C C C C C C C | R R R R R R R R + * 1 9 8 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 + * 0 + * ---------------- BIT ------------------ + * 1 1 1 1 1 1 1 1 1 0 0 | 0 0 0 0 0 0 0 0 + * 8 7 6 5 4 3 2 1 0 9 8 | 7 6 5 4 3 2 1 0 + */ + +#define ADP_ROW(x) (1 << (x)) +#define ADP_COL(x) (1 << (x + 8)) + +struct adp5589_kpad_platform_data { + unsigned keypad_en_mask; /* Keypad (Rows/Columns) enable mask */ + const unsigned short *keymap; /* Pointer to keymap */ + unsigned short keymapsize; /* Keymap size */ + bool repeat; /* Enable key repeat */ + bool en_keylock; /* Enable key lock feature */ + unsigned char unlock_key1; /* Unlock Key 1 */ + unsigned char unlock_key2; /* Unlock Key 2 */ + unsigned char unlock_timer; /* Time in seconds [0..7] between the two unlock keys 0=disable */ + unsigned char scan_cycle_time; /* Time between consecutive scan cycles */ + unsigned char reset_cfg; /* Reset config */ + unsigned short reset1_key_1; /* Reset Key 1 */ + unsigned short reset1_key_2; /* Reset Key 2 */ + unsigned short reset1_key_3; /* Reset Key 3 */ + unsigned short reset2_key_1; /* Reset Key 1 */ + unsigned short reset2_key_2; /* Reset Key 2 */ + unsigned debounce_dis_mask; /* Disable debounce mask */ + unsigned pull_dis_mask; /* Disable all pull resistors mask */ + unsigned pullup_en_100k; /* Pull-Up 100k Enable Mask */ + unsigned pullup_en_300k; /* Pull-Up 300k Enable Mask */ + unsigned pulldown_en_300k; /* Pull-Down 300k Enable Mask */ + const struct adp5589_gpi_map *gpimap; + unsigned short gpimapsize; + const struct adp5589_gpio_platform_data *gpio_data; +}; + +struct i2c_client; /* forward declaration */ + +struct adp5589_gpio_platform_data { + int gpio_start; /* GPIO Chip base # */ + int (*setup)(struct i2c_client *client, + int gpio, unsigned ngpio, + void *context); + int (*teardown)(struct i2c_client *client, + int gpio, unsigned ngpio, + void *context); + void *context; +}; + +#endif -- cgit v1.2.3 From 2d42552d1c1659b014851cf449ad2fe458509128 Mon Sep 17 00:00:00 2001 From: Martin Schwidefsky Date: Mon, 23 May 2011 10:24:39 +0200 Subject: [S390] merge page_test_dirty and page_clear_dirty The page_clear_dirty primitive always sets the default storage key which resets the access control bits and the fetch protection bit. That will surprise a KVM guest that sets non-zero access control bits or the fetch protection bit. Merge page_test_dirty and page_clear_dirty back to a single function and only clear the dirty bit from the storage key. In addition move the function page_test_and_clear_dirty and page_test_and_clear_young to page.h where they belong. This requires to change the parameter from a struct page * to a page frame number. Signed-off-by: Martin Schwidefsky --- arch/s390/include/asm/page.h | 56 ++++++++++++++++++++++++++++++++++----- arch/s390/include/asm/pgtable.h | 58 +++++++---------------------------------- include/asm-generic/pgtable.h | 12 +++------ include/linux/page-flags.h | 2 +- mm/rmap.c | 11 +++----- 5 files changed, 68 insertions(+), 71 deletions(-) (limited to 'include/linux') diff --git a/arch/s390/include/asm/page.h b/arch/s390/include/asm/page.h index 3c987e9ec8d6..81ee2776088d 100644 --- a/arch/s390/include/asm/page.h +++ b/arch/s390/include/asm/page.h @@ -107,8 +107,8 @@ typedef pte_t *pgtable_t; #define __pgd(x) ((pgd_t) { (x) } ) #define __pgprot(x) ((pgprot_t) { (x) } ) -static inline void -page_set_storage_key(unsigned long addr, unsigned int skey, int mapped) +static inline void page_set_storage_key(unsigned long addr, + unsigned char skey, int mapped) { if (!mapped) asm volatile(".insn rrf,0xb22b0000,%0,%1,8,0" @@ -117,15 +117,59 @@ page_set_storage_key(unsigned long addr, unsigned int skey, int mapped) asm volatile("sske %0,%1" : : "d" (skey), "a" (addr)); } -static inline unsigned int -page_get_storage_key(unsigned long addr) +static inline unsigned char page_get_storage_key(unsigned long addr) { - unsigned int skey; + unsigned char skey; - asm volatile("iske %0,%1" : "=d" (skey) : "a" (addr), "0" (0)); + asm volatile("iske %0,%1" : "=d" (skey) : "a" (addr)); return skey; } +static inline int page_reset_referenced(unsigned long addr) +{ + unsigned int ipm; + + asm volatile( + " rrbe 0,%1\n" + " ipm %0\n" + : "=d" (ipm) : "a" (addr) : "cc"); + return !!(ipm & 0x20000000); +} + +/* Bits int the storage key */ +#define _PAGE_CHANGED 0x02 /* HW changed bit */ +#define _PAGE_REFERENCED 0x04 /* HW referenced bit */ +#define _PAGE_FP_BIT 0x08 /* HW fetch protection bit */ +#define _PAGE_ACC_BITS 0xf0 /* HW access control bits */ + +/* + * Test and clear dirty bit in storage key. + * We can't clear the changed bit atomically. This is a potential + * race against modification of the referenced bit. This function + * should therefore only be called if it is not mapped in any + * address space. + */ +#define __HAVE_ARCH_PAGE_TEST_AND_CLEAR_DIRTY +static inline int page_test_and_clear_dirty(unsigned long pfn, int mapped) +{ + unsigned char skey; + + skey = page_get_storage_key(pfn << PAGE_SHIFT); + if (!(skey & _PAGE_CHANGED)) + return 0; + page_set_storage_key(pfn << PAGE_SHIFT, skey & ~_PAGE_CHANGED, mapped); + return 1; +} + +/* + * Test and clear referenced bit in storage key. + */ +#define __HAVE_ARCH_PAGE_TEST_AND_CLEAR_YOUNG +static inline int page_test_and_clear_young(unsigned long pfn) +{ + return page_reset_referenced(pfn << PAGE_SHIFT); +} + struct page; void arch_free_page(struct page *page, int order); void arch_alloc_page(struct page *page, int order); diff --git a/arch/s390/include/asm/pgtable.h b/arch/s390/include/asm/pgtable.h index 763620ec7925..4ca4dd2b329a 100644 --- a/arch/s390/include/asm/pgtable.h +++ b/arch/s390/include/asm/pgtable.h @@ -373,10 +373,6 @@ extern unsigned long VMALLOC_START; #define _ASCE_USER_BITS (_ASCE_SPACE_SWITCH | _ASCE_PRIVATE_SPACE | \ _ASCE_ALT_EVENT) -/* Bits int the storage key */ -#define _PAGE_CHANGED 0x02 /* HW changed bit */ -#define _PAGE_REFERENCED 0x04 /* HW referenced bit */ - /* * Page protection definitions. */ @@ -555,8 +551,6 @@ static inline void rcp_unlock(pte_t *ptep) #endif } -/* forward declaration for SetPageUptodate in page-flags.h*/ -static inline void page_clear_dirty(struct page *page, int mapped); #include static inline void ptep_rcp_copy(pte_t *ptep) @@ -566,7 +560,7 @@ static inline void ptep_rcp_copy(pte_t *ptep) unsigned int skey; unsigned long *pgste = (unsigned long *) (ptep + PTRS_PER_PTE); - skey = page_get_storage_key(page_to_phys(page)); + skey = page_get_storage_key(pte_val(*ptep) >> PAGE_SHIFT); if (skey & _PAGE_CHANGED) { set_bit_simple(RCP_GC_BIT, pgste); set_bit_simple(KVM_UD_BIT, pgste); @@ -760,6 +754,7 @@ static inline int kvm_s390_test_and_clear_page_dirty(struct mm_struct *mm, { int dirty; unsigned long *pgste; + unsigned long pfn; struct page *page; unsigned int skey; @@ -767,8 +762,9 @@ static inline int kvm_s390_test_and_clear_page_dirty(struct mm_struct *mm, return -EINVAL; rcp_lock(ptep); pgste = (unsigned long *) (ptep + PTRS_PER_PTE); - page = virt_to_page(pte_val(*ptep)); - skey = page_get_storage_key(page_to_phys(page)); + pfn = pte_val(*ptep) >> PAGE_SHIFT; + page = pfn_to_page(pfn); + skey = page_get_storage_key(pfn); if (skey & _PAGE_CHANGED) { set_bit_simple(RCP_GC_BIT, pgste); set_bit_simple(KVM_UD_BIT, pgste); @@ -779,7 +775,7 @@ static inline int kvm_s390_test_and_clear_page_dirty(struct mm_struct *mm, } dirty = test_and_clear_bit_simple(KVM_UD_BIT, pgste); if (skey & _PAGE_CHANGED) - page_clear_dirty(page, 1); + page_set_storage_key(pfn, skey & ~_PAGE_CHANGED, 1); rcp_unlock(ptep); return dirty; } @@ -790,16 +786,16 @@ static inline int ptep_test_and_clear_young(struct vm_area_struct *vma, unsigned long addr, pte_t *ptep) { #ifdef CONFIG_PGSTE - unsigned long physpage; + unsigned long pfn; int young; unsigned long *pgste; if (!vma->vm_mm->context.has_pgste) return 0; - physpage = pte_val(*ptep) & PAGE_MASK; + pfn = pte_val(*ptep) >> PAGE_SHIFT; pgste = (unsigned long *) (ptep + PTRS_PER_PTE); - young = ((page_get_storage_key(physpage) & _PAGE_REFERENCED) != 0); + young = ((page_get_storage_key(pfn) & _PAGE_REFERENCED) != 0); rcp_lock(ptep); if (young) set_bit_simple(RCP_GR_BIT, pgste); @@ -936,42 +932,6 @@ static inline pte_t ptep_get_and_clear_full(struct mm_struct *mm, __changed; \ }) -/* - * Test and clear dirty bit in storage key. - * We can't clear the changed bit atomically. This is a potential - * race against modification of the referenced bit. This function - * should therefore only be called if it is not mapped in any - * address space. - */ -#define __HAVE_ARCH_PAGE_TEST_DIRTY -static inline int page_test_dirty(struct page *page) -{ - return (page_get_storage_key(page_to_phys(page)) & _PAGE_CHANGED) != 0; -} - -#define __HAVE_ARCH_PAGE_CLEAR_DIRTY -static inline void page_clear_dirty(struct page *page, int mapped) -{ - page_set_storage_key(page_to_phys(page), PAGE_DEFAULT_KEY, mapped); -} - -/* - * Test and clear referenced bit in storage key. - */ -#define __HAVE_ARCH_PAGE_TEST_AND_CLEAR_YOUNG -static inline int page_test_and_clear_young(struct page *page) -{ - unsigned long physpage = page_to_phys(page); - int ccode; - - asm volatile( - " rrbe 0,%1\n" - " ipm %0\n" - " srl %0,28\n" - : "=d" (ccode) : "a" (physpage) : "cc" ); - return ccode & 2; -} - /* * Conversion functions: convert a page and protection to a page entry, * and a page entry and page directory to the page they refer to. diff --git a/include/asm-generic/pgtable.h b/include/asm-generic/pgtable.h index b4bfe338ea0e..e9b8e5926bef 100644 --- a/include/asm-generic/pgtable.h +++ b/include/asm-generic/pgtable.h @@ -184,22 +184,18 @@ static inline int pmd_same(pmd_t pmd_a, pmd_t pmd_b) #endif /* CONFIG_TRANSPARENT_HUGEPAGE */ #endif -#ifndef __HAVE_ARCH_PAGE_TEST_DIRTY -#define page_test_dirty(page) (0) +#ifndef __HAVE_ARCH_PAGE_TEST_AND_CLEAR_DIRTY +#define page_test_and_clear_dirty(pfn, mapped) (0) #endif -#ifndef __HAVE_ARCH_PAGE_CLEAR_DIRTY -#define page_clear_dirty(page, mapped) do { } while (0) -#endif - -#ifndef __HAVE_ARCH_PAGE_TEST_DIRTY +#ifndef __HAVE_ARCH_PAGE_TEST_AND_CLEAR_DIRTY #define pte_maybe_dirty(pte) pte_dirty(pte) #else #define pte_maybe_dirty(pte) (1) #endif #ifndef __HAVE_ARCH_PAGE_TEST_AND_CLEAR_YOUNG -#define page_test_and_clear_young(page) (0) +#define page_test_and_clear_young(pfn) (0) #endif #ifndef __HAVE_ARCH_PGD_OFFSET_GATE diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h index 811183de1ef5..79a6700b7162 100644 --- a/include/linux/page-flags.h +++ b/include/linux/page-flags.h @@ -308,7 +308,7 @@ static inline void SetPageUptodate(struct page *page) { #ifdef CONFIG_S390 if (!test_and_set_bit(PG_uptodate, &page->flags)) - page_clear_dirty(page, 0); + page_set_storage_key(page_to_pfn(page), PAGE_DEFAULT_KEY, 0); #else /* * Memory barrier must be issued before setting the PG_uptodate bit, diff --git a/mm/rmap.c b/mm/rmap.c index 8da044a1db0f..522e4a93cadd 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -719,7 +719,7 @@ int page_referenced(struct page *page, unlock_page(page); } out: - if (page_test_and_clear_young(page)) + if (page_test_and_clear_young(page_to_pfn(page))) referenced++; return referenced; @@ -785,10 +785,8 @@ int page_mkclean(struct page *page) struct address_space *mapping = page_mapping(page); if (mapping) { ret = page_mkclean_file(mapping, page); - if (page_test_dirty(page)) { - page_clear_dirty(page, 1); + if (page_test_and_clear_dirty(page_to_pfn(page), 1)) ret = 1; - } } } @@ -981,10 +979,9 @@ void page_remove_rmap(struct page *page) * not if it's in swapcache - there might be another pte slot * containing the swap entry, but page not yet written to swap. */ - if ((!PageAnon(page) || PageSwapCache(page)) && page_test_dirty(page)) { - page_clear_dirty(page, 1); + if ((!PageAnon(page) || PageSwapCache(page)) && + page_test_and_clear_dirty(page_to_pfn(page), 1)) set_page_dirty(page); - } /* * Hugepages are not counted in NR_ANON_PAGES nor NR_FILE_MAPPED * and not charged by memcg for now. -- cgit v1.2.3 From 901025d2f3194b4868980c8ba80df4cc0aa1282c Mon Sep 17 00:00:00 2001 From: David Teigland Date: Wed, 2 Mar 2011 14:20:04 -0600 Subject: dlm: make plock operation killable Allow processes blocked on plock requests to be interrupted when they are killed. This leaves the problem of cleaning up the lock state in userspace. This has three parts: 1. Add a flag to unlock operations sent to userspace indicating the file is being closed. Userspace will then look for and clear any waiting plock operations that were abandoned by an interrupted process. 2. Queue an unlock-close operation (like in 1) to clean up userspace from an interrupted plock request. This is needed because the vfs will not send a cleanup-unlock if it sees no locks on the file, which it won't if the interrupted operation was the only one. 3. Do not use replies from userspace for unlock-close operations because they are unnecessary (they are just cleaning up for the process which did not make an unlock call). This also simplifies the new unlock-close generated from point 2. Signed-off-by: David Teigland --- fs/dlm/plock.c | 65 ++++++++++++++++++++++++++++++++++++++++++++--- include/linux/dlm_plock.h | 6 +++-- 2 files changed, 65 insertions(+), 6 deletions(-) (limited to 'include/linux') diff --git a/fs/dlm/plock.c b/fs/dlm/plock.c index 30d8b85febbf..e2b878004364 100644 --- a/fs/dlm/plock.c +++ b/fs/dlm/plock.c @@ -71,6 +71,36 @@ static void send_op(struct plock_op *op) wake_up(&send_wq); } +/* If a process was killed while waiting for the only plock on a file, + locks_remove_posix will not see any lock on the file so it won't + send an unlock-close to us to pass on to userspace to clean up the + abandoned waiter. So, we have to insert the unlock-close when the + lock call is interrupted. */ + +static void do_unlock_close(struct dlm_ls *ls, u64 number, + struct file *file, struct file_lock *fl) +{ + struct plock_op *op; + + op = kzalloc(sizeof(*op), GFP_NOFS); + if (!op) + return; + + op->info.optype = DLM_PLOCK_OP_UNLOCK; + op->info.pid = fl->fl_pid; + op->info.fsid = ls->ls_global_id; + op->info.number = number; + op->info.start = 0; + op->info.end = OFFSET_MAX; + if (fl->fl_lmops && fl->fl_lmops->fl_grant) + op->info.owner = (__u64) fl->fl_pid; + else + op->info.owner = (__u64)(long) fl->fl_owner; + + op->info.flags |= DLM_PLOCK_FL_CLOSE; + send_op(op); +} + int dlm_posix_lock(dlm_lockspace_t *lockspace, u64 number, struct file *file, int cmd, struct file_lock *fl) { @@ -114,9 +144,19 @@ int dlm_posix_lock(dlm_lockspace_t *lockspace, u64 number, struct file *file, send_op(op); - if (xop->callback == NULL) - wait_event(recv_wq, (op->done != 0)); - else { + if (xop->callback == NULL) { + rv = wait_event_killable(recv_wq, (op->done != 0)); + if (rv == -ERESTARTSYS) { + log_debug(ls, "dlm_posix_lock: wait killed %llx", + (unsigned long long)number); + spin_lock(&ops_lock); + list_del(&op->list); + spin_unlock(&ops_lock); + kfree(xop); + do_unlock_close(ls, number, file, fl); + goto out; + } + } else { rv = FILE_LOCK_DEFERRED; goto out; } @@ -233,6 +273,13 @@ int dlm_posix_unlock(dlm_lockspace_t *lockspace, u64 number, struct file *file, else op->info.owner = (__u64)(long) fl->fl_owner; + if (fl->fl_flags & FL_CLOSE) { + op->info.flags |= DLM_PLOCK_FL_CLOSE; + send_op(op); + rv = 0; + goto out; + } + send_op(op); wait_event(recv_wq, (op->done != 0)); @@ -334,7 +381,10 @@ static ssize_t dev_read(struct file *file, char __user *u, size_t count, spin_lock(&ops_lock); if (!list_empty(&send_list)) { op = list_entry(send_list.next, struct plock_op, list); - list_move(&op->list, &recv_list); + if (op->info.flags & DLM_PLOCK_FL_CLOSE) + list_del(&op->list); + else + list_move(&op->list, &recv_list); memcpy(&info, &op->info, sizeof(info)); } spin_unlock(&ops_lock); @@ -342,6 +392,13 @@ static ssize_t dev_read(struct file *file, char __user *u, size_t count, if (!op) return -EAGAIN; + /* there is no need to get a reply from userspace for unlocks + that were generated by the vfs cleaning up for a close + (the process did not make an unlock call). */ + + if (op->info.flags & DLM_PLOCK_FL_CLOSE) + kfree(op); + if (copy_to_user(u, &info, sizeof(info))) return -EFAULT; return sizeof(info); diff --git a/include/linux/dlm_plock.h b/include/linux/dlm_plock.h index 2dd21243104f..3b1cc1be419f 100644 --- a/include/linux/dlm_plock.h +++ b/include/linux/dlm_plock.h @@ -14,7 +14,7 @@ #define DLM_PLOCK_MISC_NAME "dlm_plock" #define DLM_PLOCK_VERSION_MAJOR 1 -#define DLM_PLOCK_VERSION_MINOR 1 +#define DLM_PLOCK_VERSION_MINOR 2 #define DLM_PLOCK_VERSION_PATCH 0 enum { @@ -23,12 +23,14 @@ enum { DLM_PLOCK_OP_GET, }; +#define DLM_PLOCK_FL_CLOSE 1 + struct dlm_plock_info { __u32 version[3]; __u8 optype; __u8 ex; __u8 wait; - __u8 pad; + __u8 flags; __u32 pid; __s32 nodeid; __s32 rv; -- cgit v1.2.3 From 1b4ac2a935aaf194241a2f4165d6407ba9650e1a Mon Sep 17 00:00:00 2001 From: Richard Weinberger Date: Tue, 24 May 2011 00:18:05 +0200 Subject: x86: Get rid of asmregparm As UML does no longer need asmregparm we can remove it. Signed-off-by: Richard Weinberger Cc: namhyung@gmail.com Cc: davem@davemloft.net Cc: fweisbec@gmail.com Cc: dhowells@redhat.com Link: http://lkml.kernel.org/r/%3C1306189085-29896-1-git-send-email-richard%40nod.at%3E Signed-off-by: Thomas Gleixner --- arch/x86/include/asm/linkage.h | 5 ----- arch/x86/kernel/ptrace.c | 4 ++-- include/linux/linkage.h | 4 ---- 3 files changed, 2 insertions(+), 11 deletions(-) (limited to 'include/linux') diff --git a/arch/x86/include/asm/linkage.h b/arch/x86/include/asm/linkage.h index 12d55e773eb6..48142971b25d 100644 --- a/arch/x86/include/asm/linkage.h +++ b/arch/x86/include/asm/linkage.h @@ -8,11 +8,6 @@ #ifdef CONFIG_X86_32 #define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0))) -/* - * For 32-bit UML - mark functions implemented in assembly that use - * regparm input parameters: - */ -#define asmregparm __attribute__((regparm(3))) /* * Make sure the compiler doesn't do anything stupid with the diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c index f65e5b521dbd..807c2a2b80f1 100644 --- a/arch/x86/kernel/ptrace.c +++ b/arch/x86/kernel/ptrace.c @@ -1363,7 +1363,7 @@ void send_sigtrap(struct task_struct *tsk, struct pt_regs *regs, * We must return the syscall number to actually look up in the table. * This can be -1L to skip running any syscall at all. */ -asmregparm long syscall_trace_enter(struct pt_regs *regs) +long syscall_trace_enter(struct pt_regs *regs) { long ret = 0; @@ -1408,7 +1408,7 @@ asmregparm long syscall_trace_enter(struct pt_regs *regs) return ret ?: regs->orig_ax; } -asmregparm void syscall_trace_leave(struct pt_regs *regs) +void syscall_trace_leave(struct pt_regs *regs) { bool step; diff --git a/include/linux/linkage.h b/include/linux/linkage.h index 7135ebc8428c..3f46aedea42f 100644 --- a/include/linux/linkage.h +++ b/include/linux/linkage.h @@ -14,10 +14,6 @@ #define asmlinkage CPP_ASMLINKAGE #endif -#ifndef asmregparm -# define asmregparm -#endif - #define __page_aligned_data __section(.data..page_aligned) __aligned(PAGE_SIZE) #define __page_aligned_bss __section(.bss..page_aligned) __aligned(PAGE_SIZE) -- cgit v1.2.3