nuttx/drivers/sensors/t67xx.c
Catalin Visinescu 081e4c478a drivers/: Multiple Drivers Are Registered With World Writable - Part 2
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>
2026-07-15 15:27:28 +08:00

756 lines
21 KiB
C

/****************************************************************************
* drivers/sensors/t67xx.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.
*
****************************************************************************/
/* WARNING for developers:
*
* This driver uses the legacy style of writing sensor drivers for NuttX. The
* project has since decided to adopt a new sensor framework in order to
* have a consistent API and feature-set.
*
* Sensors which use the uORB framework are typically suffixed "_uorb". You
* can also visit the documentation about the new sensor framework to learn
* more.
*/
#warning "This is a deprecated legacy sensor driver."
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/debug.h>
#include <string.h>
#include <time.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/random.h>
#include <nuttx/sensors/t67xx.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#if !defined(CONFIG_I2C)
# error i2c support required
#endif
/* This driver is tested with Telaire T6713. Likely works for Telaire T6703
* and other models as well, but this has not been verified. No test
* animals were gassed during testing.
*/
/* Modbus registers */
#define T67XX_REG_FWREV 0x1389 /* Firmware revision */
#define T67XX_REG_STATUS 0x138a /* Status */
#define T67XX_REG_GASPPM 0x138b /* Gas parts per million */
#define T67XX_REG_RESET 0x03e8 /* Reset device */
#define T67XX_REG_SPCAL 0x03ec /* Single point calibration */
#define T67XX_REG_SLAVEADDR 0x0fa5 /* Slave address */
#define T67XX_REG_ABCLOGIC 0x03ee /* ABC Logic enable/disable */
/* Status register bits */
#define T67XX_STATUS_ERROR (1 << 0) /* Error condition */
#define T67XX_STATUS_FLASH_ERROR (1 << 1) /* Flash error */
#define T67XX_STATUS_CALIB_ERROR (1 << 2) /* Calibration error */
/* [3:7] reserved */
#define T67XX_STATUS_RS232 (1 << 8) /* RS-232 error */
#define T67XX_STATUS_RS485 (1 << 9) /* RS-485 error */
#define T67XX_STATUS_I2C (1 << 10) /* I2C error */
#define T67XX_STATUS_WARMUP (1 << 11) /* Warm-up mode */
/* [12:14] reserved */
#define T67XX_STATUS_SPCAL (1 << 15) /* Single point calibration */
/* Command bits */
#define RESET_SENSOR 0xff00 /* Reset sensor */
#define SPCAL_START 0xff00 /* Start SP calibration */
#define SPCAL_STOP 0x0000 /* Stop SP calibration */
#define ABCLOGIC_ENABLE 0xff00 /* Enable ABC Logic */
#define ABCLOGIC_DISABLE 0x0000 /* Disable ABC Logic */
/* Required times before sensor reaches either minimal or full
* operational accuracy.
*/
#define T67XX_UPTIME_MINIMAL_SEC 120 /* two minutes */
#define T67XX_UPTIME_FULL_SEC (10 * 60) /* ten minutes */
#define T67XX_UPTIME_ABCLOGIC_SEC (24 * 60 * 60) /* a day */
/* I2C constants */
#define T67XX_I2C_FREQUENCY 100000 /* Only supported I2C speed */
#define T67XX_I2C_DELAY_MSEC 10 /* 5-10 ms recommended, 10 ms
* is always safe. */
/****************************************************************************
* Private Types
****************************************************************************/
struct t67xx_dev_s
{
FAR struct i2c_master_s *i2c; /* I2C interface */
uint8_t addr; /* I2C address */
struct timespec boot_time; /* When sensor was booted */
mutex_t devlock;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* I2C Helpers */
static int t67xx_read16(FAR struct t67xx_dev_s *priv, uint16_t regaddr,
FAR uint16_t *regvalue);
static int t67xx_write16(FAR struct t67xx_dev_s *priv, uint16_t regaddr,
FAR uint16_t regvalue, bool read_reply);
/* Driver features */
static int t67xx_check_status(FAR struct t67xx_dev_s *priv,
bool *warming_up, bool *calibrating);
static int t67xx_read_fwrev(FAR struct t67xx_dev_s *priv, uint16_t *rev);
static int t67xx_read_gas_ppm(FAR struct t67xx_dev_s *priv,
FAR struct t67xx_value_s *buffer);
static int t67xx_spcal(FAR struct t67xx_dev_s *priv, bool start);
static int t67xx_abclogic(FAR struct t67xx_dev_s *priv, bool enable);
static int t67xx_reset(FAR struct t67xx_dev_s *priv);
/* Character driver methods */
static ssize_t t67xx_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t t67xx_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen);
static int t67xx_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_t67xxfops =
{
NULL, /* open */
NULL, /* close */
t67xx_read, /* read */
t67xx_write, /* write */
NULL, /* seek */
t67xx_ioctl, /* ioctl */
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: t67xx_read16
*
* Description:
* Read 16-bit register
*
****************************************************************************/
static int t67xx_read16(FAR struct t67xx_dev_s *priv, uint16_t regaddr,
FAR uint16_t *regvalue)
{
struct i2c_config_s config;
uint8_t buf[5];
uint8_t rxbuf[4] =
{
0
};
int ret;
DEBUGASSERT(priv != NULL);
DEBUGASSERT(regvalue != NULL);
/* Set up the I2C configuration. */
config.frequency = T67XX_I2C_FREQUENCY;
config.address = priv->addr;
config.addrlen = 7;
/* Set up the Modbus read request. */
buf[0] = 4;
buf[1] = (regaddr >> 8) & 0xff;
buf[2] = regaddr & 0xff;
buf[3] = 0;
buf[4] = 1;
/* Write the Modbus read request. */
ret = i2c_write(priv->i2c, &config, buf, sizeof(buf));
if (ret < 0)
{
snerr("ERROR: i2c_write failed: %d\n", ret);
return ret;
}
/* Wait and read response. */
up_mdelay(T67XX_I2C_DELAY_MSEC);
ret = i2c_read(priv->i2c, &config, rxbuf, sizeof(rxbuf));
if (ret < 0)
{
snerr("ERROR: i2c_read failed: %d\n", ret);
return ret;
}
if (rxbuf[0] != 4 || rxbuf[1] != 2)
{
#ifdef CONFIG_DEBUG_SENSORS_ERROR
lib_dumpbuffer("ERROR: sensor wrong data", rxbuf, sizeof(rxbuf));
#endif
return -EIO;
}
*regvalue = ((uint16_t)rxbuf[2] << 8) | rxbuf[3];
return ret;
}
/****************************************************************************
* Name: t67xx_write16
*
* Description:
* Write 16-bit register. If 'reply' is true, sensor response is read
* for verification.
*
****************************************************************************/
static int t67xx_write16(FAR struct t67xx_dev_s *priv, uint16_t regaddr,
FAR uint16_t regvalue, bool reply)
{
struct i2c_config_s config;
uint8_t buf[5];
uint8_t rxbuf[5] =
{
0
};
int ret;
DEBUGASSERT(priv != NULL);
/* Set up the I2C configuration. */
config.frequency = T67XX_I2C_FREQUENCY;
config.address = priv->addr;
config.addrlen = 7;
/* Set up the Modbus write request. */
buf[0] = 5;
buf[1] = (regaddr >> 8) & 0xff;
buf[2] = regaddr & 0xff;
buf[3] = (regvalue >> 8) & 0xff;
buf[4] = regvalue & 0xff;
/* Write the Modbus write request. */
ret = i2c_write(priv->i2c, &config, buf, sizeof(buf));
if (ret < 0)
{
snerr("ERROR: i2c_write failed: %d\n", ret);
return ret;
}
/* There is no reply for Reset command, exit early. */
if (!reply)
{
return ret;
}
/* Wait and read response. */
up_mdelay(T67XX_I2C_DELAY_MSEC);
ret = i2c_read(priv->i2c, &config, rxbuf, sizeof(rxbuf));
if (ret < 0)
{
snerr("ERROR: i2c_read failed: %d\n", ret);
return ret;
}
if (memcmp(rxbuf, buf, sizeof(rxbuf)) != 0)
{
#ifdef CONFIG_DEBUG_SENSORS_ERROR
lib_dumpbuffer("ERROR: sensor wrong data", rxbuf, sizeof(rxbuf));
#endif
return -EIO;
}
return ret;
}
/****************************************************************************
* Name: t67xx_check_status
*
* Description:
* Check status register.
*
****************************************************************************/
static int t67xx_check_status(FAR struct t67xx_dev_s *priv,
bool *warming_up, bool *calibrating)
{
uint16_t status;
int ret;
/* Read the status register. */
ret = t67xx_read16(priv, T67XX_REG_STATUS, &status);
if (ret < 0)
{
snerr("ERROR: sensor cannot read status: %d\n", ret);
return ret;
}
sninfo("status 0x%04x\n", status);
/* We ignore I2C error here, as it is checked for every bus
* operation anyway.
*/
if (status & (T67XX_STATUS_ERROR |
T67XX_STATUS_FLASH_ERROR | T67XX_STATUS_CALIB_ERROR))
{
snerr("ERROR: sensor error 0x%04x\n", status);
return ERROR;
}
*warming_up = status & T67XX_STATUS_WARMUP;
*calibrating = status & T67XX_STATUS_SPCAL;
return OK;
}
/****************************************************************************
* Name: t67xx_read_fwrev
*
* Description:
* Check firmware revision register.
*
****************************************************************************/
static int t67xx_read_fwrev(FAR struct t67xx_dev_s *priv, uint16_t *rev)
{
uint16_t fwrev;
int ret;
/* Read the FW revision register. */
ret = t67xx_read16(priv, T67XX_REG_FWREV, &fwrev);
if (ret < 0)
{
snerr("ERROR: sensor cannot read reg: %d\n", ret);
return ret;
}
sninfo("sensor FW rev: 0x%04x\n", fwrev);
if (rev)
{
*rev = fwrev;
}
return OK;
}
/****************************************************************************
* Name: has_time_passed
*
* Description:
* Return true if curr >= start + secs_since_start
*
****************************************************************************/
static bool has_time_passed(struct timespec curr,
struct timespec start,
unsigned int secs_since_start)
{
if ((start.tv_sec + secs_since_start) - curr.tv_sec == 0)
{
return start.tv_nsec <= curr.tv_nsec;
}
return (start.tv_sec + secs_since_start) - curr.tv_sec <= 0;
}
/****************************************************************************
* Name: t67xx_read_gas_ppm
*
* Description:
* Read the current carbon dioxide level.
*
****************************************************************************/
static int t67xx_read_gas_ppm(FAR struct t67xx_dev_s *priv,
FAR struct t67xx_value_s *buffer)
{
uint16_t ppm;
bool warming_up;
bool calibrating;
struct timespec ts;
int ret;
clock_systime_timespec(&ts);
if (!has_time_passed(ts, priv->boot_time, T67XX_UPTIME_MINIMAL_SEC))
{
snwarn("WARN: sensor not ready yet\n");
return -EAGAIN;
}
/* Check sensor status. */
ret = t67xx_check_status(priv, &warming_up, &calibrating);
if (ret != OK)
{
return ret;
}
/* Sanity check warm-up behavior. */
if (!has_time_passed(ts, priv->boot_time, T67XX_UPTIME_FULL_SEC))
{
if (!warming_up)
{
snwarn("WARN: sensor not fully warm-up\n");
warming_up = true;
}
}
else if (warming_up)
{
snwarn("WARN: sensor still warming up after %d secs\n",
T67XX_UPTIME_FULL_SEC);
}
/* Read the CO2 level. */
ret = t67xx_read16(priv, T67XX_REG_GASPPM, &ppm);
if (ret < 0)
{
return ret;
}
add_sensor_randomness(ts.tv_nsec ^ (int)ppm);
buffer->gas_ppm = ppm;
buffer->warming_up = warming_up;
buffer->calibrating = calibrating;
sninfo("ppm %d, warming up %d, calibrating %d\n",
(int)ppm, warming_up, calibrating);
return OK;
}
/****************************************************************************
* Name: t67xx_spcal
*
* Description:
* Start or stop sensor single-point calibration.
*
****************************************************************************/
static int t67xx_spcal(FAR struct t67xx_dev_s *priv, bool start)
{
int ret;
ret = t67xx_write16(priv, T67XX_REG_SPCAL,
start ? SPCAL_START : SPCAL_STOP, true);
if (ret < 0)
{
snerr("ERROR: sp calibration failed: %d\n", ret);
return ret;
}
return ret;
}
/****************************************************************************
* Name: t67xx_abclogic
*
* Description:
* Start or stop sensor ABC Logic feature.
*
* Automatic Background Logic, is a self-calibration technique that is
* designed to be used in applications where concentrations will drop to
* outside ambient conditions (400 ppm) at least three times in a 7 days.
*
* With ABC Logic enabled, the sensor will typically reach its operational
* accuracy after 24 hours of continuous operation at a condition that it
* was exposed to ambient reference levels of air at 400 ppm CO2. Sensor
* will maintain accuracy specifications with ABC Logic enabled, given
* that it is at least four times in 21 days exposed to the reference
* value and this reference value is the lowest concentration to which
* the sensor is exposed. ABC Logic requires continuous operation of the
* sensor for periods of at least 24 hours.
*
* Currently driver does not try to check for these constraints.
*
****************************************************************************/
static int t67xx_abclogic(FAR struct t67xx_dev_s *priv, bool enable)
{
int ret;
ret = t67xx_write16(priv, T67XX_REG_ABCLOGIC,
enable ? ABCLOGIC_ENABLE : ABCLOGIC_DISABLE, true);
if (ret < 0)
{
snerr("ERROR: ABC Logic failed: %d\n", ret);
return ret;
}
return ret;
}
/****************************************************************************
* Name: t67xx_reset
*
* Description:
* Reset sensor. Data-sheet does not recommend using this.
*
****************************************************************************/
static int t67xx_reset(FAR struct t67xx_dev_s *priv)
{
int ret;
ret = t67xx_write16(priv, T67XX_REG_RESET, RESET_SENSOR, false);
if (ret < 0)
{
snerr("ERROR: reset failed: %d\n", ret);
return ret;
}
/* Sensor uptime starting again from zero. */
clock_systime_timespec(&priv->boot_time);
return ret;
}
/****************************************************************************
* Name: t67xx_read
****************************************************************************/
static ssize_t t67xx_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
FAR struct inode *inode = filep->f_inode;
FAR struct t67xx_dev_s *priv = inode->i_private;
FAR struct t67xx_value_s *ptr;
ssize_t nsamples;
int i;
int ret;
/* Get exclusive access */
ret = nxmutex_lock(&priv->devlock);
if (ret < 0)
{
return ret;
}
/* How many samples were requested to get? */
nsamples = buflen / sizeof(struct t67xx_value_s);
ptr = (FAR struct t67xx_value_s *)buffer;
sninfo("buflen: %d nsamples: %d\n", buflen, nsamples);
/* Get the requested number of samples */
for (i = 0; i < nsamples; i++)
{
struct t67xx_value_s gas_ppm;
/* Read the next struct t67xx_value_s */
ret = t67xx_read_gas_ppm(priv, &gas_ppm);
if (ret < 0)
{
snerr("ERROR: t67xx_read_gas_ppm failed: %d\n", ret);
nxmutex_unlock(&priv->devlock);
return (ssize_t)ret;
}
/* Save the value in the user buffer */
*ptr++ = gas_ppm;
}
nxmutex_unlock(&priv->devlock);
return nsamples * sizeof(struct t67xx_value_s);
}
/****************************************************************************
* Name: t67xx_write
****************************************************************************/
static ssize_t t67xx_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen)
{
return -ENOSYS;
}
/****************************************************************************
* Name: t67xx_ioctl
****************************************************************************/
static int t67xx_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct inode *inode = filep->f_inode;
FAR struct t67xx_dev_s *priv = inode->i_private;
int ret;
/* Get exclusive access */
ret = nxmutex_lock(&priv->devlock);
if (ret < 0)
{
return ret;
}
switch (cmd)
{
/* Soft reset the sensor, Arg: None */
case SNIOC_RESET:
ret = t67xx_reset(priv);
sninfo("reset ret: %d\n", ret);
break;
/* Dump registers, currently just FWREV reg, Arg: None */
case SNIOC_DUMP_REGS:
ret = t67xx_read_fwrev(priv, NULL);
break;
/* Calibrate the sensor, Arg: uint8_t value */
case SNIOC_SPCALIB:
ret = t67xx_spcal(priv, !!arg);
sninfo("spcal ret: %d\n", ret);
break;
/* Enable/disable the ABC Logic feature, Arg: uint8_t value */
case SNIOC_ABCLOGIC:
ret = t67xx_abclogic(priv, !!arg);
sninfo("abclogic ret: %d\n", ret);
break;
default:
sninfo("Unrecognized cmd: %d\n", cmd);
ret = -ENOTTY;
break;
}
nxmutex_unlock(&priv->devlock);
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: t67xx_register
*
* Description:
* Register the T67XX character device as 'devpath'
*
* Input Parameters:
* devpath - The full path to the driver to register. E.g., "/dev/co2_0"
* i2c - An instance of the I2C interface to use to communicate with T67XX
* addr - The I2C address of the T67XX. For T6713 this is initially 0x15
* but it can be changed by the SLAVE ADDRESS command.
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
int t67xx_register(FAR const char *devpath, FAR struct i2c_master_s *i2c,
uint8_t addr)
{
FAR struct t67xx_dev_s *priv;
int ret;
DEBUGASSERT(i2c != NULL);
DEBUGASSERT(addr == T67XX_I2C_ADDR);
/* Initialize the t67xx device structure. */
priv = kmm_malloc(sizeof(struct t67xx_dev_s));
if (priv == NULL)
{
snerr("ERROR: Failed to allocate instance\n");
return -ENOMEM;
}
priv->i2c = i2c;
priv->addr = addr;
nxmutex_init(&priv->devlock);
clock_systime_timespec(&priv->boot_time);
/* Register the character driver. */
ret = register_driver(devpath, &g_t67xxfops, 0600, priv);
if (ret < 0)
{
snerr("ERROR: Failed to register driver: %d\n", ret);
goto errout;
}
sninfo("(addr=0x%02x) registered at %s\n", priv->addr, devpath);
return ret;
errout:
nxmutex_destroy(&priv->devlock);
kmm_free(priv);
return ret;
}