2020-10-23 10:36:11 +02:00
|
|
|
/****************************************************************************
|
|
|
|
* drivers/rc/lirc_dev.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.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Included Files
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
|
|
|
|
#include <stdio.h>
|
2023-02-01 14:41:12 +01:00
|
|
|
#include <sys/param.h>
|
2021-05-18 08:59:14 +02:00
|
|
|
#include <assert.h>
|
2021-05-14 04:45:57 +02:00
|
|
|
#include <debug.h>
|
2020-10-23 10:36:11 +02:00
|
|
|
#include <errno.h>
|
|
|
|
#include <poll.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
|
|
#include <nuttx/kmalloc.h>
|
2022-09-06 08:18:45 +02:00
|
|
|
#include <nuttx/mutex.h>
|
2020-11-12 11:40:37 +01:00
|
|
|
#include <nuttx/mm/circbuf.h>
|
2020-10-23 10:36:11 +02:00
|
|
|
#include <nuttx/rc/lirc_dev.h>
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Pre-processor Definitions
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
#define DEVNAME_FMT "/dev/lirc%d"
|
|
|
|
#define DEVNAME_MAX 32
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Private Types
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
/* This structure describes the state of the upper half driver */
|
|
|
|
|
|
|
|
struct lirc_upperhalf_s
|
|
|
|
{
|
|
|
|
struct list_node fh; /* list of struct lirc_fh_s object */
|
|
|
|
FAR struct lirc_lowerhalf_s *lower; /* the handle of lower half driver */
|
2022-09-06 08:18:45 +02:00
|
|
|
mutex_t lock; /* Manages exclusive access to lowerhalf */
|
2020-10-23 10:36:11 +02:00
|
|
|
bool gap; /* true if we're in a gap */
|
|
|
|
uint64_t gap_start; /* time when gap starts */
|
|
|
|
uint64_t gap_duration; /* duration of initial gap */
|
|
|
|
};
|
|
|
|
|
|
|
|
/* The structure describes an open lirc file */
|
|
|
|
|
|
|
|
struct lirc_fh_s
|
|
|
|
{
|
|
|
|
struct list_node node; /* list of open file handles */
|
|
|
|
FAR struct lirc_lowerhalf_s *lower; /* the pointer to lirc_lowerhalf_s */
|
2020-11-12 11:40:37 +01:00
|
|
|
struct circbuf_s buffer; /* buffer for incoming IR */
|
2023-11-19 12:19:53 +01:00
|
|
|
FAR struct pollfd *fds; /* poll structures of threads waiting for driver events */
|
2020-10-23 10:36:11 +02:00
|
|
|
sem_t waitsem; /* sem of wait buffer for ready */
|
|
|
|
int carrier_low; /* when setting the carrier range, first the low end must be
|
|
|
|
* set with an ioctl and then the high end with another ioctl
|
|
|
|
*/
|
|
|
|
unsigned char send_mode; /* lirc mode for sending, LIRC_MODE_PULSE LIRC_MODE_SCANCODE */
|
|
|
|
unsigned char rec_mode; /* lirc mode for receiving, LIRC_MODE_MODE2 LIRC_MODE_SCANCODE */
|
|
|
|
bool send_timeout_reports; /* report timeouts in lirc raw IR. */
|
|
|
|
};
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Private Function Prototypes
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
static int lirc_open(FAR struct file *filep);
|
|
|
|
static int lirc_close(FAR struct file *filep);
|
|
|
|
static int lirc_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
|
|
|
|
static int lirc_poll(FAR struct file *filep, FAR struct pollfd *fds,
|
2023-11-19 12:19:53 +01:00
|
|
|
bool setup);
|
2020-10-23 10:36:11 +02:00
|
|
|
static ssize_t lirc_write(FAR struct file *filep, FAR const char *buffer,
|
|
|
|
size_t buflen);
|
|
|
|
static ssize_t lirc_read(FAR struct file *filep, FAR char *buffer,
|
|
|
|
size_t buflen);
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Private Data
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
static const struct file_operations g_lirc_fops =
|
|
|
|
{
|
2022-01-10 15:51:17 +01:00
|
|
|
lirc_open, /* open */
|
2020-10-23 10:36:11 +02:00
|
|
|
lirc_close, /* close */
|
2022-01-10 15:51:17 +01:00
|
|
|
lirc_read, /* read */
|
2020-10-23 10:36:11 +02:00
|
|
|
lirc_write, /* write */
|
2022-01-10 15:51:17 +01:00
|
|
|
NULL, /* seek */
|
2020-10-23 10:36:11 +02:00
|
|
|
lirc_ioctl, /* ioctl */
|
2023-01-02 14:02:51 +01:00
|
|
|
NULL, /* mmap */
|
2023-01-02 18:06:12 +01:00
|
|
|
NULL, /* truncate */
|
2022-01-10 15:51:17 +01:00
|
|
|
lirc_poll /* poll */
|
2020-10-23 10:36:11 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Private Functions
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
static int lirc_open(FAR struct file *filep)
|
|
|
|
{
|
|
|
|
FAR struct inode *inode = filep->f_inode;
|
|
|
|
FAR struct lirc_upperhalf_s *upper = inode->i_private;
|
|
|
|
FAR struct lirc_lowerhalf_s *lower = upper->lower;
|
|
|
|
FAR struct lirc_fh_s *fh;
|
|
|
|
irqstate_t flags;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
fh = kmm_zalloc(sizeof(*fh));
|
|
|
|
if (!fh)
|
|
|
|
{
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (lower->ops->driver_type)
|
|
|
|
{
|
|
|
|
case LIRC_DRIVER_SCANCODE:
|
|
|
|
fh->rec_mode = LIRC_MODE_SCANCODE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fh->rec_mode = LIRC_MODE_MODE2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lower->ops->tx_scancode)
|
|
|
|
{
|
|
|
|
fh->send_mode = LIRC_MODE_SCANCODE;
|
|
|
|
}
|
|
|
|
else if (lower->ops->tx_ir)
|
|
|
|
{
|
|
|
|
fh->send_mode = LIRC_MODE_PULSE;
|
|
|
|
}
|
|
|
|
|
2020-11-12 11:40:37 +01:00
|
|
|
ret = circbuf_init(&fh->buffer, NULL, lower->buffer_bytes);
|
|
|
|
if (ret < 0)
|
2020-10-23 10:36:11 +02:00
|
|
|
{
|
|
|
|
goto buffer_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
nxsem_init(&fh->waitsem, 0, 0);
|
|
|
|
|
|
|
|
fh->lower = lower;
|
|
|
|
fh->send_timeout_reports = true;
|
|
|
|
|
|
|
|
if (list_is_empty(&upper->fh))
|
|
|
|
{
|
|
|
|
ret = lower->ops->open(lower);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
goto open_err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
flags = enter_critical_section();
|
|
|
|
list_add_tail(&upper->fh, &fh->node);
|
|
|
|
leave_critical_section(flags);
|
|
|
|
|
|
|
|
filep->f_priv = fh;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
open_err:
|
|
|
|
nxsem_destroy(&fh->waitsem);
|
2020-11-12 11:40:37 +01:00
|
|
|
circbuf_uninit(&fh->buffer);
|
2020-10-23 10:36:11 +02:00
|
|
|
buffer_err:
|
|
|
|
kmm_free(fh);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int lirc_close(FAR struct file *filep)
|
|
|
|
{
|
|
|
|
FAR struct inode *inode = filep->f_inode;
|
|
|
|
FAR struct lirc_upperhalf_s *upper = inode->i_private;
|
|
|
|
FAR struct lirc_fh_s *fh = filep->f_priv;
|
|
|
|
FAR struct lirc_lowerhalf_s *lower = fh->lower;
|
|
|
|
irqstate_t flags;
|
|
|
|
|
|
|
|
flags = enter_critical_section();
|
|
|
|
list_delete(&fh->node);
|
|
|
|
leave_critical_section(flags);
|
|
|
|
|
|
|
|
nxsem_destroy(&fh->waitsem);
|
2020-11-12 11:40:37 +01:00
|
|
|
circbuf_uninit(&fh->buffer);
|
2020-10-23 10:36:11 +02:00
|
|
|
|
|
|
|
kmm_free(fh);
|
|
|
|
if (list_is_empty(&upper->fh))
|
|
|
|
{
|
|
|
|
lower->ops->close(lower);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int lirc_poll(FAR struct file *filep,
|
|
|
|
FAR struct pollfd *fds, bool setup)
|
|
|
|
{
|
|
|
|
FAR struct lirc_fh_s *fh = filep->f_priv;
|
|
|
|
pollevent_t eventset = 0;
|
|
|
|
irqstate_t flags;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
flags = enter_critical_section();
|
|
|
|
if (setup)
|
|
|
|
{
|
2023-11-19 12:19:53 +01:00
|
|
|
if (fh->fds)
|
2020-10-23 10:36:11 +02:00
|
|
|
{
|
|
|
|
ret = -EBUSY;
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
2023-11-19 12:19:53 +01:00
|
|
|
fh->fds = fds;
|
|
|
|
fds->priv = &fh->fds;
|
2020-10-23 10:36:11 +02:00
|
|
|
|
2020-11-12 11:40:37 +01:00
|
|
|
if (!circbuf_is_empty(&fh->buffer))
|
2020-10-23 10:36:11 +02:00
|
|
|
{
|
2022-09-19 05:08:57 +02:00
|
|
|
eventset |= POLLIN | POLLRDNORM;
|
2020-10-23 10:36:11 +02:00
|
|
|
}
|
|
|
|
|
2023-11-19 12:19:53 +01:00
|
|
|
poll_notify(&fds, 1, eventset);
|
2020-10-23 10:36:11 +02:00
|
|
|
}
|
|
|
|
else if (fds->priv != NULL)
|
|
|
|
{
|
|
|
|
FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;
|
|
|
|
|
|
|
|
if (!slot)
|
|
|
|
{
|
|
|
|
ret = -EIO;
|
|
|
|
goto errout;
|
|
|
|
}
|
|
|
|
|
|
|
|
*slot = NULL;
|
|
|
|
fds->priv = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
errout:
|
|
|
|
leave_critical_section(flags);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int lirc_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
|
|
|
{
|
|
|
|
FAR struct lirc_fh_s *fh = filep->f_priv;
|
|
|
|
FAR struct lirc_lowerhalf_s *lower = fh->lower;
|
|
|
|
FAR struct lirc_upperhalf_s *upper = lower->priv;
|
|
|
|
FAR unsigned int *val = (unsigned int *)(uintptr_t)arg;
|
|
|
|
int ret;
|
|
|
|
|
2022-09-06 08:18:45 +02:00
|
|
|
ret = nxmutex_lock(&upper->lock);
|
2020-10-23 10:36:11 +02:00
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (cmd)
|
|
|
|
{
|
|
|
|
case LIRC_GET_FEATURES:
|
|
|
|
switch (lower->ops->driver_type)
|
|
|
|
{
|
|
|
|
case LIRC_DRIVER_SCANCODE:
|
|
|
|
*val = LIRC_CAN_REC_SCANCODE;
|
|
|
|
break;
|
|
|
|
case LIRC_DRIVER_IR_RAW:
|
|
|
|
*val = LIRC_CAN_REC_MODE2;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
*val = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lower->rx_resolution)
|
|
|
|
{
|
|
|
|
*val |= LIRC_CAN_GET_REC_RESOLUTION;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lower->ops->tx_ir)
|
|
|
|
{
|
|
|
|
*val |= LIRC_CAN_SEND_PULSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lower->ops->tx_scancode)
|
|
|
|
{
|
|
|
|
*val |= LIRC_CAN_SEND_SCANCODE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lower->ops->s_tx_mask)
|
|
|
|
{
|
|
|
|
*val |= LIRC_CAN_SET_TRANSMITTER_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lower->ops->s_tx_carrier)
|
|
|
|
{
|
|
|
|
*val |= LIRC_CAN_SET_SEND_CARRIER;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lower->ops->s_tx_duty_cycle)
|
|
|
|
{
|
|
|
|
*val |= LIRC_CAN_SET_SEND_DUTY_CYCLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lower->ops->s_rx_carrier_range)
|
|
|
|
{
|
|
|
|
*val |= LIRC_CAN_SET_REC_CARRIER |
|
|
|
|
LIRC_CAN_SET_REC_CARRIER_RANGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lower->ops->s_learning_mode)
|
|
|
|
{
|
|
|
|
*val |= LIRC_CAN_USE_WIDEBAND_RECEIVER;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lower->ops->s_carrier_report)
|
|
|
|
{
|
|
|
|
*val |= LIRC_CAN_MEASURE_CARRIER;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lower->max_timeout)
|
|
|
|
{
|
|
|
|
*val |= LIRC_CAN_SET_REC_TIMEOUT;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* mode support */
|
|
|
|
|
|
|
|
case LIRC_GET_REC_MODE:
|
|
|
|
if (lower->ops->driver_type == LIRC_DRIVER_IR_RAW_TX)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*val = fh->rec_mode;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_SET_REC_MODE:
|
|
|
|
switch (lower->ops->driver_type)
|
|
|
|
{
|
|
|
|
case LIRC_DRIVER_IR_RAW_TX:
|
|
|
|
ret = -ENOTTY;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_DRIVER_SCANCODE:
|
|
|
|
if (arg != LIRC_MODE_SCANCODE)
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_DRIVER_IR_RAW:
|
|
|
|
if (arg != LIRC_MODE_MODE2)
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret >= 0)
|
|
|
|
{
|
|
|
|
fh->rec_mode = arg;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_GET_SEND_MODE:
|
|
|
|
if (!lower->ops->tx_ir && !lower->ops->tx_scancode)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*val = fh->send_mode;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_SET_SEND_MODE:
|
|
|
|
if ((arg == LIRC_MODE_PULSE && lower->ops->tx_ir) ||
|
|
|
|
(arg == LIRC_MODE_SCANCODE && lower->ops->tx_scancode))
|
|
|
|
{
|
|
|
|
fh->send_mode = arg;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* TX settings */
|
|
|
|
|
|
|
|
case LIRC_SET_TRANSMITTER_MASK:
|
|
|
|
if (!lower->ops->s_tx_mask)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = lower->ops->s_tx_mask(lower, arg);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_SET_SEND_CARRIER:
|
|
|
|
if (!lower->ops->s_tx_carrier)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = lower->ops->s_tx_carrier(lower, arg);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_SET_SEND_DUTY_CYCLE:
|
|
|
|
if (!lower->ops->s_tx_duty_cycle)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else if (arg <= 0 || arg >= 100)
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = lower->ops->s_tx_duty_cycle(lower, arg);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* RX settings */
|
|
|
|
|
|
|
|
case LIRC_SET_REC_CARRIER:
|
|
|
|
if (!lower->ops->s_rx_carrier_range)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else if (arg <= 0)
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = lower->ops->s_rx_carrier_range(lower,
|
|
|
|
fh->carrier_low, arg);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_SET_REC_CARRIER_RANGE:
|
|
|
|
if (!lower->ops->s_rx_carrier_range)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else if (arg <= 0)
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fh->carrier_low = arg;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_GET_REC_RESOLUTION:
|
|
|
|
if (!lower->rx_resolution)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*val = lower->rx_resolution;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_SET_WIDEBAND_RECEIVER:
|
|
|
|
if (!lower->ops->s_learning_mode)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = lower->ops->s_learning_mode(lower, !!arg);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_SET_MEASURE_CARRIER_MODE:
|
|
|
|
if (!lower->ops->s_carrier_report)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = lower->ops->s_carrier_report(lower, !!arg);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* Generic timeout support */
|
|
|
|
|
|
|
|
case LIRC_GET_MIN_TIMEOUT:
|
|
|
|
if (!lower->min_timeout)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*val = lower->min_timeout;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_GET_MAX_TIMEOUT:
|
|
|
|
if (!lower->max_timeout)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*val = lower->max_timeout;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_SET_REC_TIMEOUT:
|
|
|
|
if (!lower->max_timeout)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (arg < lower->min_timeout || arg > lower->max_timeout)
|
|
|
|
{
|
|
|
|
ret = -EINVAL;
|
|
|
|
}
|
|
|
|
else if (lower->ops->s_timeout)
|
|
|
|
{
|
|
|
|
ret = lower->ops->s_timeout(lower, arg);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lower->timeout = arg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_GET_REC_TIMEOUT:
|
|
|
|
if (!lower->timeout)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*val = lower->timeout;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LIRC_SET_REC_TIMEOUT_REPORTS:
|
|
|
|
if (lower->ops->driver_type != LIRC_DRIVER_IR_RAW)
|
|
|
|
{
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fh->send_timeout_reports = !!arg;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
ret = -ENOTTY;
|
|
|
|
}
|
|
|
|
|
2022-09-06 08:18:45 +02:00
|
|
|
nxmutex_unlock(&upper->lock);
|
2020-10-23 10:36:11 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t lirc_write_pulse(FAR struct file *filep,
|
|
|
|
FAR const char *buffer, size_t buflen)
|
|
|
|
{
|
|
|
|
FAR struct lirc_fh_s *fh = filep->f_priv;
|
|
|
|
FAR struct lirc_lowerhalf_s *lower = fh->lower;
|
|
|
|
size_t count;
|
|
|
|
|
|
|
|
if (buflen < sizeof(unsigned int) || buflen % sizeof(unsigned int))
|
|
|
|
{
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
count = buflen / sizeof(unsigned int);
|
|
|
|
if (count % 2 == 0)
|
|
|
|
{
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* tx_ir need sleep some time to wait for thr actual IR signal
|
|
|
|
* to be transmitted before returning
|
|
|
|
*/
|
|
|
|
|
|
|
|
return lower->ops->tx_ir(lower, (FAR unsigned int *)buffer, count);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t lirc_write_scancode(FAR struct file *filep,
|
|
|
|
FAR const char *buffer, size_t buflen)
|
|
|
|
{
|
|
|
|
FAR struct lirc_fh_s *fh = filep->f_priv;
|
|
|
|
FAR struct lirc_lowerhalf_s *lower = fh->lower;
|
|
|
|
|
|
|
|
if (buflen != sizeof(struct lirc_scancode))
|
|
|
|
{
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* tx_scancode need sleep some time to wait for thr actual IR signal
|
|
|
|
* to be transmitted before returning
|
|
|
|
*/
|
|
|
|
|
|
|
|
return lower->ops->tx_scancode(lower,
|
|
|
|
(FAR struct lirc_scancode *)buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t lirc_write(FAR struct file *filep, FAR const char *buffer,
|
|
|
|
size_t buflen)
|
|
|
|
{
|
|
|
|
FAR struct inode *inode = filep->f_inode;
|
|
|
|
FAR struct lirc_upperhalf_s *upper = inode->i_private;
|
|
|
|
FAR struct lirc_fh_s *fh = filep->f_priv;
|
|
|
|
ssize_t ret;
|
|
|
|
|
2022-09-06 08:18:45 +02:00
|
|
|
ret = nxmutex_lock(&upper->lock);
|
2020-10-23 10:36:11 +02:00
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fh->send_mode == LIRC_MODE_SCANCODE)
|
|
|
|
{
|
|
|
|
ret = lirc_write_scancode(filep, buffer, buflen);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = lirc_write_pulse(filep, buffer, buflen);
|
|
|
|
}
|
|
|
|
|
2022-09-06 08:18:45 +02:00
|
|
|
nxmutex_unlock(&upper->lock);
|
2020-10-23 10:36:11 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t lirc_read_scancode(FAR struct file *filep, FAR char *buffer,
|
|
|
|
size_t length)
|
|
|
|
{
|
|
|
|
FAR struct lirc_fh_s *fh = filep->f_priv;
|
|
|
|
irqstate_t flags;
|
2020-11-20 13:17:57 +01:00
|
|
|
ssize_t ret;
|
2020-10-23 10:36:11 +02:00
|
|
|
|
|
|
|
if (length < sizeof(struct lirc_scancode) ||
|
|
|
|
length % sizeof(struct lirc_scancode))
|
|
|
|
{
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
flags = enter_critical_section();
|
|
|
|
do
|
|
|
|
{
|
2020-11-12 11:40:37 +01:00
|
|
|
if (circbuf_is_empty(&fh->buffer))
|
2020-10-23 10:36:11 +02:00
|
|
|
{
|
|
|
|
if (filep->f_oflags & O_NONBLOCK)
|
|
|
|
{
|
|
|
|
ret = -EAGAIN;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = nxsem_wait_uninterruptible(&fh->waitsem);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-20 13:17:57 +01:00
|
|
|
ret = circbuf_read(&fh->buffer, buffer, length);
|
2020-10-23 10:36:11 +02:00
|
|
|
}
|
2020-11-20 13:17:57 +01:00
|
|
|
while (ret <= 0);
|
2020-10-23 10:36:11 +02:00
|
|
|
|
|
|
|
err:
|
|
|
|
leave_critical_section(flags);
|
2020-11-20 13:17:57 +01:00
|
|
|
return ret;
|
2020-10-23 10:36:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t lirc_read_mode2(FAR struct file *filep, FAR char *buffer,
|
|
|
|
size_t length)
|
|
|
|
{
|
|
|
|
FAR struct lirc_fh_s *fh = filep->f_priv;
|
|
|
|
irqstate_t flags;
|
2020-11-20 13:17:57 +01:00
|
|
|
ssize_t ret = 0;
|
2020-10-23 10:36:11 +02:00
|
|
|
|
|
|
|
if (length < sizeof(unsigned int) || length % sizeof(unsigned int))
|
|
|
|
{
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
flags = enter_critical_section();
|
|
|
|
do
|
|
|
|
{
|
2020-11-12 11:40:37 +01:00
|
|
|
if (circbuf_is_empty(&fh->buffer))
|
2020-10-23 10:36:11 +02:00
|
|
|
{
|
|
|
|
if (filep->f_oflags & O_NONBLOCK)
|
|
|
|
{
|
|
|
|
ret = -EAGAIN;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = nxsem_wait_uninterruptible(&fh->waitsem);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-20 13:17:57 +01:00
|
|
|
ret = circbuf_read(&fh->buffer, buffer, length);
|
2020-10-23 10:36:11 +02:00
|
|
|
}
|
2020-11-20 13:17:57 +01:00
|
|
|
while (ret <= 0);
|
2020-10-23 10:36:11 +02:00
|
|
|
|
|
|
|
err:
|
|
|
|
leave_critical_section(flags);
|
2020-11-20 13:17:57 +01:00
|
|
|
return ret;
|
2020-10-23 10:36:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t lirc_read(FAR struct file *filep, FAR char *buffer,
|
|
|
|
size_t len)
|
|
|
|
{
|
|
|
|
FAR struct lirc_fh_s *fh = filep->f_priv;
|
|
|
|
|
|
|
|
if (fh->rec_mode == LIRC_MODE_MODE2)
|
|
|
|
{
|
|
|
|
return lirc_read_mode2(filep, buffer, len);
|
|
|
|
}
|
|
|
|
else /* LIRC_MODE_SCANCODE */
|
|
|
|
{
|
|
|
|
return lirc_read_scancode(filep, buffer, len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Public Functions
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Name: lirc_register
|
|
|
|
*
|
|
|
|
* Description:
|
|
|
|
* This function binds an instance of a "lower half" lirc driver with the
|
|
|
|
* "upper half" RC device and registers that device so that can be used
|
|
|
|
* by application code.
|
|
|
|
*
|
|
|
|
* We will register the chararter device. ex: /dev/lirc%d(0, 1, ...)
|
|
|
|
*
|
|
|
|
* Input Parameters:
|
|
|
|
* lower - A pointer to an instance of lower half lirc driver.
|
|
|
|
* devno - The user specifies device number, from 0. If the
|
|
|
|
* devno alerady exists, -EEXIST will be returned.
|
|
|
|
*
|
|
|
|
* Returned Value:
|
|
|
|
* OK if the driver was successfully register; A negated errno value is
|
|
|
|
* returned on any failure.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
int lirc_register(FAR struct lirc_lowerhalf_s *lower, int devno)
|
|
|
|
{
|
|
|
|
FAR struct lirc_upperhalf_s *upper;
|
|
|
|
char path[DEVNAME_MAX];
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
DEBUGASSERT(lower != NULL);
|
|
|
|
|
|
|
|
/* Allocate and init the upper-half data structure */
|
|
|
|
|
|
|
|
upper = kmm_zalloc(sizeof(struct lirc_upperhalf_s));
|
|
|
|
if (!upper)
|
|
|
|
{
|
|
|
|
snerr("ERROR: Allocation failed\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
upper->lower = lower;
|
|
|
|
list_initialize(&upper->fh);
|
2022-09-06 08:18:45 +02:00
|
|
|
nxmutex_init(&upper->lock);
|
2020-10-23 10:36:11 +02:00
|
|
|
lower->priv = upper;
|
|
|
|
|
|
|
|
/* Register remote control character device */
|
|
|
|
|
|
|
|
snprintf(path, DEVNAME_MAX, DEVNAME_FMT, devno);
|
|
|
|
ret = register_driver(path, &g_lirc_fops, 0666, upper);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
goto drv_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
drv_err:
|
2022-09-06 08:18:45 +02:00
|
|
|
nxmutex_destroy(&upper->lock);
|
2020-10-23 10:36:11 +02:00
|
|
|
kmm_free(upper);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Name: lirc_unregister
|
|
|
|
*
|
|
|
|
* Description:
|
|
|
|
* This function unregister character node and release all resource about
|
|
|
|
* upper half driver.
|
|
|
|
*
|
|
|
|
* Input Parameters:
|
|
|
|
* lower - A pointer to an instance of lower half lirc driver.
|
|
|
|
* devno - The user specifies device number, from 0.
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
void lirc_unregister(FAR struct lirc_lowerhalf_s *lower, int devno)
|
|
|
|
{
|
|
|
|
FAR struct lirc_upperhalf_s *upper = lower->priv;
|
|
|
|
char path[DEVNAME_MAX];
|
|
|
|
|
2022-09-06 08:18:45 +02:00
|
|
|
nxmutex_destroy(&upper->lock);
|
2020-10-23 10:36:11 +02:00
|
|
|
snprintf(path, DEVNAME_MAX, DEVNAME_FMT, devno);
|
|
|
|
rcinfo("UnRegistering %s\n", path);
|
|
|
|
unregister_driver(path);
|
|
|
|
kmm_free(upper);
|
|
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Name: lirc_raw_event
|
|
|
|
*
|
|
|
|
* Description:
|
|
|
|
* Lirc lowerhalf driver sends IR data to lirc upperhalf buffer, to
|
|
|
|
* notify userspace to read IR data.
|
|
|
|
*
|
|
|
|
* The type of data is struct lirc_raw_event_s.
|
|
|
|
*
|
|
|
|
* Input Parameters:
|
|
|
|
* lower - A pointer to an instance of lower half lirc driver.
|
|
|
|
* ev - The data of receiving from IR device
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
void lirc_raw_event(FAR struct lirc_lowerhalf_s *lower,
|
|
|
|
struct lirc_raw_event_s ev)
|
|
|
|
{
|
|
|
|
FAR struct lirc_upperhalf_s *upper = lower->priv;
|
|
|
|
FAR struct list_node *node;
|
|
|
|
FAR struct list_node *tmp;
|
|
|
|
FAR struct lirc_fh_s *fh;
|
|
|
|
unsigned int sample;
|
|
|
|
irqstate_t flags;
|
|
|
|
int semcount;
|
|
|
|
int gap;
|
|
|
|
|
|
|
|
/* Packet start */
|
|
|
|
|
|
|
|
if (ev.reset)
|
|
|
|
{
|
|
|
|
/* Userspace expects a long space event before the start of
|
|
|
|
* the signal to use as a sync. This may be done with repeat
|
|
|
|
* packets and normal samples. But if a reset has been sent
|
|
|
|
* then we assume that a long time has passed, so we send a
|
|
|
|
* space with the maximum time value.
|
|
|
|
*/
|
|
|
|
|
|
|
|
sample = LIRC_SPACE(LIRC_VALUE_MASK);
|
|
|
|
rcinfo("delivering reset sync space to lirc_dev\n");
|
|
|
|
}
|
|
|
|
else if (ev.carrier_report)
|
|
|
|
{
|
|
|
|
/* Carrier reports */
|
|
|
|
|
|
|
|
sample = LIRC_FREQUENCY(ev.carrier);
|
|
|
|
rcinfo("carrier report (freq: %d)\n", sample);
|
|
|
|
}
|
|
|
|
else if (ev.timeout)
|
|
|
|
{
|
|
|
|
/* Packet end */
|
|
|
|
|
|
|
|
if (upper->gap)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
upper->gap = true;
|
|
|
|
upper->gap_start = lirc_get_timestamp() / 1000;
|
|
|
|
upper->gap_duration = ev.duration;
|
|
|
|
|
|
|
|
sample = LIRC_TIMEOUT(ev.duration);
|
|
|
|
rcinfo("timeout report (duration: %d)\n", sample);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Normal sample */
|
|
|
|
|
|
|
|
if (upper->gap)
|
|
|
|
{
|
|
|
|
upper->gap_duration += (lirc_get_timestamp() / 1000) -
|
|
|
|
upper->gap_start;
|
|
|
|
|
|
|
|
/* Cap by LIRC_VALUE_MASK */
|
|
|
|
|
|
|
|
upper->gap_duration = MIN(upper->gap_duration, LIRC_VALUE_MASK);
|
|
|
|
gap = LIRC_SPACE(upper->gap_duration);
|
|
|
|
|
|
|
|
flags = enter_critical_section();
|
|
|
|
list_for_every_safe(&upper->fh, node, tmp)
|
|
|
|
{
|
|
|
|
fh = (FAR struct lirc_fh_s *)node;
|
2020-11-12 11:40:37 +01:00
|
|
|
if (circbuf_write(&fh->buffer, &gap, sizeof(int)) > 0)
|
2020-10-23 10:36:11 +02:00
|
|
|
{
|
2023-11-19 12:19:53 +01:00
|
|
|
poll_notify(&fh->fds, 1, POLLIN | POLLRDNORM);
|
2020-10-23 10:36:11 +02:00
|
|
|
nxsem_get_value(&fh->waitsem, &semcount);
|
|
|
|
if (semcount < 1)
|
|
|
|
{
|
|
|
|
nxsem_post(&fh->waitsem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
upper->gap = false;
|
|
|
|
}
|
2021-04-09 05:23:15 +02:00
|
|
|
|
|
|
|
leave_critical_section(flags);
|
2020-10-23 10:36:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sample = ev.pulse ? LIRC_PULSE(ev.duration) : LIRC_SPACE(ev.duration);
|
2022-08-03 14:06:46 +02:00
|
|
|
rcinfo("delivering %" PRIu32 "us %u to lirc\n",
|
|
|
|
ev.duration, ev.pulse ? 1 : 0);
|
2020-10-23 10:36:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
flags = enter_critical_section();
|
|
|
|
list_for_every_safe(&upper->fh, node, tmp)
|
|
|
|
{
|
|
|
|
fh = (FAR struct lirc_fh_s *)node;
|
|
|
|
if (LIRC_IS_TIMEOUT(sample) && !fh->send_timeout_reports)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-11-12 11:40:37 +01:00
|
|
|
if (circbuf_write(&fh->buffer, &sample, sizeof(unsigned int)) > 0)
|
2020-10-23 10:36:11 +02:00
|
|
|
{
|
2023-11-19 12:19:53 +01:00
|
|
|
poll_notify(&fh->fds, 1, POLLIN | POLLRDNORM);
|
2020-10-23 10:36:11 +02:00
|
|
|
nxsem_get_value(&fh->waitsem, &semcount);
|
|
|
|
if (semcount < 1)
|
|
|
|
{
|
|
|
|
nxsem_post(&fh->waitsem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
leave_critical_section(flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Name: lirc_scancode_event
|
|
|
|
*
|
|
|
|
* Description:
|
|
|
|
* Lirc lowerhalf driver sends IR data to lirc upperhalf buffer, to
|
|
|
|
* notify userspace to read IR data.
|
|
|
|
*
|
|
|
|
* The type of data is struct lirc_scancode.
|
|
|
|
*
|
|
|
|
* Input Parameters:
|
|
|
|
* lower - A pointer to an instance of lower half lirc driver.
|
|
|
|
* lsc - The data of receiving from IR device
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
void lirc_scancode_event(FAR struct lirc_lowerhalf_s *lower,
|
|
|
|
FAR struct lirc_scancode *lsc)
|
|
|
|
{
|
|
|
|
FAR struct lirc_upperhalf_s *upper = lower->priv;
|
|
|
|
FAR struct list_node *node;
|
|
|
|
FAR struct list_node *tmp;
|
|
|
|
FAR struct lirc_fh_s *fh;
|
|
|
|
irqstate_t flags;
|
|
|
|
int semcount;
|
|
|
|
|
|
|
|
lsc->timestamp = lirc_get_timestamp();
|
|
|
|
|
|
|
|
flags = enter_critical_section();
|
|
|
|
list_for_every_safe(&upper->fh, node, tmp)
|
|
|
|
{
|
|
|
|
fh = (FAR struct lirc_fh_s *)node;
|
2020-11-12 11:40:37 +01:00
|
|
|
if (circbuf_write(&fh->buffer, lsc, sizeof(*lsc)) > 0)
|
2020-10-23 10:36:11 +02:00
|
|
|
{
|
2023-11-19 12:19:53 +01:00
|
|
|
poll_notify(&fh->fds, 1, POLLIN | POLLRDNORM);
|
2020-10-23 10:36:11 +02:00
|
|
|
nxsem_get_value(&fh->waitsem, &semcount);
|
|
|
|
if (semcount < 1)
|
|
|
|
{
|
|
|
|
nxsem_post(&fh->waitsem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
leave_critical_section(flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
|
|
* Name: lirc_sample_event
|
|
|
|
*
|
|
|
|
* Description:
|
|
|
|
* Lirc lowerhalf driver sends raw IR data to lirc upperhalf buffer, to
|
|
|
|
* notify userspace to read IR data.
|
|
|
|
*
|
|
|
|
* The type of data is a sequence of pulse and space codes, as a seriers
|
|
|
|
* of unsigned int values.
|
|
|
|
*
|
|
|
|
* The upper 8 bits determine the packet type, and the lower 24 bits the
|
|
|
|
* payload.
|
|
|
|
*
|
|
|
|
* Input Parameters:
|
|
|
|
* lower - A pointer to an instance of lower half lirc driver.
|
|
|
|
* sample - The data of receiving from IR device
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
void lirc_sample_event(FAR struct lirc_lowerhalf_s *lower,
|
|
|
|
unsigned int sample)
|
|
|
|
{
|
|
|
|
FAR struct lirc_upperhalf_s *upper = lower->priv;
|
|
|
|
FAR struct list_node *node;
|
|
|
|
FAR struct list_node *tmp;
|
|
|
|
FAR struct lirc_fh_s *fh;
|
|
|
|
irqstate_t flags;
|
|
|
|
int semcount;
|
|
|
|
|
|
|
|
flags = enter_critical_section();
|
|
|
|
list_for_every_safe(&upper->fh, node, tmp)
|
|
|
|
{
|
|
|
|
fh = (FAR struct lirc_fh_s *)node;
|
2020-11-12 11:40:37 +01:00
|
|
|
if (circbuf_write(&fh->buffer, &sample, sizeof(unsigned int)) > 0)
|
2020-10-23 10:36:11 +02:00
|
|
|
{
|
2023-11-19 12:19:53 +01:00
|
|
|
poll_notify(&fh->fds, 1, POLLIN | POLLRDNORM);
|
2020-10-23 10:36:11 +02:00
|
|
|
nxsem_get_value(&fh->waitsem, &semcount);
|
|
|
|
if (semcount < 1)
|
|
|
|
{
|
|
|
|
nxsem_post(&fh->waitsem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
leave_critical_section(flags);
|
|
|
|
}
|