| /**************************************************************************** |
| * drivers/leds/userled_upper.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /* This file provides a driver for a LED input devices. |
| * |
| * The LEDs driver exports a standard character driver interface. By |
| * convention, the LED driver is registered as an input device at |
| * /dev/btnN where N uniquely identifies the driver instance. |
| */ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/irq.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/mutex.h> |
| #include <nuttx/fs/fs.h> |
| #include <nuttx/leds/userled.h> |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* This structure provides the state of one LED driver */ |
| |
| struct userled_upperhalf_s |
| { |
| /* Saved binding to the lower half LED driver */ |
| |
| FAR const struct userled_lowerhalf_s *lu_lower; |
| |
| userled_set_t lu_supported; /* The set of supported LEDs */ |
| userled_set_t lu_ledset; /* Current state of LEDs */ |
| mutex_t lu_lock; /* Supports exclusive access to the device */ |
| |
| /* The following is a singly linked list of open references to the |
| * LED device. |
| */ |
| |
| FAR struct userled_open_s *lu_open; |
| }; |
| |
| /* This structure describes the state of one open LED driver instance */ |
| |
| struct userled_open_s |
| { |
| /* Supports a singly linked list */ |
| |
| FAR struct userled_open_s *bo_flink; |
| |
| /* The following will be true if we are closing */ |
| |
| volatile bool bo_closing; |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Character driver methods */ |
| |
| static int userled_open(FAR struct file *filep); |
| static int userled_close(FAR struct file *filep); |
| static ssize_t userled_write(FAR struct file *filep, FAR const char *buffer, |
| size_t buflen); |
| static int userled_ioctl(FAR struct file *filep, int cmd, |
| unsigned long arg); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static const struct file_operations g_userled_fops = |
| { |
| userled_open, /* open */ |
| userled_close, /* close */ |
| NULL, /* read */ |
| userled_write, /* write */ |
| NULL, /* seek */ |
| userled_ioctl, /* ioctl */ |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: userled_open |
| ****************************************************************************/ |
| |
| static int userled_open(FAR struct file *filep) |
| { |
| FAR struct inode *inode; |
| FAR struct userled_upperhalf_s *priv; |
| FAR struct userled_open_s *opriv; |
| int ret; |
| |
| inode = filep->f_inode; |
| DEBUGASSERT(inode->i_private); |
| priv = inode->i_private; |
| |
| /* Get exclusive access to the driver structure */ |
| |
| ret = nxmutex_lock(&priv->lu_lock); |
| if (ret < 0) |
| { |
| lederr("ERROR: nxmutex_lock failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* Allocate a new open structure */ |
| |
| opriv = (FAR struct userled_open_s *) |
| kmm_zalloc(sizeof(struct userled_open_s)); |
| if (!opriv) |
| { |
| lederr("ERROR: Failed to allocate open structure\n"); |
| ret = -ENOMEM; |
| goto errout_with_lock; |
| } |
| |
| /* Attach the open structure to the device */ |
| |
| opriv->bo_flink = priv->lu_open; |
| priv->lu_open = opriv; |
| |
| /* Attach the open structure to the file structure */ |
| |
| filep->f_priv = opriv; |
| ret = OK; |
| |
| errout_with_lock: |
| nxmutex_unlock(&priv->lu_lock); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: userled_close |
| ****************************************************************************/ |
| |
| static int userled_close(FAR struct file *filep) |
| { |
| FAR struct inode *inode; |
| FAR struct userled_upperhalf_s *priv; |
| FAR struct userled_open_s *opriv; |
| FAR struct userled_open_s *curr; |
| FAR struct userled_open_s *prev; |
| irqstate_t flags; |
| bool closing; |
| int ret; |
| |
| DEBUGASSERT(filep->f_priv); |
| opriv = filep->f_priv; |
| inode = filep->f_inode; |
| DEBUGASSERT(inode->i_private); |
| priv = inode->i_private; |
| |
| /* Handle an improbable race conditions with the following atomic test |
| * and set. |
| * |
| * This is actually a pretty feeble attempt to handle this. The |
| * improbable race condition occurs if two different threads try to |
| * close the LED driver at the same time. The rule: don't do |
| * that! It is feeble because we do not really enforce stale pointer |
| * detection anyway. |
| */ |
| |
| flags = enter_critical_section(); |
| closing = opriv->bo_closing; |
| opriv->bo_closing = true; |
| leave_critical_section(flags); |
| |
| if (closing) |
| { |
| /* Another thread is doing the close */ |
| |
| return OK; |
| } |
| |
| /* Get exclusive access to the driver structure */ |
| |
| ret = nxmutex_lock(&priv->lu_lock); |
| if (ret < 0) |
| { |
| lederr("ERROR: nxmutex_lock failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* Find the open structure in the list of open structures for the device */ |
| |
| for (prev = NULL, curr = priv->lu_open; |
| curr && curr != opriv; |
| prev = curr, curr = curr->bo_flink); |
| |
| DEBUGASSERT(curr); |
| if (!curr) |
| { |
| lederr("ERROR: Failed to find open entry\n"); |
| ret = -ENOENT; |
| goto errout_with_lock; |
| } |
| |
| /* Remove the structure from the device */ |
| |
| if (prev) |
| { |
| prev->bo_flink = opriv->bo_flink; |
| } |
| else |
| { |
| priv->lu_open = opriv->bo_flink; |
| } |
| |
| /* And free the open structure */ |
| |
| kmm_free(opriv); |
| ret = OK; |
| |
| errout_with_lock: |
| nxmutex_unlock(&priv->lu_lock); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: userled_write |
| ****************************************************************************/ |
| |
| static ssize_t userled_write(FAR struct file *filep, FAR const char *buffer, |
| size_t len) |
| { |
| FAR struct inode *inode; |
| FAR struct userled_upperhalf_s *priv; |
| FAR const struct userled_lowerhalf_s *lower; |
| userled_set_t ledset; |
| int ret; |
| |
| inode = filep->f_inode; |
| DEBUGASSERT(inode->i_private); |
| priv = inode->i_private; |
| |
| /* Make sure that the buffer is sufficiently large to hold at least one |
| * complete sample. |
| * |
| * REVISIT: Should also check buffer alignment. |
| */ |
| |
| if (len < sizeof(userled_set_t)) |
| { |
| lederr("ERROR: buffer too small: %lu\n", (unsigned long)len); |
| return -EINVAL; |
| } |
| |
| /* Get the LED set to write. |
| * REVISIT: if sizeof(userled_set_t) > 1, then we will have to address |
| * some buffer alignment issues. |
| */ |
| |
| DEBUGASSERT(buffer != NULL); |
| ledset = *(userled_set_t *)buffer; |
| |
| /* Get exclusive access to the driver structure */ |
| |
| ret = nxmutex_lock(&priv->lu_lock); |
| if (ret < 0) |
| { |
| lederr("ERROR: nxmutex_lock failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* Read and return the current state of the LEDs */ |
| |
| lower = priv->lu_lower; |
| DEBUGASSERT(lower && lower->ll_setall); |
| lower->ll_setall(lower, ledset); |
| |
| nxmutex_unlock(&priv->lu_lock); |
| return (ssize_t)sizeof(userled_set_t); |
| } |
| |
| /**************************************************************************** |
| * Name: userled_ioctl |
| ****************************************************************************/ |
| |
| static int userled_ioctl(FAR struct file *filep, int cmd, unsigned long arg) |
| { |
| FAR struct inode *inode; |
| FAR struct userled_upperhalf_s *priv; |
| FAR const struct userled_lowerhalf_s *lower; |
| int ret; |
| |
| DEBUGASSERT(filep->f_priv != NULL && |
| filep->f_inode != NULL); |
| inode = filep->f_inode; |
| DEBUGASSERT(inode->i_private); |
| priv = inode->i_private; |
| |
| /* Get exclusive access to the driver structure */ |
| |
| ret = nxmutex_lock(&priv->lu_lock); |
| if (ret < 0) |
| { |
| lederr("ERROR: nxmutex_lock failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* Handle the ioctl command */ |
| |
| ret = -EINVAL; |
| switch (cmd) |
| { |
| /* Command: ULEDIOC_SUPPORTED |
| * Description: Report the set of LEDs supported by the hardware; |
| * Argument: A pointer to writeable userled_set_t value in which to |
| * return the set of supported LEDs. |
| * Return: Zero (OK) on success. Minus one will be returned on |
| * failure with the errno value set appropriately. |
| */ |
| |
| case ULEDIOC_SUPPORTED: |
| { |
| FAR userled_set_t *supported = (FAR userled_set_t *)((uintptr_t)arg); |
| |
| /* Verify that a non-NULL pointer was provided */ |
| |
| if (supported) |
| { |
| *supported = priv->lu_supported; |
| ret = OK; |
| } |
| } |
| break; |
| |
| /* Command: ULEDIOC_SETLED |
| * Description: Set the state of one LED. |
| * Argument: A read-only pointer to an instance of struct userled_s |
| * Return: Zero (OK) on success. Minus one will be returned on |
| * failure with the errno value set appropriately. |
| */ |
| |
| case ULEDIOC_SETLED: |
| { |
| FAR struct userled_s *userled = (FAR struct userled_s *) |
| ((uintptr_t)arg); |
| int led; |
| bool ledon; |
| |
| /* Verify that a non-NULL pointer was provided */ |
| |
| if (userled) |
| { |
| led = userled->ul_led; |
| ledon = userled->ul_on; |
| |
| /* Check that a valid LED is being set */ |
| |
| if ((size_t)led < 8 * sizeof(userled_set_t) && |
| (priv->lu_supported & (1 << led)) != 0) |
| { |
| /* Update the LED state */ |
| |
| if (ledon) |
| { |
| priv->lu_ledset |= (1 << led); |
| } |
| else |
| { |
| priv->lu_ledset &= ~(1 << led); |
| } |
| |
| /* Set the LED state */ |
| |
| lower = priv->lu_lower; |
| DEBUGASSERT(lower != NULL && lower->ll_setled != NULL); |
| lower->ll_setled(lower, led, ledon); |
| ret = OK; |
| } |
| } |
| } |
| break; |
| |
| /* Command: ULEDIOC_SETALL |
| * Description: Set the state of all LEDs. |
| * Argument: A value of type userled_set_t cast to unsigned long |
| * Return: Zero (OK) on success. Minus one will be returned on |
| * failure with the errno value set appropriately. |
| */ |
| |
| case ULEDIOC_SETALL: |
| { |
| userled_set_t ledset = (userled_set_t)((uintptr_t)arg); |
| |
| /* Verify that a valid LED set was provided */ |
| |
| if ((ledset & ~priv->lu_supported) == 0) |
| { |
| /* Update the LED state */ |
| |
| priv->lu_ledset = ledset; |
| |
| /* Set the new LED state */ |
| |
| lower = priv->lu_lower; |
| DEBUGASSERT(lower != NULL && lower->ll_setall != NULL); |
| lower->ll_setall(lower, ledset); |
| ret = OK; |
| } |
| } |
| break; |
| |
| /* Command: ULEDIOC_GETALL |
| * Description: Get the state of all LEDs. |
| * Argument: A write-able pointer to a userled_set_t memory location |
| * in which to return the LED state. |
| * Return: Zero (OK) on success. Minus one will be returned on |
| * failure with the errno value set appropriately. |
| */ |
| |
| case ULEDIOC_GETALL: |
| { |
| FAR userled_set_t *ledset = (FAR userled_set_t *)((uintptr_t)arg); |
| |
| /* Verify that a non-NULL pointer was provided */ |
| |
| if (ledset) |
| { |
| #ifdef CONFIG_USERLED_LOWER_READSTATE |
| lower = priv->lu_lower; |
| DEBUGASSERT(lower != NULL && lower->ll_getall != NULL); |
| lower->ll_getall(lower, ledset); |
| #else |
| *ledset = priv->lu_ledset; |
| #endif |
| ret = OK; |
| } |
| } |
| break; |
| |
| #ifdef CONFIG_USERLED_EFFECTS |
| /* Command: ULEDIOC_SUPEFFECT |
| * Description: Get supported effects of one LED. |
| * Argument: A write-able pointer to a struct userled_effect_sup_s |
| * which to return the supported LED effects. |
| * Return: Zero (OK) on success. Minus one will be returned on |
| * failure with the errno value set appropriately. |
| */ |
| |
| case ULEDIOC_SUPEFFECT: |
| { |
| FAR struct userled_effect_sup_s *effect = |
| (FAR struct userled_effect_sup_s *)((uintptr_t)arg); |
| |
| if (effect) |
| { |
| lower = priv->lu_lower; |
| DEBUGASSERT(lower != NULL && lower->ll_effect_sup != NULL); |
| lower->ll_effect_sup(lower, effect); |
| ret = OK; |
| } |
| } |
| break; |
| |
| /* Command: ULEDIOC_SETEFFECT |
| * Description: Set effects for one LED. |
| * Argument: A read-only pointer to an instance of |
| * struct userled_effect_set_s. |
| * Return: Zero (OK) on success. Minus one will be returned on |
| * failure with the errno value set appropriately. |
| */ |
| |
| case ULEDIOC_SETEFFECT: |
| { |
| FAR struct userled_effect_set_s *effect = |
| (FAR struct userled_effect_set_s *)((uintptr_t)arg); |
| |
| if (effect) |
| { |
| lower = priv->lu_lower; |
| DEBUGASSERT(lower != NULL && lower->ll_effect_set != NULL); |
| ret = lower->ll_effect_set(lower, effect); |
| } |
| } |
| break; |
| #endif |
| |
| default: |
| lederr("ERROR: Unrecognized command: %d\n", cmd); |
| ret = -ENOTTY; |
| break; |
| } |
| |
| nxmutex_unlock(&priv->lu_lock); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: userled_register |
| * |
| * Description: |
| * Bind the lower half LED driver to an instance of the upper half |
| * LED driver and register the composite character driver as the |
| * specified device. |
| * |
| * Input Parameters: |
| * devname - The name of the LED device to be registered. |
| * This should be a string of the form "/dev/ledN" where N is the |
| * minor device number. |
| * lower - An instance of the platform-specific LED lower half driver. |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success. Otherwise a negated errno value is |
| * returned to indicate the nature of the failure. |
| * |
| ****************************************************************************/ |
| |
| int userled_register(FAR const char *devname, |
| FAR const struct userled_lowerhalf_s *lower) |
| { |
| FAR struct userled_upperhalf_s *priv; |
| int ret; |
| |
| DEBUGASSERT(devname && lower); |
| |
| /* Allocate a new LED driver instance */ |
| |
| priv = (FAR struct userled_upperhalf_s *) |
| kmm_zalloc(sizeof(struct userled_upperhalf_s)); |
| |
| if (!priv) |
| { |
| lederr("ERROR: Failed to allocate device structure\n"); |
| return -ENOMEM; |
| } |
| |
| /* Initialize the new LED driver instance */ |
| |
| priv->lu_lower = lower; |
| nxmutex_init(&priv->lu_lock); |
| |
| DEBUGASSERT(lower && lower->ll_supported); |
| priv->lu_supported = lower->ll_supported(lower); |
| |
| DEBUGASSERT(lower && lower->ll_setall); |
| priv->lu_ledset = 0; |
| lower->ll_setall(lower, priv->lu_ledset); |
| |
| /* And register the LED driver */ |
| |
| ret = register_driver(devname, &g_userled_fops, 0666, priv); |
| if (ret < 0) |
| { |
| lederr("ERROR: register_driver failed: %d\n", ret); |
| goto errout_with_priv; |
| } |
| |
| return OK; |
| |
| errout_with_priv: |
| nxmutex_destroy(&priv->lu_lock); |
| kmm_free(priv); |
| return ret; |
| } |