zephyr/drivers/video/video_st_mipid02.c
sudarsan N 48bbbfe976 drivers: video: fix NULL dereference in mipid02_get_fmt
Category: Null pointer dereference (CWE-476)

Corrects the logic that validates the result of mipid04_get_format_desc().
Previously, the check was inverted, which could lead to a NULL pointer
dereference when accessing desc->pixelformat.

Fixes Coverity CID: 525183

Signed-off-by: sudarsan N <sudarsansamy2002@gmail.com>
2025-06-20 11:50:49 +01:00

458 lines
12 KiB
C

/*
* Copyright (c) 2025 STMicroelectronics.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT st_mipid02
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/video.h>
#include <zephyr/drivers/video-controls.h>
#include <zephyr/dt-bindings/video/video-interfaces.h>
#include <zephyr/logging/log.h>
#include "video_common.h"
#include "video_device.h"
LOG_MODULE_REGISTER(video_st_mipid02, CONFIG_VIDEO_LOG_LEVEL);
#define MIPID02_MAX_DATA_LANE_NB 2
struct mipid02_config {
struct i2c_dt_spec i2c;
struct gpio_dt_spec reset_gpio;
const struct device *source_dev;
struct {
uint32_t nb_lanes;
uint8_t lanes[MIPID02_MAX_DATA_LANE_NB];
} csi;
uint8_t mode_reg2;
};
struct mipid02_data {
struct video_format fmt;
const struct mipid02_format_desc *desc;
struct video_format_cap *caps;
};
K_HEAP_DEFINE(mipid02_video_caps_heap, CONFIG_VIDEO_ST_MIPID02_CAPS_HEAP_SIZE);
#define MIPID02_REG8(addr) ((uint32_t)(addr) | VIDEO_REG_ADDR16_DATA8)
#define MIPID02_CLK_LANE_REG1 MIPID02_REG8(0x0002)
#define MIPID02_CLK_LANE_REG1_EN BIT(0)
#define MIPID02_CLK_LANE_REG1_UI_MSK 0xfc
#define MIPID02_CLK_LANE_REG3 MIPID02_REG8(0x0004)
#define MIPID02_CLK_LANE_REG3_CSI BIT(1)
#define MIPID02_DATA_LANE0_REG1 MIPID02_REG8(0x0005)
#define MIPID02_DATA_LANE0_REG1_EN BIT(0)
#define MIPID02_DATA_LANE0_REG1_NORMAL BIT(1)
#define MIPID02_DATA_LANE0_REG2 MIPID02_REG8(0x0006)
#define MIPID02_DATA_LANE0_REG2_CSI BIT(0)
#define MIPID02_DATA_LANE1_REG1 MIPID02_REG8(0x0009)
#define MIPID02_DATA_LANE1_REG1_EN BIT(0)
#define MIPID02_DATA_LANE1_REG2 MIPID02_REG8(0x000a)
#define MIPID02_DATA_LANE1_REG2_CSI BIT(0)
#define MIPID02_MODE_REG1 MIPID02_REG8(0x0014)
#define MIPID02_MODE_REG1_2LANES BIT(1)
#define MIPID02_MODE_REG1_SWAP BIT(2)
#define MIPID02_MODE_REG1_NO_BYPASS BIT(6)
#define MIPID02_MODE_REG2 MIPID02_REG8(0x0015)
#define MIPID02_MODE_REG2_HSYNC_INV BIT(1)
#define MIPID02_MODE_REG2_VSYNC_INV BIT(2)
#define MIPID02_MODE_REG2_PCLK_INV BIT(3)
#define MIPID02_DATA_ID_REG MIPID02_REG8(0x0017)
#define MIPID02_DATA_SEL_CTRL_REG MIPID02_REG8(0x0019)
#define MIPID02_DATA_SEL_CTRL_REG_R_MODE BIT(2)
#define MIPID02_FMT(fmt, csi_fmt, dt) \
{ \
.pixelformat = VIDEO_PIX_FMT_##fmt, \
.csi_pixelformat = VIDEO_PIX_FMT_##csi_fmt, \
.csi_datatype = dt, \
}
struct mipid02_format_desc {
uint32_t pixelformat;
uint32_t csi_pixelformat;
uint8_t csi_datatype;
};
static const struct mipid02_format_desc mipid02_supported_formats[] = {
/* Raw 8 bit */
MIPID02_FMT(SBGGR8, SBGGR8, VIDEO_MIPI_CSI2_DT_RAW8),
MIPID02_FMT(SGBRG8, SGBRG8, VIDEO_MIPI_CSI2_DT_RAW8),
MIPID02_FMT(SGRBG8, SGRBG8, VIDEO_MIPI_CSI2_DT_RAW8),
MIPID02_FMT(SRGGB8, SRGGB8, VIDEO_MIPI_CSI2_DT_RAW8),
/* Raw 10 bit */
MIPID02_FMT(SBGGR10, SBGGR10P, VIDEO_MIPI_CSI2_DT_RAW10),
MIPID02_FMT(SGBRG10, SGBRG10P, VIDEO_MIPI_CSI2_DT_RAW10),
MIPID02_FMT(SGRBG10, SGRBG10P, VIDEO_MIPI_CSI2_DT_RAW10),
MIPID02_FMT(SRGGB10, SRGGB10P, VIDEO_MIPI_CSI2_DT_RAW10),
/* Raw 12 bit */
MIPID02_FMT(SBGGR12, SBGGR12P, VIDEO_MIPI_CSI2_DT_RAW12),
MIPID02_FMT(SGBRG12, SGBRG12P, VIDEO_MIPI_CSI2_DT_RAW12),
MIPID02_FMT(SGRBG12, SGRBG12P, VIDEO_MIPI_CSI2_DT_RAW12),
MIPID02_FMT(SRGGB12, SRGGB12P, VIDEO_MIPI_CSI2_DT_RAW12),
/* Yxx */
MIPID02_FMT(GREY, GREY, VIDEO_MIPI_CSI2_DT_RAW8),
/* RGB */
MIPID02_FMT(RGB565, RGB565, VIDEO_MIPI_CSI2_DT_RGB565),
/* YUV */
MIPID02_FMT(YUYV, YUYV, VIDEO_MIPI_CSI2_DT_YUV422_8),
/* JPEG */
MIPID02_FMT(JPEG, JPEG, 0),
};
static const struct mipid02_format_desc *mipid02_get_format_desc(uint32_t pixelformat)
{
for (int i = 0; i < ARRAY_SIZE(mipid02_supported_formats); i++) {
if (pixelformat == mipid02_supported_formats[i].pixelformat ||
pixelformat == mipid02_supported_formats[i].csi_pixelformat) {
return &mipid02_supported_formats[i];
}
}
return NULL;
}
static int mipid02_set_fmt(const struct device *dev, struct video_format *fmt)
{
struct mipid02_data *drv_data = dev->data;
const struct mipid02_config *cfg = dev->config;
const struct mipid02_format_desc *desc;
struct video_format sensor_fmt = *fmt;
int ret;
/* Check that format is supported */
desc = mipid02_get_format_desc(fmt->pixelformat);
if (!desc) {
return -EINVAL;
}
/* Configure the DT and perform set_fmt on sensor */
if (desc->csi_datatype != 0) {
ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_ID_REG,
desc->csi_datatype);
if (ret < 0) {
return ret;
}
ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_SEL_CTRL_REG,
MIPID02_DATA_SEL_CTRL_REG_R_MODE);
if (ret < 0) {
return ret;
}
} else {
ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_SEL_CTRL_REG, 0);
if (ret < 0) {
return ret;
}
}
/* Call video_set_format to the sensor */
sensor_fmt.pixelformat = desc->csi_pixelformat;
ret = video_set_format(cfg->source_dev, &sensor_fmt);
if (ret < 0) {
return ret;
}
drv_data->fmt = *fmt;
drv_data->desc = desc;
return 0;
}
static int mipid02_get_fmt(const struct device *dev, struct video_format *fmt)
{
const struct mipid02_config *cfg = dev->config;
struct mipid02_data *drv_data = dev->data;
const struct mipid02_format_desc *desc;
int ret;
if (!drv_data->desc) {
ret = video_get_format(cfg->source_dev, fmt);
if (ret < 0) {
return ret;
}
desc = mipid02_get_format_desc(fmt->pixelformat);
if (desc == NULL) {
LOG_ERR("Sensor format not supported by the ST-MIPID02");
return -EIO;
}
/* Override the pixelformat */
fmt->pixelformat = desc->pixelformat;
drv_data->fmt = *fmt;
drv_data->desc = desc;
}
*fmt = drv_data->fmt;
return 0;
}
static int mipid02_get_caps(const struct device *dev, struct video_caps *caps)
{
const struct mipid02_config *cfg = dev->config;
struct mipid02_data *drv_data = dev->data;
int ind = 0, caps_nb = 0;
int ret;
ret = video_get_caps(cfg->source_dev, caps);
if (ret < 0) {
return ret;
}
while (caps->format_caps[ind].pixelformat) {
if (mipid02_get_format_desc(caps->format_caps[ind].pixelformat)) {
caps_nb++;
}
ind++;
}
k_heap_free(&mipid02_video_caps_heap, drv_data->caps);
drv_data->caps = k_heap_alloc(&mipid02_video_caps_heap,
(caps_nb + 1) * sizeof(struct video_format_cap), K_FOREVER);
if (!drv_data->caps) {
return -ENOMEM;
}
ind = 0;
for (int i = 0; caps->format_caps[i].pixelformat; i++) {
const struct mipid02_format_desc *desc =
mipid02_get_format_desc(caps->format_caps[i].pixelformat);
if (!desc) {
continue;
}
drv_data->caps[ind] = caps->format_caps[i];
drv_data->caps[ind].pixelformat = desc->pixelformat;
ind++;
if (ind == caps_nb) {
memset(&drv_data->caps[ind], 0, sizeof(drv_data->caps[ind]));
break;
}
}
caps->format_caps = drv_data->caps;
caps->min_vbuf_count = 1;
caps->min_line_count = LINE_COUNT_HEIGHT;
caps->max_line_count = LINE_COUNT_HEIGHT;
return 0;
}
static int mipid02_set_stream(const struct device *dev, bool enable, enum video_buf_type type)
{
const struct mipid02_config *cfg = dev->config;
struct mipid02_data *drv_data = dev->data;
const struct mipid02_format_desc *desc;
int32_t link_freq;
int ret;
if (!enable) {
ret = video_stream_stop(cfg->source_dev, type);
if (ret < 0) {
return ret;
}
/* Disable lanes */
ret = video_write_cci_reg(&cfg->i2c, MIPID02_CLK_LANE_REG1, 0);
if (ret < 0) {
return ret;
}
ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE0_REG1, 0);
if (ret < 0) {
return ret;
}
if (cfg->csi.nb_lanes == 2) {
ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE1_REG1, 0);
if (ret < 0) {
return ret;
}
}
return 0;
}
desc = mipid02_get_format_desc(drv_data->fmt.pixelformat);
if (!desc) {
LOG_ERR("No valid format desc available, should get/set_fmt prior to set_stream");
return -EIO;
}
/* Get link-frequency information from source */
link_freq = video_get_csi_link_freq(cfg->source_dev,
video_bits_per_pixel(desc->csi_pixelformat),
cfg->csi.nb_lanes);
if (link_freq < 0) {
LOG_ERR("Failed to retrieve source link-frequency");
return -EIO;
}
/* Enable lanes */
ret = video_write_cci_reg(&cfg->i2c, MIPID02_CLK_LANE_REG1,
FIELD_PREP(MIPID02_CLK_LANE_REG1_UI_MSK, 2000000000 / link_freq) |
MIPID02_CLK_LANE_REG1_EN);
if (ret < 0) {
return ret;
}
ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE0_REG1,
MIPID02_DATA_LANE0_REG1_EN | MIPID02_DATA_LANE0_REG1_NORMAL);
if (ret < 0) {
return ret;
}
if (cfg->csi.nb_lanes == 2) {
ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE1_REG1,
MIPID02_DATA_LANE1_REG1_EN);
if (ret < 0) {
return ret;
}
}
return video_stream_start(cfg->source_dev, type);
}
static int mipid02_set_frmival(const struct device *dev, struct video_frmival *frmival)
{
const struct mipid02_config *cfg = dev->config;
return video_set_frmival(cfg->source_dev, frmival);
}
static int mipid02_get_frmival(const struct device *dev, struct video_frmival *frmival)
{
const struct mipid02_config *cfg = dev->config;
return video_get_frmival(cfg->source_dev, frmival);
}
static int mipid02_enum_frmival(const struct device *dev, struct video_frmival_enum *fie)
{
const struct mipid02_config *cfg = dev->config;
return video_enum_frmival(cfg->source_dev, fie);
}
static DEVICE_API(video, mipid02_driver_api) = {
.set_format = mipid02_set_fmt,
.get_format = mipid02_get_fmt,
.get_caps = mipid02_get_caps,
.set_stream = mipid02_set_stream,
.set_frmival = mipid02_set_frmival,
.get_frmival = mipid02_get_frmival,
.enum_frmival = mipid02_enum_frmival,
};
static int mipid02_init(const struct device *dev)
{
const struct mipid02_config *cfg = dev->config;
int ret;
if (!device_is_ready(cfg->i2c.bus)) {
LOG_ERR("Bus device is not ready");
return -ENODEV;
}
if (!gpio_is_ready_dt(&cfg->reset_gpio)) {
LOG_ERR("%s: device %s is not ready", dev->name, cfg->reset_gpio.port->name);
return -ENODEV;
}
/* Power up sequence */
if (cfg->reset_gpio.port) {
ret = gpio_pin_configure_dt(&cfg->reset_gpio, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return ret;
}
}
k_sleep(K_MSEC(10));
if (cfg->reset_gpio.port) {
gpio_pin_set_dt(&cfg->reset_gpio, 0);
}
/* Set CSI mode */
ret = video_write_cci_reg(&cfg->i2c, MIPID02_CLK_LANE_REG3, MIPID02_CLK_LANE_REG3_CSI);
if (ret < 0) {
return ret;
}
ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE0_REG2, MIPID02_DATA_LANE0_REG2_CSI);
if (ret < 0) {
return ret;
}
if (cfg->csi.nb_lanes == 2) {
ret = video_write_cci_reg(&cfg->i2c, MIPID02_DATA_LANE1_REG2,
MIPID02_DATA_LANE1_REG2_CSI);
if (ret < 0) {
return ret;
}
}
/* Configure parallel port synchro signals */
ret = video_write_cci_reg(&cfg->i2c, MIPID02_MODE_REG2, cfg->mode_reg2);
if (ret < 0) {
return ret;
}
/* Perform the lanes configuration */
return video_write_cci_reg(&cfg->i2c, MIPID02_MODE_REG1,
MIPID02_MODE_REG1_NO_BYPASS |
(cfg->csi.nb_lanes == 2 ? MIPID02_MODE_REG1_2LANES : 0) |
(cfg->csi.lanes[0] == 1 ? 0 : MIPID02_MODE_REG1_SWAP));
}
#define SOURCE_DEV(n) DEVICE_DT_GET(DT_NODE_REMOTE_DEVICE(DT_INST_ENDPOINT_BY_ID(n, 0, 0)))
#define MIPID02_INIT(n) \
static struct mipid02_data mipid02_data_##n; \
\
static const struct mipid02_config mipid02_cfg_##n = { \
.i2c = I2C_DT_SPEC_INST_GET(n), \
.source_dev = SOURCE_DEV(n), \
.reset_gpio = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {0}), \
.csi.nb_lanes = DT_PROP_LEN(DT_INST_ENDPOINT_BY_ID(n, 0, 0), data_lanes), \
.csi.lanes[0] = DT_PROP_BY_IDX(DT_INST_ENDPOINT_BY_ID(n, 0, 0), \
data_lanes, 0), \
.csi.lanes[1] = COND_CODE_1(DT_PROP_LEN(DT_INST_ENDPOINT_BY_ID(n, 0, 0), \
data_lanes), \
(0), \
(DT_PROP_BY_IDX(DT_INST_ENDPOINT_BY_ID(n, 0, 0), \
data_lanes, 1))), \
.mode_reg2 = (DT_PROP_OR(DT_INST_ENDPOINT_BY_ID(n, 2, 0), hsync_active, 0) ? \
MIPID02_MODE_REG2_HSYNC_INV : 0) | \
(DT_PROP_OR(DT_INST_ENDPOINT_BY_ID(n, 2, 0), vsync_active, 0) ? \
MIPID02_MODE_REG2_VSYNC_INV : 0) | \
(DT_PROP_OR(DT_INST_ENDPOINT_BY_ID(n, 2, 0), pclk_sample, 0) ? \
MIPID02_MODE_REG2_PCLK_INV : 0), \
}; \
\
DEVICE_DT_INST_DEFINE(n, &mipid02_init, NULL, &mipid02_data_##n, &mipid02_cfg_##n, \
POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &mipid02_driver_api); \
\
VIDEO_DEVICE_DEFINE(mipid02_##n, DEVICE_DT_INST_GET(n), NULL);
DT_INST_FOREACH_STATUS_OKAY(MIPID02_INIT)