18621f407SFrançois Tigeot /* 28621f407SFrançois Tigeot * Copyright © 2015 Intel Corporation 38621f407SFrançois Tigeot * 48621f407SFrançois Tigeot * Permission is hereby granted, free of charge, to any person obtaining a 58621f407SFrançois Tigeot * copy of this software and associated documentation files (the "Software"), 68621f407SFrançois Tigeot * to deal in the Software without restriction, including without limitation 78621f407SFrançois Tigeot * the rights to use, copy, modify, merge, publish, distribute, sublicense, 88621f407SFrançois Tigeot * and/or sell copies of the Software, and to permit persons to whom the 98621f407SFrançois Tigeot * Software is furnished to do so, subject to the following conditions: 108621f407SFrançois Tigeot * 118621f407SFrançois Tigeot * The above copyright notice and this permission notice (including the next 128621f407SFrançois Tigeot * paragraph) shall be included in all copies or substantial portions of the 138621f407SFrançois Tigeot * Software. 148621f407SFrançois Tigeot * 158621f407SFrançois Tigeot * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 168621f407SFrançois Tigeot * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 178621f407SFrançois Tigeot * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 188621f407SFrançois Tigeot * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 198621f407SFrançois Tigeot * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 208621f407SFrançois Tigeot * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 218621f407SFrançois Tigeot * IN THE SOFTWARE. 228621f407SFrançois Tigeot * 238621f407SFrançois Tigeot * Authors: 248621f407SFrançois Tigeot * Rafael Antognolli <rafael.antognolli@intel.com> 258621f407SFrançois Tigeot * 268621f407SFrançois Tigeot */ 278621f407SFrançois Tigeot 288621f407SFrançois Tigeot #include <linux/device.h> 298621f407SFrançois Tigeot #include <linux/fs.h> 308621f407SFrançois Tigeot #include <linux/slab.h> 31*1e12ee3bSFrançois Tigeot #include <linux/init.h> 328621f407SFrançois Tigeot #include <linux/kernel.h> 338621f407SFrançois Tigeot #include <linux/module.h> 348621f407SFrançois Tigeot #include <linux/uaccess.h> 358621f407SFrançois Tigeot #include <drm/drm_dp_helper.h> 368621f407SFrançois Tigeot #include <drm/drm_crtc.h> 37*1e12ee3bSFrançois Tigeot #include <drm/drmP.h> 388621f407SFrançois Tigeot 391dedbd3bSFrançois Tigeot #include "drm_crtc_helper_internal.h" 401dedbd3bSFrançois Tigeot 418621f407SFrançois Tigeot struct drm_dp_aux_dev { 428621f407SFrançois Tigeot unsigned index; 438621f407SFrançois Tigeot struct drm_dp_aux *aux; 448621f407SFrançois Tigeot struct device *dev; 458621f407SFrançois Tigeot struct kref refcount; 468621f407SFrançois Tigeot atomic_t usecount; 478621f407SFrançois Tigeot }; 488621f407SFrançois Tigeot 498621f407SFrançois Tigeot #define DRM_AUX_MINORS 256 508621f407SFrançois Tigeot #define AUX_MAX_OFFSET (1 << 20) 518621f407SFrançois Tigeot #if 0 528621f407SFrançois Tigeot static DEFINE_IDR(aux_idr); 538621f407SFrançois Tigeot static DEFINE_MUTEX(aux_idr_mutex); 548621f407SFrançois Tigeot static struct class *drm_dp_aux_dev_class; 558621f407SFrançois Tigeot static int drm_dev_major = -1; 568621f407SFrançois Tigeot 578621f407SFrançois Tigeot static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_minor(unsigned index) 588621f407SFrançois Tigeot { 598621f407SFrançois Tigeot struct drm_dp_aux_dev *aux_dev = NULL; 608621f407SFrançois Tigeot 618621f407SFrançois Tigeot mutex_lock(&aux_idr_mutex); 628621f407SFrançois Tigeot aux_dev = idr_find(&aux_idr, index); 638621f407SFrançois Tigeot if (!kref_get_unless_zero(&aux_dev->refcount)) 648621f407SFrançois Tigeot aux_dev = NULL; 658621f407SFrançois Tigeot mutex_unlock(&aux_idr_mutex); 668621f407SFrançois Tigeot 678621f407SFrançois Tigeot return aux_dev; 688621f407SFrançois Tigeot } 698621f407SFrançois Tigeot 708621f407SFrançois Tigeot static struct drm_dp_aux_dev *alloc_drm_dp_aux_dev(struct drm_dp_aux *aux) 718621f407SFrançois Tigeot { 728621f407SFrançois Tigeot struct drm_dp_aux_dev *aux_dev; 738621f407SFrançois Tigeot int index; 748621f407SFrançois Tigeot 758621f407SFrançois Tigeot aux_dev = kzalloc(sizeof(*aux_dev), GFP_KERNEL); 768621f407SFrançois Tigeot if (!aux_dev) 778621f407SFrançois Tigeot return ERR_PTR(-ENOMEM); 788621f407SFrançois Tigeot aux_dev->aux = aux; 798621f407SFrançois Tigeot atomic_set(&aux_dev->usecount, 1); 808621f407SFrançois Tigeot kref_init(&aux_dev->refcount); 818621f407SFrançois Tigeot 828621f407SFrançois Tigeot mutex_lock(&aux_idr_mutex); 838621f407SFrançois Tigeot index = idr_alloc_cyclic(&aux_idr, aux_dev, 0, DRM_AUX_MINORS, 848621f407SFrançois Tigeot GFP_KERNEL); 858621f407SFrançois Tigeot mutex_unlock(&aux_idr_mutex); 868621f407SFrançois Tigeot if (index < 0) { 878621f407SFrançois Tigeot kfree(aux_dev); 888621f407SFrançois Tigeot return ERR_PTR(index); 898621f407SFrançois Tigeot } 908621f407SFrançois Tigeot aux_dev->index = index; 918621f407SFrançois Tigeot 928621f407SFrançois Tigeot return aux_dev; 938621f407SFrançois Tigeot } 948621f407SFrançois Tigeot 958621f407SFrançois Tigeot static void release_drm_dp_aux_dev(struct kref *ref) 968621f407SFrançois Tigeot { 978621f407SFrançois Tigeot struct drm_dp_aux_dev *aux_dev = 988621f407SFrançois Tigeot container_of(ref, struct drm_dp_aux_dev, refcount); 998621f407SFrançois Tigeot 1008621f407SFrançois Tigeot kfree(aux_dev); 1018621f407SFrançois Tigeot } 1028621f407SFrançois Tigeot 1038621f407SFrançois Tigeot static ssize_t name_show(struct device *dev, 1048621f407SFrançois Tigeot struct device_attribute *attr, char *buf) 1058621f407SFrançois Tigeot { 1068621f407SFrançois Tigeot ssize_t res; 1078621f407SFrançois Tigeot struct drm_dp_aux_dev *aux_dev = 1088621f407SFrançois Tigeot drm_dp_aux_dev_get_by_minor(MINOR(dev->devt)); 1098621f407SFrançois Tigeot 1108621f407SFrançois Tigeot if (!aux_dev) 1118621f407SFrançois Tigeot return -ENODEV; 1128621f407SFrançois Tigeot 1138621f407SFrançois Tigeot res = sprintf(buf, "%s\n", aux_dev->aux->name); 1148621f407SFrançois Tigeot kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); 1158621f407SFrançois Tigeot 1168621f407SFrançois Tigeot return res; 1178621f407SFrançois Tigeot } 1188621f407SFrançois Tigeot static DEVICE_ATTR_RO(name); 1198621f407SFrançois Tigeot 1208621f407SFrançois Tigeot static struct attribute *drm_dp_aux_attrs[] = { 1218621f407SFrançois Tigeot &dev_attr_name.attr, 1228621f407SFrançois Tigeot NULL, 1238621f407SFrançois Tigeot }; 1248621f407SFrançois Tigeot ATTRIBUTE_GROUPS(drm_dp_aux); 1258621f407SFrançois Tigeot 1268621f407SFrançois Tigeot static int auxdev_open(struct inode *inode, struct file *file) 1278621f407SFrançois Tigeot { 1288621f407SFrançois Tigeot unsigned int minor = iminor(inode); 1298621f407SFrançois Tigeot struct drm_dp_aux_dev *aux_dev; 1308621f407SFrançois Tigeot 1318621f407SFrançois Tigeot aux_dev = drm_dp_aux_dev_get_by_minor(minor); 1328621f407SFrançois Tigeot if (!aux_dev) 1338621f407SFrançois Tigeot return -ENODEV; 1348621f407SFrançois Tigeot 1358621f407SFrançois Tigeot file->private_data = aux_dev; 1368621f407SFrançois Tigeot return 0; 1378621f407SFrançois Tigeot } 1388621f407SFrançois Tigeot 1398621f407SFrançois Tigeot static loff_t auxdev_llseek(struct file *file, loff_t offset, int whence) 1408621f407SFrançois Tigeot { 1418621f407SFrançois Tigeot return fixed_size_llseek(file, offset, whence, AUX_MAX_OFFSET); 1428621f407SFrançois Tigeot } 1438621f407SFrançois Tigeot 1448621f407SFrançois Tigeot static ssize_t auxdev_read(struct file *file, char __user *buf, size_t count, 1458621f407SFrançois Tigeot loff_t *offset) 1468621f407SFrançois Tigeot { 1478621f407SFrançois Tigeot size_t bytes_pending, num_bytes_processed = 0; 1488621f407SFrançois Tigeot struct drm_dp_aux_dev *aux_dev = file->private_data; 1498621f407SFrançois Tigeot ssize_t res = 0; 1508621f407SFrançois Tigeot 1518621f407SFrançois Tigeot if (!atomic_inc_not_zero(&aux_dev->usecount)) 1528621f407SFrançois Tigeot return -ENODEV; 1538621f407SFrançois Tigeot 1548621f407SFrançois Tigeot bytes_pending = min((loff_t)count, AUX_MAX_OFFSET - (*offset)); 1558621f407SFrançois Tigeot 1568621f407SFrançois Tigeot if (!access_ok(VERIFY_WRITE, buf, bytes_pending)) { 1578621f407SFrançois Tigeot res = -EFAULT; 1588621f407SFrançois Tigeot goto out; 1598621f407SFrançois Tigeot } 1608621f407SFrançois Tigeot 1618621f407SFrançois Tigeot while (bytes_pending > 0) { 1628621f407SFrançois Tigeot uint8_t localbuf[DP_AUX_MAX_PAYLOAD_BYTES]; 1638621f407SFrançois Tigeot ssize_t todo = min_t(size_t, bytes_pending, sizeof(localbuf)); 1648621f407SFrançois Tigeot 1658621f407SFrançois Tigeot if (signal_pending(current)) { 1668621f407SFrançois Tigeot res = num_bytes_processed ? 1678621f407SFrançois Tigeot num_bytes_processed : -ERESTARTSYS; 1688621f407SFrançois Tigeot goto out; 1698621f407SFrançois Tigeot } 1708621f407SFrançois Tigeot 1718621f407SFrançois Tigeot res = drm_dp_dpcd_read(aux_dev->aux, *offset, localbuf, todo); 1728621f407SFrançois Tigeot if (res <= 0) { 1738621f407SFrançois Tigeot res = num_bytes_processed ? num_bytes_processed : res; 1748621f407SFrançois Tigeot goto out; 1758621f407SFrançois Tigeot } 1768621f407SFrançois Tigeot if (__copy_to_user(buf + num_bytes_processed, localbuf, res)) { 1778621f407SFrançois Tigeot res = num_bytes_processed ? 1788621f407SFrançois Tigeot num_bytes_processed : -EFAULT; 1798621f407SFrançois Tigeot goto out; 1808621f407SFrançois Tigeot } 1818621f407SFrançois Tigeot bytes_pending -= res; 1828621f407SFrançois Tigeot *offset += res; 1838621f407SFrançois Tigeot num_bytes_processed += res; 1848621f407SFrançois Tigeot res = num_bytes_processed; 1858621f407SFrançois Tigeot } 1868621f407SFrançois Tigeot 1878621f407SFrançois Tigeot out: 1888621f407SFrançois Tigeot atomic_dec(&aux_dev->usecount); 1898621f407SFrançois Tigeot wake_up_atomic_t(&aux_dev->usecount); 1908621f407SFrançois Tigeot return res; 1918621f407SFrançois Tigeot } 1928621f407SFrançois Tigeot 1938621f407SFrançois Tigeot static ssize_t auxdev_write(struct file *file, const char __user *buf, 1948621f407SFrançois Tigeot size_t count, loff_t *offset) 1958621f407SFrançois Tigeot { 1968621f407SFrançois Tigeot size_t bytes_pending, num_bytes_processed = 0; 1978621f407SFrançois Tigeot struct drm_dp_aux_dev *aux_dev = file->private_data; 1988621f407SFrançois Tigeot ssize_t res = 0; 1998621f407SFrançois Tigeot 2008621f407SFrançois Tigeot if (!atomic_inc_not_zero(&aux_dev->usecount)) 2018621f407SFrançois Tigeot return -ENODEV; 2028621f407SFrançois Tigeot 2038621f407SFrançois Tigeot bytes_pending = min((loff_t)count, AUX_MAX_OFFSET - *offset); 2048621f407SFrançois Tigeot 2058621f407SFrançois Tigeot if (!access_ok(VERIFY_READ, buf, bytes_pending)) { 2068621f407SFrançois Tigeot res = -EFAULT; 2078621f407SFrançois Tigeot goto out; 2088621f407SFrançois Tigeot } 2098621f407SFrançois Tigeot 2108621f407SFrançois Tigeot while (bytes_pending > 0) { 2118621f407SFrançois Tigeot uint8_t localbuf[DP_AUX_MAX_PAYLOAD_BYTES]; 2128621f407SFrançois Tigeot ssize_t todo = min_t(size_t, bytes_pending, sizeof(localbuf)); 2138621f407SFrançois Tigeot 2148621f407SFrançois Tigeot if (signal_pending(current)) { 2158621f407SFrançois Tigeot res = num_bytes_processed ? 2168621f407SFrançois Tigeot num_bytes_processed : -ERESTARTSYS; 2178621f407SFrançois Tigeot goto out; 2188621f407SFrançois Tigeot } 2198621f407SFrançois Tigeot 2208621f407SFrançois Tigeot if (__copy_from_user(localbuf, 2218621f407SFrançois Tigeot buf + num_bytes_processed, todo)) { 2228621f407SFrançois Tigeot res = num_bytes_processed ? 2238621f407SFrançois Tigeot num_bytes_processed : -EFAULT; 2248621f407SFrançois Tigeot goto out; 2258621f407SFrançois Tigeot } 2268621f407SFrançois Tigeot 2278621f407SFrançois Tigeot res = drm_dp_dpcd_write(aux_dev->aux, *offset, localbuf, todo); 2288621f407SFrançois Tigeot if (res <= 0) { 2298621f407SFrançois Tigeot res = num_bytes_processed ? num_bytes_processed : res; 2308621f407SFrançois Tigeot goto out; 2318621f407SFrançois Tigeot } 2328621f407SFrançois Tigeot bytes_pending -= res; 2338621f407SFrançois Tigeot *offset += res; 2348621f407SFrançois Tigeot num_bytes_processed += res; 2358621f407SFrançois Tigeot res = num_bytes_processed; 2368621f407SFrançois Tigeot } 2378621f407SFrançois Tigeot 2388621f407SFrançois Tigeot out: 2398621f407SFrançois Tigeot atomic_dec(&aux_dev->usecount); 2408621f407SFrançois Tigeot wake_up_atomic_t(&aux_dev->usecount); 2418621f407SFrançois Tigeot return res; 2428621f407SFrançois Tigeot } 2438621f407SFrançois Tigeot 2448621f407SFrançois Tigeot static int auxdev_release(struct inode *inode, struct file *file) 2458621f407SFrançois Tigeot { 2468621f407SFrançois Tigeot struct drm_dp_aux_dev *aux_dev = file->private_data; 2478621f407SFrançois Tigeot 2488621f407SFrançois Tigeot kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); 2498621f407SFrançois Tigeot return 0; 2508621f407SFrançois Tigeot } 2518621f407SFrançois Tigeot 2528621f407SFrançois Tigeot static const struct file_operations auxdev_fops = { 2538621f407SFrançois Tigeot .owner = THIS_MODULE, 2548621f407SFrançois Tigeot .llseek = auxdev_llseek, 2558621f407SFrançois Tigeot .read = auxdev_read, 2568621f407SFrançois Tigeot .write = auxdev_write, 2578621f407SFrançois Tigeot .open = auxdev_open, 2588621f407SFrançois Tigeot .release = auxdev_release, 2598621f407SFrançois Tigeot }; 2608621f407SFrançois Tigeot 2618621f407SFrançois Tigeot #define to_auxdev(d) container_of(d, struct drm_dp_aux_dev, aux) 2628621f407SFrançois Tigeot 2638621f407SFrançois Tigeot static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_aux(struct drm_dp_aux *aux) 2648621f407SFrançois Tigeot { 2658621f407SFrançois Tigeot struct drm_dp_aux_dev *iter, *aux_dev = NULL; 2668621f407SFrançois Tigeot int id; 2678621f407SFrançois Tigeot 2688621f407SFrançois Tigeot /* don't increase kref count here because this function should only be 2698621f407SFrançois Tigeot * used by drm_dp_aux_unregister_devnode. Thus, it will always have at 2708621f407SFrançois Tigeot * least one reference - the one that drm_dp_aux_register_devnode 2718621f407SFrançois Tigeot * created 2728621f407SFrançois Tigeot */ 2738621f407SFrançois Tigeot mutex_lock(&aux_idr_mutex); 2748621f407SFrançois Tigeot idr_for_each_entry(&aux_idr, iter, id) { 2758621f407SFrançois Tigeot if (iter->aux == aux) { 2768621f407SFrançois Tigeot aux_dev = iter; 2778621f407SFrançois Tigeot break; 2788621f407SFrançois Tigeot } 2798621f407SFrançois Tigeot } 2808621f407SFrançois Tigeot mutex_unlock(&aux_idr_mutex); 2818621f407SFrançois Tigeot return aux_dev; 2828621f407SFrançois Tigeot } 2838621f407SFrançois Tigeot 2848621f407SFrançois Tigeot static int auxdev_wait_atomic_t(atomic_t *p) 2858621f407SFrançois Tigeot { 2868621f407SFrançois Tigeot schedule(); 2878621f407SFrançois Tigeot return 0; 2888621f407SFrançois Tigeot } 2891dedbd3bSFrançois Tigeot 2908621f407SFrançois Tigeot void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) 2918621f407SFrançois Tigeot { 2928621f407SFrançois Tigeot struct drm_dp_aux_dev *aux_dev; 2938621f407SFrançois Tigeot unsigned int minor; 2948621f407SFrançois Tigeot 2958621f407SFrançois Tigeot aux_dev = drm_dp_aux_dev_get_by_aux(aux); 2968621f407SFrançois Tigeot if (!aux_dev) /* attach must have failed */ 2978621f407SFrançois Tigeot return; 2988621f407SFrançois Tigeot 2998621f407SFrançois Tigeot mutex_lock(&aux_idr_mutex); 3008621f407SFrançois Tigeot idr_remove(&aux_idr, aux_dev->index); 3018621f407SFrançois Tigeot mutex_unlock(&aux_idr_mutex); 3028621f407SFrançois Tigeot 3038621f407SFrançois Tigeot atomic_dec(&aux_dev->usecount); 3048621f407SFrançois Tigeot wait_on_atomic_t(&aux_dev->usecount, auxdev_wait_atomic_t, 3058621f407SFrançois Tigeot TASK_UNINTERRUPTIBLE); 3068621f407SFrançois Tigeot 3078621f407SFrançois Tigeot minor = aux_dev->index; 3088621f407SFrançois Tigeot if (aux_dev->dev) 3098621f407SFrançois Tigeot device_destroy(drm_dp_aux_dev_class, 3108621f407SFrançois Tigeot MKDEV(drm_dev_major, minor)); 3118621f407SFrançois Tigeot 3128621f407SFrançois Tigeot DRM_DEBUG("drm_dp_aux_dev: aux [%s] unregistering\n", aux->name); 3138621f407SFrançois Tigeot kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); 3148621f407SFrançois Tigeot } 3158621f407SFrançois Tigeot 3168621f407SFrançois Tigeot int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) 3178621f407SFrançois Tigeot { 3188621f407SFrançois Tigeot struct drm_dp_aux_dev *aux_dev; 3198621f407SFrançois Tigeot int res; 3208621f407SFrançois Tigeot 3218621f407SFrançois Tigeot aux_dev = alloc_drm_dp_aux_dev(aux); 3228621f407SFrançois Tigeot if (IS_ERR(aux_dev)) 3238621f407SFrançois Tigeot return PTR_ERR(aux_dev); 3248621f407SFrançois Tigeot 3258621f407SFrançois Tigeot aux_dev->dev = device_create(drm_dp_aux_dev_class, aux->dev, 3268621f407SFrançois Tigeot MKDEV(drm_dev_major, aux_dev->index), NULL, 3278621f407SFrançois Tigeot "drm_dp_aux%d", aux_dev->index); 3288621f407SFrançois Tigeot if (IS_ERR(aux_dev->dev)) { 3298621f407SFrançois Tigeot res = PTR_ERR(aux_dev->dev); 3308621f407SFrançois Tigeot aux_dev->dev = NULL; 3318621f407SFrançois Tigeot goto error; 3328621f407SFrançois Tigeot } 3338621f407SFrançois Tigeot 3348621f407SFrançois Tigeot DRM_DEBUG("drm_dp_aux_dev: aux [%s] registered as minor %d\n", 3358621f407SFrançois Tigeot aux->name, aux_dev->index); 3368621f407SFrançois Tigeot return 0; 3378621f407SFrançois Tigeot error: 3388621f407SFrançois Tigeot drm_dp_aux_unregister_devnode(aux); 3398621f407SFrançois Tigeot return res; 3408621f407SFrançois Tigeot } 3418621f407SFrançois Tigeot 3428621f407SFrançois Tigeot int drm_dp_aux_dev_init(void) 3438621f407SFrançois Tigeot { 3448621f407SFrançois Tigeot int res; 3458621f407SFrançois Tigeot 3468621f407SFrançois Tigeot drm_dp_aux_dev_class = class_create(THIS_MODULE, "drm_dp_aux_dev"); 3478621f407SFrançois Tigeot if (IS_ERR(drm_dp_aux_dev_class)) { 3481dedbd3bSFrançois Tigeot return PTR_ERR(drm_dp_aux_dev_class); 3498621f407SFrançois Tigeot } 3508621f407SFrançois Tigeot drm_dp_aux_dev_class->dev_groups = drm_dp_aux_groups; 3518621f407SFrançois Tigeot 3528621f407SFrançois Tigeot res = register_chrdev(0, "aux", &auxdev_fops); 3538621f407SFrançois Tigeot if (res < 0) 3548621f407SFrançois Tigeot goto out; 3558621f407SFrançois Tigeot drm_dev_major = res; 3568621f407SFrançois Tigeot 3578621f407SFrançois Tigeot return 0; 3588621f407SFrançois Tigeot out: 3598621f407SFrançois Tigeot class_destroy(drm_dp_aux_dev_class); 3608621f407SFrançois Tigeot return res; 3618621f407SFrançois Tigeot } 3628621f407SFrançois Tigeot 3638621f407SFrançois Tigeot void drm_dp_aux_dev_exit(void) 3648621f407SFrançois Tigeot { 3658621f407SFrançois Tigeot unregister_chrdev(drm_dev_major, "aux"); 3668621f407SFrançois Tigeot class_destroy(drm_dp_aux_dev_class); 3678621f407SFrançois Tigeot } 368*1e12ee3bSFrançois Tigeot #endif /* 0 */ 369