Message ID | 20200623040107.22270-17-warthog618@gmail.com |
---|---|
State | New |
Headers | show |
Series | gpio: cdev: add uAPI V2 | expand |
[ The copy_to_user() overflow code is weird. Why do we need to do a atomic_read()? That suggests that there is a potential time of check time of use bug where we do: if (atomic_read(&gcdev->watch_abi_version) == 2) // <<-- time of check event_size = sizeof(struct gpioline_info_changed_v2); ... if (atomic_read(&gcdev->watch_abi_version) == 2) { // <<-- time of use copy_to_user(blah, blah, event_size); If the value for "gcdev->watch_abi_version" changes between the time of check and the time of use then it can read beyond the end of the buffer. -- dan ] Hi Kent, Thank you for the patch! Perhaps something to improve: url: https://github.com/0day-ci/linux/commits/Kent-Gibson/gpio-cdev-add-uAPI-V2/20200623-120634 base: https://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio.git for-next config: openrisc-randconfig-m031-20200623 (attached as .config) compiler: or1k-linux-gcc (GCC) 9.3.0 If you fix the issue, kindly add following tag as appropriate Reported-by: kernel test robot <lkp@intel.com> Reported-by: Dan Carpenter <dan.carpenter@oracle.com> smatch warnings: drivers/gpio/gpiolib-cdev.c:891 line_free() error: dereferencing freed memory 'line' drivers/gpio/gpiolib-cdev.c:949 line_create() warn: possible memory leak of 'line' drivers/gpio/gpiolib-cdev.c:1860 lineinfo_watch_read() error: copy_to_user() '&event_v1' too small (104 vs 168) # https://github.com/0day-ci/linux/commit/f3b3ae8752adc5ac33dcf83d49b0b02f2d7ef43b git remote add linux-review https://github.com/0day-ci/linux git remote update linux-review git checkout f3b3ae8752adc5ac33dcf83d49b0b02f2d7ef43b vim +/line +891 drivers/gpio/gpiolib-cdev.c f3b3ae8752adc5 Kent Gibson 2020-06-23 877 static void line_free(struct line *line) f3b3ae8752adc5 Kent Gibson 2020-06-23 878 { f3b3ae8752adc5 Kent Gibson 2020-06-23 879 int i; f3b3ae8752adc5 Kent Gibson 2020-06-23 880 f3b3ae8752adc5 Kent Gibson 2020-06-23 881 for (i = 0; i < line->num_descs; i++) { f3b3ae8752adc5 Kent Gibson 2020-06-23 882 if (line->edets) f3b3ae8752adc5 Kent Gibson 2020-06-23 883 edge_detector_stop(&line->edets[i]); f3b3ae8752adc5 Kent Gibson 2020-06-23 884 if (line->descs[i]) f3b3ae8752adc5 Kent Gibson 2020-06-23 885 gpiod_free(line->descs[i]); f3b3ae8752adc5 Kent Gibson 2020-06-23 886 } f3b3ae8752adc5 Kent Gibson 2020-06-23 887 kfifo_free(&line->events); f3b3ae8752adc5 Kent Gibson 2020-06-23 888 kfree(line->label); f3b3ae8752adc5 Kent Gibson 2020-06-23 889 kfree(line->edets); f3b3ae8752adc5 Kent Gibson 2020-06-23 890 kfree(line); f3b3ae8752adc5 Kent Gibson 2020-06-23 @891 put_device(&line->gdev->dev); f3b3ae8752adc5 Kent Gibson 2020-06-23 892 } f3b3ae8752adc5 Kent Gibson 2020-06-23 893 f3b3ae8752adc5 Kent Gibson 2020-06-23 894 static int line_release(struct inode *inode, struct file *file) f3b3ae8752adc5 Kent Gibson 2020-06-23 895 { f3b3ae8752adc5 Kent Gibson 2020-06-23 896 struct line *line = file->private_data; f3b3ae8752adc5 Kent Gibson 2020-06-23 897 f3b3ae8752adc5 Kent Gibson 2020-06-23 898 line_free(line); f3b3ae8752adc5 Kent Gibson 2020-06-23 899 return 0; f3b3ae8752adc5 Kent Gibson 2020-06-23 900 } f3b3ae8752adc5 Kent Gibson 2020-06-23 901 f3b3ae8752adc5 Kent Gibson 2020-06-23 902 static const struct file_operations line_fileops = { f3b3ae8752adc5 Kent Gibson 2020-06-23 903 .release = line_release, f3b3ae8752adc5 Kent Gibson 2020-06-23 904 .read = line_read, f3b3ae8752adc5 Kent Gibson 2020-06-23 905 .poll = line_poll, f3b3ae8752adc5 Kent Gibson 2020-06-23 906 .owner = THIS_MODULE, f3b3ae8752adc5 Kent Gibson 2020-06-23 907 .llseek = noop_llseek, f3b3ae8752adc5 Kent Gibson 2020-06-23 908 .unlocked_ioctl = line_ioctl, f3b3ae8752adc5 Kent Gibson 2020-06-23 909 #ifdef CONFIG_COMPAT f3b3ae8752adc5 Kent Gibson 2020-06-23 910 .compat_ioctl = line_ioctl_compat, f3b3ae8752adc5 Kent Gibson 2020-06-23 911 #endif f3b3ae8752adc5 Kent Gibson 2020-06-23 912 }; f3b3ae8752adc5 Kent Gibson 2020-06-23 913 f3b3ae8752adc5 Kent Gibson 2020-06-23 914 static int line_create(struct gpio_device *gdev, void __user *ip) f3b3ae8752adc5 Kent Gibson 2020-06-23 915 { f3b3ae8752adc5 Kent Gibson 2020-06-23 916 struct gpioline_request linereq; f3b3ae8752adc5 Kent Gibson 2020-06-23 917 struct line *line; f3b3ae8752adc5 Kent Gibson 2020-06-23 918 struct file *file; f3b3ae8752adc5 Kent Gibson 2020-06-23 919 int fd, i, ret, size; f3b3ae8752adc5 Kent Gibson 2020-06-23 920 struct gpioline_config *lc; f3b3ae8752adc5 Kent Gibson 2020-06-23 921 unsigned long *vals; f3b3ae8752adc5 Kent Gibson 2020-06-23 922 f3b3ae8752adc5 Kent Gibson 2020-06-23 923 if (copy_from_user(&linereq, ip, sizeof(linereq))) f3b3ae8752adc5 Kent Gibson 2020-06-23 924 return -EFAULT; f3b3ae8752adc5 Kent Gibson 2020-06-23 925 if ((linereq.num_lines == 0) || (linereq.num_lines > GPIOLINES_MAX)) f3b3ae8752adc5 Kent Gibson 2020-06-23 926 return -EINVAL; f3b3ae8752adc5 Kent Gibson 2020-06-23 927 f3b3ae8752adc5 Kent Gibson 2020-06-23 928 if (padding_not_zeroed(linereq.padding, GPIOLINE_REQUEST_PAD_SIZE)) f3b3ae8752adc5 Kent Gibson 2020-06-23 929 return -EINVAL; f3b3ae8752adc5 Kent Gibson 2020-06-23 930 f3b3ae8752adc5 Kent Gibson 2020-06-23 931 lc = &linereq.config; f3b3ae8752adc5 Kent Gibson 2020-06-23 932 ret = gpioline_config_validate(lc); f3b3ae8752adc5 Kent Gibson 2020-06-23 933 if (ret) f3b3ae8752adc5 Kent Gibson 2020-06-23 934 return ret; f3b3ae8752adc5 Kent Gibson 2020-06-23 935 f3b3ae8752adc5 Kent Gibson 2020-06-23 936 /* event_buffer_size only valid with edge_detection */ f3b3ae8752adc5 Kent Gibson 2020-06-23 937 if ((linereq.event_buffer_size) && f3b3ae8752adc5 Kent Gibson 2020-06-23 938 !(linereq.config.flags & GPIOLINE_FLAG_V2_EDGE_DETECTION)) f3b3ae8752adc5 Kent Gibson 2020-06-23 939 return -EINVAL; f3b3ae8752adc5 Kent Gibson 2020-06-23 940 f3b3ae8752adc5 Kent Gibson 2020-06-23 941 line = kzalloc(struct_size(line, descs, linereq.num_lines), f3b3ae8752adc5 Kent Gibson 2020-06-23 942 GFP_KERNEL); f3b3ae8752adc5 Kent Gibson 2020-06-23 943 if (!line) f3b3ae8752adc5 Kent Gibson 2020-06-23 944 return -ENOMEM; f3b3ae8752adc5 Kent Gibson 2020-06-23 945 f3b3ae8752adc5 Kent Gibson 2020-06-23 946 line->edets = kcalloc(linereq.num_lines, sizeof(*line->edets), f3b3ae8752adc5 Kent Gibson 2020-06-23 947 GFP_KERNEL); f3b3ae8752adc5 Kent Gibson 2020-06-23 948 if (!line->edets) f3b3ae8752adc5 Kent Gibson 2020-06-23 @949 return -ENOMEM; ^^^^^^^^^^^^^^^ kfree(line) before returning. f3b3ae8752adc5 Kent Gibson 2020-06-23 950 f3b3ae8752adc5 Kent Gibson 2020-06-23 951 for (i = 0; i < linereq.num_lines; i++) f3b3ae8752adc5 Kent Gibson 2020-06-23 952 line->edets[i].line = line; f3b3ae8752adc5 Kent Gibson 2020-06-23 953 f3b3ae8752adc5 Kent Gibson 2020-06-23 954 line->gdev = gdev; f3b3ae8752adc5 Kent Gibson 2020-06-23 955 get_device(&gdev->dev); f3b3ae8752adc5 Kent Gibson 2020-06-23 956 f3b3ae8752adc5 Kent Gibson 2020-06-23 957 /* Make sure this is terminated */ f3b3ae8752adc5 Kent Gibson 2020-06-23 958 linereq.consumer[sizeof(linereq.consumer)-1] = '\0'; f3b3ae8752adc5 Kent Gibson 2020-06-23 959 if (strlen(linereq.consumer)) { f3b3ae8752adc5 Kent Gibson 2020-06-23 960 line->label = kstrdup(linereq.consumer, GFP_KERNEL); f3b3ae8752adc5 Kent Gibson 2020-06-23 961 if (!line->label) { f3b3ae8752adc5 Kent Gibson 2020-06-23 962 ret = -ENOMEM; f3b3ae8752adc5 Kent Gibson 2020-06-23 963 goto out_free_line; f3b3ae8752adc5 Kent Gibson 2020-06-23 964 } f3b3ae8752adc5 Kent Gibson 2020-06-23 965 } f3b3ae8752adc5 Kent Gibson 2020-06-23 966 f3b3ae8752adc5 Kent Gibson 2020-06-23 967 mutex_init(&line->config_mutex); f3b3ae8752adc5 Kent Gibson 2020-06-23 968 init_waitqueue_head(&line->wait); f3b3ae8752adc5 Kent Gibson 2020-06-23 969 if (lc->edge_detection) { f3b3ae8752adc5 Kent Gibson 2020-06-23 970 size = linereq.event_buffer_size; f3b3ae8752adc5 Kent Gibson 2020-06-23 971 f3b3ae8752adc5 Kent Gibson 2020-06-23 972 if (size > GPIOLINES_MAX*16) f3b3ae8752adc5 Kent Gibson 2020-06-23 973 size = GPIOLINES_MAX*16; f3b3ae8752adc5 Kent Gibson 2020-06-23 974 else if (size == 0) f3b3ae8752adc5 Kent Gibson 2020-06-23 975 size = linereq.num_lines*16; f3b3ae8752adc5 Kent Gibson 2020-06-23 976 f3b3ae8752adc5 Kent Gibson 2020-06-23 977 ret = kfifo_alloc(&line->events, size, GFP_KERNEL); f3b3ae8752adc5 Kent Gibson 2020-06-23 978 if (ret) f3b3ae8752adc5 Kent Gibson 2020-06-23 979 goto out_free_line; f3b3ae8752adc5 Kent Gibson 2020-06-23 980 f3b3ae8752adc5 Kent Gibson 2020-06-23 981 line->edge_detection = lc->edge_detection; f3b3ae8752adc5 Kent Gibson 2020-06-23 982 } f3b3ae8752adc5 Kent Gibson 2020-06-23 983 f3b3ae8752adc5 Kent Gibson 2020-06-23 984 atomic_set(&line->seqno, 0); f3b3ae8752adc5 Kent Gibson 2020-06-23 985 line->num_descs = linereq.num_lines; f3b3ae8752adc5 Kent Gibson 2020-06-23 986 vals = (unsigned long *)lc->values.bitmap; f3b3ae8752adc5 Kent Gibson 2020-06-23 987 f3b3ae8752adc5 Kent Gibson 2020-06-23 988 /* Request each GPIO */ f3b3ae8752adc5 Kent Gibson 2020-06-23 989 for (i = 0; i < linereq.num_lines; i++) { f3b3ae8752adc5 Kent Gibson 2020-06-23 990 u32 offset = linereq.offsets[i]; f3b3ae8752adc5 Kent Gibson 2020-06-23 991 struct gpio_desc *desc = gpiochip_get_desc(gdev->chip, offset); f3b3ae8752adc5 Kent Gibson 2020-06-23 992 f3b3ae8752adc5 Kent Gibson 2020-06-23 993 if (IS_ERR(desc)) { f3b3ae8752adc5 Kent Gibson 2020-06-23 994 ret = PTR_ERR(desc); f3b3ae8752adc5 Kent Gibson 2020-06-23 995 goto out_free_line; f3b3ae8752adc5 Kent Gibson 2020-06-23 996 } f3b3ae8752adc5 Kent Gibson 2020-06-23 997 f3b3ae8752adc5 Kent Gibson 2020-06-23 998 ret = gpiod_request(desc, line->label); f3b3ae8752adc5 Kent Gibson 2020-06-23 999 if (ret) f3b3ae8752adc5 Kent Gibson 2020-06-23 1000 goto out_free_line; f3b3ae8752adc5 Kent Gibson 2020-06-23 1001 f3b3ae8752adc5 Kent Gibson 2020-06-23 1002 line->descs[i] = desc; f3b3ae8752adc5 Kent Gibson 2020-06-23 1003 gpioline_config_to_desc_flags(lc, &desc->flags); f3b3ae8752adc5 Kent Gibson 2020-06-23 1004 f3b3ae8752adc5 Kent Gibson 2020-06-23 1005 ret = gpiod_set_transitory(desc, false); f3b3ae8752adc5 Kent Gibson 2020-06-23 1006 if (ret < 0) f3b3ae8752adc5 Kent Gibson 2020-06-23 1007 goto out_free_line; f3b3ae8752adc5 Kent Gibson 2020-06-23 1008 f3b3ae8752adc5 Kent Gibson 2020-06-23 1009 /* f3b3ae8752adc5 Kent Gibson 2020-06-23 1010 * Lines have to be requested explicitly for input f3b3ae8752adc5 Kent Gibson 2020-06-23 1011 * or output, else the line will be treated "as is". f3b3ae8752adc5 Kent Gibson 2020-06-23 1012 */ f3b3ae8752adc5 Kent Gibson 2020-06-23 1013 if (lc->flags & GPIOLINE_FLAG_V2_DIRECTION) { f3b3ae8752adc5 Kent Gibson 2020-06-23 1014 if (lc->direction == GPIOLINE_DIRECTION_OUTPUT) { f3b3ae8752adc5 Kent Gibson 2020-06-23 1015 int val = test_bit(i, vals); f3b3ae8752adc5 Kent Gibson 2020-06-23 1016 f3b3ae8752adc5 Kent Gibson 2020-06-23 1017 ret = gpiod_direction_output(desc, val); f3b3ae8752adc5 Kent Gibson 2020-06-23 1018 if (ret) f3b3ae8752adc5 Kent Gibson 2020-06-23 1019 goto out_free_line; f3b3ae8752adc5 Kent Gibson 2020-06-23 1020 } else { f3b3ae8752adc5 Kent Gibson 2020-06-23 1021 ret = gpiod_direction_input(desc); f3b3ae8752adc5 Kent Gibson 2020-06-23 1022 if (ret) f3b3ae8752adc5 Kent Gibson 2020-06-23 1023 goto out_free_line; f3b3ae8752adc5 Kent Gibson 2020-06-23 1024 ret = edge_detector_setup(&line->edets[i], lc); f3b3ae8752adc5 Kent Gibson 2020-06-23 1025 if (ret) f3b3ae8752adc5 Kent Gibson 2020-06-23 1026 goto out_free_line; f3b3ae8752adc5 Kent Gibson 2020-06-23 1027 } f3b3ae8752adc5 Kent Gibson 2020-06-23 1028 } f3b3ae8752adc5 Kent Gibson 2020-06-23 1029 f3b3ae8752adc5 Kent Gibson 2020-06-23 1030 atomic_notifier_call_chain(&desc->gdev->notifier, f3b3ae8752adc5 Kent Gibson 2020-06-23 1031 GPIOLINE_CHANGED_REQUESTED, desc); f3b3ae8752adc5 Kent Gibson 2020-06-23 1032 f3b3ae8752adc5 Kent Gibson 2020-06-23 1033 dev_dbg(&gdev->dev, "registered chardev handle for line %d\n", f3b3ae8752adc5 Kent Gibson 2020-06-23 1034 offset); f3b3ae8752adc5 Kent Gibson 2020-06-23 1035 } f3b3ae8752adc5 Kent Gibson 2020-06-23 1036 f3b3ae8752adc5 Kent Gibson 2020-06-23 1037 fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC); f3b3ae8752adc5 Kent Gibson 2020-06-23 1038 if (fd < 0) { f3b3ae8752adc5 Kent Gibson 2020-06-23 1039 ret = fd; f3b3ae8752adc5 Kent Gibson 2020-06-23 1040 goto out_free_line; f3b3ae8752adc5 Kent Gibson 2020-06-23 1041 } f3b3ae8752adc5 Kent Gibson 2020-06-23 1042 f3b3ae8752adc5 Kent Gibson 2020-06-23 1043 file = anon_inode_getfile("gpio-line", f3b3ae8752adc5 Kent Gibson 2020-06-23 1044 &line_fileops, f3b3ae8752adc5 Kent Gibson 2020-06-23 1045 line, f3b3ae8752adc5 Kent Gibson 2020-06-23 1046 O_RDONLY | O_CLOEXEC); f3b3ae8752adc5 Kent Gibson 2020-06-23 1047 if (IS_ERR(file)) { f3b3ae8752adc5 Kent Gibson 2020-06-23 1048 ret = PTR_ERR(file); f3b3ae8752adc5 Kent Gibson 2020-06-23 1049 goto out_put_unused_fd; f3b3ae8752adc5 Kent Gibson 2020-06-23 1050 } f3b3ae8752adc5 Kent Gibson 2020-06-23 1051 f3b3ae8752adc5 Kent Gibson 2020-06-23 1052 linereq.fd = fd; f3b3ae8752adc5 Kent Gibson 2020-06-23 1053 if (copy_to_user(ip, &linereq, sizeof(linereq))) { f3b3ae8752adc5 Kent Gibson 2020-06-23 1054 /* f3b3ae8752adc5 Kent Gibson 2020-06-23 1055 * fput() will trigger the release() callback, so do not go onto f3b3ae8752adc5 Kent Gibson 2020-06-23 1056 * the regular error cleanup path here. f3b3ae8752adc5 Kent Gibson 2020-06-23 1057 */ f3b3ae8752adc5 Kent Gibson 2020-06-23 1058 fput(file); f3b3ae8752adc5 Kent Gibson 2020-06-23 1059 put_unused_fd(fd); f3b3ae8752adc5 Kent Gibson 2020-06-23 1060 return -EFAULT; f3b3ae8752adc5 Kent Gibson 2020-06-23 1061 } f3b3ae8752adc5 Kent Gibson 2020-06-23 1062 f3b3ae8752adc5 Kent Gibson 2020-06-23 1063 fd_install(fd, file); f3b3ae8752adc5 Kent Gibson 2020-06-23 1064 f3b3ae8752adc5 Kent Gibson 2020-06-23 1065 dev_dbg(&gdev->dev, "registered chardev handle for %d lines\n", f3b3ae8752adc5 Kent Gibson 2020-06-23 1066 line->num_descs); f3b3ae8752adc5 Kent Gibson 2020-06-23 1067 f3b3ae8752adc5 Kent Gibson 2020-06-23 1068 return 0; f3b3ae8752adc5 Kent Gibson 2020-06-23 1069 f3b3ae8752adc5 Kent Gibson 2020-06-23 1070 out_put_unused_fd: f3b3ae8752adc5 Kent Gibson 2020-06-23 1071 put_unused_fd(fd); f3b3ae8752adc5 Kent Gibson 2020-06-23 1072 out_free_line: f3b3ae8752adc5 Kent Gibson 2020-06-23 1073 line_free(line); f3b3ae8752adc5 Kent Gibson 2020-06-23 1074 return ret; f3b3ae8752adc5 Kent Gibson 2020-06-23 1075 } --- 0-DAY CI Kernel Test Service, Intel Corporation https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
On Tue, Jun 23, 2020 at 08:44:38PM +0300, Dan Carpenter wrote: > [ The copy_to_user() overflow code is weird. Why do we need to do a > atomic_read()? That suggests that there is a potential time of check > time of use bug where we do: > It is weird, but you conveniently left out the guard comment: /* must be after kfifo check so watch_abi_version is set */ > if (atomic_read(&gcdev->watch_abi_version) == 2) // <<-- time of check > event_size = sizeof(struct gpioline_info_changed_v2); > For something to be in the fifo lineinfo_ensure_abi_version must've been called. And the watch_abi_version can only be set once by lineinfo_ensure_abi_version, so it cannot change between. But point taken, I'll change the "time of use" condition to if (event_size == sizeof(struct gpioline_info_changed_v2)) { if (copy_to_user(buf + bytes_read, &event, event_size)) > ... > > if (atomic_read(&gcdev->watch_abi_version) == 2) { // <<-- time of use > copy_to_user(blah, blah, event_size); > > If the value for "gcdev->watch_abi_version" changes between the time > of check and the time of use then it can read beyond the end of the > buffer. > > -- dan ] > > Hi Kent, > > Thank you for the patch! Perhaps something to improve: > > url: https://github.com/0day-ci/linux/commits/Kent-Gibson/gpio-cdev-add-uAPI-V2/20200623-120634 > base: https://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio.git for-next > config: openrisc-randconfig-m031-20200623 (attached as .config) > compiler: or1k-linux-gcc (GCC) 9.3.0 > > If you fix the issue, kindly add following tag as appropriate > Reported-by: kernel test robot <lkp@intel.com> > Reported-by: Dan Carpenter <dan.carpenter@oracle.com> > > smatch warnings: > drivers/gpio/gpiolib-cdev.c:891 line_free() error: dereferencing freed memory 'line' > drivers/gpio/gpiolib-cdev.c:949 line_create() warn: possible memory leak of 'line' > drivers/gpio/gpiolib-cdev.c:1860 lineinfo_watch_read() error: copy_to_user() '&event_v1' too small (104 vs 168) > > # https://github.com/0day-ci/linux/commit/f3b3ae8752adc5ac33dcf83d49b0b02f2d7ef43b > git remote add linux-review https://github.com/0day-ci/linux > git remote update linux-review > git checkout f3b3ae8752adc5ac33dcf83d49b0b02f2d7ef43b > vim +/line +891 drivers/gpio/gpiolib-cdev.c > > f3b3ae8752adc5 Kent Gibson 2020-06-23 877 static void line_free(struct line *line) > f3b3ae8752adc5 Kent Gibson 2020-06-23 878 { > f3b3ae8752adc5 Kent Gibson 2020-06-23 879 int i; > f3b3ae8752adc5 Kent Gibson 2020-06-23 880 > f3b3ae8752adc5 Kent Gibson 2020-06-23 881 for (i = 0; i < line->num_descs; i++) { > f3b3ae8752adc5 Kent Gibson 2020-06-23 882 if (line->edets) > f3b3ae8752adc5 Kent Gibson 2020-06-23 883 edge_detector_stop(&line->edets[i]); > f3b3ae8752adc5 Kent Gibson 2020-06-23 884 if (line->descs[i]) > f3b3ae8752adc5 Kent Gibson 2020-06-23 885 gpiod_free(line->descs[i]); > f3b3ae8752adc5 Kent Gibson 2020-06-23 886 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 887 kfifo_free(&line->events); > f3b3ae8752adc5 Kent Gibson 2020-06-23 888 kfree(line->label); > f3b3ae8752adc5 Kent Gibson 2020-06-23 889 kfree(line->edets); > f3b3ae8752adc5 Kent Gibson 2020-06-23 890 kfree(line); > f3b3ae8752adc5 Kent Gibson 2020-06-23 @891 put_device(&line->gdev->dev); > f3b3ae8752adc5 Kent Gibson 2020-06-23 892 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 893 > f3b3ae8752adc5 Kent Gibson 2020-06-23 894 static int line_release(struct inode *inode, struct file *file) > f3b3ae8752adc5 Kent Gibson 2020-06-23 895 { > f3b3ae8752adc5 Kent Gibson 2020-06-23 896 struct line *line = file->private_data; > f3b3ae8752adc5 Kent Gibson 2020-06-23 897 > f3b3ae8752adc5 Kent Gibson 2020-06-23 898 line_free(line); > f3b3ae8752adc5 Kent Gibson 2020-06-23 899 return 0; > f3b3ae8752adc5 Kent Gibson 2020-06-23 900 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 901 > f3b3ae8752adc5 Kent Gibson 2020-06-23 902 static const struct file_operations line_fileops = { > f3b3ae8752adc5 Kent Gibson 2020-06-23 903 .release = line_release, > f3b3ae8752adc5 Kent Gibson 2020-06-23 904 .read = line_read, > f3b3ae8752adc5 Kent Gibson 2020-06-23 905 .poll = line_poll, > f3b3ae8752adc5 Kent Gibson 2020-06-23 906 .owner = THIS_MODULE, > f3b3ae8752adc5 Kent Gibson 2020-06-23 907 .llseek = noop_llseek, > f3b3ae8752adc5 Kent Gibson 2020-06-23 908 .unlocked_ioctl = line_ioctl, > f3b3ae8752adc5 Kent Gibson 2020-06-23 909 #ifdef CONFIG_COMPAT > f3b3ae8752adc5 Kent Gibson 2020-06-23 910 .compat_ioctl = line_ioctl_compat, > f3b3ae8752adc5 Kent Gibson 2020-06-23 911 #endif > f3b3ae8752adc5 Kent Gibson 2020-06-23 912 }; > f3b3ae8752adc5 Kent Gibson 2020-06-23 913 > f3b3ae8752adc5 Kent Gibson 2020-06-23 914 static int line_create(struct gpio_device *gdev, void __user *ip) > f3b3ae8752adc5 Kent Gibson 2020-06-23 915 { > f3b3ae8752adc5 Kent Gibson 2020-06-23 916 struct gpioline_request linereq; > f3b3ae8752adc5 Kent Gibson 2020-06-23 917 struct line *line; > f3b3ae8752adc5 Kent Gibson 2020-06-23 918 struct file *file; > f3b3ae8752adc5 Kent Gibson 2020-06-23 919 int fd, i, ret, size; > f3b3ae8752adc5 Kent Gibson 2020-06-23 920 struct gpioline_config *lc; > f3b3ae8752adc5 Kent Gibson 2020-06-23 921 unsigned long *vals; > f3b3ae8752adc5 Kent Gibson 2020-06-23 922 > f3b3ae8752adc5 Kent Gibson 2020-06-23 923 if (copy_from_user(&linereq, ip, sizeof(linereq))) > f3b3ae8752adc5 Kent Gibson 2020-06-23 924 return -EFAULT; > f3b3ae8752adc5 Kent Gibson 2020-06-23 925 if ((linereq.num_lines == 0) || (linereq.num_lines > GPIOLINES_MAX)) > f3b3ae8752adc5 Kent Gibson 2020-06-23 926 return -EINVAL; > f3b3ae8752adc5 Kent Gibson 2020-06-23 927 > f3b3ae8752adc5 Kent Gibson 2020-06-23 928 if (padding_not_zeroed(linereq.padding, GPIOLINE_REQUEST_PAD_SIZE)) > f3b3ae8752adc5 Kent Gibson 2020-06-23 929 return -EINVAL; > f3b3ae8752adc5 Kent Gibson 2020-06-23 930 > f3b3ae8752adc5 Kent Gibson 2020-06-23 931 lc = &linereq.config; > f3b3ae8752adc5 Kent Gibson 2020-06-23 932 ret = gpioline_config_validate(lc); > f3b3ae8752adc5 Kent Gibson 2020-06-23 933 if (ret) > f3b3ae8752adc5 Kent Gibson 2020-06-23 934 return ret; > f3b3ae8752adc5 Kent Gibson 2020-06-23 935 > f3b3ae8752adc5 Kent Gibson 2020-06-23 936 /* event_buffer_size only valid with edge_detection */ > f3b3ae8752adc5 Kent Gibson 2020-06-23 937 if ((linereq.event_buffer_size) && > f3b3ae8752adc5 Kent Gibson 2020-06-23 938 !(linereq.config.flags & GPIOLINE_FLAG_V2_EDGE_DETECTION)) > f3b3ae8752adc5 Kent Gibson 2020-06-23 939 return -EINVAL; > f3b3ae8752adc5 Kent Gibson 2020-06-23 940 > f3b3ae8752adc5 Kent Gibson 2020-06-23 941 line = kzalloc(struct_size(line, descs, linereq.num_lines), > f3b3ae8752adc5 Kent Gibson 2020-06-23 942 GFP_KERNEL); > f3b3ae8752adc5 Kent Gibson 2020-06-23 943 if (!line) > f3b3ae8752adc5 Kent Gibson 2020-06-23 944 return -ENOMEM; > f3b3ae8752adc5 Kent Gibson 2020-06-23 945 > f3b3ae8752adc5 Kent Gibson 2020-06-23 946 line->edets = kcalloc(linereq.num_lines, sizeof(*line->edets), > f3b3ae8752adc5 Kent Gibson 2020-06-23 947 GFP_KERNEL); > f3b3ae8752adc5 Kent Gibson 2020-06-23 948 if (!line->edets) > f3b3ae8752adc5 Kent Gibson 2020-06-23 @949 return -ENOMEM; > ^^^^^^^^^^^^^^^ > kfree(line) before returning. > Yeah, that is bad. Good pickup - it should be a goto out_free_line like the one below for line->label. Cheers, Kent. > f3b3ae8752adc5 Kent Gibson 2020-06-23 950 > f3b3ae8752adc5 Kent Gibson 2020-06-23 951 for (i = 0; i < linereq.num_lines; i++) > f3b3ae8752adc5 Kent Gibson 2020-06-23 952 line->edets[i].line = line; > f3b3ae8752adc5 Kent Gibson 2020-06-23 953 > f3b3ae8752adc5 Kent Gibson 2020-06-23 954 line->gdev = gdev; > f3b3ae8752adc5 Kent Gibson 2020-06-23 955 get_device(&gdev->dev); > f3b3ae8752adc5 Kent Gibson 2020-06-23 956 > f3b3ae8752adc5 Kent Gibson 2020-06-23 957 /* Make sure this is terminated */ > f3b3ae8752adc5 Kent Gibson 2020-06-23 958 linereq.consumer[sizeof(linereq.consumer)-1] = '\0'; > f3b3ae8752adc5 Kent Gibson 2020-06-23 959 if (strlen(linereq.consumer)) { > f3b3ae8752adc5 Kent Gibson 2020-06-23 960 line->label = kstrdup(linereq.consumer, GFP_KERNEL); > f3b3ae8752adc5 Kent Gibson 2020-06-23 961 if (!line->label) { > f3b3ae8752adc5 Kent Gibson 2020-06-23 962 ret = -ENOMEM; > f3b3ae8752adc5 Kent Gibson 2020-06-23 963 goto out_free_line; > f3b3ae8752adc5 Kent Gibson 2020-06-23 964 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 965 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 966 > f3b3ae8752adc5 Kent Gibson 2020-06-23 967 mutex_init(&line->config_mutex); > f3b3ae8752adc5 Kent Gibson 2020-06-23 968 init_waitqueue_head(&line->wait); > f3b3ae8752adc5 Kent Gibson 2020-06-23 969 if (lc->edge_detection) { > f3b3ae8752adc5 Kent Gibson 2020-06-23 970 size = linereq.event_buffer_size; > f3b3ae8752adc5 Kent Gibson 2020-06-23 971 > f3b3ae8752adc5 Kent Gibson 2020-06-23 972 if (size > GPIOLINES_MAX*16) > f3b3ae8752adc5 Kent Gibson 2020-06-23 973 size = GPIOLINES_MAX*16; > f3b3ae8752adc5 Kent Gibson 2020-06-23 974 else if (size == 0) > f3b3ae8752adc5 Kent Gibson 2020-06-23 975 size = linereq.num_lines*16; > f3b3ae8752adc5 Kent Gibson 2020-06-23 976 > f3b3ae8752adc5 Kent Gibson 2020-06-23 977 ret = kfifo_alloc(&line->events, size, GFP_KERNEL); > f3b3ae8752adc5 Kent Gibson 2020-06-23 978 if (ret) > f3b3ae8752adc5 Kent Gibson 2020-06-23 979 goto out_free_line; > f3b3ae8752adc5 Kent Gibson 2020-06-23 980 > f3b3ae8752adc5 Kent Gibson 2020-06-23 981 line->edge_detection = lc->edge_detection; > f3b3ae8752adc5 Kent Gibson 2020-06-23 982 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 983 > f3b3ae8752adc5 Kent Gibson 2020-06-23 984 atomic_set(&line->seqno, 0); > f3b3ae8752adc5 Kent Gibson 2020-06-23 985 line->num_descs = linereq.num_lines; > f3b3ae8752adc5 Kent Gibson 2020-06-23 986 vals = (unsigned long *)lc->values.bitmap; > f3b3ae8752adc5 Kent Gibson 2020-06-23 987 > f3b3ae8752adc5 Kent Gibson 2020-06-23 988 /* Request each GPIO */ > f3b3ae8752adc5 Kent Gibson 2020-06-23 989 for (i = 0; i < linereq.num_lines; i++) { > f3b3ae8752adc5 Kent Gibson 2020-06-23 990 u32 offset = linereq.offsets[i]; > f3b3ae8752adc5 Kent Gibson 2020-06-23 991 struct gpio_desc *desc = gpiochip_get_desc(gdev->chip, offset); > f3b3ae8752adc5 Kent Gibson 2020-06-23 992 > f3b3ae8752adc5 Kent Gibson 2020-06-23 993 if (IS_ERR(desc)) { > f3b3ae8752adc5 Kent Gibson 2020-06-23 994 ret = PTR_ERR(desc); > f3b3ae8752adc5 Kent Gibson 2020-06-23 995 goto out_free_line; > f3b3ae8752adc5 Kent Gibson 2020-06-23 996 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 997 > f3b3ae8752adc5 Kent Gibson 2020-06-23 998 ret = gpiod_request(desc, line->label); > f3b3ae8752adc5 Kent Gibson 2020-06-23 999 if (ret) > f3b3ae8752adc5 Kent Gibson 2020-06-23 1000 goto out_free_line; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1001 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1002 line->descs[i] = desc; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1003 gpioline_config_to_desc_flags(lc, &desc->flags); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1004 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1005 ret = gpiod_set_transitory(desc, false); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1006 if (ret < 0) > f3b3ae8752adc5 Kent Gibson 2020-06-23 1007 goto out_free_line; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1008 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1009 /* > f3b3ae8752adc5 Kent Gibson 2020-06-23 1010 * Lines have to be requested explicitly for input > f3b3ae8752adc5 Kent Gibson 2020-06-23 1011 * or output, else the line will be treated "as is". > f3b3ae8752adc5 Kent Gibson 2020-06-23 1012 */ > f3b3ae8752adc5 Kent Gibson 2020-06-23 1013 if (lc->flags & GPIOLINE_FLAG_V2_DIRECTION) { > f3b3ae8752adc5 Kent Gibson 2020-06-23 1014 if (lc->direction == GPIOLINE_DIRECTION_OUTPUT) { > f3b3ae8752adc5 Kent Gibson 2020-06-23 1015 int val = test_bit(i, vals); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1016 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1017 ret = gpiod_direction_output(desc, val); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1018 if (ret) > f3b3ae8752adc5 Kent Gibson 2020-06-23 1019 goto out_free_line; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1020 } else { > f3b3ae8752adc5 Kent Gibson 2020-06-23 1021 ret = gpiod_direction_input(desc); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1022 if (ret) > f3b3ae8752adc5 Kent Gibson 2020-06-23 1023 goto out_free_line; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1024 ret = edge_detector_setup(&line->edets[i], lc); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1025 if (ret) > f3b3ae8752adc5 Kent Gibson 2020-06-23 1026 goto out_free_line; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1027 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 1028 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 1029 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1030 atomic_notifier_call_chain(&desc->gdev->notifier, > f3b3ae8752adc5 Kent Gibson 2020-06-23 1031 GPIOLINE_CHANGED_REQUESTED, desc); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1032 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1033 dev_dbg(&gdev->dev, "registered chardev handle for line %d\n", > f3b3ae8752adc5 Kent Gibson 2020-06-23 1034 offset); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1035 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 1036 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1037 fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1038 if (fd < 0) { > f3b3ae8752adc5 Kent Gibson 2020-06-23 1039 ret = fd; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1040 goto out_free_line; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1041 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 1042 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1043 file = anon_inode_getfile("gpio-line", > f3b3ae8752adc5 Kent Gibson 2020-06-23 1044 &line_fileops, > f3b3ae8752adc5 Kent Gibson 2020-06-23 1045 line, > f3b3ae8752adc5 Kent Gibson 2020-06-23 1046 O_RDONLY | O_CLOEXEC); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1047 if (IS_ERR(file)) { > f3b3ae8752adc5 Kent Gibson 2020-06-23 1048 ret = PTR_ERR(file); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1049 goto out_put_unused_fd; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1050 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 1051 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1052 linereq.fd = fd; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1053 if (copy_to_user(ip, &linereq, sizeof(linereq))) { > f3b3ae8752adc5 Kent Gibson 2020-06-23 1054 /* > f3b3ae8752adc5 Kent Gibson 2020-06-23 1055 * fput() will trigger the release() callback, so do not go onto > f3b3ae8752adc5 Kent Gibson 2020-06-23 1056 * the regular error cleanup path here. > f3b3ae8752adc5 Kent Gibson 2020-06-23 1057 */ > f3b3ae8752adc5 Kent Gibson 2020-06-23 1058 fput(file); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1059 put_unused_fd(fd); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1060 return -EFAULT; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1061 } > f3b3ae8752adc5 Kent Gibson 2020-06-23 1062 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1063 fd_install(fd, file); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1064 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1065 dev_dbg(&gdev->dev, "registered chardev handle for %d lines\n", > f3b3ae8752adc5 Kent Gibson 2020-06-23 1066 line->num_descs); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1067 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1068 return 0; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1069 > f3b3ae8752adc5 Kent Gibson 2020-06-23 1070 out_put_unused_fd: > f3b3ae8752adc5 Kent Gibson 2020-06-23 1071 put_unused_fd(fd); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1072 out_free_line: > f3b3ae8752adc5 Kent Gibson 2020-06-23 1073 line_free(line); > f3b3ae8752adc5 Kent Gibson 2020-06-23 1074 return ret; > f3b3ae8752adc5 Kent Gibson 2020-06-23 1075 } > > --- > 0-DAY CI Kernel Test Service, Intel Corporation > https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index b6878fc87dfc..d4a22d78953f 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/anon_inodes.h> +#include <linux/atomic.h> #include <linux/bitmap.h> +#include <linux/build_bug.h> #include <linux/cdev.h> #include <linux/compat.h> #include <linux/device.h> @@ -14,11 +16,13 @@ #include <linux/kernel.h> #include <linux/kfifo.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/pinctrl/consumer.h> #include <linux/poll.h> #include <linux/spinlock.h> #include <linux/timekeeping.h> #include <linux/uaccess.h> +#include <linux/workqueue.h> #include <uapi/linux/gpio.h> #include "gpiolib.h" @@ -34,6 +38,7 @@ * GPIO line handle management */ +#ifdef CONFIG_GPIO_CDEV_V1 /** * struct linehandle_state - contains the state of a userspace handle * @gdev: the GPIO device the handle pertains to @@ -377,6 +382,699 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip) put_device(&gdev->dev); return ret; } +#endif /* CONFIG_GPIO_CDEV_V1 */ + +/** + * struct edge_detector - contains the state of a line edge detector + * @line: the corresponding line request + * @irq: the interrupt triggered in response to events on this GPIO + * @timestamp: cache for the timestamp storing it between hardirq and IRQ + * thread, used to bring the timestamp close to the actual event + * @seqno: the seqno for the current edge event in the sequence of events + * for the corresponding line request. Ths is drawn from the @line. + * @line_seqno: the seqno for the current edge event in the sequence of + * events for this line. + */ +struct edge_detector { + struct line *line; + unsigned int irq; + /* + * timestamp and seqno are shared by edge_irq_handler and + * edge_irq_thread which are themselves mutually exclusive. + */ + u64 timestamp; + u32 seqno; + u32 line_seqno; +}; + +/** + * struct line - contains the state of a userspace line request + * @gdev: the GPIO device the line request pertains to + * @label: consumer label used to tag descriptors + * @num_descs: the number of descriptors held in the descs array + * @edge_detection: the type of edge detection applied + * @wait: wait queue that handles blocking reads of events + * @events: KFIFO for the GPIO events + * @seqno: the sequence number for edge events generated on all lines in + * this line request. Note that this is not used when @num_descs is 1, as + * the line_seqno is then the same and is cheaper to calculate. + * @config_mutex: mutex serializing GPIOLINE_SET_CONFIG_IOCTL ioctl() calls to + * ensure consistency of configuration changes. + * @edets: an array of edge detectors, of size @num_descs + * @descs: the GPIO descriptors held by this line request, with @num_descs + * elements. + */ +struct line { + struct gpio_device *gdev; + const char *label; + u32 num_descs; + enum gpioline_edge edge_detection; + wait_queue_head_t wait; + DECLARE_KFIFO_PTR(events, struct gpioline_event); + atomic_t seqno; + struct mutex config_mutex; /* serializes line_set_config calls */ + struct edge_detector *edets; + /* descs must be last so it can be dynamically sized */ + struct gpio_desc *descs[]; +}; + +static inline struct gpio_desc *edge_detector_desc( + const struct edge_detector *edet) +{ + return edet->line->descs[edet - &edet->line->edets[0]]; +} + +static irqreturn_t edge_irq_thread(int irq, void *p) +{ + struct edge_detector *edet = p; + struct line *line = edet->line; + struct gpio_desc *desc = edge_detector_desc(edet); + struct gpioline_event le; + int ret; + + /* Do not leak kernel stack to userspace */ + memset(&le, 0, sizeof(le)); + + /* + * We may be running from a nested threaded interrupt in which case + * we didn't get the timestamp from edge_irq_handler(). + */ + if (!edet->timestamp) { + le.timestamp = ktime_get_ns(); + if (line->num_descs != 1) + edet->seqno = atomic_inc_return(&line->seqno); + } else { + le.timestamp = edet->timestamp; + } + + edet->timestamp = 0; + + if (line->edge_detection == GPIOLINE_EDGE_BOTH) { + int level = gpiod_get_value_cansleep(desc); + + if (level) + /* Emit low-to-high event */ + le.id = GPIOLINE_EVENT_RISING_EDGE; + else + /* Emit high-to-low event */ + le.id = GPIOLINE_EVENT_FALLING_EDGE; + } else if (line->edge_detection == GPIOLINE_EDGE_RISING) { + /* Emit low-to-high event */ + le.id = GPIOLINE_EVENT_RISING_EDGE; + } else if (line->edge_detection == GPIOLINE_EDGE_FALLING) { + /* Emit high-to-low event */ + le.id = GPIOLINE_EVENT_FALLING_EDGE; + } else { + return IRQ_NONE; + } + edet->line_seqno++; + le.line_seqno = edet->line_seqno; + le.seqno = (line->num_descs == 1) ? le.line_seqno : edet->seqno; + le.offset = gpio_chip_hwgpio(desc); + + ret = kfifo_in_spinlocked_noirqsave(&line->events, &le, + 1, &line->wait.lock); + if (ret) + wake_up_poll(&line->wait, EPOLLIN); + else + pr_debug_ratelimited("event FIFO is full - event dropped\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t edge_irq_handler(int irq, void *p) +{ + struct edge_detector *edet = p; + struct line *line = edet->line; + + /* + * Just store the timestamp in hardirq context so we get it as + * close in time as possible to the actual event. + */ + edet->timestamp = ktime_get_ns(); + + if (line->num_descs != 1) + edet->seqno = atomic_inc_return(&line->seqno); + + return IRQ_WAKE_THREAD; +} + +static int edge_detector_start(struct edge_detector *edet) +{ + int ret, irq, irqflags = 0; + struct gpio_desc *desc; + + desc = edge_detector_desc(edet); + irq = gpiod_to_irq(desc); + + if (irq <= 0) + return -ENODEV; + + edet->seqno = 0; + edet->line_seqno = 0; + + if (edet->line->edge_detection & GPIOLINE_EDGE_RISING) + irqflags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ? + IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING; + if (edet->line->edge_detection & GPIOLINE_EDGE_FALLING) + irqflags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ? + IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING; + irqflags |= IRQF_ONESHOT; + + /* Request a thread to read the events */ + ret = request_threaded_irq(irq, + edge_irq_handler, + edge_irq_thread, + irqflags, + edet->line->label, + edet); + if (ret) + return ret; + + edet->irq = irq; + return 0; +} + +static void edge_detector_stop(struct edge_detector *edet) +{ + if (edet->irq) { + free_irq(edet->irq, edet); + edet->irq = 0; + } +} + +static int edge_detector_setup(struct edge_detector *edet, + struct gpioline_config *lc) +{ + if (lc->edge_detection) + return edge_detector_start(edet); + return 0; +} + +static bool padding_not_zeroed(__u32 *padding, int pad_size) +{ + int i, sum = 0; + + for (i = 0; i < pad_size; i++) + sum |= padding[i]; + + return sum; +} + +#define GPIOLINE_CONFIG_VALID_FLAGS \ + (GPIOLINE_FLAG_V2_ACTIVE_LOW | \ + GPIOLINE_FLAG_V2_DIRECTION | \ + GPIOLINE_FLAG_V2_DRIVE | \ + GPIOLINE_FLAG_V2_BIAS | \ + GPIOLINE_FLAG_V2_EDGE_DETECTION | \ + GPIOLINE_FLAG_V2_DEBOUNCE) + +static int gpioline_config_validate(struct gpioline_config *config) +{ + u32 lflags = config->flags; + + /* Return an error if an unknown flag is set */ + if (lflags & ~GPIOLINE_CONFIG_VALID_FLAGS) + return -EINVAL; + + /* Check ranges */ + if (lflags & GPIOLINE_FLAG_V2_DIRECTION) { + if (config->direction > GPIOLINE_DIRECTION_OUTPUT) + return -EINVAL; + } else if (config->direction != 0) { + return -EINVAL; + } + + if (lflags & GPIOLINE_FLAG_V2_DRIVE) { + if (config->drive > GPIOLINE_DRIVE_OPEN_SOURCE) + return -EINVAL; + } else if (config->drive != 0) { + return -EINVAL; + } + + if (lflags & GPIOLINE_FLAG_V2_BIAS) { + if (config->bias > GPIOLINE_BIAS_PULL_DOWN) + return -EINVAL; + } else if (config->bias != 0) { + return -EINVAL; + } + + if (lflags & GPIOLINE_FLAG_V2_EDGE_DETECTION) { + if (config->edge_detection > GPIOLINE_EDGE_BOTH) + return -EINVAL; + } else if (config->edge_detection != 0) { + return -EINVAL; + } + + if (config->debounce_period && !(lflags & GPIOLINE_FLAG_V2_DEBOUNCE)) + return -EINVAL; + + /* Drive requires explicit output direction. */ + if ((lflags & GPIOLINE_FLAG_V2_DRIVE) && + (!(lflags & GPIOLINE_FLAG_V2_DIRECTION) || + (config->direction != GPIOLINE_DIRECTION_OUTPUT))) + return -EINVAL; + + /* Bias requires explicit direction. */ + if ((lflags & GPIOLINE_FLAG_V2_BIAS) && + !(lflags & GPIOLINE_FLAG_V2_DIRECTION)) + return -EINVAL; + + /* Edge detection requires explicit input direction. */ + if ((lflags & GPIOLINE_FLAG_V2_EDGE_DETECTION) && + (!(lflags & GPIOLINE_FLAG_V2_DIRECTION) || + (config->direction != GPIOLINE_DIRECTION_INPUT))) + return -EINVAL; + + /* Debounce requires explicit input direction. */ + if ((lflags & GPIOLINE_FLAG_V2_DEBOUNCE) && + (!(lflags & GPIOLINE_FLAG_V2_DIRECTION) || + (config->direction != GPIOLINE_DIRECTION_INPUT))) + return -EINVAL; + + if (padding_not_zeroed(config->padding, GPIOLINE_CONFIG_PAD_SIZE)) + return -EINVAL; + + return 0; +} + +static void gpioline_config_to_desc_flags(struct gpioline_config *lc, + unsigned long *flagsp) +{ + assign_bit(FLAG_ACTIVE_LOW, flagsp, + lc->flags & GPIOLINE_FLAG_V2_ACTIVE_LOW); + if (lc->flags & GPIOLINE_FLAG_V2_DRIVE) { + assign_bit(FLAG_OPEN_DRAIN, flagsp, + lc->drive == GPIOLINE_DRIVE_OPEN_DRAIN); + assign_bit(FLAG_OPEN_SOURCE, flagsp, + lc->drive == GPIOLINE_DRIVE_OPEN_SOURCE); + } + if (lc->flags & GPIOLINE_FLAG_V2_BIAS) { + assign_bit(FLAG_PULL_UP, flagsp, + lc->bias == GPIOLINE_BIAS_PULL_UP); + assign_bit(FLAG_PULL_DOWN, flagsp, + lc->bias == GPIOLINE_BIAS_PULL_DOWN); + assign_bit(FLAG_BIAS_DISABLE, flagsp, + lc->bias == GPIOLINE_BIAS_DISABLED); + } +} + +static long line_set_config_locked(struct line *line, + struct gpioline_config *lc) +{ + struct gpio_desc *desc; + unsigned long *vals; + int i, ret; + + /* disallow edge detection changes */ + if (line->edge_detection != lc->edge_detection) + return -EINVAL; + + desc = line->descs[0]; + + if (line->edge_detection != GPIOLINE_EDGE_NONE) { + /* disallow polarity changes */ + if (test_bit(FLAG_ACTIVE_LOW, &desc->flags) != + ((lc->flags & GPIOLINE_FLAG_V2_ACTIVE_LOW) != 0)) + return -EINVAL; + } + + vals = (unsigned long *)lc->values.bitmap; + + for (i = 0; i < line->num_descs; i++) { + desc = line->descs[i]; + gpioline_config_to_desc_flags(lc, &desc->flags); + /* + * Lines have to be requested explicitly for input + * or output, else the line will be treated "as is". + */ + if (lc->flags & GPIOLINE_FLAG_V2_DIRECTION) { + if (lc->direction == GPIOLINE_DIRECTION_OUTPUT) { + int val = test_bit(i, vals); + + ret = gpiod_direction_output(desc, val); + if (ret) + return ret; + } else { + ret = gpiod_direction_input(desc); + if (ret) + return ret; + } + } + + atomic_notifier_call_chain(&desc->gdev->notifier, + GPIOLINE_CHANGED_CONFIG, desc); + } + return 0; +} + +static long line_set_config(struct line *line, void __user *ip) +{ + struct gpioline_config lc; + int ret; + + if (copy_from_user(&lc, ip, sizeof(lc))) + return -EFAULT; + + ret = gpioline_config_validate(&lc); + if (ret) + return ret; + + mutex_lock(&line->config_mutex); + + ret = line_set_config_locked(line, &lc); + + mutex_unlock(&line->config_mutex); + + return ret; +} + +static long line_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct line *line = file->private_data; + void __user *ip = (void __user *)arg; + struct gpioline_values glv; + unsigned long *vals = (unsigned long *)glv.bitmap; + int ret; + + if (cmd == GPIOLINE_GET_VALUES_IOCTL) { + /* NOTE: It's ok to read values of output lines. */ + memset(&glv, 0, sizeof(glv)); + ret = gpiod_get_array_value_complex(false, + true, + line->num_descs, + line->descs, + NULL, + vals); + if (ret) + return ret; + + if (copy_to_user(ip, &glv, sizeof(glv))) + return -EFAULT; + + return 0; + } else if (cmd == GPIOLINE_SET_VALUES_IOCTL) { + /* + * All line descriptors were created at once with the same + * flags so just check if the first one is really output. + */ + if (!test_bit(FLAG_IS_OUT, &line->descs[0]->flags)) + return -EPERM; + + if (copy_from_user(&glv, ip, sizeof(glv))) + return -EFAULT; + + /* Reuse the array setting function */ + return gpiod_set_array_value_complex(false, + true, + line->num_descs, + line->descs, + NULL, + vals); + } else if (cmd == GPIOLINE_SET_CONFIG_IOCTL) { + return line_set_config(line, ip); + } + return -EINVAL; +} + +#ifdef CONFIG_COMPAT +static long line_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return line_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static __poll_t line_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct line *line = file->private_data; + __poll_t events = 0; + + poll_wait(file, &line->wait, wait); + + if (!kfifo_is_empty_spinlocked_noirqsave(&line->events, &line->wait.lock)) + events = EPOLLIN | EPOLLRDNORM; + + return events; +} + +static ssize_t line_read(struct file *file, + char __user *buf, + size_t count, + loff_t *f_ps) +{ + struct line *line = file->private_data; + struct gpioline_event le; + ssize_t bytes_read = 0; + int ret; + + if (count < sizeof(le)) + return -EINVAL; + + do { + spin_lock(&line->wait.lock); + if (kfifo_is_empty(&line->events)) { + if (bytes_read) { + spin_unlock(&line->wait.lock); + return bytes_read; + } + + if (file->f_flags & O_NONBLOCK) { + spin_unlock(&line->wait.lock); + return -EAGAIN; + } + + ret = wait_event_interruptible_locked(line->wait, + !kfifo_is_empty(&line->events)); + if (ret) { + spin_unlock(&line->wait.lock); + return ret; + } + } + + ret = kfifo_out(&line->events, &le, 1); + spin_unlock(&line->wait.lock); + if (ret != 1) { + /* + * This should never happen - we were holding the lock + * from the moment we learned the fifo is no longer + * empty until now. + */ + ret = -EIO; + break; + } + + if (copy_to_user(buf + bytes_read, &le, sizeof(le))) + return -EFAULT; + bytes_read += sizeof(le); + } while (count >= bytes_read + sizeof(le)); + + return bytes_read; +} + +static void line_free(struct line *line) +{ + int i; + + for (i = 0; i < line->num_descs; i++) { + if (line->edets) + edge_detector_stop(&line->edets[i]); + if (line->descs[i]) + gpiod_free(line->descs[i]); + } + kfifo_free(&line->events); + kfree(line->label); + kfree(line->edets); + kfree(line); + put_device(&line->gdev->dev); +} + +static int line_release(struct inode *inode, struct file *file) +{ + struct line *line = file->private_data; + + line_free(line); + return 0; +} + +static const struct file_operations line_fileops = { + .release = line_release, + .read = line_read, + .poll = line_poll, + .owner = THIS_MODULE, + .llseek = noop_llseek, + .unlocked_ioctl = line_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = line_ioctl_compat, +#endif +}; + +static int line_create(struct gpio_device *gdev, void __user *ip) +{ + struct gpioline_request linereq; + struct line *line; + struct file *file; + int fd, i, ret, size; + struct gpioline_config *lc; + unsigned long *vals; + + if (copy_from_user(&linereq, ip, sizeof(linereq))) + return -EFAULT; + if ((linereq.num_lines == 0) || (linereq.num_lines > GPIOLINES_MAX)) + return -EINVAL; + + if (padding_not_zeroed(linereq.padding, GPIOLINE_REQUEST_PAD_SIZE)) + return -EINVAL; + + lc = &linereq.config; + ret = gpioline_config_validate(lc); + if (ret) + return ret; + + /* event_buffer_size only valid with edge_detection */ + if ((linereq.event_buffer_size) && + !(linereq.config.flags & GPIOLINE_FLAG_V2_EDGE_DETECTION)) + return -EINVAL; + + line = kzalloc(struct_size(line, descs, linereq.num_lines), + GFP_KERNEL); + if (!line) + return -ENOMEM; + + line->edets = kcalloc(linereq.num_lines, sizeof(*line->edets), + GFP_KERNEL); + if (!line->edets) + return -ENOMEM; + + for (i = 0; i < linereq.num_lines; i++) + line->edets[i].line = line; + + line->gdev = gdev; + get_device(&gdev->dev); + + /* Make sure this is terminated */ + linereq.consumer[sizeof(linereq.consumer)-1] = '\0'; + if (strlen(linereq.consumer)) { + line->label = kstrdup(linereq.consumer, GFP_KERNEL); + if (!line->label) { + ret = -ENOMEM; + goto out_free_line; + } + } + + mutex_init(&line->config_mutex); + init_waitqueue_head(&line->wait); + if (lc->edge_detection) { + size = linereq.event_buffer_size; + + if (size > GPIOLINES_MAX*16) + size = GPIOLINES_MAX*16; + else if (size == 0) + size = linereq.num_lines*16; + + ret = kfifo_alloc(&line->events, size, GFP_KERNEL); + if (ret) + goto out_free_line; + + line->edge_detection = lc->edge_detection; + } + + atomic_set(&line->seqno, 0); + line->num_descs = linereq.num_lines; + vals = (unsigned long *)lc->values.bitmap; + + /* Request each GPIO */ + for (i = 0; i < linereq.num_lines; i++) { + u32 offset = linereq.offsets[i]; + struct gpio_desc *desc = gpiochip_get_desc(gdev->chip, offset); + + if (IS_ERR(desc)) { + ret = PTR_ERR(desc); + goto out_free_line; + } + + ret = gpiod_request(desc, line->label); + if (ret) + goto out_free_line; + + line->descs[i] = desc; + gpioline_config_to_desc_flags(lc, &desc->flags); + + ret = gpiod_set_transitory(desc, false); + if (ret < 0) + goto out_free_line; + + /* + * Lines have to be requested explicitly for input + * or output, else the line will be treated "as is". + */ + if (lc->flags & GPIOLINE_FLAG_V2_DIRECTION) { + if (lc->direction == GPIOLINE_DIRECTION_OUTPUT) { + int val = test_bit(i, vals); + + ret = gpiod_direction_output(desc, val); + if (ret) + goto out_free_line; + } else { + ret = gpiod_direction_input(desc); + if (ret) + goto out_free_line; + ret = edge_detector_setup(&line->edets[i], lc); + if (ret) + goto out_free_line; + } + } + + atomic_notifier_call_chain(&desc->gdev->notifier, + GPIOLINE_CHANGED_REQUESTED, desc); + + dev_dbg(&gdev->dev, "registered chardev handle for line %d\n", + offset); + } + + fd = get_unused_fd_flags(O_RDONLY | O_CLOEXEC); + if (fd < 0) { + ret = fd; + goto out_free_line; + } + + file = anon_inode_getfile("gpio-line", + &line_fileops, + line, + O_RDONLY | O_CLOEXEC); + if (IS_ERR(file)) { + ret = PTR_ERR(file); + goto out_put_unused_fd; + } + + linereq.fd = fd; + if (copy_to_user(ip, &linereq, sizeof(linereq))) { + /* + * fput() will trigger the release() callback, so do not go onto + * the regular error cleanup path here. + */ + fput(file); + put_unused_fd(fd); + return -EFAULT; + } + + fd_install(fd, file); + + dev_dbg(&gdev->dev, "registered chardev handle for %d lines\n", + line->num_descs); + + return 0; + +out_put_unused_fd: + put_unused_fd(fd); +out_free_line: + line_free(line); + return ret; +} + +#ifdef CONFIG_GPIO_CDEV_V1 /* * GPIO line event management @@ -750,10 +1448,63 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip) return ret; } +static void gpioline_info_v2_to_v1(struct gpioline_info_v2 *info_v2, + struct gpioline_info *info_v1) +{ + int flagsv2 = info_v2->config.flags; + + strncpy(info_v1->name, info_v2->name, sizeof(info_v1->name)); + strncpy(info_v1->consumer, info_v2->consumer, + sizeof(info_v1->consumer)); + info_v1->line_offset = info_v2->offset; + info_v1->flags = 0; + + if (flagsv2 & GPIOLINE_FLAG_V2_USED) + info_v1->flags |= GPIOLINE_FLAG_KERNEL; + + if (info_v2->config.direction == GPIOLINE_DIRECTION_OUTPUT) + info_v1->flags |= GPIOLINE_FLAG_IS_OUT; + + if (flagsv2 & GPIOLINE_FLAG_V2_ACTIVE_LOW) + info_v1->flags |= GPIOLINE_FLAG_ACTIVE_LOW; + + if (flagsv2 & GPIOLINE_FLAG_V2_DRIVE) { + enum gpioline_drive drive = info_v2->config.drive; + + if (drive == GPIOLINE_DRIVE_OPEN_DRAIN) + info_v1->flags |= GPIOLINE_FLAG_OPEN_DRAIN; + else if (drive == GPIOLINE_DRIVE_OPEN_SOURCE) + info_v1->flags |= GPIOLINE_FLAG_OPEN_SOURCE; + } + + if (flagsv2 & GPIOLINE_FLAG_V2_BIAS) { + enum gpioline_bias bias = info_v2->config.bias; + + if (bias == GPIOLINE_BIAS_PULL_UP) + info_v1->flags |= GPIOLINE_FLAG_BIAS_PULL_UP; + else if (bias == GPIOLINE_BIAS_PULL_DOWN) + info_v1->flags |= GPIOLINE_FLAG_BIAS_PULL_DOWN; + else if (bias == GPIOLINE_BIAS_DISABLED) + info_v1->flags |= GPIOLINE_FLAG_BIAS_DISABLE; + } +} + +static void gpioline_info_changed_v2_to_v1( + struct gpioline_info_changed_v2 *lic_v2, + struct gpioline_info_changed *lic_v1) +{ + gpioline_info_v2_to_v1(&lic_v2->info, &lic_v1->info); + lic_v1->timestamp = lic_v2->timestamp; + lic_v1->event_type = lic_v2->event_type; +} + +#endif /* CONFIG_GPIO_CDEV_V1 */ + static void gpio_desc_to_lineinfo(struct gpio_desc *desc, - struct gpioline_info *info) + struct gpioline_info_v2 *info) { struct gpio_chip *gc = desc->gdev->chip; + struct gpioline_config *lc = &info->config; bool ok_for_pinctrl; unsigned long flags; @@ -765,7 +1516,7 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, * lock common to both frameworks? */ ok_for_pinctrl = - pinctrl_gpio_can_use_line(gc->base + info->line_offset); + pinctrl_gpio_can_use_line(gc->base + info->offset); spin_lock_irqsave(&gpio_lock, flags); @@ -783,34 +1534,46 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, info->consumer[0] = '\0'; } + lc->flags = GPIOLINE_FLAG_V2_DIRECTION; /* * Userspace only need to know that the kernel is using this GPIO so * it can't use it. */ - info->flags = 0; if (test_bit(FLAG_REQUESTED, &desc->flags) || test_bit(FLAG_IS_HOGGED, &desc->flags) || test_bit(FLAG_USED_AS_IRQ, &desc->flags) || test_bit(FLAG_EXPORT, &desc->flags) || test_bit(FLAG_SYSFS, &desc->flags) || !ok_for_pinctrl) - info->flags |= GPIOLINE_FLAG_KERNEL; - if (test_bit(FLAG_IS_OUT, &desc->flags)) - info->flags |= GPIOLINE_FLAG_IS_OUT; + lc->flags |= GPIOLINE_FLAG_V2_USED; + if (test_bit(FLAG_IS_OUT, &desc->flags)) { + lc->direction = GPIOLINE_DIRECTION_OUTPUT; + lc->flags |= GPIOLINE_FLAG_V2_DRIVE; + if (test_bit(FLAG_OPEN_DRAIN, &desc->flags)) + lc->drive = GPIOLINE_DRIVE_OPEN_DRAIN; + else if (test_bit(FLAG_OPEN_SOURCE, &desc->flags)) + lc->drive = GPIOLINE_DRIVE_OPEN_SOURCE; + else + lc->drive = GPIOLINE_DRIVE_PUSH_PULL; + } else { + lc->direction = GPIOLINE_DIRECTION_INPUT; + } + if (test_bit(FLAG_ACTIVE_LOW, &desc->flags)) - info->flags |= GPIOLINE_FLAG_ACTIVE_LOW; - if (test_bit(FLAG_OPEN_DRAIN, &desc->flags)) - info->flags |= (GPIOLINE_FLAG_OPEN_DRAIN | - GPIOLINE_FLAG_IS_OUT); - if (test_bit(FLAG_OPEN_SOURCE, &desc->flags)) - info->flags |= (GPIOLINE_FLAG_OPEN_SOURCE | - GPIOLINE_FLAG_IS_OUT); - if (test_bit(FLAG_BIAS_DISABLE, &desc->flags)) - info->flags |= GPIOLINE_FLAG_BIAS_DISABLE; - if (test_bit(FLAG_PULL_DOWN, &desc->flags)) - info->flags |= GPIOLINE_FLAG_BIAS_PULL_DOWN; - if (test_bit(FLAG_PULL_UP, &desc->flags)) - info->flags |= GPIOLINE_FLAG_BIAS_PULL_UP; + lc->flags |= GPIOLINE_FLAG_V2_ACTIVE_LOW; + + if (test_bit(FLAG_BIAS_DISABLE, &desc->flags)) { + lc->flags |= GPIOLINE_FLAG_V2_BIAS; + lc->bias = GPIOLINE_BIAS_DISABLED; + } else if (test_bit(FLAG_PULL_DOWN, &desc->flags)) { + lc->flags |= GPIOLINE_FLAG_V2_BIAS; + lc->bias = GPIOLINE_BIAS_PULL_DOWN; + } else if (test_bit(FLAG_PULL_UP, &desc->flags)) { + lc->flags |= GPIOLINE_FLAG_V2_BIAS; + lc->bias = GPIOLINE_BIAS_PULL_UP; + } + + lc->edge_detection = 0; spin_unlock_irqrestore(&gpio_lock, flags); } @@ -818,11 +1581,62 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, struct gpio_chardev_data { struct gpio_device *gdev; wait_queue_head_t wait; - DECLARE_KFIFO(events, struct gpioline_info_changed, 32); + DECLARE_KFIFO(events, struct gpioline_info_changed_v2, 32); struct notifier_block lineinfo_changed_nb; unsigned long *watched_lines; +#ifdef CONFIG_GPIO_CDEV_V1 + atomic_t watch_abi_version; +#endif }; +#ifdef CONFIG_GPIO_CDEV_V1 +static int lineinfo_ensure_abi_version(struct gpio_chardev_data *cdata, + unsigned int version) +{ + int abiv = atomic_read(&cdata->watch_abi_version); + + if (abiv == 0) { + atomic_cmpxchg(&cdata->watch_abi_version, 0, version); + abiv = atomic_read(&cdata->watch_abi_version); + } + if (abiv != version) + return -EPERM; + return 0; +} +#endif + +static int lineinfo_get(struct gpio_chardev_data *gcdev, void __user *ip, + unsigned int cmd) +{ + struct gpio_desc *desc; + struct gpioline_info_v2 lineinfo; + + if (copy_from_user(&lineinfo, ip, sizeof(lineinfo))) + return -EFAULT; + + if (padding_not_zeroed(lineinfo.padding, GPIOLINE_INFO_V2_PAD_SIZE)) + return -EINVAL; + + desc = gpiochip_get_desc(gcdev->gdev->chip, lineinfo.offset); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + if (cmd == GPIO_GET_LINEINFO_WATCH_V2_IOCTL) { +#ifdef CONFIG_GPIO_CDEV_V1 + if (lineinfo_ensure_abi_version(gcdev, 2)) + return -EPERM; +#endif + if (test_and_set_bit(lineinfo.offset, gcdev->watched_lines)) + return -EBUSY; + } + gpio_desc_to_lineinfo(desc, &lineinfo); + + if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) + return -EFAULT; + + return 0; +} + /* * gpio_ioctl() - ioctl handler for the GPIO chardev */ @@ -855,8 +1669,10 @@ static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) if (copy_to_user(ip, &chipinfo, sizeof(chipinfo))) return -EFAULT; return 0; +#ifdef CONFIG_GPIO_CDEV_V1 } else if (cmd == GPIO_GET_LINEINFO_IOCTL) { struct gpioline_info lineinfo; + struct gpioline_info_v2 lineinfo_v2; if (copy_from_user(&lineinfo, ip, sizeof(lineinfo))) return -EFAULT; @@ -866,7 +1682,9 @@ static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) if (IS_ERR(desc)) return PTR_ERR(desc); - gpio_desc_to_lineinfo(desc, &lineinfo); + lineinfo_v2.offset = lineinfo.line_offset; + gpio_desc_to_lineinfo(desc, &lineinfo_v2); + gpioline_info_v2_to_v1(&lineinfo_v2, &lineinfo); if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) return -EFAULT; @@ -877,6 +1695,7 @@ static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return lineevent_create(gdev, ip); } else if (cmd == GPIO_GET_LINEINFO_WATCH_IOCTL) { struct gpioline_info lineinfo; + struct gpioline_info_v2 lineinfo_v2; if (copy_from_user(&lineinfo, ip, sizeof(lineinfo))) return -EFAULT; @@ -886,15 +1705,26 @@ static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) if (IS_ERR(desc)) return PTR_ERR(desc); + if (lineinfo_ensure_abi_version(gcdev, 1)) + return -EPERM; + if (test_and_set_bit(lineinfo.line_offset, gcdev->watched_lines)) return -EBUSY; - gpio_desc_to_lineinfo(desc, &lineinfo); + lineinfo_v2.offset = lineinfo.line_offset; + gpio_desc_to_lineinfo(desc, &lineinfo_v2); + gpioline_info_v2_to_v1(&lineinfo_v2, &lineinfo); if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) return -EFAULT; return 0; +#endif /* CONFIG_GPIO_CDEV_V1 */ + } else if (cmd == GPIO_GET_LINEINFO_V2_IOCTL || + cmd == GPIO_GET_LINEINFO_WATCH_V2_IOCTL) { + return lineinfo_get(gcdev, ip, cmd); + } else if (cmd == GPIO_GET_LINE_IOCTL) { + return line_create(gdev, ip); } else if (cmd == GPIO_GET_LINEINFO_UNWATCH_IOCTL) { if (copy_from_user(&offset, ip, sizeof(offset))) return -EFAULT; @@ -928,7 +1758,7 @@ static int lineinfo_changed_notify(struct notifier_block *nb, unsigned long action, void *data) { struct gpio_chardev_data *gcdev = to_gpio_chardev_data(nb); - struct gpioline_info_changed chg; + struct gpioline_info_changed_v2 chg; struct gpio_desc *desc = data; int ret; @@ -936,7 +1766,7 @@ static int lineinfo_changed_notify(struct notifier_block *nb, return NOTIFY_DONE; memset(&chg, 0, sizeof(chg)); - chg.info.line_offset = gpio_chip_hwgpio(desc); + chg.info.offset = gpio_chip_hwgpio(desc); chg.event_type = action; chg.timestamp = ktime_get_ns(); gpio_desc_to_lineinfo(desc, &chg.info); @@ -969,12 +1799,16 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf, size_t count, loff_t *off) { struct gpio_chardev_data *gcdev = file->private_data; - struct gpioline_info_changed event; + struct gpioline_info_changed_v2 event; ssize_t bytes_read = 0; int ret; + size_t event_size; - if (count < sizeof(event)) +#ifndef CONFIG_GPIO_CDEV_V1 + event_size = sizeof(struct gpioline_info_changed_v2); + if (count < event_size) return -EINVAL; +#endif do { spin_lock(&gcdev->wait.lock); @@ -996,7 +1830,17 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf, return ret; } } - +#ifdef CONFIG_GPIO_CDEV_V1 + /* must be after kfifo check so watch_abi_version is set */ + if (atomic_read(&gcdev->watch_abi_version) == 2) + event_size = sizeof(struct gpioline_info_changed_v2); + else + event_size = sizeof(struct gpioline_info_changed); + if (count < event_size) { + spin_unlock(&gcdev->wait.lock); + return -EINVAL; + } +#endif ret = kfifo_out(&gcdev->events, &event, 1); spin_unlock(&gcdev->wait.lock); if (ret != 1) { @@ -1005,9 +1849,23 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf, /* We should never get here. See lineevent_read(). */ } - if (copy_to_user(buf + bytes_read, &event, sizeof(event))) +#ifdef CONFIG_GPIO_CDEV_V1 + if (atomic_read(&gcdev->watch_abi_version) == 2) { + if (copy_to_user(buf + bytes_read, &event, event_size)) + return -EFAULT; + } else { + struct gpioline_info_changed event_v1; + + gpioline_info_changed_v2_to_v1(&event, &event_v1); + if (copy_to_user(buf + bytes_read, &event_v1, + event_size)) + return -EFAULT; + } +#else + if (copy_to_user(buf + bytes_read, &event, event_size)) return -EFAULT; - bytes_read += sizeof(event); +#endif + bytes_read += event_size; } while (count >= bytes_read + sizeof(event)); return bytes_read; @@ -1121,4 +1979,22 @@ int gpiolib_cdev_register(struct gpio_device *gdev, dev_t devt) void gpiolib_cdev_unregister(struct gpio_device *gdev) { cdev_device_del(&gdev->chrdev, &gdev->dev); + + /* + * array sizes must be a multiple of 8 to ensure 64bit alignment and + * to not create holes in the struct packing. + */ + BUILD_BUG_ON(GPIOLINES_MAX % 8); + BUILD_BUG_ON(GPIO_MAX_NAME_SIZE % 8); + + /* + * check that uAPI structs are 64bit aligned for 32/64bit + * compatibility + */ + BUILD_BUG_ON(sizeof(struct gpioline_config) % 8); + BUILD_BUG_ON(sizeof(struct gpioline_request) % 8); + BUILD_BUG_ON(sizeof(struct gpioline_info_v2) % 8); + BUILD_BUG_ON(sizeof(struct gpioline_info_changed_v2) % 8); + BUILD_BUG_ON(sizeof(struct gpioline_event) % 8); + BUILD_BUG_ON(sizeof(struct gpioline_values) % 8); }
Add implementation of the V2 uAPI up to parity with V1. Signed-off-by: Kent Gibson <warthog618@gmail.com> --- This patch only covers the V2 functionality that is a direct analogue of the V1. New V2 functionality is added in subsequent patches. The core of the implementation is the struct line, which is drawn from the existing struct linehandle_state implementation, but modified to deal with the V2 uAPI structs. The struct edge_detector provides the the edge detection functionality, and is drawn from the existing lineevent_state implementation. The two uAPI versions are mostly independent - other than where they both provide line info changes via reads on the chip fd. As the info change structs differ between V1 and V2, the infowatch implementation tracks which version of the infowatch ioctl, either GPIO_GET_LINEINFO_WATCH_IOCTL or GPIO_GET_LINEINFO_WATCH_V2_IOCTL, initiates the initial watch and returns the corresponding info change struct to the read. The version supported on that fd locks to that version on the first watch request, so subsequent watches from that process must use the same uAPI version. drivers/gpio/gpiolib-cdev.c | 934 ++++++++++++++++++++++++++++++++++-- 1 file changed, 905 insertions(+), 29 deletions(-)