mirror of
https://github.com/apache/nuttx.git
synced 2026-08-27 20:30:38 +00:00
Permissions (Part 2) 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. DISCLAIMER: I tried to be strict with the settings, better to relax them later if it's needed. This is part of https://github.com/apache/nuttx/issues/19410 See https://github.com/apache/nuttx/issues/19410 Compiles ok. Signed-off-by: Catalin Visinescu <catalin_visinescu@yahoo.com>
521 lines
15 KiB
C
521 lines
15 KiB
C
/****************************************************************************
|
|
* drivers/power/supply/smps.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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/* Upper-half, character driver for SMPS (switched-mode power supply) */
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <nuttx/debug.h>
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/power/smps.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static int smps_open(FAR struct file *filep);
|
|
static int smps_close(FAR struct file *filep);
|
|
static ssize_t smps_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t smps_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static int smps_ioctl(FAR struct file *filep, int cmd,
|
|
unsigned long arg);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct file_operations g_smps_fops =
|
|
{
|
|
smps_open, /* open */
|
|
smps_close, /* close */
|
|
smps_read, /* read */
|
|
smps_write, /* write */
|
|
NULL, /* seek */
|
|
smps_ioctl, /* ioctl */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: smps_open
|
|
*
|
|
* Description:
|
|
* This function is called whenever the SMPS device is opened.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int smps_open(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct smps_dev_s *dev = inode->i_private;
|
|
uint8_t tmp;
|
|
int ret;
|
|
|
|
/* If the port is the middle of closing, wait until the close is finished */
|
|
|
|
ret = nxmutex_lock(&dev->closelock);
|
|
if (ret >= 0)
|
|
{
|
|
/* Increment the count of references to the device. If this the first
|
|
* time that the driver has been opened for this device, then
|
|
* initialize the device.
|
|
*/
|
|
|
|
tmp = dev->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->ops->setup(dev);
|
|
if (ret == OK)
|
|
{
|
|
/* Save the new open count on success */
|
|
|
|
dev->ocount = tmp;
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
}
|
|
}
|
|
|
|
nxmutex_unlock(&dev->closelock);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: smps_close
|
|
*
|
|
* Description:
|
|
* This routine is called when the SMPS device is closed.
|
|
* It waits for the last remaining data to be sent.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int smps_close(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct smps_dev_s *dev = inode->i_private;
|
|
irqstate_t flags;
|
|
int ret;
|
|
|
|
ret = nxmutex_lock(&dev->closelock);
|
|
if (ret >= 0)
|
|
{
|
|
/* Decrement the references to the driver. If the reference count will
|
|
* decrement to 0, then uninitialize the driver.
|
|
*/
|
|
|
|
if (dev->ocount > 1)
|
|
{
|
|
dev->ocount--;
|
|
nxmutex_unlock(&dev->closelock);
|
|
}
|
|
else
|
|
{
|
|
/* There are no more references to the port */
|
|
|
|
dev->ocount = 0;
|
|
|
|
/* Free the IRQ and disable the SMPS device */
|
|
|
|
flags = enter_critical_section(); /* Disable interrupts */
|
|
dev->ops->shutdown(dev); /* Disable the SMPS */
|
|
leave_critical_section(flags);
|
|
|
|
nxmutex_unlock(&dev->closelock);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: smps_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t smps_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: smps_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t smps_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: smps_ioctl
|
|
****************************************************************************/
|
|
|
|
static int smps_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct smps_dev_s *dev = inode->i_private;
|
|
FAR struct smps_s *smps = (FAR struct smps_s *)dev->priv;
|
|
int ret;
|
|
|
|
switch (cmd)
|
|
{
|
|
case PWRIOC_START:
|
|
{
|
|
/* Allow SMPS start only when some limits available
|
|
* and structure is locked.
|
|
* REVISIT: not sure if it is needed here
|
|
*/
|
|
|
|
if ((smps->limits.lock == false) ||
|
|
(smps->limits.v_in <= 0 && smps->limits.v_out <= 0 &&
|
|
smps->limits.i_in <= 0 && smps->limits.i_out <= 0 &&
|
|
smps->limits.p_in <= 0 && smps->limits.p_out <= 0))
|
|
{
|
|
pwrerr("ERROR: SMPS limits data must be set"
|
|
" and locked before SMPS start\n");
|
|
|
|
ret = -EPERM;
|
|
goto errout;
|
|
}
|
|
|
|
/* Check SMPS mode */
|
|
|
|
if (smps->opmode == SMPS_OPMODE_INIT)
|
|
{
|
|
pwrerr("ERROR: SMPS operation mode not specified\n");
|
|
|
|
ret = -EPERM;
|
|
goto errout;
|
|
}
|
|
|
|
/* When constant current mode, then output current must be
|
|
* provided.
|
|
*/
|
|
|
|
if (smps->opmode == SMPS_OPMODE_CC && smps->param.i_out <= 0)
|
|
{
|
|
pwrerr("ERROR: CC selected but i_out not specified!\n");
|
|
|
|
ret = -EPERM;
|
|
goto errout;
|
|
}
|
|
|
|
/* When constant voltage mode, then output voltage must be
|
|
* provided.
|
|
*/
|
|
|
|
if (smps->opmode == SMPS_OPMODE_CV && smps->param.v_out <= 0)
|
|
{
|
|
pwrerr("ERROR: CV selected but v_out not specified!\n");
|
|
|
|
ret = -EPERM;
|
|
goto errout;
|
|
}
|
|
|
|
/* When constant power mode, then output power must be provided */
|
|
|
|
if (smps->opmode == SMPS_OPMODE_CP && smps->param.p_out <= 0)
|
|
{
|
|
pwrerr("ERROR: CP selected but p_out not specified!\n");
|
|
|
|
ret = -EPERM;
|
|
goto errout;
|
|
}
|
|
|
|
/* REVISIT: something else ?? */
|
|
|
|
/* Finally, call start from lower-half driver */
|
|
|
|
ret = dev->ops->start(dev);
|
|
if (ret != OK)
|
|
{
|
|
pwrerr("ERROR: PWRIOC_START failed %d\n", ret);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PWRIOC_STOP:
|
|
{
|
|
ret = dev->ops->stop(dev);
|
|
if (ret != OK)
|
|
{
|
|
pwrerr("ERROR: PWRIOC_STOP failed %d\n", ret);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PWRIOC_SET_MODE:
|
|
{
|
|
uint8_t mode = ((uint8_t)arg);
|
|
|
|
ret = dev->ops->mode_set(dev, mode);
|
|
if (ret != OK)
|
|
{
|
|
pwrerr("ERROR: PWRIOC_SET_MODE failed %d\n", ret);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PWRIOC_SET_LIMITS:
|
|
{
|
|
FAR struct smps_limits_s *limits =
|
|
(FAR struct smps_limits_s *)((uintptr_t)arg);
|
|
|
|
if (smps->limits.lock == true)
|
|
{
|
|
pwrerr("ERROR: SMPS limits locked!\n");
|
|
|
|
ret = -EPERM;
|
|
goto errout;
|
|
}
|
|
|
|
/* NOTE: this call must set the smps_limits_s structure */
|
|
|
|
ret = dev->ops->limits_set(dev, limits);
|
|
if (ret != OK)
|
|
{
|
|
pwrerr("ERROR: PWRIOC_SET_LIMITS failed %d\n", ret);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PWRIOC_GET_STATE:
|
|
{
|
|
FAR struct smps_state_s *state =
|
|
(FAR struct smps_state_s *)((uintptr_t)arg);
|
|
|
|
ret = dev->ops->state_get(dev, state);
|
|
if (ret != OK)
|
|
{
|
|
pwrerr("ERROR: PWRIOC_GET_STATE failed %d\n", ret);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PWRIOC_SET_FAULT:
|
|
{
|
|
uint8_t fault = ((uint8_t)arg);
|
|
|
|
ret = dev->ops->fault_set(dev, fault);
|
|
if (ret != OK)
|
|
{
|
|
pwrerr("ERROR: PWRIOC_SET_FAULT failed %d\n", ret);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PWRIOC_GET_FAULT:
|
|
{
|
|
FAR uint8_t *fault = ((FAR uint8_t *)arg);
|
|
|
|
ret = dev->ops->fault_get(dev, fault);
|
|
if (ret != OK)
|
|
{
|
|
pwrerr("ERROR: PWRIOC_GET_FAULT failed %d\n", ret);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PWRIOC_CLEAN_FAULT:
|
|
{
|
|
uint8_t fault = ((uint8_t)arg);
|
|
|
|
ret = dev->ops->fault_clean(dev, fault);
|
|
if (ret != OK)
|
|
{
|
|
pwrerr("ERROR: PWRIOC_CLEAN_FAULT failed %d\n", ret);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case PWRIOC_SET_PARAMS:
|
|
{
|
|
FAR struct smps_params_s *params =
|
|
(FAR struct smps_params_s *)((uintptr_t)arg);
|
|
|
|
if (smps->param.lock == true)
|
|
{
|
|
pwrerr("ERROR: SMPS params locked!\n");
|
|
|
|
ret = -EPERM;
|
|
goto errout;
|
|
}
|
|
|
|
if ((smps->limits.lock == false) ||
|
|
(smps->limits.v_in <= 0 && smps->limits.v_out <= 0 &&
|
|
smps->limits.i_in <= 0 && smps->limits.i_out <= 0 &&
|
|
smps->limits.p_in <= 0 && smps->limits.p_out <= 0))
|
|
{
|
|
pwrerr("ERROR: limits must be set prior to params!\n");
|
|
|
|
ret = -EPERM;
|
|
goto errout;
|
|
}
|
|
|
|
/* Check output voltage configuration */
|
|
|
|
if (smps->limits.v_out > 0 && params->v_out > smps->limits.v_out)
|
|
{
|
|
pwrerr("ERROR: params->v_out > limits.v_out: %.2f > %.2f\n",
|
|
params->v_out, smps->limits.v_out);
|
|
|
|
ret = -EPERM;
|
|
goto errout;
|
|
}
|
|
|
|
/* Check output current configuration */
|
|
|
|
if (smps->limits.i_out > 0 && params->i_out > smps->limits.i_out)
|
|
{
|
|
pwrerr("ERROR: params->i_out > limits.i_out: %.2f > %.2f\n",
|
|
params->i_out, smps->limits.i_out);
|
|
|
|
ret = -EPERM;
|
|
goto errout;
|
|
}
|
|
|
|
/* Check output power configuration */
|
|
|
|
if (smps->limits.p_out > 0 && params->p_out > smps->limits.p_out)
|
|
{
|
|
pwrerr("ERROR: params->p_out > limits.p_out: %.2f > %.2f\n",
|
|
params->p_out, smps->limits.p_out);
|
|
|
|
ret = -EPERM;
|
|
goto errout;
|
|
}
|
|
|
|
/* TODO: limits */
|
|
|
|
ret = dev->ops->params_set(dev, params);
|
|
if (ret != OK)
|
|
{
|
|
pwrerr("ERROR: PWRIOC_SET_PARAMS failed %d\n", ret);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
pwrinfo("Forwarding unrecognized cmd: %d arg: %ld\n", cmd, arg);
|
|
ret = dev->ops->ioctl(dev, cmd, arg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
errout:
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: smps_register
|
|
****************************************************************************/
|
|
|
|
int smps_register(FAR const char *path, FAR struct smps_dev_s *dev,
|
|
FAR void *lower)
|
|
{
|
|
int ret;
|
|
|
|
DEBUGASSERT(path != NULL && dev != NULL && lower != NULL);
|
|
DEBUGASSERT(dev->ops != NULL);
|
|
|
|
/* For safety reason, when some necessary low-level logic is not provided,
|
|
* system should fail before low-level hardware initialization, so:
|
|
* - all ops are checked here, before character driver registration
|
|
* - all ops must be provided, even if not used
|
|
*/
|
|
|
|
DEBUGASSERT(dev->ops->setup != NULL);
|
|
DEBUGASSERT(dev->ops->shutdown != NULL);
|
|
DEBUGASSERT(dev->ops->stop != NULL);
|
|
DEBUGASSERT(dev->ops->start != NULL);
|
|
DEBUGASSERT(dev->ops->params_set != NULL);
|
|
DEBUGASSERT(dev->ops->mode_set != NULL);
|
|
DEBUGASSERT(dev->ops->limits_set != NULL);
|
|
DEBUGASSERT(dev->ops->fault_set != NULL);
|
|
DEBUGASSERT(dev->ops->state_get != NULL);
|
|
DEBUGASSERT(dev->ops->fault_get != NULL);
|
|
DEBUGASSERT(dev->ops->fault_clean != NULL);
|
|
DEBUGASSERT(dev->ops->ioctl != NULL);
|
|
|
|
/* Initialize the SMPS device structure */
|
|
|
|
dev->ocount = 0;
|
|
|
|
/* Initialize mutex */
|
|
|
|
nxmutex_init(&dev->closelock);
|
|
|
|
/* Connect SMPS driver with lower level interface */
|
|
|
|
dev->lower = lower;
|
|
|
|
/* Register the SMPS character driver */
|
|
|
|
ret = register_driver(path, &g_smps_fops, 0600, dev);
|
|
if (ret < 0)
|
|
{
|
|
nxmutex_destroy(&dev->closelock);
|
|
}
|
|
|
|
return ret;
|
|
}
|