mirror of
https://github.com/apache/nuttx.git
synced 2026-08-01 20:28:58 +00:00
Description: In kernel builds, any unprivileged process running on the NuttX device can open /dev/efuse and attempt to read/write fuse content. Reading the fuses may provide valuable information to an attacker controlling the user process. The write operation, in extreme cases where the fuse blocks are not locked, may brick the device. This is part of https://github.com/apache/nuttx/issues/19410 Compiles ok. Signed-off-by: Catalin Visinescu <catalin_visinescu@yahoo.com>
507 lines
14 KiB
C
507 lines
14 KiB
C
/****************************************************************************
|
|
* drivers/analog/dac.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* 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 <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <nuttx/debug.h>
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/signal.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/analog/dac.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define HALF_SECOND_MSEC 500
|
|
#define HALF_SECOND_USEC 500000L
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static int dac_open(FAR struct file *filep);
|
|
static int dac_close(FAR struct file *filep);
|
|
static ssize_t dac_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static int dac_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct file_operations g_dac_fops =
|
|
{
|
|
dac_open, /* open */
|
|
dac_close, /* close */
|
|
NULL, /* read */
|
|
dac_write, /* write */
|
|
NULL, /* seek */
|
|
dac_ioctl, /* ioctl */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: dac_open
|
|
*
|
|
* Description:
|
|
* This function is called whenever the DAC device is opened.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int dac_open(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct dac_dev_s *dev = inode->i_private;
|
|
uint8_t tmp;
|
|
int ret;
|
|
|
|
/* If the port is in the middle of closing, wait until the close is
|
|
* finished.
|
|
*/
|
|
|
|
ret = nxmutex_lock(&dev->ad_closelock);
|
|
if (ret >= 0)
|
|
{
|
|
/* Increment the count of references to the device. If this is the
|
|
* first time that the driver has been opened for this device, then
|
|
* initialize the device.
|
|
*/
|
|
|
|
tmp = dev->ad_ocount + 1;
|
|
if (tmp == 0)
|
|
{
|
|
/* More than 255 opens; uint8_t overflows to zero */
|
|
|
|
ret = -EMFILE;
|
|
}
|
|
else
|
|
{
|
|
/* Check if this is the first time that the driver has been
|
|
* opened.
|
|
*/
|
|
|
|
if (tmp == 1)
|
|
{
|
|
/* Yes.. perform one time hardware initialization. */
|
|
|
|
irqstate_t flags = enter_critical_section();
|
|
ret = dev->ad_ops->ao_setup(dev);
|
|
if (ret == OK)
|
|
{
|
|
/* Mark the FIFOs empty */
|
|
|
|
dev->ad_xmit.af_head = 0;
|
|
dev->ad_xmit.af_tail = 0;
|
|
|
|
/* Save the new open count on success */
|
|
|
|
dev->ad_ocount = tmp;
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
}
|
|
}
|
|
|
|
nxmutex_unlock(&dev->ad_closelock);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dac_close
|
|
*
|
|
* Description:
|
|
* This routine is called when the DAC device is closed.
|
|
* It waits for the last remaining data to be sent.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int dac_close(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct dac_dev_s *dev = inode->i_private;
|
|
irqstate_t flags;
|
|
int ret;
|
|
|
|
ret = nxmutex_lock(&dev->ad_closelock);
|
|
if (ret >= 0)
|
|
{
|
|
/* Decrement the references to the driver. If the reference count will
|
|
* decrement to 0, then uninitialize the driver.
|
|
*/
|
|
|
|
if (dev->ad_ocount > 1)
|
|
{
|
|
dev->ad_ocount--;
|
|
nxmutex_unlock(&dev->ad_closelock);
|
|
}
|
|
else
|
|
{
|
|
/* There are no more references to the port */
|
|
|
|
dev->ad_ocount = 0;
|
|
|
|
/* Now we wait for the transmit FIFO to clear */
|
|
|
|
while (dev->ad_xmit.af_head != dev->ad_xmit.af_tail)
|
|
{
|
|
nxsched_usleep(HALF_SECOND_USEC);
|
|
}
|
|
|
|
/* Free the IRQ and disable the DAC device */
|
|
|
|
flags = enter_critical_section(); /* Disable interrupts */
|
|
dev->ad_ops->ao_shutdown(dev); /* Disable the DAC */
|
|
leave_critical_section(flags);
|
|
|
|
nxmutex_unlock(&dev->ad_closelock);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dac_xmit
|
|
*
|
|
* Description:
|
|
* Send the message at the head of the ad_xmit FIFO
|
|
*
|
|
* Assumptions:
|
|
* Called with interrupts disabled
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int dac_xmit(FAR struct dac_dev_s *dev)
|
|
{
|
|
bool enable = false;
|
|
int ret = OK;
|
|
|
|
/* Check if the xmit FIFO is empty */
|
|
|
|
if (dev->ad_xmit.af_head != dev->ad_xmit.af_tail)
|
|
{
|
|
/* Send the next message at the head of the FIFO */
|
|
|
|
ret = dev->ad_ops->ao_send(dev,
|
|
&dev->ad_xmit.af_buffer[dev->ad_xmit.af_head]);
|
|
|
|
/* Make sure the TX done interrupts are enabled */
|
|
|
|
enable = (ret == OK ? true : false);
|
|
}
|
|
|
|
dev->ad_ops->ao_txint(dev, enable);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dac_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t dac_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct dac_dev_s *dev = inode->i_private;
|
|
FAR struct dac_fifo_s *fifo = &dev->ad_xmit;
|
|
FAR struct dac_msg_s *msg;
|
|
bool empty;
|
|
ssize_t nsent = 0;
|
|
irqstate_t flags;
|
|
int nexttail;
|
|
int msglen;
|
|
int ret = 0;
|
|
|
|
/* Interrupts must be disabled throughout the following */
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Check if the TX FIFO was empty when we started. That is a clue that we
|
|
* have to kick off a new TX sequence.
|
|
*/
|
|
|
|
empty = (fifo->af_head == fifo->af_tail);
|
|
|
|
/* Add the messages to the FIFO. Ignore any trailing messages that are
|
|
* shorter than the minimum.
|
|
*/
|
|
|
|
if (buflen % 5 == 0)
|
|
{
|
|
msglen = 5;
|
|
}
|
|
else if (buflen % 4 == 0)
|
|
{
|
|
msglen = 4;
|
|
}
|
|
else if (buflen % 3 == 0)
|
|
{
|
|
msglen = 3;
|
|
}
|
|
else if (buflen % 2 == 0)
|
|
{
|
|
msglen = 2;
|
|
}
|
|
else if (buflen == 1)
|
|
{
|
|
msglen = 1;
|
|
}
|
|
else
|
|
{
|
|
msglen = 5;
|
|
}
|
|
|
|
while ((buflen - nsent) >= msglen)
|
|
{
|
|
/* Check if adding this new message would over-run the drivers ability
|
|
* to enqueue xmit data.
|
|
*/
|
|
|
|
nexttail = fifo->af_tail + 1;
|
|
if (nexttail >= CONFIG_DAC_FIFOSIZE)
|
|
{
|
|
nexttail = 0;
|
|
}
|
|
|
|
/* If the XMIT FIFO becomes full, then wait for space to become
|
|
* available.
|
|
*/
|
|
|
|
while (nexttail == fifo->af_head)
|
|
{
|
|
/* The transmit FIFO is full -- was non-blocking mode selected? */
|
|
|
|
if (filep->f_oflags & O_NONBLOCK)
|
|
{
|
|
if (nsent == 0)
|
|
{
|
|
ret = -EAGAIN;
|
|
}
|
|
else
|
|
{
|
|
ret = nsent;
|
|
}
|
|
|
|
goto return_with_irqdisabled;
|
|
}
|
|
|
|
/* If the FIFO was empty when we started, then we will have to
|
|
* start the XMIT sequence to clear the FIFO.
|
|
*/
|
|
|
|
if (empty)
|
|
{
|
|
dac_xmit(dev);
|
|
}
|
|
|
|
/* Wait for a message to be sent */
|
|
|
|
ret = nxsem_wait_uninterruptible(&fifo->af_sem);
|
|
if (ret < 0)
|
|
{
|
|
goto return_with_irqdisabled;
|
|
}
|
|
|
|
/* Re-check the FIFO state */
|
|
|
|
empty = (fifo->af_head == fifo->af_tail);
|
|
}
|
|
|
|
/* We get here if there is space at the end of the FIFO. Add the new
|
|
* DAC message at the tail of the FIFO.
|
|
*/
|
|
|
|
if (msglen == 5)
|
|
{
|
|
msg = (FAR struct dac_msg_s *)&buffer[nsent];
|
|
memcpy(&fifo->af_buffer[fifo->af_tail], msg, msglen);
|
|
}
|
|
else if (msglen == 4)
|
|
{
|
|
fifo->af_buffer[fifo->af_tail].am_channel = buffer[nsent];
|
|
fifo->af_buffer[fifo->af_tail].am_data =
|
|
*(FAR uint32_t *)&buffer[nsent];
|
|
fifo->af_buffer[fifo->af_tail].am_data &= 0xffffff00;
|
|
}
|
|
else if (msglen == 3)
|
|
{
|
|
fifo->af_buffer[fifo->af_tail].am_channel = buffer[nsent];
|
|
fifo->af_buffer[fifo->af_tail].am_data =
|
|
(*(FAR uint16_t *)&buffer[nsent + 1]);
|
|
fifo->af_buffer[fifo->af_tail].am_data <<= 16;
|
|
}
|
|
else if (msglen == 2)
|
|
{
|
|
fifo->af_buffer[fifo->af_tail].am_channel = 0;
|
|
fifo->af_buffer[fifo->af_tail].am_data =
|
|
(*(FAR uint16_t *)&buffer[nsent]);
|
|
fifo->af_buffer[fifo->af_tail].am_data <<= 16;
|
|
}
|
|
else if (msglen == 1)
|
|
{
|
|
fifo->af_buffer[fifo->af_tail].am_channel = 0;
|
|
fifo->af_buffer[fifo->af_tail].am_data = buffer[nsent];
|
|
fifo->af_buffer[fifo->af_tail].am_data <<= 24;
|
|
}
|
|
|
|
/* Increment the tail of the circular buffer */
|
|
|
|
fifo->af_tail = nexttail;
|
|
|
|
/* Increment the number of bytes that were sent */
|
|
|
|
nsent += msglen;
|
|
}
|
|
|
|
/* We get here after all messages have been added to the FIFO. Check if
|
|
* we need to kick of the XMIT sequence.
|
|
*/
|
|
|
|
if (empty)
|
|
{
|
|
dac_xmit(dev);
|
|
}
|
|
|
|
/* Return the number of bytes that were sent */
|
|
|
|
ret = nsent;
|
|
|
|
return_with_irqdisabled:
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dac_ioctl
|
|
****************************************************************************/
|
|
|
|
static int dac_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct dac_dev_s *dev = inode->i_private;
|
|
int ret;
|
|
|
|
ret = dev->ad_ops->ao_ioctl(dev, cmd, arg);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: dac_txdone
|
|
*
|
|
* Description:
|
|
* Called from the DAC interrupt handler at the completion of a send
|
|
* operation.
|
|
*
|
|
* Returned Value:
|
|
* OK on success; a negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int dac_txdone(FAR struct dac_dev_s *dev)
|
|
{
|
|
int ret = -ENOENT;
|
|
int sval;
|
|
|
|
/* Verify that the xmit FIFO is not empty */
|
|
|
|
if (dev->ad_xmit.af_head != dev->ad_xmit.af_tail)
|
|
{
|
|
/* Remove the message at the head of the xmit FIFO */
|
|
|
|
if (++dev->ad_xmit.af_head >= CONFIG_DAC_FIFOSIZE)
|
|
{
|
|
dev->ad_xmit.af_head = 0;
|
|
}
|
|
|
|
/* Send the next message in the FIFO */
|
|
|
|
ret = dac_xmit(dev);
|
|
if (ret == OK)
|
|
{
|
|
/* Inform any waiting threads that new xmit space is available */
|
|
|
|
ret = nxsem_get_value(&dev->ad_xmit.af_sem, &sval);
|
|
if (ret == OK && sval <= 0)
|
|
{
|
|
ret = nxsem_post(&dev->ad_xmit.af_sem);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: dac_register
|
|
*
|
|
* Description:
|
|
* Register a dac driver.
|
|
*
|
|
* Input Parameters:
|
|
* path - The full path to the DAC device to be registered. This could
|
|
* be, as an example, "/dev/dac0"
|
|
* dev - An instance of the device-specific DAC interface
|
|
*
|
|
* Returned Value:
|
|
* Zero on success; A negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int dac_register(FAR const char *path, FAR struct dac_dev_s *dev)
|
|
{
|
|
/* Initialize the DAC device structure */
|
|
|
|
dev->ad_ocount = 0;
|
|
|
|
/* Initialize semaphores & mutex */
|
|
|
|
nxsem_init(&dev->ad_xmit.af_sem, 0, 0);
|
|
nxmutex_init(&dev->ad_closelock);
|
|
|
|
dev->ad_ops->ao_reset(dev);
|
|
|
|
return register_driver(path, &g_dac_fops, 0200, dev);
|
|
}
|