diff options
Diffstat (limited to 'drivers/media/test-drivers')
32 files changed, 6802 insertions, 322 deletions
diff --git a/drivers/media/test-drivers/Kconfig b/drivers/media/test-drivers/Kconfig index 188381c85593..e27d6602545d 100644 --- a/drivers/media/test-drivers/Kconfig +++ b/drivers/media/test-drivers/Kconfig @@ -24,3 +24,19 @@ config VIDEO_VIM2M source "drivers/media/test-drivers/vicodec/Kconfig" endif #V4L_TEST_DRIVERS + +menuconfig DVB_TEST_DRIVERS + bool "DVB test drivers" + depends on DVB_CORE && MEDIA_SUPPORT && I2C + help + Enables DVB test drivers. + + This enables the DVB test drivers. They are meant as an aid for + DVB device driver writers and developers working on userspace + media applications. + +if DVB_TEST_DRIVERS + +source "drivers/media/test-drivers/vidtv/Kconfig" + +endif #DVB_TEST_DRIVERS diff --git a/drivers/media/test-drivers/Makefile b/drivers/media/test-drivers/Makefile index 74410d3a9f2d..9f0e4ebb2efe 100644 --- a/drivers/media/test-drivers/Makefile +++ b/drivers/media/test-drivers/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_VIDEO_VIMC) += vimc/ obj-$(CONFIG_VIDEO_VIVID) += vivid/ obj-$(CONFIG_VIDEO_VIM2M) += vim2m.o obj-$(CONFIG_VIDEO_VICODEC) += vicodec/ +obj-$(CONFIG_DVB_VIDTV) += vidtv/ diff --git a/drivers/media/test-drivers/vicodec/vicodec-core.c b/drivers/media/test-drivers/vicodec/vicodec-core.c index 8941d73f6611..0e115683f8da 100644 --- a/drivers/media/test-drivers/vicodec/vicodec-core.c +++ b/drivers/media/test-drivers/vicodec/vicodec-core.c @@ -1310,7 +1310,7 @@ static int vicodec_subscribe_event(struct v4l2_fh *fh, case V4L2_EVENT_SOURCE_CHANGE: if (ctx->is_enc) return -EINVAL; - /* fall through */ + fallthrough; case V4L2_EVENT_EOS: if (ctx->is_stateless) return -EINVAL; @@ -1671,8 +1671,8 @@ static void vicodec_stop_streaming(struct vb2_queue *q) ctx->comp_size = 0; ctx->header_size = 0; ctx->comp_magic_cnt = 0; - ctx->comp_has_frame = 0; - ctx->comp_has_next_frame = 0; + ctx->comp_has_frame = false; + ctx->comp_has_next_frame = false; } } @@ -1994,6 +1994,7 @@ static int vicodec_request_validate(struct media_request *req) } ctrl = v4l2_ctrl_request_hdl_ctrl_find(hdl, vicodec_ctrl_stateless_state.id); + v4l2_ctrl_request_hdl_put(hdl); if (!ctrl) { v4l2_info(&ctx->dev->v4l2_dev, "Missing required codec control\n"); diff --git a/drivers/media/test-drivers/vidtv/Kconfig b/drivers/media/test-drivers/vidtv/Kconfig new file mode 100644 index 000000000000..22c4fd39461f --- /dev/null +++ b/drivers/media/test-drivers/vidtv/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_VIDTV + tristate "Virtual DVB Driver (vidtv)" + depends on DVB_CORE && MEDIA_SUPPORT && I2C + help + The virtual DVB test driver serves as a reference DVB driver and helps + validate the existing APIs in the media subsystem. It can also aid developers + working on userspace applications. + + + When in doubt, say N. diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile new file mode 100644 index 000000000000..330089e3b70c --- /dev/null +++ b/drivers/media/test-drivers/vidtv/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 + +dvb-vidtv-tuner-objs := vidtv_tuner.o +dvb-vidtv-demod-objs := vidtv_demod.o +dvb-vidtv-bridge-objs := vidtv_bridge.o vidtv_common.o vidtv_ts.o vidtv_psi.o \ + vidtv_pes.o vidtv_s302m.o vidtv_channel.o vidtv_mux.o + +obj-$(CONFIG_DVB_VIDTV) += dvb-vidtv-tuner.o dvb-vidtv-demod.o \ + dvb-vidtv-bridge.o diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c new file mode 100644 index 000000000000..74b054947bbe --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c @@ -0,0 +1,566 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Virtual DTV test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * When this module is loaded, it will attempt to modprobe 'dvb_vidtv_tuner' and 'dvb_vidtv_demod'. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#include <linux/moduleparam.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/dev_printk.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include "vidtv_bridge.h" +#include "vidtv_demod.h" +#include "vidtv_tuner.h" +#include "vidtv_ts.h" +#include "vidtv_mux.h" +#include "vidtv_common.h" + +//#define MUX_BUF_MAX_SZ +//#define MUX_BUF_MIN_SZ +#define TUNER_DEFAULT_ADDR 0x68 +#define DEMOD_DEFAULT_ADDR 0x60 + +/* LNBf fake parameters: ranges used by an Universal (extended) European LNBf */ +#define LNB_CUT_FREQUENCY 11700000 +#define LNB_LOW_FREQ 9750000 +#define LNB_HIGH_FREQ 10600000 + + +static unsigned int drop_tslock_prob_on_low_snr; +module_param(drop_tslock_prob_on_low_snr, uint, 0); +MODULE_PARM_DESC(drop_tslock_prob_on_low_snr, + "Probability of losing the TS lock if the signal quality is bad"); + +static unsigned int recover_tslock_prob_on_good_snr; +module_param(recover_tslock_prob_on_good_snr, uint, 0); +MODULE_PARM_DESC(recover_tslock_prob_on_good_snr, + "Probability recovering the TS lock when the signal improves"); + +static unsigned int mock_power_up_delay_msec; +module_param(mock_power_up_delay_msec, uint, 0); +MODULE_PARM_DESC(mock_power_up_delay_msec, "Simulate a power up delay"); + +static unsigned int mock_tune_delay_msec; +module_param(mock_tune_delay_msec, uint, 0); +MODULE_PARM_DESC(mock_tune_delay_msec, "Simulate a tune delay"); + +static unsigned int vidtv_valid_dvb_t_freqs[NUM_VALID_TUNER_FREQS] = { + 474000000 +}; + +module_param_array(vidtv_valid_dvb_t_freqs, uint, NULL, 0); +MODULE_PARM_DESC(vidtv_valid_dvb_t_freqs, + "Valid DVB-T frequencies to simulate, in Hz"); + +static unsigned int vidtv_valid_dvb_c_freqs[NUM_VALID_TUNER_FREQS] = { + 474000000 +}; + +module_param_array(vidtv_valid_dvb_c_freqs, uint, NULL, 0); +MODULE_PARM_DESC(vidtv_valid_dvb_c_freqs, + "Valid DVB-C frequencies to simulate, in Hz"); + +static unsigned int vidtv_valid_dvb_s_freqs[NUM_VALID_TUNER_FREQS] = { + 11362000 +}; +module_param_array(vidtv_valid_dvb_s_freqs, uint, NULL, 0); +MODULE_PARM_DESC(vidtv_valid_dvb_s_freqs, + "Valid DVB-S/S2 frequencies to simulate at Ku-Band, in kHz"); + +static unsigned int max_frequency_shift_hz; +module_param(max_frequency_shift_hz, uint, 0); +MODULE_PARM_DESC(max_frequency_shift_hz, + "Maximum shift in HZ allowed when tuning in a channel"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nums); + +/* + * Influences the signal acquisition time. See ISO/IEC 13818-1 : 2000. p. 113. + */ +static unsigned int si_period_msec = 40; +module_param(si_period_msec, uint, 0); +MODULE_PARM_DESC(si_period_msec, "How often to send SI packets. Default: 40ms"); + +static unsigned int pcr_period_msec = 40; +module_param(pcr_period_msec, uint, 0); +MODULE_PARM_DESC(pcr_period_msec, "How often to send PCR packets. Default: 40ms"); + +static unsigned int mux_rate_kbytes_sec = 4096; +module_param(mux_rate_kbytes_sec, uint, 0); +MODULE_PARM_DESC(mux_rate_kbytes_sec, "Mux rate: will pad stream if below"); + +static unsigned int pcr_pid = 0x200; +module_param(pcr_pid, uint, 0); +MODULE_PARM_DESC(pcr_pid, "PCR PID for all channels: defaults to 0x200"); + +static unsigned int mux_buf_sz_pkts; +module_param(mux_buf_sz_pkts, uint, 0); +MODULE_PARM_DESC(mux_buf_sz_pkts, "Size for the internal mux buffer in multiples of 188 bytes"); + +#define MUX_BUF_MIN_SZ 90164 +#define MUX_BUF_MAX_SZ (MUX_BUF_MIN_SZ * 10) + +static u32 vidtv_bridge_mux_buf_sz_for_mux_rate(void) +{ + u32 max_elapsed_time_msecs = VIDTV_MAX_SLEEP_USECS / USEC_PER_MSEC; + u32 nbytes_expected; + u32 mux_buf_sz = mux_buf_sz_pkts * TS_PACKET_LEN; + + nbytes_expected = mux_rate_kbytes_sec; + nbytes_expected *= max_elapsed_time_msecs; + + mux_buf_sz = roundup(nbytes_expected, TS_PACKET_LEN); + mux_buf_sz += mux_buf_sz / 10; + + if (mux_buf_sz < MUX_BUF_MIN_SZ) + mux_buf_sz = MUX_BUF_MIN_SZ; + + if (mux_buf_sz > MUX_BUF_MAX_SZ) + mux_buf_sz = MUX_BUF_MAX_SZ; + + return mux_buf_sz; +} + +static bool vidtv_bridge_check_demod_lock(struct vidtv_dvb *dvb, u32 n) +{ + enum fe_status status; + + dvb->fe[n]->ops.read_status(dvb->fe[n], &status); + + return status == (FE_HAS_SIGNAL | + FE_HAS_CARRIER | + FE_HAS_VITERBI | + FE_HAS_SYNC | + FE_HAS_LOCK); +} + +static void +vidtv_bridge_on_new_pkts_avail(void *priv, u8 *buf, u32 npkts) +{ + /* + * called on a separate thread by the mux when new packets become + * available + */ + struct vidtv_dvb *dvb = (struct vidtv_dvb *)priv; + + /* drop packets if we lose the lock */ + if (vidtv_bridge_check_demod_lock(dvb, 0)) + dvb_dmx_swfilter_packets(&dvb->demux, buf, npkts); +} + +static int vidtv_start_streaming(struct vidtv_dvb *dvb) +{ + struct vidtv_mux_init_args mux_args = {0}; + struct device *dev = &dvb->pdev->dev; + u32 mux_buf_sz; + + if (dvb->streaming) { + dev_warn_ratelimited(dev, "Already streaming. Skipping.\n"); + return 0; + } + + mux_buf_sz = (mux_buf_sz_pkts) ? mux_buf_sz_pkts : vidtv_bridge_mux_buf_sz_for_mux_rate(); + + mux_args.mux_rate_kbytes_sec = mux_rate_kbytes_sec; + mux_args.on_new_packets_available_cb = vidtv_bridge_on_new_pkts_avail; + mux_args.mux_buf_sz = mux_buf_sz; + mux_args.pcr_period_usecs = pcr_period_msec * 1000; + mux_args.si_period_usecs = si_period_msec * 1000; + mux_args.pcr_pid = pcr_pid; + mux_args.transport_stream_id = VIDTV_DEFAULT_TS_ID; + mux_args.priv = dvb; + + dvb->streaming = true; + dvb->mux = vidtv_mux_init(dvb->fe[0], dev, mux_args); + vidtv_mux_start_thread(dvb->mux); + + dev_dbg_ratelimited(dev, "Started streaming\n"); + return 0; +} + +static int vidtv_stop_streaming(struct vidtv_dvb *dvb) +{ + struct device *dev = &dvb->pdev->dev; + + dvb->streaming = false; + vidtv_mux_stop_thread(dvb->mux); + vidtv_mux_destroy(dvb->mux); + dvb->mux = NULL; + + dev_dbg_ratelimited(dev, "Stopped streaming\n"); + return 0; +} + +static int vidtv_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct vidtv_dvb *dvb = demux->priv; + int rc; + int ret; + + if (!demux->dmx.frontend) + return -EINVAL; + + mutex_lock(&dvb->feed_lock); + + dvb->nfeeds++; + rc = dvb->nfeeds; + + if (dvb->nfeeds == 1) { + ret = vidtv_start_streaming(dvb); + if (ret < 0) + rc = ret; + } + + mutex_unlock(&dvb->feed_lock); + return rc; +} + +static int vidtv_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct vidtv_dvb *dvb = demux->priv; + int err = 0; + + mutex_lock(&dvb->feed_lock); + dvb->nfeeds--; + + if (!dvb->nfeeds) + err = vidtv_stop_streaming(dvb); + + mutex_unlock(&dvb->feed_lock); + return err; +} + +static struct dvb_frontend *vidtv_get_frontend_ptr(struct i2c_client *c) +{ + /* the demod will set this when its probe function runs */ + struct vidtv_demod_state *state = i2c_get_clientdata(c); + + return &state->frontend; +} + +static int vidtv_master_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msgs[], + int num) +{ + return 0; +} + +static u32 vidtv_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm vidtv_i2c_algorithm = { + .master_xfer = vidtv_master_xfer, + .functionality = vidtv_i2c_func, +}; + +static int vidtv_bridge_i2c_register_adap(struct vidtv_dvb *dvb) +{ + struct i2c_adapter *i2c_adapter = &dvb->i2c_adapter; + + strscpy(i2c_adapter->name, "vidtv_i2c", sizeof(i2c_adapter->name)); + i2c_adapter->owner = THIS_MODULE; + i2c_adapter->algo = &vidtv_i2c_algorithm; + i2c_adapter->algo_data = NULL; + i2c_adapter->timeout = 500; + i2c_adapter->retries = 3; + i2c_adapter->dev.parent = &dvb->pdev->dev; + + i2c_set_adapdata(i2c_adapter, dvb); + return i2c_add_adapter(&dvb->i2c_adapter); +} + +static int vidtv_bridge_register_adap(struct vidtv_dvb *dvb) +{ + int ret = 0; + + ret = dvb_register_adapter(&dvb->adapter, + KBUILD_MODNAME, + THIS_MODULE, + &dvb->i2c_adapter.dev, + adapter_nums); + + return ret; +} + +static int vidtv_bridge_dmx_init(struct vidtv_dvb *dvb) +{ + dvb->demux.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING; + + dvb->demux.priv = dvb; + dvb->demux.filternum = 256; + dvb->demux.feednum = 256; + dvb->demux.start_feed = vidtv_start_feed; + dvb->demux.stop_feed = vidtv_stop_feed; + + return dvb_dmx_init(&dvb->demux); +} + +static int vidtv_bridge_dmxdev_init(struct vidtv_dvb *dvb) +{ + dvb->dmx_dev.filternum = 256; + dvb->dmx_dev.demux = &dvb->demux.dmx; + dvb->dmx_dev.capabilities = 0; + + return dvb_dmxdev_init(&dvb->dmx_dev, &dvb->adapter); +} + +static int vidtv_bridge_probe_demod(struct vidtv_dvb *dvb, u32 n) +{ + struct vidtv_demod_config cfg = {}; + + cfg.drop_tslock_prob_on_low_snr = drop_tslock_prob_on_low_snr; + cfg.recover_tslock_prob_on_good_snr = recover_tslock_prob_on_good_snr; + + dvb->i2c_client_demod[n] = dvb_module_probe("dvb_vidtv_demod", + NULL, + &dvb->i2c_adapter, + DEMOD_DEFAULT_ADDR, + &cfg); + + /* driver will not work anyways so bail out */ + if (!dvb->i2c_client_demod[n]) + return -ENODEV; + + /* retrieve a ptr to the frontend state */ + dvb->fe[n] = vidtv_get_frontend_ptr(dvb->i2c_client_demod[n]); + + return 0; +} + +static int vidtv_bridge_probe_tuner(struct vidtv_dvb *dvb, u32 n) +{ + struct vidtv_tuner_config cfg = {}; + u32 freq; + int i; + + cfg.fe = dvb->fe[n]; + cfg.mock_power_up_delay_msec = mock_power_up_delay_msec; + cfg.mock_tune_delay_msec = mock_tune_delay_msec; + + /* TODO: check if the frequencies are at a valid range */ + + memcpy(cfg.vidtv_valid_dvb_t_freqs, + vidtv_valid_dvb_t_freqs, + sizeof(vidtv_valid_dvb_t_freqs)); + + memcpy(cfg.vidtv_valid_dvb_c_freqs, + vidtv_valid_dvb_c_freqs, + sizeof(vidtv_valid_dvb_c_freqs)); + + /* + * Convert Satellite frequencies from Ku-band in kHZ into S-band + * frequencies in Hz. + */ + for (i = 0; i < ARRAY_SIZE(vidtv_valid_dvb_s_freqs); i++) { + freq = vidtv_valid_dvb_s_freqs[i]; + if (freq) { + if (freq < LNB_CUT_FREQUENCY) + freq = abs(freq - LNB_LOW_FREQ); + else + freq = abs(freq - LNB_HIGH_FREQ); + } + cfg.vidtv_valid_dvb_s_freqs[i] = freq; + } + + cfg.max_frequency_shift_hz = max_frequency_shift_hz; + + dvb->i2c_client_tuner[n] = dvb_module_probe("dvb_vidtv_tuner", + NULL, + &dvb->i2c_adapter, + TUNER_DEFAULT_ADDR, + &cfg); + + return (dvb->i2c_client_tuner[n]) ? 0 : -ENODEV; +} + +static int vidtv_bridge_dvb_init(struct vidtv_dvb *dvb) +{ + int ret; + int i; + int j; + + ret = vidtv_bridge_i2c_register_adap(dvb); + if (ret < 0) + goto fail_i2c; + + ret = vidtv_bridge_register_adap(dvb); + if (ret < 0) + goto fail_adapter; + + for (i = 0; i < NUM_FE; ++i) { + ret = vidtv_bridge_probe_demod(dvb, i); + if (ret < 0) + goto fail_demod_probe; + + ret = vidtv_bridge_probe_tuner(dvb, i); + if (ret < 0) + goto fail_tuner_probe; + + ret = dvb_register_frontend(&dvb->adapter, dvb->fe[i]); + if (ret < 0) + goto fail_fe; + } + + ret = vidtv_bridge_dmx_init(dvb); + if (ret < 0) + goto fail_dmx; + + ret = vidtv_bridge_dmxdev_init(dvb); + if (ret < 0) + goto fail_dmx_dev; + + for (j = 0; j < NUM_FE; ++j) { + ret = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, + &dvb->dmx_fe[j]); + if (ret < 0) + goto fail_dmx_conn; + + /* + * The source of the demux is a frontend connected + * to the demux. + */ + dvb->dmx_fe[j].source = DMX_FRONTEND_0; + } + + return ret; + +fail_dmx_conn: + for (j = j - 1; j >= 0; --j) + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, + &dvb->dmx_fe[j]); +fail_dmx_dev: + dvb_dmxdev_release(&dvb->dmx_dev); +fail_dmx: + dvb_dmx_release(&dvb->demux); +fail_fe: + for (j = i; j >= 0; --j) + dvb_unregister_frontend(dvb->fe[j]); +fail_tuner_probe: + for (j = i; j >= 0; --j) + if (dvb->i2c_client_tuner[j]) + dvb_module_release(dvb->i2c_client_tuner[j]); + +fail_demod_probe: + for (j = i; j >= 0; --j) + if (dvb->i2c_client_demod[j]) + dvb_module_release(dvb->i2c_client_demod[j]); + +fail_adapter: + dvb_unregister_adapter(&dvb->adapter); + +fail_i2c: + i2c_del_adapter(&dvb->i2c_adapter); + + return ret; +} + +static int vidtv_bridge_probe(struct platform_device *pdev) +{ + struct vidtv_dvb *dvb; + int ret; + + dvb = kzalloc(sizeof(*dvb), GFP_KERNEL); + if (!dvb) + return -ENOMEM; + + dvb->pdev = pdev; + + ret = vidtv_bridge_dvb_init(dvb); + if (ret < 0) + goto err_dvb; + + mutex_init(&dvb->feed_lock); + + platform_set_drvdata(pdev, dvb); + + dev_info(&pdev->dev, "Successfully initialized vidtv!\n"); + return ret; + +err_dvb: + kfree(dvb); + return ret; +} + +static int vidtv_bridge_remove(struct platform_device *pdev) +{ + struct vidtv_dvb *dvb; + u32 i; + + dvb = platform_get_drvdata(pdev); + + mutex_destroy(&dvb->feed_lock); + + for (i = 0; i < NUM_FE; ++i) { + dvb_unregister_frontend(dvb->fe[i]); + dvb_module_release(dvb->i2c_client_tuner[i]); + dvb_module_release(dvb->i2c_client_demod[i]); + } + + dvb_dmxdev_release(&dvb->dmx_dev); + dvb_dmx_release(&dvb->demux); + dvb_unregister_adapter(&dvb->adapter); + + return 0; +} + +static void vidtv_bridge_dev_release(struct device *dev) +{ +} + +static struct platform_device vidtv_bridge_dev = { + .name = "vidtv_bridge", + .dev.release = vidtv_bridge_dev_release, +}; + +static struct platform_driver vidtv_bridge_driver = { + .driver = { + .name = "vidtv_bridge", + .suppress_bind_attrs = true, + }, + .probe = vidtv_bridge_probe, + .remove = vidtv_bridge_remove, +}; + +static void __exit vidtv_bridge_exit(void) +{ + platform_driver_unregister(&vidtv_bridge_driver); + platform_device_unregister(&vidtv_bridge_dev); +} + +static int __init vidtv_bridge_init(void) +{ + int ret; + + ret = platform_device_register(&vidtv_bridge_dev); + if (ret) + return ret; + + ret = platform_driver_register(&vidtv_bridge_driver); + if (ret) + platform_device_unregister(&vidtv_bridge_dev); + + return ret; +} + +module_init(vidtv_bridge_init); +module_exit(vidtv_bridge_exit); + +MODULE_DESCRIPTION("Virtual Digital TV Test Driver"); +MODULE_AUTHOR("Daniel W. S. Almeida"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("vidtv"); +MODULE_ALIAS("dvb_vidtv"); diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.h b/drivers/media/test-drivers/vidtv/vidtv_bridge.h new file mode 100644 index 000000000000..78fe8472fa37 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * The Virtual DTV test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * When this module is loaded, it will attempt to modprobe 'dvb_vidtv_tuner' and 'dvb_vidtv_demod'. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#ifndef VIDTV_BRIDGE_H +#define VIDTV_BRIDGE_H + +/* + * For now, only one frontend is supported. See vidtv_start_streaming() + */ +#define NUM_FE 1 + +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <media/dmxdev.h> +#include <media/dvb_demux.h> +#include <media/dvb_frontend.h> +#include "vidtv_mux.h" + +/** + * struct vidtv_dvb - Vidtv bridge state + * @pdev: The platform device. Obtained when the bridge is probed. + * @fe: The frontends. Obtained when probing the demodulator modules. + * @adapter: Represents a DTV adapter. See 'dvb_register_adapter'. + * @demux: The demux used by the dvb_dmx_swfilter_packets() call. + * @dmx_dev: Represents a demux device. + * @dmx_frontend: The frontends associated with the demux. + * @i2c_adapter: The i2c_adapter associated with the bridge driver. + * @i2c_client_demod: The i2c_clients associated with the demodulator modules. + * @i2c_client_tuner: The i2c_clients associated with the tuner modules. + * @nfeeds: The number of feeds active. + * @feed_lock: Protects access to the start/stop stream logic/data. + * @streaming: Whether we are streaming now. + * @mux: The abstraction responsible for delivering MPEG TS packets to the bridge. + */ +struct vidtv_dvb { + struct platform_device *pdev; + struct dvb_frontend *fe[NUM_FE]; + struct dvb_adapter adapter; + struct dvb_demux demux; + struct dmxdev dmx_dev; + struct dmx_frontend dmx_fe[NUM_FE]; + struct i2c_adapter i2c_adapter; + struct i2c_client *i2c_client_demod[NUM_FE]; + struct i2c_client *i2c_client_tuner[NUM_FE]; + + u32 nfeeds; + struct mutex feed_lock; /* Protects access to the start/stop stream logic/data. */ + + bool streaming; + + struct vidtv_mux *mux; +}; + +#endif // VIDTV_BRIDG_H diff --git a/drivers/media/test-drivers/vidtv/vidtv_channel.c b/drivers/media/test-drivers/vidtv/vidtv_channel.c new file mode 100644 index 000000000000..f2b97cf08e87 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_channel.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the code for a 'channel' abstraction. + * + * When vidtv boots, it will create some hardcoded channels. + * Their services will be concatenated to populate the SDT. + * Their programs will be concatenated to populate the PAT + * For each program in the PAT, a PMT section will be created + * The PMT section for a channel will be assigned its streams. + * Every stream will have its corresponding encoder polled to produce TS packets + * These packets may be interleaved by the mux and then delivered to the bridge + * + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/dev_printk.h> +#include <linux/ratelimit.h> + +#include "vidtv_channel.h" +#include "vidtv_psi.h" +#include "vidtv_encoder.h" +#include "vidtv_mux.h" +#include "vidtv_common.h" +#include "vidtv_s302m.h" + +static void vidtv_channel_encoder_destroy(struct vidtv_encoder *e) +{ + struct vidtv_encoder *curr = e; + struct vidtv_encoder *tmp = NULL; + + while (curr) { + /* forward the call to the derived type */ + tmp = curr; + curr = curr->next; + tmp->destroy(tmp); + } +} + +#define ENCODING_ISO8859_15 "\x0b" + +struct vidtv_channel +*vidtv_channel_s302m_init(struct vidtv_channel *head, u16 transport_stream_id) +{ + /* + * init an audio only channel with a s302m encoder + */ + const u16 s302m_service_id = 0x880; + const u16 s302m_program_num = 0x880; + const u16 s302m_program_pid = 0x101; /* packet id for PMT*/ + const u16 s302m_es_pid = 0x111; /* packet id for the ES */ + const __be32 s302m_fid = cpu_to_be32(VIDTV_S302M_FORMAT_IDENTIFIER); + + char *name = ENCODING_ISO8859_15 "Beethoven"; + char *provider = ENCODING_ISO8859_15 "LinuxTV.org"; + + struct vidtv_channel *s302m = kzalloc(sizeof(*s302m), GFP_KERNEL); + struct vidtv_s302m_encoder_init_args encoder_args = {}; + + s302m->name = kstrdup(name, GFP_KERNEL); + + s302m->service = vidtv_psi_sdt_service_init(NULL, s302m_service_id); + + s302m->service->descriptor = (struct vidtv_psi_desc *) + vidtv_psi_service_desc_init(NULL, + DIGITAL_TELEVISION_SERVICE, + name, + provider); + + s302m->transport_stream_id = transport_stream_id; + + s302m->program = vidtv_psi_pat_program_init(NULL, + s302m_service_id, + s302m_program_pid); + + s302m->program_num = s302m_program_num; + + s302m->streams = vidtv_psi_pmt_stream_init(NULL, + STREAM_PRIVATE_DATA, + s302m_es_pid); + + s302m->streams->descriptor = (struct vidtv_psi_desc *) + vidtv_psi_registration_desc_init(NULL, + s302m_fid, + NULL, + 0); + encoder_args.es_pid = s302m_es_pid; + + s302m->encoders = vidtv_s302m_encoder_init(encoder_args); + + if (head) { + while (head->next) + head = head->next; + + head->next = s302m; + } + + return s302m; +} + +static struct vidtv_psi_table_sdt_service +*vidtv_channel_sdt_serv_cat_into_new(struct vidtv_mux *m) +{ + /* Concatenate the services */ + const struct vidtv_channel *cur_chnl = m->channels; + + struct vidtv_psi_table_sdt_service *curr = NULL; + struct vidtv_psi_table_sdt_service *head = NULL; + struct vidtv_psi_table_sdt_service *tail = NULL; + + struct vidtv_psi_desc *desc = NULL; + u16 service_id; + + if (!cur_chnl) + return NULL; + + while (cur_chnl) { + curr = cur_chnl->service; + + if (!curr) + dev_warn_ratelimited(m->dev, + "No services found for channel %s\n", cur_chnl->name); + + while (curr) { + service_id = be16_to_cpu(curr->service_id); + tail = vidtv_psi_sdt_service_init(tail, service_id); + + desc = vidtv_psi_desc_clone(curr->descriptor); + vidtv_psi_desc_assign(&tail->descriptor, desc); + + if (!head) + head = tail; + + curr = curr->next; + } + + cur_chnl = cur_chnl->next; + } + + return head; +} + +static struct vidtv_psi_table_pat_program* +vidtv_channel_pat_prog_cat_into_new(struct vidtv_mux *m) +{ + /* Concatenate the programs */ + const struct vidtv_channel *cur_chnl = m->channels; + struct vidtv_psi_table_pat_program *curr = NULL; + struct vidtv_psi_table_pat_program *head = NULL; + struct vidtv_psi_table_pat_program *tail = NULL; + u16 serv_id; + u16 pid; + + if (!cur_chnl) + return NULL; + + while (cur_chnl) { + curr = cur_chnl->program; + + if (!curr) + dev_warn_ratelimited(m->dev, + "No programs found for channel %s\n", + cur_chnl->name); + + while (curr) { + serv_id = be16_to_cpu(curr->service_id); + pid = vidtv_psi_get_pat_program_pid(curr); + tail = vidtv_psi_pat_program_init(tail, + serv_id, + pid); + + if (!head) + head = tail; + + curr = curr->next; + } + + cur_chnl = cur_chnl->next; + } + + return head; +} + +static void +vidtv_channel_pmt_match_sections(struct vidtv_channel *channels, + struct vidtv_psi_table_pmt **sections, + u32 nsections) +{ + /* + * Match channels to their respective PMT sections, then assign the + * streams + */ + struct vidtv_psi_table_pmt *curr_section = NULL; + struct vidtv_channel *cur_chnl = channels; + + struct vidtv_psi_table_pmt_stream *s = NULL; + struct vidtv_psi_table_pmt_stream *head = NULL; + struct vidtv_psi_table_pmt_stream *tail = NULL; + + struct vidtv_psi_desc *desc = NULL; + u32 j; + u16 curr_id; + u16 e_pid; /* elementary stream pid */ + + while (cur_chnl) { + for (j = 0; j < nsections; ++j) { + curr_section = sections[j]; + + if (!curr_section) + continue; + + curr_id = be16_to_cpu(curr_section->header.id); + + /* we got a match */ + if (curr_id == cur_chnl->program_num) { + s = cur_chnl->streams; + + /* clone the streams for the PMT */ + while (s) { + e_pid = vidtv_psi_pmt_stream_get_elem_pid(s); + tail = vidtv_psi_pmt_stream_init(tail, + s->type, + e_pid); + + if (!head) + head = tail; + + desc = vidtv_psi_desc_clone(s->descriptor); + vidtv_psi_desc_assign(&tail->descriptor, desc); + + s = s->next; + } + + vidtv_psi_pmt_stream_assign(curr_section, head); + break; + } + } + + cur_chnl = cur_chnl->next; + } +} + +void vidtv_channel_si_init(struct vidtv_mux *m) +{ + struct vidtv_psi_table_pat_program *programs = NULL; + struct vidtv_psi_table_sdt_service *services = NULL; + + m->si.pat = vidtv_psi_pat_table_init(m->transport_stream_id); + + m->si.sdt = vidtv_psi_sdt_table_init(m->transport_stream_id); + + programs = vidtv_channel_pat_prog_cat_into_new(m); + services = vidtv_channel_sdt_serv_cat_into_new(m); + + /* assemble all programs and assign to PAT */ + vidtv_psi_pat_program_assign(m->si.pat, programs); + + /* assemble all services and assign to SDT */ + vidtv_psi_sdt_service_assign(m->si.sdt, services); + + m->si.pmt_secs = vidtv_psi_pmt_create_sec_for_each_pat_entry(m->si.pat, m->pcr_pid); + + vidtv_channel_pmt_match_sections(m->channels, + m->si.pmt_secs, + m->si.pat->programs); +} + +void vidtv_channel_si_destroy(struct vidtv_mux *m) +{ + u32 i; + u16 num_programs = m->si.pat->programs; + + vidtv_psi_pat_table_destroy(m->si.pat); + + for (i = 0; i < num_programs; ++i) + vidtv_psi_pmt_table_destroy(m->si.pmt_secs[i]); + + kfree(m->si.pmt_secs); + vidtv_psi_sdt_table_destroy(m->si.sdt); +} + +void vidtv_channels_init(struct vidtv_mux *m) +{ + /* this is the place to add new 'channels' for vidtv */ + m->channels = vidtv_channel_s302m_init(NULL, m->transport_stream_id); +} + +void vidtv_channels_destroy(struct vidtv_mux *m) +{ + struct vidtv_channel *curr = m->channels; + struct vidtv_channel *tmp = NULL; + + while (curr) { + kfree(curr->name); + vidtv_psi_sdt_service_destroy(curr->service); + vidtv_psi_pat_program_destroy(curr->program); + vidtv_psi_pmt_stream_destroy(curr->streams); + vidtv_channel_encoder_destroy(curr->encoders); + + tmp = curr; + curr = curr->next; + kfree(tmp); + } +} diff --git a/drivers/media/test-drivers/vidtv/vidtv_channel.h b/drivers/media/test-drivers/vidtv/vidtv_channel.h new file mode 100644 index 000000000000..2c3cba4313b0 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_channel.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the code for a 'channel' abstraction. + * + * When vidtv boots, it will create some hardcoded channels. + * Their services will be concatenated to populate the SDT. + * Their programs will be concatenated to populate the PAT + * For each program in the PAT, a PMT section will be created + * The PMT section for a channel will be assigned its streams. + * Every stream will have its corresponding encoder polled to produce TS packets + * These packets may be interleaved by the mux and then delivered to the bridge + * + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#ifndef VIDTV_CHANNEL_H +#define VIDTV_CHANNEL_H + +#include <linux/types.h> +#include "vidtv_psi.h" +#include "vidtv_encoder.h" +#include "vidtv_mux.h" + +/** + * struct vidtv_channel - A 'channel' abstraction + * + * When vidtv boots, it will create some hardcoded channels. + * Their services will be concatenated to populate the SDT. + * Their programs will be concatenated to populate the PAT + * For each program in the PAT, a PMT section will be created + * The PMT section for a channel will be assigned its streams. + * Every stream will have its corresponding encoder polled to produce TS packets + * These packets may be interleaved by the mux and then delivered to the bridge + * + * @transport_stream_id: a number to identify the TS, chosen at will. + * @service: A _single_ service. Will be concatenated into the SDT. + * @program_num: The link between PAT, PMT and SDT. + * @program: A _single_ program with one or more streams associated with it. + * Will be concatenated into the PAT. + * @streams: A stream loop used to populate the PMT section for 'program' + * @encoders: A encoder loop. There must be one encoder for each stream. + * @next: Optionally chain this channel. + */ +struct vidtv_channel { + char *name; + u16 transport_stream_id; + struct vidtv_psi_table_sdt_service *service; + u16 program_num; + struct vidtv_psi_table_pat_program *program; + struct vidtv_psi_table_pmt_stream *streams; + struct vidtv_encoder *encoders; + struct vidtv_channel *next; +}; + +/** + * vidtv_channel_si_init - Init the PSI tables from the channels in the mux + * @m: The mux containing the channels. + */ +void vidtv_channel_si_init(struct vidtv_mux *m); +void vidtv_channel_si_destroy(struct vidtv_mux *m); + +/** + * vidtv_channels_init - Init hardcoded, fake 'channels'. + * @m: The mux to store the channels into. + */ +void vidtv_channels_init(struct vidtv_mux *m); +struct vidtv_channel +*vidtv_channel_s302m_init(struct vidtv_channel *head, u16 transport_stream_id); +void vidtv_channels_destroy(struct vidtv_mux *m); + +#endif //VIDTV_CHANNEL_H diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.c b/drivers/media/test-drivers/vidtv/vidtv_common.c new file mode 100644 index 000000000000..63b3055bd715 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_common.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Virtual DVB test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ +#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__ + +#include <linux/printk.h> +#include <linux/ratelimit.h> +#include <linux/string.h> +#include <linux/types.h> + +#include "vidtv_common.h" + +/** + * vidtv_memcpy() - wrapper routine to be used by MPEG-TS + * generator, in order to avoid going past the + * output buffer. + * @to: Starting element to where a MPEG-TS packet will + * be copied. + * @to_offset: Starting position of the @to buffer to be filled. + * @to_size: Size of the @to buffer. + * @from: Starting element of the buffer to be copied. + * @len: Number of elements to be copy from @from buffer + * into @to+ @to_offset buffer. + * + * Note: + * Real digital TV demod drivers should not have memcpy + * wrappers. We use it here because emulating MPEG-TS + * generation at kernelspace requires some extra care. + * + * Return: + * Returns the number of bytes written + */ +u32 vidtv_memcpy(void *to, + size_t to_offset, + size_t to_size, + const void *from, + size_t len) +{ + if (unlikely(to_offset + len > to_size)) { + pr_err_ratelimited("overflow detected, skipping. Try increasing the buffer size. Needed %zu, had %zu\n", + to_offset + len, + to_size); + return 0; + } + + memcpy(to + to_offset, from, len); + return len; +} + +/** + * vidtv_memset() - wrapper routine to be used by MPEG-TS + * generator, in order to avoid going past the + * output buffer. + * @to: Starting element to set + * @to_offset: Starting position of the @to buffer to be filled. + * @to_size: Size of the @to buffer. + * @c: The value to set the memory to. + * @len: Number of elements to be copy from @from buffer + * into @to+ @to_offset buffer. + * + * Note: + * Real digital TV demod drivers should not have memset + * wrappers. We use it here because emulating MPEG-TS + * generation at kernelspace requires some extra care. + * + * Return: + * Returns the number of bytes written + */ +u32 vidtv_memset(void *to, + size_t to_offset, + size_t to_size, + const int c, + size_t len) +{ + if (unlikely(to_offset + len > to_size)) { + pr_err_ratelimited("overflow detected, skipping. Try increasing the buffer size. Needed %zu, had %zu\n", + to_offset + len, + to_size); + return 0; + } + + memset(to + to_offset, c, len); + return len; +} diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.h b/drivers/media/test-drivers/vidtv/vidtv_common.h new file mode 100644 index 000000000000..818e7f2b9ec5 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_common.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * The Virtual DVB test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#ifndef VIDTV_COMMON_H +#define VIDTV_COMMON_H + +#include <linux/types.h> + +#define CLOCK_UNIT_90KHZ 90000 +#define CLOCK_UNIT_27MHZ 27000000 +#define VIDTV_SLEEP_USECS 10000 +#define VIDTV_MAX_SLEEP_USECS (2 * VIDTV_SLEEP_USECS) +#define VIDTV_DEFAULT_TS_ID 0x744 + +u32 vidtv_memcpy(void *to, + size_t to_offset, + size_t to_size, + const void *from, + size_t len); + +u32 vidtv_memset(void *to, + size_t to_offset, + size_t to_size, + int c, + size_t len); + +#endif // VIDTV_COMMON_H diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.c b/drivers/media/test-drivers/vidtv/vidtv_demod.c new file mode 100644 index 000000000000..eba7fe1a1b48 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.c @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * The Virtual DVB test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Copyright (C) 2020 Daniel W. S. Almeida + * Based on the example driver written by Emard <emard@softhome.net> + */ + +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/printk.h> +#include <linux/random.h> +#include <linux/ratelimit.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/workqueue.h> +#include <media/dvb_frontend.h> + +#include "vidtv_demod.h" + +#define POLL_THRD_TIME 2000 /* ms */ + +static const struct vidtv_demod_cnr_to_qual_s vidtv_demod_c_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QAM_256, FEC_NONE, 34000, 38000}, + { QAM_64, FEC_NONE, 30000, 34000}, +}; + +static const struct vidtv_demod_cnr_to_qual_s vidtv_demod_s_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QPSK, FEC_1_2, 7000, 10000}, + { QPSK, FEC_2_3, 9000, 12000}, + { QPSK, FEC_3_4, 10000, 13000}, + { QPSK, FEC_5_6, 11000, 14000}, + { QPSK, FEC_7_8, 12000, 15000}, +}; + +static const struct vidtv_demod_cnr_to_qual_s vidtv_demod_s2_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QPSK, FEC_1_2, 9000, 12000}, + { QPSK, FEC_2_3, 11000, 14000}, + { QPSK, FEC_3_4, 12000, 15000}, + { QPSK, FEC_5_6, 12000, 15000}, + { QPSK, FEC_8_9, 13000, 16000}, + { QPSK, FEC_9_10, 13500, 16500}, + { PSK_8, FEC_2_3, 14500, 17500}, + { PSK_8, FEC_3_4, 16000, 19000}, + { PSK_8, FEC_5_6, 17500, 20500}, + { PSK_8, FEC_8_9, 19000, 22000}, +}; + +static const struct vidtv_demod_cnr_to_qual_s vidtv_demod_t_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db*/ + { QPSK, FEC_1_2, 4100, 5900}, + { QPSK, FEC_2_3, 6100, 9600}, + { QPSK, FEC_3_4, 7200, 12400}, + { QPSK, FEC_5_6, 8500, 15600}, + { QPSK, FEC_7_8, 9200, 17500}, + { QAM_16, FEC_1_2, 9800, 11800}, + { QAM_16, FEC_2_3, 12100, 15300}, + { QAM_16, FEC_3_4, 13400, 18100}, + { QAM_16, FEC_5_6, 14800, 21300}, + { QAM_16, FEC_7_8, 15700, 23600}, + { QAM_64, FEC_1_2, 14000, 16000}, + { QAM_64, FEC_2_3, 19900, 25400}, + { QAM_64, FEC_3_4, 24900, 27900}, + { QAM_64, FEC_5_6, 21300, 23300}, + { QAM_64, FEC_7_8, 22000, 24000}, +}; + +static const struct vidtv_demod_cnr_to_qual_s *vidtv_match_cnr_s(struct dvb_frontend *fe) +{ + const struct vidtv_demod_cnr_to_qual_s *cnr2qual = NULL; + struct device *dev = fe->dvb->device; + struct dtv_frontend_properties *c; + u32 array_size = 0; + u32 i; + + c = &fe->dtv_property_cache; + + switch (c->delivery_system) { + case SYS_DVBT: + case SYS_DVBT2: + cnr2qual = vidtv_demod_t_cnr_2_qual; + array_size = ARRAY_SIZE(vidtv_demod_t_cnr_2_qual); + break; + + case SYS_DVBS: + cnr2qual = vidtv_demod_s_cnr_2_qual; + array_size = ARRAY_SIZE(vidtv_demod_s_cnr_2_qual); + break; + + case SYS_DVBS2: + cnr2qual = vidtv_demod_s2_cnr_2_qual; + array_size = ARRAY_SIZE(vidtv_demod_s2_cnr_2_qual); + break; + + case SYS_DVBC_ANNEX_A: + cnr2qual = vidtv_demod_c_cnr_2_qual; + array_size = ARRAY_SIZE(vidtv_demod_c_cnr_2_qual); + break; + + default: + dev_warn_ratelimited(dev, + "%s: unsupported delivery system: %u\n", + __func__, + c->delivery_system); + break; + } + + for (i = 0; i < array_size; i++) + if (cnr2qual[i].modulation == c->modulation && + cnr2qual[i].fec == c->fec_inner) + return &cnr2qual[i]; + + return NULL; /* not found */ +} + +static void vidtv_clean_stats(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + /* Fill the length of each status counter */ + + /* Signal is always available */ + c->strength.len = 1; + c->strength.stat[0].scale = FE_SCALE_DECIBEL; + c->strength.stat[0].svalue = 0; + + /* Usually available only after Viterbi lock */ + c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->cnr.stat[0].svalue = 0; + c->cnr.len = 1; + + /* Those depends on full lock */ + c->pre_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->pre_bit_error.stat[0].uvalue = 0; + c->pre_bit_error.len = 1; + c->pre_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->pre_bit_count.stat[0].uvalue = 0; + c->pre_bit_count.len = 1; + c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_error.stat[0].uvalue = 0; + c->post_bit_error.len = 1; + c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->post_bit_count.stat[0].uvalue = 0; + c->post_bit_count.len = 1; + c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_error.stat[0].uvalue = 0; + c->block_error.len = 1; + c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE; + c->block_count.stat[0].uvalue = 0; + c->block_count.len = 1; +} + +static void vidtv_demod_update_stats(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct vidtv_demod_state *state = fe->demodulator_priv; + u32 scale; + + if (state->status & FE_HAS_LOCK) { + scale = FE_SCALE_COUNTER; + c->cnr.stat[0].scale = FE_SCALE_DECIBEL; + } else { + scale = FE_SCALE_NOT_AVAILABLE; + c->cnr.stat[0].scale = scale; + } + + c->pre_bit_error.stat[0].scale = scale; + c->pre_bit_count.stat[0].scale = scale; + c->post_bit_error.stat[0].scale = scale; + c->post_bit_count.stat[0].scale = scale; + c->block_error.stat[0].scale = scale; + c->block_count.stat[0].scale = scale; + + /* + * Add a 0.5% of randomness at the signal strength and CNR, + * and make them different, as we want to have something closer + * to a real case scenario. + * + * Also, usually, signal strength is a negative number in dBm. + */ + c->strength.stat[0].svalue = state->tuner_cnr; + c->strength.stat[0].svalue -= prandom_u32_max(state->tuner_cnr / 50); + c->strength.stat[0].svalue -= 68000; /* Adjust to a better range */ + + c->cnr.stat[0].svalue = state->tuner_cnr; + c->cnr.stat[0].svalue -= prandom_u32_max(state->tuner_cnr / 50); + +} + +static int vidtv_demod_read_status(struct dvb_frontend *fe, + enum fe_status *status) +{ + struct vidtv_demod_state *state = fe->demodulator_priv; + const struct vidtv_demod_cnr_to_qual_s *cnr2qual = NULL; + struct vidtv_demod_config *config = &state->config; + u16 snr = 0; + + /* Simulate random lost of signal due to a bad-tuned channel */ + cnr2qual = vidtv_match_cnr_s(&state->frontend); + + if (cnr2qual && state->tuner_cnr < cnr2qual->cnr_good && + state->frontend.ops.tuner_ops.get_rf_strength) { + state->frontend.ops.tuner_ops.get_rf_strength(&state->frontend, + &snr); + + if (snr < cnr2qual->cnr_ok) { + /* eventually lose the TS lock */ + if (prandom_u32_max(100) < config->drop_tslock_prob_on_low_snr) + state->status = 0; + } else { + /* recover if the signal improves */ + if (prandom_u32_max(100) < + config->recover_tslock_prob_on_good_snr) + state->status = FE_HAS_SIGNAL | + FE_HAS_CARRIER | + FE_HAS_VITERBI | + FE_HAS_SYNC | + FE_HAS_LOCK; + } + } + + vidtv_demod_update_stats(&state->frontend); + + *status = state->status; + + return 0; +} + +static int vidtv_demod_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + + *strength = c->strength.stat[0].uvalue; + + return 0; +} + +/* + * NOTE: + * This is implemented here just to be used as an example for real + * demod drivers. + * + * Should only be implemented if it actually reads something from the hardware. + * Also, it should check for the locks, in order to avoid report wrong data + * to userspace. + */ +static int vidtv_demod_get_frontend(struct dvb_frontend *fe, + struct dtv_frontend_properties *p) +{ + return 0; +} + +static int vidtv_demod_set_frontend(struct dvb_frontend *fe) +{ + struct vidtv_demod_state *state = fe->demodulator_priv; + u32 tuner_status = 0; + int ret; + + if (!fe->ops.tuner_ops.set_params) + return 0; + + fe->ops.tuner_ops.set_params(fe); + + /* store the CNR returned by the tuner */ + ret = fe->ops.tuner_ops.get_rf_strength(fe, &state->tuner_cnr); + if (ret < 0) + return ret; + + fe->ops.tuner_ops.get_status(fe, &tuner_status); + state->status = (state->tuner_cnr > 0) ? FE_HAS_SIGNAL | + FE_HAS_CARRIER | + FE_HAS_VITERBI | + FE_HAS_SYNC | + FE_HAS_LOCK : + 0; + + vidtv_demod_update_stats(fe); + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + + return 0; +} + +/* + * NOTE: + * This is implemented here just to be used as an example for real + * demod drivers. + * + * Should only be implemented if the demod has support for DVB-S or DVB-S2 + */ +static int vidtv_demod_set_tone(struct dvb_frontend *fe, + enum fe_sec_tone_mode tone) +{ + return 0; +} + +/* + * NOTE: + * This is implemented here just to be used as an example for real + * demod drivers. + * + * Should only be implemented if the demod has support for DVB-S or DVB-S2 + */ +static int vidtv_demod_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + return 0; +} + +/* + * NOTE: + * This is implemented here just to be used as an example for real + * demod drivers. + * + * Should only be implemented if the demod has support for DVB-S or DVB-S2 + */ +static int vidtv_send_diseqc_msg(struct dvb_frontend *fe, + struct dvb_diseqc_master_cmd *cmd) +{ + return 0; +} + +/* + * NOTE: + * This is implemented here just to be used as an example for real + * demod drivers. + * + * Should only be implemented if the demod has support for DVB-S or DVB-S2 + */ +static int vidtv_diseqc_send_burst(struct dvb_frontend *fe, + enum fe_sec_mini_cmd burst) +{ + return 0; +} + +static void vidtv_demod_release(struct dvb_frontend *fe) +{ + struct vidtv_demod_state *state = fe->demodulator_priv; + + kfree(state); +} + +static const struct dvb_frontend_ops vidtv_demod_ops = { + .delsys = { + SYS_DVBT, + SYS_DVBT2, + SYS_DVBC_ANNEX_A, + SYS_DVBS, + SYS_DVBS2, + }, + + .info = { + .name = "Dummy demod for DVB-T/T2/C/S/S2", + .frequency_min_hz = 51 * MHz, + .frequency_max_hz = 2150 * MHz, + .frequency_stepsize_hz = 62500, + .frequency_tolerance_hz = 29500 * kHz, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + + .caps = FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_4_5 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_6_7 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_8_9 | + FE_CAN_QAM_16 | + FE_CAN_QAM_64 | + FE_CAN_QAM_32 | + FE_CAN_QAM_128 | + FE_CAN_QAM_256 | + FE_CAN_QAM_AUTO | + FE_CAN_QPSK | + FE_CAN_FEC_AUTO | + FE_CAN_INVERSION_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = vidtv_demod_release, + + .set_frontend = vidtv_demod_set_frontend, + .get_frontend = vidtv_demod_get_frontend, + + .read_status = vidtv_demod_read_status, + .read_signal_strength = vidtv_demod_read_signal_strength, + + /* For DVB-S/S2 */ + .set_voltage = vidtv_demod_set_voltage, + .set_tone = vidtv_demod_set_tone, + .diseqc_send_master_cmd = vidtv_send_diseqc_msg, + .diseqc_send_burst = vidtv_diseqc_send_burst, + +}; + +static const struct i2c_device_id vidtv_demod_i2c_id_table[] = { + {"dvb_vidtv_demod", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, vidtv_demod_i2c_id_table); + +static int vidtv_demod_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct vidtv_tuner_config *config = client->dev.platform_data; + struct vidtv_demod_state *state; + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, + &vidtv_demod_ops, + sizeof(struct dvb_frontend_ops)); + + memcpy(&state->config, config, sizeof(state->config)); + + state->frontend.demodulator_priv = state; + i2c_set_clientdata(client, state); + + vidtv_clean_stats(&state->frontend); + + return 0; +} + +static int vidtv_demod_i2c_remove(struct i2c_client *client) +{ + struct vidtv_demod_state *state = i2c_get_clientdata(client); + + kfree(state); + + return 0; +} + +static struct i2c_driver vidtv_demod_i2c_driver = { + .driver = { + .name = "dvb_vidtv_demod", + .suppress_bind_attrs = true, + }, + .probe = vidtv_demod_i2c_probe, + .remove = vidtv_demod_i2c_remove, + .id_table = vidtv_demod_i2c_id_table, +}; + +module_i2c_driver(vidtv_demod_i2c_driver); + +MODULE_DESCRIPTION("Virtual DVB Demodulator Driver"); +MODULE_AUTHOR("Daniel W. S. Almeida"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.h b/drivers/media/test-drivers/vidtv/vidtv_demod.h new file mode 100644 index 000000000000..87651b0193e6 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * The Virtual DTV test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Copyright (C) 2020 Daniel W. S. Almeida + * Based on the example driver written by Emard <emard@softhome.net> + */ + +#ifndef VIDTV_DEMOD_H +#define VIDTV_DEMOD_H + +#include <linux/dvb/frontend.h> +#include <media/dvb_frontend.h> + +/** + * struct vidtv_demod_cnr_to_qual_s - Map CNR values to a given combination of + * modulation and fec_inner + * @modulation: see enum fe_modulation + * @fec: see enum fe_fec_rate + * + * This struct matches values for 'good' and 'ok' CNRs given the combination + * of modulation and fec_inner in use. We might simulate some noise if the + * signal quality is not too good. + * + * The values were taken from libdvbv5. + */ +struct vidtv_demod_cnr_to_qual_s { + u32 modulation; + u32 fec; + u32 cnr_ok; + u32 cnr_good; +}; + +/** + * struct vidtv_demod_config - Configuration used to init the demod + * @drop_tslock_prob_on_low_snr: probability of losing the lock due to low snr + * @recover_tslock_prob_on_good_snr: probability of recovering when the signal + * improves + * + * The configuration used to init the demodulator module, usually filled + * by a bridge driver. For vidtv, this is filled by vidtv_bridge before the + * demodulator module is probed. + */ +struct vidtv_demod_config { + u8 drop_tslock_prob_on_low_snr; + u8 recover_tslock_prob_on_good_snr; +}; + +/** + * struct vidtv_demod_state - The demodulator state + * @frontend: The frontend structure allocated by the demod. + * @config: The config used to init the demod. + * @poll_snr: The task responsible for periodically checking the simulated + * signal quality, eventually dropping or reacquiring the TS lock. + * @status: the demod status. + * @cold_start: Whether the demod has not been init yet. + * @poll_snr_thread_running: Whether the task responsible for periodically + * checking the simulated signal quality is running. + * @poll_snr_thread_restart: Whether we should restart the poll_snr task. + */ +struct vidtv_demod_state { + struct dvb_frontend frontend; + struct vidtv_demod_config config; + enum fe_status status; + u16 tuner_cnr; +}; +#endif // VIDTV_DEMOD_H diff --git a/drivers/media/test-drivers/vidtv/vidtv_encoder.h b/drivers/media/test-drivers/vidtv/vidtv_encoder.h new file mode 100644 index 000000000000..65d81daef4c3 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_encoder.h @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains a generic encoder type that can provide data for a stream + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#ifndef VIDTV_ENCODER_H +#define VIDTV_ENCODER_H + +#include <linux/types.h> + +enum vidtv_encoder_id { + /* add IDs here when implementing new encoders */ + S302M, +}; + +struct vidtv_access_unit { + u32 num_samples; + u64 pts; + u64 dts; + u32 nbytes; + u32 offset; + struct vidtv_access_unit *next; +}; + +/* Some musical notes, used by a tone generator */ +enum musical_notes { + NOTE_SILENT = 0, + + NOTE_C_2 = 65, + NOTE_CS_2 = 69, + NOTE_D_2 = 73, + NOTE_DS_2 = 78, + NOTE_E_2 = 82, + NOTE_F_2 = 87, + NOTE_FS_2 = 93, + NOTE_G_2 = 98, + NOTE_GS_2 = 104, + NOTE_A_2 = 110, + NOTE_AS_2 = 117, + NOTE_B_2 = 123, + NOTE_C_3 = 131, + NOTE_CS_3 = 139, + NOTE_D_3 = 147, + NOTE_DS_3 = 156, + NOTE_E_3 = 165, + NOTE_F_3 = 175, + NOTE_FS_3 = 185, + NOTE_G_3 = 196, + NOTE_GS_3 = 208, + NOTE_A_3 = 220, + NOTE_AS_3 = 233, + NOTE_B_3 = 247, + NOTE_C_4 = 262, + NOTE_CS_4 = 277, + NOTE_D_4 = 294, + NOTE_DS_4 = 311, + NOTE_E_4 = 330, + NOTE_F_4 = 349, + NOTE_FS_4 = 370, + NOTE_G_4 = 392, + NOTE_GS_4 = 415, + NOTE_A_4 = 440, + NOTE_AS_4 = 466, + NOTE_B_4 = 494, + NOTE_C_5 = 523, + NOTE_CS_5 = 554, + NOTE_D_5 = 587, + NOTE_DS_5 = 622, + NOTE_E_5 = 659, + NOTE_F_5 = 698, + NOTE_FS_5 = 740, + NOTE_G_5 = 784, + NOTE_GS_5 = 831, + NOTE_A_5 = 880, + NOTE_AS_5 = 932, + NOTE_B_5 = 988, + NOTE_C_6 = 1047, + NOTE_CS_6 = 1109, + NOTE_D_6 = 1175, + NOTE_DS_6 = 1245, + NOTE_E_6 = 1319, + NOTE_F_6 = 1397, + NOTE_FS_6 = 1480, + NOTE_G_6 = 1568, + NOTE_GS_6 = 1661, + NOTE_A_6 = 1760, + NOTE_AS_6 = 1865, + NOTE_B_6 = 1976, + NOTE_C_7 = 2093 +}; + +/** + * struct vidtv_encoder - A generic encoder type. + * @id: So we can cast to a concrete implementation when needed. + * @name: Usually the same as the stream name. + * @encoder_buf: The encoder internal buffer for the access units. + * @encoder_buf_sz: The encoder buffer size, in bytes + * @encoder_buf_offset: Our byte position in the encoder buffer. + * @sample_count: How many samples we have encoded in total. + * @src_buf: The source of raw data to be encoded, encoder might set a + * default if null. + * @src_buf_offset: Our position in the source buffer. + * @is_video_encoder: Whether this a video encoder (as opposed to audio) + * @ctx: Encoder-specific state. + * @stream_id: Examples: Audio streams (0xc0-0xdf), Video streams + * (0xe0-0xef). + * @es_id: The TS PID to use for the elementary stream in this encoder. + * @encode: Prepare enough AUs for the given amount of time. + * @clear: Clear the encoder output. + * @sync: Attempt to synchronize with this encoder. + * @sampling_rate_hz: The sampling rate (or fps, if video) used. + * @last_sample_cb: Called when the encoder runs out of data.This is + * so the source can read data in a + * piecemeal fashion instead of having to + * provide it all at once. + * @destroy: Destroy this encoder, freeing allocated resources. + * @next: Next in the chain + */ +struct vidtv_encoder { + enum vidtv_encoder_id id; + char *name; + + u8 *encoder_buf; + u32 encoder_buf_sz; + u32 encoder_buf_offset; + + u64 sample_count; + int last_duration; + int note_offset; + enum musical_notes last_tone; + + struct vidtv_access_unit *access_units; + + void *src_buf; + u32 src_buf_sz; + u32 src_buf_offset; + + bool is_video_encoder; + void *ctx; + + __be16 stream_id; + + __be16 es_pid; + + void *(*encode)(struct vidtv_encoder *e); + + u32 (*clear)(struct vidtv_encoder *e); + + struct vidtv_encoder *sync; + + u32 sampling_rate_hz; + + void (*last_sample_cb)(u32 sample_no); + + void (*destroy)(struct vidtv_encoder *e); + + struct vidtv_encoder *next; +}; + +#endif /* VIDTV_ENCODER_H */ diff --git a/drivers/media/test-drivers/vidtv/vidtv_mux.c b/drivers/media/test-drivers/vidtv/vidtv_mux.c new file mode 100644 index 000000000000..082740ae9d44 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_mux.c @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the multiplexer logic for TS packets from different + * elementary streams + * + * Loosely based on libavcodec/mpegtsenc.c + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/dev_printk.h> +#include <linux/ratelimit.h> +#include <linux/delay.h> +#include <linux/vmalloc.h> +#include <linux/math64.h> + +#include "vidtv_mux.h" +#include "vidtv_ts.h" +#include "vidtv_pes.h" +#include "vidtv_encoder.h" +#include "vidtv_channel.h" +#include "vidtv_common.h" +#include "vidtv_psi.h" + +static struct vidtv_mux_pid_ctx +*vidtv_mux_get_pid_ctx(struct vidtv_mux *m, u16 pid) +{ + struct vidtv_mux_pid_ctx *ctx; + + hash_for_each_possible(m->pid_ctx, ctx, h, pid) + if (ctx->pid == pid) + return ctx; + return NULL; +} + +static struct vidtv_mux_pid_ctx +*vidtv_mux_create_pid_ctx_once(struct vidtv_mux *m, u16 pid) +{ + struct vidtv_mux_pid_ctx *ctx; + + ctx = vidtv_mux_get_pid_ctx(m, pid); + + if (ctx) + goto end; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + ctx->pid = pid; + ctx->cc = 0; + hash_add(m->pid_ctx, &ctx->h, pid); + +end: + return ctx; +} + +static void vidtv_mux_pid_ctx_init(struct vidtv_mux *m) +{ + struct vidtv_psi_table_pat_program *p = m->si.pat->program; + u16 pid; + + hash_init(m->pid_ctx); + /* push the pcr pid ctx */ + vidtv_mux_create_pid_ctx_once(m, m->pcr_pid); + /* push the null packet pid ctx */ + vidtv_mux_create_pid_ctx_once(m, TS_NULL_PACKET_PID); + /* push the PAT pid ctx */ + vidtv_mux_create_pid_ctx_once(m, VIDTV_PAT_PID); + /* push the SDT pid ctx */ + vidtv_mux_create_pid_ctx_once(m, VIDTV_SDT_PID); + + /* add a ctx for all PMT sections */ + while (p) { + pid = vidtv_psi_get_pat_program_pid(p); + vidtv_mux_create_pid_ctx_once(m, pid); + p = p->next; + } +} + +static void vidtv_mux_pid_ctx_destroy(struct vidtv_mux *m) +{ + int bkt; + struct vidtv_mux_pid_ctx *ctx; + struct hlist_node *tmp; + + hash_for_each_safe(m->pid_ctx, bkt, tmp, ctx, h) { + hash_del(&ctx->h); + kfree(ctx); + } +} + +static void vidtv_mux_update_clk(struct vidtv_mux *m) +{ + /* call this at every thread iteration */ + u64 elapsed_time; + + m->timing.past_jiffies = m->timing.current_jiffies; + m->timing.current_jiffies = get_jiffies_64(); + + elapsed_time = jiffies_to_usecs(m->timing.current_jiffies - + m->timing.past_jiffies); + + /* update the 27Mhz clock proportionally to the elapsed time */ + m->timing.clk += (CLOCK_UNIT_27MHZ / USEC_PER_SEC) * elapsed_time; +} + +static u32 vidtv_mux_push_si(struct vidtv_mux *m) +{ + u32 initial_offset = m->mux_buf_offset; + + struct vidtv_mux_pid_ctx *pat_ctx; + struct vidtv_mux_pid_ctx *pmt_ctx; + struct vidtv_mux_pid_ctx *sdt_ctx; + + struct vidtv_psi_pat_write_args pat_args = {}; + struct vidtv_psi_pmt_write_args pmt_args = {}; + struct vidtv_psi_sdt_write_args sdt_args = {}; + + u32 nbytes; /* the number of bytes written by this function */ + u16 pmt_pid; + u32 i; + + pat_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_PAT_PID); + sdt_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_SDT_PID); + + pat_args.buf = m->mux_buf; + pat_args.offset = m->mux_buf_offset; + pat_args.pat = m->si.pat; + pat_args.buf_sz = m->mux_buf_sz; + pat_args.continuity_counter = &pat_ctx->cc; + + m->mux_buf_offset += vidtv_psi_pat_write_into(pat_args); + + for (i = 0; i < m->si.pat->programs; ++i) { + pmt_pid = vidtv_psi_pmt_get_pid(m->si.pmt_secs[i], + m->si.pat); + + if (pmt_pid > TS_LAST_VALID_PID) { + dev_warn_ratelimited(m->dev, + "PID: %d not found\n", pmt_pid); + continue; + } + + pmt_ctx = vidtv_mux_get_pid_ctx(m, pmt_pid); + + pmt_args.buf = m->mux_buf; + pmt_args.offset = m->mux_buf_offset; + pmt_args.pmt = m->si.pmt_secs[i]; + pmt_args.pid = pmt_pid; + pmt_args.buf_sz = m->mux_buf_sz; + pmt_args.continuity_counter = &pmt_ctx->cc; + pmt_args.pcr_pid = m->pcr_pid; + + /* write each section into buffer */ + m->mux_buf_offset += vidtv_psi_pmt_write_into(pmt_args); + } + + sdt_args.buf = m->mux_buf; + sdt_args.offset = m->mux_buf_offset; + sdt_args.sdt = m->si.sdt; + sdt_args.buf_sz = m->mux_buf_sz; + sdt_args.continuity_counter = &sdt_ctx->cc; + + m->mux_buf_offset += vidtv_psi_sdt_write_into(sdt_args); + + nbytes = m->mux_buf_offset - initial_offset; + + m->num_streamed_si++; + + return nbytes; +} + +static u32 vidtv_mux_push_pcr(struct vidtv_mux *m) +{ + struct pcr_write_args args = {}; + struct vidtv_mux_pid_ctx *ctx; + u32 nbytes = 0; + + ctx = vidtv_mux_get_pid_ctx(m, m->pcr_pid); + args.dest_buf = m->mux_buf; + args.pid = m->pcr_pid; + args.buf_sz = m->mux_buf_sz; + args.continuity_counter = &ctx->cc; + + /* the 27Mhz clock will feed both parts of the PCR bitfield */ + args.pcr = m->timing.clk; + + nbytes += vidtv_ts_pcr_write_into(args); + m->mux_buf_offset += nbytes; + + m->num_streamed_pcr++; + + return nbytes; +} + +static bool vidtv_mux_should_push_pcr(struct vidtv_mux *m) +{ + u64 next_pcr_at; + + if (m->num_streamed_pcr == 0) + return true; + + next_pcr_at = m->timing.start_jiffies + + usecs_to_jiffies(m->num_streamed_pcr * + m->timing.pcr_period_usecs); + + return time_after64(m->timing.current_jiffies, next_pcr_at); +} + +static bool vidtv_mux_should_push_si(struct vidtv_mux *m) +{ + u64 next_si_at; + + if (m->num_streamed_si == 0) + return true; + + next_si_at = m->timing.start_jiffies + + usecs_to_jiffies(m->num_streamed_si * + m->timing.si_period_usecs); + + return time_after64(m->timing.current_jiffies, next_si_at); +} + +static u32 vidtv_mux_packetize_access_units(struct vidtv_mux *m, + struct vidtv_encoder *e) +{ + u32 nbytes = 0; + + struct pes_write_args args = {}; + u32 initial_offset = m->mux_buf_offset; + struct vidtv_access_unit *au = e->access_units; + + u8 *buf = NULL; + struct vidtv_mux_pid_ctx *pid_ctx = vidtv_mux_create_pid_ctx_once(m, + be16_to_cpu(e->es_pid)); + + args.dest_buf = m->mux_buf; + args.dest_buf_sz = m->mux_buf_sz; + args.pid = be16_to_cpu(e->es_pid); + args.encoder_id = e->id; + args.continuity_counter = &pid_ctx->cc; + args.stream_id = be16_to_cpu(e->stream_id); + args.send_pts = true; + + while (au) { + buf = e->encoder_buf + au->offset; + args.from = buf; + args.access_unit_len = au->nbytes; + args.dest_offset = m->mux_buf_offset; + args.pts = au->pts; + args.pcr = m->timing.clk; + + m->mux_buf_offset += vidtv_pes_write_into(args); + + au = au->next; + } + + /* + * clear the encoder state once the ES data has been written to the mux + * buffer + */ + e->clear(e); + + nbytes = m->mux_buf_offset - initial_offset; + return nbytes; +} + +static u32 vidtv_mux_poll_encoders(struct vidtv_mux *m) +{ + u32 nbytes = 0; + u32 au_nbytes; + struct vidtv_channel *cur_chnl = m->channels; + struct vidtv_encoder *e = NULL; + + while (cur_chnl) { + e = cur_chnl->encoders; + + while (e) { + e->encode(e); + /* get the TS packets into the mux buffer */ + au_nbytes = vidtv_mux_packetize_access_units(m, e); + nbytes += au_nbytes; + m->mux_buf_offset += au_nbytes; + /* grab next encoder */ + e = e->next; + } + + /* grab the next channel */ + cur_chnl = cur_chnl->next; + } + + return nbytes; +} + +static u32 vidtv_mux_pad_with_nulls(struct vidtv_mux *m, u32 npkts) +{ + struct null_packet_write_args args = {}; + u32 initial_offset = m->mux_buf_offset; + u32 nbytes; /* the number of bytes written by this function */ + u32 i; + struct vidtv_mux_pid_ctx *ctx; + + ctx = vidtv_mux_get_pid_ctx(m, TS_NULL_PACKET_PID); + + args.dest_buf = m->mux_buf; + args.buf_sz = m->mux_buf_sz; + args.continuity_counter = &ctx->cc; + args.dest_offset = m->mux_buf_offset; + + for (i = 0; i < npkts; ++i) { + m->mux_buf_offset += vidtv_ts_null_write_into(args); + args.dest_offset = m->mux_buf_offset; + } + + nbytes = m->mux_buf_offset - initial_offset; + + /* sanity check */ + if (nbytes != npkts * TS_PACKET_LEN) + dev_err_ratelimited(m->dev, "%d != %d\n", + nbytes, npkts * TS_PACKET_LEN); + + return nbytes; +} + +static void vidtv_mux_clear(struct vidtv_mux *m) +{ + /* clear the packets currently in the mux */ + memset(m->mux_buf, 0, m->mux_buf_sz * sizeof(*m->mux_buf)); + /* point to the beginning of the buffer again */ + m->mux_buf_offset = 0; +} + +#define ERR_RATE 10000000 +static void vidtv_mux_tick(struct work_struct *work) +{ + struct vidtv_mux *m = container_of(work, + struct vidtv_mux, + mpeg_thread); + struct dtv_frontend_properties *c = &m->fe->dtv_property_cache; + u32 nbytes; + u32 npkts; + u32 tot_bits = 0; + + while (m->streaming) { + nbytes = 0; + + vidtv_mux_update_clk(m); + + if (vidtv_mux_should_push_pcr(m)) + nbytes += vidtv_mux_push_pcr(m); + + if (vidtv_mux_should_push_si(m)) + nbytes += vidtv_mux_push_si(m); + + nbytes += vidtv_mux_poll_encoders(m); + nbytes += vidtv_mux_pad_with_nulls(m, 256); + + npkts = nbytes / TS_PACKET_LEN; + + /* if the buffer is not aligned there is a bug somewhere */ + if (nbytes % TS_PACKET_LEN) + dev_err_ratelimited(m->dev, "Misaligned buffer\n"); + + if (m->on_new_packets_available_cb) + m->on_new_packets_available_cb(m->priv, + m->mux_buf, + npkts); + + vidtv_mux_clear(m); + + /* + * Update bytes and packet counts at DVBv5 stats + * + * For now, both pre and post bit counts are identical, + * but post BER count can be lower than pre BER, if the error + * correction logic discards packages. + */ + c->pre_bit_count.stat[0].uvalue = nbytes * 8; + c->post_bit_count.stat[0].uvalue = nbytes * 8; + c->block_count.stat[0].uvalue += npkts; + + /* + * Even without any visible errors for the user, the pre-BER + * stats usually have an error range up to 1E-6. So, + * add some random error increment count to it. + * + * Please notice that this is a poor guy's implementation, + * as it will produce one corrected bit error every time + * ceil(total bytes / ERR_RATE) is incremented, without + * any sort of (pseudo-)randomness. + */ + tot_bits += nbytes * 8; + if (tot_bits > ERR_RATE) { + c->pre_bit_error.stat[0].uvalue++; + tot_bits -= ERR_RATE; + } + + usleep_range(VIDTV_SLEEP_USECS, VIDTV_MAX_SLEEP_USECS); + } +} + +void vidtv_mux_start_thread(struct vidtv_mux *m) +{ + if (m->streaming) { + dev_warn_ratelimited(m->dev, "Already streaming. Skipping.\n"); + return; + } + + m->streaming = true; + m->timing.start_jiffies = get_jiffies_64(); + schedule_work(&m->mpeg_thread); +} + +void vidtv_mux_stop_thread(struct vidtv_mux *m) +{ + if (m->streaming) { + m->streaming = false; /* thread will quit */ + cancel_work_sync(&m->mpeg_thread); + } +} + +struct vidtv_mux *vidtv_mux_init(struct dvb_frontend *fe, + struct device *dev, + struct vidtv_mux_init_args args) +{ + struct vidtv_mux *m = kzalloc(sizeof(*m), GFP_KERNEL); + + m->dev = dev; + m->fe = fe; + m->timing.pcr_period_usecs = args.pcr_period_usecs; + m->timing.si_period_usecs = args.si_period_usecs; + + m->mux_rate_kbytes_sec = args.mux_rate_kbytes_sec; + + m->on_new_packets_available_cb = args.on_new_packets_available_cb; + + m->mux_buf = vzalloc(args.mux_buf_sz); + m->mux_buf_sz = args.mux_buf_sz; + + m->pcr_pid = args.pcr_pid; + m->transport_stream_id = args.transport_stream_id; + m->priv = args.priv; + m->timing.current_jiffies = get_jiffies_64(); + + if (args.channels) + m->channels = args.channels; + else + vidtv_channels_init(m); + + /* will alloc data for pmt_sections after initializing pat */ + vidtv_channel_si_init(m); + + INIT_WORK(&m->mpeg_thread, vidtv_mux_tick); + + vidtv_mux_pid_ctx_init(m); + + return m; +} + +void vidtv_mux_destroy(struct vidtv_mux *m) +{ + vidtv_mux_stop_thread(m); + vidtv_mux_pid_ctx_destroy(m); + vidtv_channel_si_destroy(m); + vidtv_channels_destroy(m); + vfree(m->mux_buf); + kfree(m); +} diff --git a/drivers/media/test-drivers/vidtv/vidtv_mux.h b/drivers/media/test-drivers/vidtv/vidtv_mux.h new file mode 100644 index 000000000000..2caa60623e97 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_mux.h @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the muxer logic for TS packets from different + * elementary streams. + * + * Loosely based on libavcodec/mpegtsenc.c + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#ifndef VIDTV_MUX_H +#define VIDTV_MUX_H + +#include <linux/types.h> +#include <linux/hashtable.h> +#include <linux/workqueue.h> +#include <media/dvb_frontend.h> + +#include "vidtv_psi.h" + +/** + * struct vidtv_mux_timing - Timing related information + * + * This is used to decide when PCR or PSI packets should be sent. This will also + * provide storage for the clock, which is used to compute the value for the PCR. + * + * @start_jiffies: The value of 'jiffies' when we started the mux thread. + * @current_jiffies: The value of 'jiffies' for the current iteration. + * @past_jiffies: The value of 'jiffies' for the past iteration. + * @clk: A 27Mhz clock from which we will drive the PCR. Updated proportionally + * on every iteration. + * @pcr_period_usecs: How often we should send PCR packets. + * @si_period_usecs: How often we should send PSI packets. + */ +struct vidtv_mux_timing { + u64 start_jiffies; + u64 current_jiffies; + u64 past_jiffies; + + u64 clk; + + u64 pcr_period_usecs; + u64 si_period_usecs; +}; + +/** + * struct vidtv_mux_si - Store the PSI context. + * + * This is used to store the PAT, PMT sections and SDT in use by the muxer. + * + * The muxer acquire these by looking into the hardcoded channels in + * vidtv_channel and then periodically sends the TS packets for them> + * + * @pat: The PAT in use by the muxer. + * @pmt_secs: The PMT sections in use by the muxer. One for each program in the PAT. + * @sdt: The SDT in use by the muxer. + */ +struct vidtv_mux_si { + /* the SI tables */ + struct vidtv_psi_table_pat *pat; + struct vidtv_psi_table_pmt **pmt_secs; /* the PMT sections */ + struct vidtv_psi_table_sdt *sdt; +}; + +/** + * struct vidtv_mux_pid_ctx - Store the context for a given TS PID. + * @pid: The TS PID. + * @cc: The continuity counter for this PID. It is incremented on every TS + * pack and it will wrap around at 0xf0. If the decoder notices a sudden jump in + * this counter this will trigger a discontinuity state. + * @h: This is embedded in a hash table, mapping pid -> vidtv_mux_pid_ctx + */ +struct vidtv_mux_pid_ctx { + u16 pid; + u8 cc; /* continuity counter */ + struct hlist_node h; +}; + +/** + * struct vidtv_mux - A muxer abstraction loosely based in libavcodec/mpegtsenc.c + * @mux_rate_kbytes_sec: The bit rate for the TS, in kbytes. + * @timing: Keeps track of timing related information. + * @pid_ctx: A hash table to keep track of per-PID metadata. + * @on_new_packets_available_cb: A callback to inform of new TS packets ready. + * @mux_buf: A pointer to a buffer for this muxer. TS packets are stored there + * and then passed on to the bridge driver. + * @mux_buf_sz: The size for 'mux_buf'. + * @mux_buf_offset: The current offset into 'mux_buf'. + * @channels: The channels associated with this muxer. + * @si: Keeps track of the PSI context. + * @num_streamed_pcr: Number of PCR packets streamed. + * @num_streamed_si: The number of PSI packets streamed. + * @mpeg_thread: Thread responsible for the muxer loop. + * @streaming: whether 'mpeg_thread' is running. + * @pcr_pid: The TS PID used for the PSI packets. All channels will share the + * same PCR. + * @transport_stream_id: The transport stream ID + * @priv: Private data. + */ +struct vidtv_mux { + struct dvb_frontend *fe; + struct device *dev; + + struct vidtv_mux_timing timing; + + u32 mux_rate_kbytes_sec; + + DECLARE_HASHTABLE(pid_ctx, 3); + + void (*on_new_packets_available_cb)(void *priv, u8 *buf, u32 npackets); + + u8 *mux_buf; + u32 mux_buf_sz; + u32 mux_buf_offset; + + struct vidtv_channel *channels; + + struct vidtv_mux_si si; + u64 num_streamed_pcr; + u64 num_streamed_si; + + struct work_struct mpeg_thread; + bool streaming; + + u16 pcr_pid; + u16 transport_stream_id; + void *priv; +}; + +/** + * struct vidtv_mux_init_args - Arguments used to inix the muxer. + * @mux_rate_kbytes_sec: The bit rate for the TS, in kbytes. + * @on_new_packets_available_cb: A callback to inform of new TS packets ready. + * @mux_buf_sz: The size for 'mux_buf'. + * @pcr_period_usecs: How often we should send PCR packets. + * @si_period_usecs: How often we should send PSI packets. + * @pcr_pid: The TS PID used for the PSI packets. All channels will share the + * same PCR. + * @transport_stream_id: The transport stream ID + * @channels: an optional list of channels to use + * @priv: Private data. + */ +struct vidtv_mux_init_args { + u32 mux_rate_kbytes_sec; + void (*on_new_packets_available_cb)(void *priv, u8 *buf, u32 npackets); + u32 mux_buf_sz; + u64 pcr_period_usecs; + u64 si_period_usecs; + u16 pcr_pid; + u16 transport_stream_id; + struct vidtv_channel *channels; + void *priv; +}; + +struct vidtv_mux *vidtv_mux_init(struct dvb_frontend *fe, + struct device *dev, + struct vidtv_mux_init_args args); +void vidtv_mux_destroy(struct vidtv_mux *m); + +void vidtv_mux_start_thread(struct vidtv_mux *m); +void vidtv_mux_stop_thread(struct vidtv_mux *m); + +#endif //VIDTV_MUX_H diff --git a/drivers/media/test-drivers/vidtv/vidtv_pes.c b/drivers/media/test-drivers/vidtv/vidtv_pes.c new file mode 100644 index 000000000000..1c75f88070e9 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_pes.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the logic to translate the ES data for one access unit + * from an encoder into MPEG TS packets. It does so by first encapsulating it + * with a PES header and then splitting it into TS packets. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__ + +#include <linux/types.h> +#include <linux/printk.h> +#include <linux/ratelimit.h> +#include <asm/byteorder.h> + +#include "vidtv_pes.h" +#include "vidtv_common.h" +#include "vidtv_encoder.h" +#include "vidtv_ts.h" + +#define PRIVATE_STREAM_1_ID 0xbd /* private_stream_1. See SMPTE 302M-2007 p.6 */ +#define PES_HEADER_MAX_STUFFING_BYTES 32 +#define PES_TS_HEADER_MAX_STUFFING_BYTES 182 + +static u32 vidtv_pes_op_get_len(bool send_pts, bool send_dts) +{ + u32 len = 0; + + /* the flags must always be sent */ + len += sizeof(struct vidtv_pes_optional); + + /* From all optionals, we might send these for now */ + if (send_pts && send_dts) + len += sizeof(struct vidtv_pes_optional_pts_dts); + else if (send_pts) + len += sizeof(struct vidtv_pes_optional_pts); + + return len; +} + +#define SIZE_PCR (6 + sizeof(struct vidtv_mpeg_ts_adaption)) + +static u32 vidtv_pes_h_get_len(bool send_pts, bool send_dts) +{ + u32 len = 0; + + /* PES header length notwithstanding stuffing bytes */ + + len += sizeof(struct vidtv_mpeg_pes); + len += vidtv_pes_op_get_len(send_pts, send_dts); + + return len; +} + +static u32 vidtv_pes_write_header_stuffing(struct pes_header_write_args args) +{ + /* + * This is a fixed 8-bit value equal to '0xFF' that can be inserted + * by the encoder, for example to meet the requirements of the channel. + * It is discarded by the decoder. No more than 32 stuffing bytes shall + * be present in one PES packet header. + */ + if (args.n_pes_h_s_bytes > PES_HEADER_MAX_STUFFING_BYTES) { + pr_warn_ratelimited("More than %d stuffing bytes in PES packet header\n", + PES_HEADER_MAX_STUFFING_BYTES); + args.n_pes_h_s_bytes = PES_HEADER_MAX_STUFFING_BYTES; + } + + return vidtv_memset(args.dest_buf, + args.dest_offset, + args.dest_buf_sz, + TS_FILL_BYTE, + args.n_pes_h_s_bytes); +} + +static u32 vidtv_pes_write_pts_dts(struct pes_header_write_args args) +{ + u32 nbytes = 0; /* the number of bytes written by this function */ + + struct vidtv_pes_optional_pts pts = {}; + struct vidtv_pes_optional_pts_dts pts_dts = {}; + void *op = NULL; + size_t op_sz = 0; + u64 mask1; + u64 mask2; + u64 mask3; + + if (!args.send_pts && args.send_dts) + return 0; + + mask1 = GENMASK_ULL(32, 30); + mask2 = GENMASK_ULL(29, 15); + mask3 = GENMASK_ULL(14, 0); + + /* see ISO/IEC 13818-1 : 2000 p. 32 */ + if (args.send_pts && args.send_dts) { + pts_dts.pts1 = (0x3 << 4) | ((args.pts & mask1) >> 29) | 0x1; + pts_dts.pts2 = cpu_to_be16(((args.pts & mask2) >> 14) | 0x1); + pts_dts.pts3 = cpu_to_be16(((args.pts & mask3) << 1) | 0x1); + + pts_dts.dts1 = (0x1 << 4) | ((args.dts & mask1) >> 29) | 0x1; + pts_dts.dts2 = cpu_to_be16(((args.dts & mask2) >> 14) | 0x1); + pts_dts.dts3 = cpu_to_be16(((args.dts & mask3) << 1) | 0x1); + + op = &pts_dts; + op_sz = sizeof(pts_dts); + + } else if (args.send_pts) { + pts.pts1 = (0x1 << 5) | ((args.pts & mask1) >> 29) | 0x1; + pts.pts2 = cpu_to_be16(((args.pts & mask2) >> 14) | 0x1); + pts.pts3 = cpu_to_be16(((args.pts & mask3) << 1) | 0x1); + + op = &pts; + op_sz = sizeof(pts); + } + + /* copy PTS/DTS optional */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + op, + op_sz); + + return nbytes; +} + +static u32 vidtv_pes_write_h(struct pes_header_write_args args) +{ + u32 nbytes = 0; /* the number of bytes written by this function */ + + struct vidtv_mpeg_pes pes_header = {}; + struct vidtv_pes_optional pes_optional = {}; + struct pes_header_write_args pts_dts_args = args; + u32 stream_id = (args.encoder_id == S302M) ? PRIVATE_STREAM_1_ID : args.stream_id; + u16 pes_opt_bitfield = 0x01 << 15; + + pes_header.bitfield = cpu_to_be32((PES_START_CODE_PREFIX << 8) | stream_id); + + pes_header.length = cpu_to_be16(vidtv_pes_op_get_len(args.send_pts, + args.send_dts) + + args.access_unit_len); + + if (args.send_pts && args.send_dts) + pes_opt_bitfield |= (0x3 << 6); + else if (args.send_pts) + pes_opt_bitfield |= (0x1 << 7); + + pes_optional.bitfield = cpu_to_be16(pes_opt_bitfield); + pes_optional.length = vidtv_pes_op_get_len(args.send_pts, args.send_dts) + + args.n_pes_h_s_bytes - + sizeof(struct vidtv_pes_optional); + + /* copy header */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + &pes_header, + sizeof(pes_header)); + + /* copy optional header bits */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + &pes_optional, + sizeof(pes_optional)); + + /* copy the timing information */ + pts_dts_args.dest_offset = args.dest_offset + nbytes; + nbytes += vidtv_pes_write_pts_dts(pts_dts_args); + + /* write any PES header stuffing */ + nbytes += vidtv_pes_write_header_stuffing(args); + + return nbytes; +} + +static u32 vidtv_pes_write_pcr_bits(u8 *to, u32 to_offset, u64 pcr) +{ + /* Exact same from ffmpeg. PCR is a counter driven by a 27Mhz clock */ + u64 div; + u64 rem; + u8 *buf = to + to_offset; + u64 pcr_low; + u64 pcr_high; + + div = div64_u64_rem(pcr, 300, &rem); + + pcr_low = rem; /* pcr_low = pcr % 300 */ + pcr_high = div; /* pcr_high = pcr / 300 */ + + *buf++ = pcr_high >> 25; + *buf++ = pcr_high >> 17; + *buf++ = pcr_high >> 9; + *buf++ = pcr_high >> 1; + *buf++ = pcr_high << 7 | pcr_low >> 8 | 0x7e; + *buf++ = pcr_low; + + return 6; +} + +static u32 vidtv_pes_write_stuffing(struct pes_ts_header_write_args *args, + u32 dest_offset, bool need_pcr, + u64 *last_pcr) +{ + struct vidtv_mpeg_ts_adaption ts_adap = {}; + int stuff_nbytes; + u32 nbytes = 0; + + if (!args->n_stuffing_bytes) + return 0; + + ts_adap.random_access = 1; + + /* length _immediately_ following 'adaptation_field_length' */ + if (need_pcr) { + ts_adap.PCR = 1; + ts_adap.length = SIZE_PCR; + } else { + ts_adap.length = sizeof(ts_adap); + } + stuff_nbytes = args->n_stuffing_bytes - ts_adap.length; + + ts_adap.length -= sizeof(ts_adap.length); + + if (unlikely(stuff_nbytes < 0)) + stuff_nbytes = 0; + + ts_adap.length += stuff_nbytes; + + /* write the adap after the TS header */ + nbytes += vidtv_memcpy(args->dest_buf, + dest_offset + nbytes, + args->dest_buf_sz, + &ts_adap, + sizeof(ts_adap)); + + /* write the optional PCR */ + if (need_pcr) { + nbytes += vidtv_pes_write_pcr_bits(args->dest_buf, + dest_offset + nbytes, + args->pcr); + + *last_pcr = args->pcr; + } + + /* write the stuffing bytes, if are there anything left */ + if (stuff_nbytes) + nbytes += vidtv_memset(args->dest_buf, + dest_offset + nbytes, + args->dest_buf_sz, + TS_FILL_BYTE, + stuff_nbytes); + + /* + * The n_stuffing_bytes contain a pre-calculated value of + * the amount of data that this function would read, made from + * vidtv_pes_h_get_len(). If something went wrong, print a warning + */ + if (nbytes != args->n_stuffing_bytes) + pr_warn_ratelimited("write size was %d, expected %d\n", + nbytes, args->n_stuffing_bytes); + + return nbytes; +} + +static u32 vidtv_pes_write_ts_h(struct pes_ts_header_write_args args, + bool need_pcr, u64 *last_pcr) +{ + /* number of bytes written by this function */ + u32 nbytes = 0; + struct vidtv_mpeg_ts ts_header = {}; + u16 payload_start = !args.wrote_pes_header; + + ts_header.sync_byte = TS_SYNC_BYTE; + ts_header.bitfield = cpu_to_be16((payload_start << 14) | args.pid); + ts_header.scrambling = 0; + ts_header.adaptation_field = (args.n_stuffing_bytes) > 0; + ts_header.payload = (args.n_stuffing_bytes) < PES_TS_HEADER_MAX_STUFFING_BYTES; + + ts_header.continuity_counter = *args.continuity_counter; + + vidtv_ts_inc_cc(args.continuity_counter); + + /* write the TS header */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + &ts_header, + sizeof(ts_header)); + + /* write stuffing, if any */ + nbytes += vidtv_pes_write_stuffing(&args, args.dest_offset + nbytes, + need_pcr, last_pcr); + + return nbytes; +} + +u32 vidtv_pes_write_into(struct pes_write_args args) +{ + u32 unaligned_bytes = (args.dest_offset % TS_PACKET_LEN); + struct pes_ts_header_write_args ts_header_args = {}; + struct pes_header_write_args pes_header_args = {}; + u32 remaining_len = args.access_unit_len; + bool wrote_pes_header = false; + u64 last_pcr = args.pcr; + bool need_pcr = true; + u32 available_space; + u32 payload_size; + u32 stuff_bytes; + u32 nbytes = 0; + + if (unaligned_bytes) { + pr_warn_ratelimited("buffer is misaligned, while starting PES\n"); + + /* forcibly align and hope for the best */ + nbytes += vidtv_memset(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + TS_FILL_BYTE, + TS_PACKET_LEN - unaligned_bytes); + } + + if (args.send_dts && !args.send_pts) { + pr_warn_ratelimited("forbidden value '01' for PTS_DTS flags\n"); + args.send_pts = true; + args.pts = args.dts; + } + + /* see SMPTE 302M clause 6.4 */ + if (args.encoder_id == S302M) { + args.send_dts = false; + args.send_pts = true; + } + + while (remaining_len) { + available_space = TS_PAYLOAD_LEN; + /* + * The amount of space initially available in the TS packet. + * if this is the beginning of the PES packet, take into account + * the space needed for the TS header _and_ for the PES header + */ + if (!wrote_pes_header) + available_space -= vidtv_pes_h_get_len(args.send_pts, + args.send_dts); + + /* + * if the encoder has inserted stuffing bytes in the PES + * header, account for them. + */ + available_space -= args.n_pes_h_s_bytes; + + /* Take the extra adaptation into account if need to send PCR */ + if (need_pcr) { + available_space -= SIZE_PCR; + stuff_bytes = SIZE_PCR; + } else { + stuff_bytes = 0; + } + + /* + * how much of the _actual_ payload should be written in this + * packet. + */ + if (remaining_len >= available_space) { + payload_size = available_space; + } else { + /* Last frame should ensure 188-bytes PS alignment */ + payload_size = remaining_len; + stuff_bytes += available_space - payload_size; + + /* + * Ensure that the stuff bytes will be within the + * allowed range, decrementing the number of payload + * bytes to write if needed. + */ + if (stuff_bytes > PES_TS_HEADER_MAX_STUFFING_BYTES) { + u32 tmp = stuff_bytes - PES_TS_HEADER_MAX_STUFFING_BYTES; + + stuff_bytes = PES_TS_HEADER_MAX_STUFFING_BYTES; + payload_size -= tmp; + } + } + + /* write ts header */ + ts_header_args.dest_buf = args.dest_buf; + ts_header_args.dest_offset = args.dest_offset + nbytes; + ts_header_args.dest_buf_sz = args.dest_buf_sz; + ts_header_args.pid = args.pid; + ts_header_args.pcr = args.pcr; + ts_header_args.continuity_counter = args.continuity_counter; + ts_header_args.wrote_pes_header = wrote_pes_header; + ts_header_args.n_stuffing_bytes = stuff_bytes; + + nbytes += vidtv_pes_write_ts_h(ts_header_args, need_pcr, + &last_pcr); + + need_pcr = false; + + if (!wrote_pes_header) { + /* write the PES header only once */ + pes_header_args.dest_buf = args.dest_buf; + + pes_header_args.dest_offset = args.dest_offset + + nbytes; + + pes_header_args.dest_buf_sz = args.dest_buf_sz; + pes_header_args.encoder_id = args.encoder_id; + pes_header_args.send_pts = args.send_pts; + pes_header_args.pts = args.pts; + pes_header_args.send_dts = args.send_dts; + pes_header_args.dts = args.dts; + pes_header_args.stream_id = args.stream_id; + pes_header_args.n_pes_h_s_bytes = args.n_pes_h_s_bytes; + pes_header_args.access_unit_len = args.access_unit_len; + + nbytes += vidtv_pes_write_h(pes_header_args); + wrote_pes_header = true; + } + + /* write as much of the payload as we possibly can */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + args.from, + payload_size); + + args.from += payload_size; + + remaining_len -= payload_size; + } + + return nbytes; +} diff --git a/drivers/media/test-drivers/vidtv/vidtv_pes.h b/drivers/media/test-drivers/vidtv/vidtv_pes.h new file mode 100644 index 000000000000..0ea9e863024d --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_pes.h @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the logic to translate the ES data for one access unit + * from an encoder into MPEG TS packets. It does so by first encapsulating it + * with a PES header and then splitting it into TS packets. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#ifndef VIDTV_PES_H +#define VIDTV_PES_H + +#include <asm/byteorder.h> +#include <linux/types.h> + +#include "vidtv_common.h" + +#define PES_MAX_LEN 65536 /* Set 'length' to 0 if greater. Only possible for video. */ +#define PES_START_CODE_PREFIX 0x001 /* 00 00 01 */ + +/* Used when sending PTS, but not DTS */ +struct vidtv_pes_optional_pts { + u8 pts1; + __be16 pts2; + __be16 pts3; +} __packed; + +/* Used when sending both PTS and DTS */ +struct vidtv_pes_optional_pts_dts { + u8 pts1; + __be16 pts2; + __be16 pts3; + + u8 dts1; + __be16 dts2; + __be16 dts3; +} __packed; + +/* PES optional flags */ +struct vidtv_pes_optional { + /* + * These flags show which components are actually + * present in the "optional fields" in the optional PES + * header and which are not + * + * u16 two:2; //0x2 + * u16 PES_scrambling_control:2; + * u16 PES_priority:1; + * u16 data_alignment_indicator:1; // unused + * u16 copyright:1; + * u16 original_or_copy:1; + * u16 PTS_DTS:2; + * u16 ESCR:1; + * u16 ES_rate:1; + * u16 DSM_trick_mode:1; + * u16 additional_copy_info:1; + * u16 PES_CRC:1; + * u16 PES_extension:1; + */ + __be16 bitfield; + u8 length; +} __packed; + +/* The PES header */ +struct vidtv_mpeg_pes { + __be32 bitfield; /* packet_start_code_prefix:24, stream_id: 8 */ + /* after this field until the end of the PES data payload */ + __be16 length; + struct vidtv_pes_optional optional[]; +} __packed; + +/** + * struct pes_header_write_args - Arguments to write a PES header. + * @dest_buf: The buffer to write into. + * @dest_offset: where to start writing in the dest_buffer. + * @dest_buf_sz: The size of the dest_buffer + * @encoder_id: Encoder id (see vidtv_encoder.h) + * @send_pts: Should we send PTS? + * @pts: PTS value to send. + * @send_dts: Should we send DTS? + * @dts: DTS value to send. + * @stream_id: The stream id to use. Ex: Audio streams (0xc0-0xdf), Video + * streams (0xe0-0xef). + * @n_pes_h_s_bytes: Padding bytes. Might be used by an encoder if needed, gets + * discarded by the decoder. + * @access_unit_len: The size of _one_ access unit (with any headers it might need) + */ +struct pes_header_write_args { + void *dest_buf; + u32 dest_offset; + u32 dest_buf_sz; + u32 encoder_id; + + bool send_pts; + u64 pts; + + bool send_dts; + u64 dts; + + u16 stream_id; + /* might be used by an encoder if needed, gets discarded by decoder */ + u32 n_pes_h_s_bytes; + u32 access_unit_len; +}; + +/** + * struct pes_ts_header_write_args - Arguments to write a TS header. + * @dest_buf: The buffer to write into. + * @dest_offset: where to start writing in the dest_buffer. + * @dest_buf_sz: The size of the dest_buffer + * @pid: The PID to use for the TS packets. + * @continuity_counter: Incremented on every new TS packet. + * @n_pes_h_s_bytes: Padding bytes. Might be used by an encoder if needed, gets + * discarded by the decoder. + */ +struct pes_ts_header_write_args { + void *dest_buf; + u32 dest_offset; + u32 dest_buf_sz; + u16 pid; + u8 *continuity_counter; + bool wrote_pes_header; + u32 n_stuffing_bytes; + u64 pcr; +}; + +/** + * struct pes_write_args - Arguments for the packetizer. + * @dest_buf: The buffer to write into. + * @from: A pointer to the encoder buffer containing one access unit. + * @access_unit_len: The size of _one_ access unit (with any headers it might need) + * @dest_offset: where to start writing in the dest_buffer. + * @dest_buf_sz: The size of the dest_buffer + * @pid: The PID to use for the TS packets. + * @encoder_id: Encoder id (see vidtv_encoder.h) + * @continuity_counter: Incremented on every new TS packet. + * @stream_id: The stream id to use. Ex: Audio streams (0xc0-0xdf), Video + * streams (0xe0-0xef). + * @send_pts: Should we send PTS? + * @pts: PTS value to send. + * @send_dts: Should we send DTS? + * @dts: DTS value to send. + * @n_pes_h_s_bytes: Padding bytes. Might be used by an encoder if needed, gets + * discarded by the decoder. + */ +struct pes_write_args { + void *dest_buf; + void *from; + u32 access_unit_len; + + u32 dest_offset; + u32 dest_buf_sz; + u16 pid; + + u32 encoder_id; + + u8 *continuity_counter; + + u16 stream_id; + + bool send_pts; + u64 pts; + + bool send_dts; + u64 dts; + + u32 n_pes_h_s_bytes; + u64 pcr; +}; + +/** + * vidtv_pes_write_into - Write a PES packet as MPEG-TS packets into a buffer. + * @args: The args to use when writing + * + * This function translate the ES data for one access unit + * from an encoder into MPEG TS packets. It does so by first encapsulating it + * with a PES header and then splitting it into TS packets. + * + * The data is then written into the buffer pointed to by 'args.buf' + * + * Return: The number of bytes written into the buffer. This is usually NOT + * equal to the size of the access unit, since we need space for PES headers, TS headers + * and padding bytes, if any. + */ +u32 vidtv_pes_write_into(struct pes_write_args args); + +#endif // VIDTV_PES_H diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.c b/drivers/media/test-drivers/vidtv/vidtv_psi.c new file mode 100644 index 000000000000..82cf67dd27c0 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_psi.c @@ -0,0 +1,1322 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file contains the logic to work with MPEG Program-Specific Information. + * These are defined both in ISO/IEC 13818-1 (systems) and ETSI EN 300 468. + * PSI is carried in the form of table structures, and although each table might + * technically be broken into one or more sections, we do not do this here, + * hence 'table' and 'section' are interchangeable for vidtv. + * + * This code currently supports three tables: PAT, PMT and SDT. These are the + * bare minimum to get userspace to recognize our MPEG transport stream. It can + * be extended to support more PSI tables in the future. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/crc32.h> +#include <linux/string.h> +#include <linux/printk.h> +#include <linux/ratelimit.h> +#include <linux/string.h> +#include <asm/byteorder.h> + +#include "vidtv_psi.h" +#include "vidtv_common.h" +#include "vidtv_ts.h" + +#define CRC_SIZE_IN_BYTES 4 +#define MAX_VERSION_NUM 32 + +static const u32 CRC_LUT[256] = { + /* from libdvbv5 */ + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +static inline u32 dvb_crc32(u32 crc, u8 *data, u32 len) +{ + /* from libdvbv5 */ + while (len--) + crc = (crc << 8) ^ CRC_LUT[((crc >> 24) ^ *data++) & 0xff]; + return crc; +} + +static void vidtv_psi_update_version_num(struct vidtv_psi_table_header *h) +{ + h->version++; +} + +static inline u16 vidtv_psi_sdt_serv_get_desc_loop_len(struct vidtv_psi_table_sdt_service *s) +{ + u16 mask; + u16 ret; + + mask = GENMASK(11, 0); + + ret = be16_to_cpu(s->bitfield) & mask; + return ret; +} + +static inline u16 vidtv_psi_pmt_stream_get_desc_loop_len(struct vidtv_psi_table_pmt_stream *s) +{ + u16 mask; + u16 ret; + + mask = GENMASK(9, 0); + + ret = be16_to_cpu(s->bitfield2) & mask; + return ret; +} + +static inline u16 vidtv_psi_pmt_get_desc_loop_len(struct vidtv_psi_table_pmt *p) +{ + u16 mask; + u16 ret; + + mask = GENMASK(9, 0); + + ret = be16_to_cpu(p->bitfield2) & mask; + return ret; +} + +static inline u16 vidtv_psi_get_sec_len(struct vidtv_psi_table_header *h) +{ + u16 mask; + u16 ret; + + mask = GENMASK(11, 0); + + ret = be16_to_cpu(h->bitfield) & mask; + return ret; +} + +inline u16 vidtv_psi_get_pat_program_pid(struct vidtv_psi_table_pat_program *p) +{ + u16 mask; + u16 ret; + + mask = GENMASK(12, 0); + + ret = be16_to_cpu(p->bitfield) & mask; + return ret; +} + +inline u16 vidtv_psi_pmt_stream_get_elem_pid(struct vidtv_psi_table_pmt_stream *s) +{ + u16 mask; + u16 ret; + + mask = GENMASK(12, 0); + + ret = be16_to_cpu(s->bitfield) & mask; + return ret; +} + +static inline void vidtv_psi_set_desc_loop_len(__be16 *bitfield, u16 new_len, u8 desc_len_nbits) +{ + u16 mask; + __be16 new; + + mask = GENMASK(15, desc_len_nbits); + + new = cpu_to_be16((be16_to_cpu(*bitfield) & mask) | new_len); + *bitfield = new; +} + +static void vidtv_psi_set_sec_len(struct vidtv_psi_table_header *h, u16 new_len) +{ + u16 old_len = vidtv_psi_get_sec_len(h); + __be16 new; + u16 mask; + + mask = GENMASK(15, 13); + + new = cpu_to_be16((be16_to_cpu(h->bitfield) & mask) | new_len); + + if (old_len > MAX_SECTION_LEN) + pr_warn_ratelimited("section length: %d > %d, old len was %d\n", + new_len, + MAX_SECTION_LEN, + old_len); + + h->bitfield = new; +} + +static u32 vidtv_psi_ts_psi_write_into(struct psi_write_args args) +{ + /* + * Packetize PSI sections into TS packets: + * push a TS header (4bytes) every 184 bytes + * manage the continuity_counter + * add stuffing (i.e. padding bytes) after the CRC + */ + + u32 nbytes_past_boundary = (args.dest_offset % TS_PACKET_LEN); + bool aligned = (nbytes_past_boundary == 0); + struct vidtv_mpeg_ts ts_header = {}; + + /* number of bytes written by this function */ + u32 nbytes = 0; + /* how much there is left to write */ + u32 remaining_len = args.len; + /* how much we can be written in this packet */ + u32 payload_write_len = 0; + /* where we are in the source */ + u32 payload_offset = 0; + + const u16 PAYLOAD_START = args.new_psi_section; + + if (!args.crc && !args.is_crc) + pr_warn_ratelimited("Missing CRC for chunk\n"); + + if (args.crc) + *args.crc = dvb_crc32(*args.crc, args.from, args.len); + + if (args.new_psi_section && !aligned) { + pr_warn_ratelimited("Cannot write a new PSI section in a misaligned buffer\n"); + + /* forcibly align and hope for the best */ + nbytes += vidtv_memset(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + TS_FILL_BYTE, + TS_PACKET_LEN - nbytes_past_boundary); + } + + while (remaining_len) { + nbytes_past_boundary = (args.dest_offset + nbytes) % TS_PACKET_LEN; + aligned = (nbytes_past_boundary == 0); + + if (aligned) { + /* if at a packet boundary, write a new TS header */ + ts_header.sync_byte = TS_SYNC_BYTE; + ts_header.bitfield = cpu_to_be16((PAYLOAD_START << 14) | args.pid); + ts_header.scrambling = 0; + ts_header.continuity_counter = *args.continuity_counter; + ts_header.payload = 1; + /* no adaptation field */ + ts_header.adaptation_field = 0; + + /* copy the header */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + &ts_header, + sizeof(ts_header)); + /* + * This will trigger a discontinuity if the buffer is full, + * effectively dropping the packet. + */ + vidtv_ts_inc_cc(args.continuity_counter); + } + + /* write the pointer_field in the first byte of the payload */ + if (args.new_psi_section) + nbytes += vidtv_memset(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + 0x0, + 1); + + /* write as much of the payload as possible */ + nbytes_past_boundary = (args.dest_offset + nbytes) % TS_PACKET_LEN; + payload_write_len = min(TS_PACKET_LEN - nbytes_past_boundary, remaining_len); + + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + args.from + payload_offset, + payload_write_len); + + /* 'payload_write_len' written from a total of 'len' requested*/ + remaining_len -= payload_write_len; + payload_offset += payload_write_len; + } + + /* + * fill the rest of the packet if there is any remaining space unused + */ + + nbytes_past_boundary = (args.dest_offset + nbytes) % TS_PACKET_LEN; + + if (args.is_crc) + nbytes += vidtv_memset(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + TS_FILL_BYTE, + TS_PACKET_LEN - nbytes_past_boundary); + + return nbytes; +} + +static u32 table_section_crc32_write_into(struct crc32_write_args args) +{ + /* the CRC is the last entry in the section */ + u32 nbytes = 0; + struct psi_write_args psi_args = {}; + + psi_args.dest_buf = args.dest_buf; + psi_args.from = &args.crc; + psi_args.len = CRC_SIZE_IN_BYTES; + psi_args.dest_offset = args.dest_offset; + psi_args.pid = args.pid; + psi_args.new_psi_section = false; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = true; + psi_args.dest_buf_sz = args.dest_buf_sz; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + return nbytes; +} + +struct vidtv_psi_desc_service *vidtv_psi_service_desc_init(struct vidtv_psi_desc *head, + enum service_type service_type, + char *service_name, + char *provider_name) +{ + struct vidtv_psi_desc_service *desc; + u32 service_name_len = service_name ? strlen(service_name) : 0; + u32 provider_name_len = provider_name ? strlen(provider_name) : 0; + + desc = kzalloc(sizeof(*desc), GFP_KERNEL); + + desc->type = SERVICE_DESCRIPTOR; + + desc->length = sizeof_field(struct vidtv_psi_desc_service, service_type) + + sizeof_field(struct vidtv_psi_desc_service, provider_name_len) + + provider_name_len + + sizeof_field(struct vidtv_psi_desc_service, service_name_len) + + service_name_len; + + desc->service_type = service_type; + + desc->service_name_len = service_name_len; + + if (service_name && service_name_len) + desc->service_name = kstrdup(service_name, GFP_KERNEL); + + desc->provider_name_len = provider_name_len; + + if (provider_name && provider_name_len) + desc->provider_name = kstrdup(provider_name, GFP_KERNEL); + + if (head) { + while (head->next) + head = head->next; + + head->next = (struct vidtv_psi_desc *)desc; + } + return desc; +} + +struct vidtv_psi_desc_registration +*vidtv_psi_registration_desc_init(struct vidtv_psi_desc *head, + __be32 format_id, + u8 *additional_ident_info, + u32 additional_info_len) +{ + struct vidtv_psi_desc_registration *desc; + + desc = kzalloc(sizeof(*desc) + sizeof(format_id) + additional_info_len, GFP_KERNEL); + + desc->type = REGISTRATION_DESCRIPTOR; + + desc->length = sizeof_field(struct vidtv_psi_desc_registration, format_id) + + additional_info_len; + + desc->format_id = format_id; + + if (additional_ident_info && additional_info_len) + memcpy(desc->additional_identification_info, + additional_ident_info, + additional_info_len); + + if (head) { + while (head->next) + head = head->next; + + head->next = (struct vidtv_psi_desc *)desc; + } + + return desc; +} + +struct vidtv_psi_desc *vidtv_psi_desc_clone(struct vidtv_psi_desc *desc) +{ + struct vidtv_psi_desc *head = NULL; + struct vidtv_psi_desc *prev = NULL; + struct vidtv_psi_desc *curr = NULL; + + struct vidtv_psi_desc_service *service; + + while (desc) { + switch (desc->type) { + case SERVICE_DESCRIPTOR: + service = (struct vidtv_psi_desc_service *)desc; + curr = (struct vidtv_psi_desc *) + vidtv_psi_service_desc_init(head, + service->service_type, + service->service_name, + service->provider_name); + break; + + case REGISTRATION_DESCRIPTOR: + default: + curr = kzalloc(sizeof(*desc) + desc->length, GFP_KERNEL); + memcpy(curr, desc, sizeof(*desc) + desc->length); + break; + } + + if (curr) + curr->next = NULL; + if (!head) + head = curr; + if (prev) + prev->next = curr; + + prev = curr; + desc = desc->next; + } + + return head; +} + +void vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc) +{ + struct vidtv_psi_desc *curr = desc; + struct vidtv_psi_desc *tmp = NULL; + + while (curr) { + tmp = curr; + curr = curr->next; + + switch (tmp->type) { + case SERVICE_DESCRIPTOR: + kfree(((struct vidtv_psi_desc_service *)tmp)->provider_name); + kfree(((struct vidtv_psi_desc_service *)tmp)->service_name); + + break; + case REGISTRATION_DESCRIPTOR: + /* nothing to do */ + break; + + default: + pr_warn_ratelimited("Possible leak: not handling descriptor type %d\n", + tmp->type); + break; + } + + kfree(tmp); + } +} + +static u16 +vidtv_psi_desc_comp_loop_len(struct vidtv_psi_desc *desc) +{ + u32 length = 0; + + if (!desc) + return 0; + + while (desc) { + length += sizeof_field(struct vidtv_psi_desc, type); + length += sizeof_field(struct vidtv_psi_desc, length); + length += desc->length; /* from 'length' field until the end of the descriptor */ + desc = desc->next; + } + + return length; +} + +void vidtv_psi_desc_assign(struct vidtv_psi_desc **to, + struct vidtv_psi_desc *desc) +{ + if (desc == *to) + return; + + if (*to) + vidtv_psi_desc_destroy(*to); + + *to = desc; +} + +void vidtv_pmt_desc_assign(struct vidtv_psi_table_pmt *pmt, + struct vidtv_psi_desc **to, + struct vidtv_psi_desc *desc) +{ + vidtv_psi_desc_assign(to, desc); + vidtv_psi_pmt_table_update_sec_len(pmt); + + if (vidtv_psi_get_sec_len(&pmt->header) > MAX_SECTION_LEN) + vidtv_psi_desc_assign(to, NULL); + + vidtv_psi_update_version_num(&pmt->header); +} + +void vidtv_sdt_desc_assign(struct vidtv_psi_table_sdt *sdt, + struct vidtv_psi_desc **to, + struct vidtv_psi_desc *desc) +{ + vidtv_psi_desc_assign(to, desc); + vidtv_psi_sdt_table_update_sec_len(sdt); + + if (vidtv_psi_get_sec_len(&sdt->header) > MAX_SECTION_LEN) + vidtv_psi_desc_assign(to, NULL); + + vidtv_psi_update_version_num(&sdt->header); +} + +static u32 vidtv_psi_desc_write_into(struct desc_write_args args) +{ + /* the number of bytes written by this function */ + u32 nbytes = 0; + struct psi_write_args psi_args = {}; + + psi_args.dest_buf = args.dest_buf; + psi_args.from = &args.desc->type; + + psi_args.len = sizeof_field(struct vidtv_psi_desc, type) + + sizeof_field(struct vidtv_psi_desc, length); + + psi_args.dest_offset = args.dest_offset + nbytes; + psi_args.pid = args.pid; + psi_args.new_psi_section = false; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = false; + psi_args.dest_buf_sz = args.dest_buf_sz; + psi_args.crc = args.crc; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + switch (args.desc->type) { + case SERVICE_DESCRIPTOR: + psi_args.dest_offset = args.dest_offset + nbytes; + psi_args.len = sizeof_field(struct vidtv_psi_desc_service, service_type) + + sizeof_field(struct vidtv_psi_desc_service, provider_name_len); + psi_args.from = &((struct vidtv_psi_desc_service *)args.desc)->service_type; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + psi_args.dest_offset = args.dest_offset + nbytes; + psi_args.len = ((struct vidtv_psi_desc_service *)args.desc)->provider_name_len; + psi_args.from = ((struct vidtv_psi_desc_service *)args.desc)->provider_name; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + psi_args.dest_offset = args.dest_offset + nbytes; + psi_args.len = sizeof_field(struct vidtv_psi_desc_service, service_name_len); + psi_args.from = &((struct vidtv_psi_desc_service *)args.desc)->service_name_len; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + psi_args.dest_offset = args.dest_offset + nbytes; + psi_args.len = ((struct vidtv_psi_desc_service *)args.desc)->service_name_len; + psi_args.from = ((struct vidtv_psi_desc_service *)args.desc)->service_name; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + break; + + case REGISTRATION_DESCRIPTOR: + default: + psi_args.dest_offset = args.dest_offset + nbytes; + psi_args.len = args.desc->length; + psi_args.from = &args.desc->data; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + break; + } + + return nbytes; +} + +static u32 +vidtv_psi_table_header_write_into(struct header_write_args args) +{ + /* the number of bytes written by this function */ + u32 nbytes = 0; + struct psi_write_args psi_args = {}; + + psi_args.dest_buf = args.dest_buf; + psi_args.from = args.h; + psi_args.len = sizeof(struct vidtv_psi_table_header); + psi_args.dest_offset = args.dest_offset; + psi_args.pid = args.pid; + psi_args.new_psi_section = true; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = false; + psi_args.dest_buf_sz = args.dest_buf_sz; + psi_args.crc = args.crc; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + return nbytes; +} + +void +vidtv_psi_pat_table_update_sec_len(struct vidtv_psi_table_pat *pat) +{ + /* see ISO/IEC 13818-1 : 2000 p.43 */ + u16 length = 0; + u32 i; + + /* from immediately after 'section_length' until 'last_section_number'*/ + length += PAT_LEN_UNTIL_LAST_SECTION_NUMBER; + + /* do not count the pointer */ + for (i = 0; i < pat->programs; ++i) + length += sizeof(struct vidtv_psi_table_pat_program) - + sizeof(struct vidtv_psi_table_pat_program *); + + length += CRC_SIZE_IN_BYTES; + + vidtv_psi_set_sec_len(&pat->header, length); +} + +void vidtv_psi_pmt_table_update_sec_len(struct vidtv_psi_table_pmt *pmt) +{ + /* see ISO/IEC 13818-1 : 2000 p.46 */ + u16 length = 0; + struct vidtv_psi_table_pmt_stream *s = pmt->stream; + u16 desc_loop_len; + + /* from immediately after 'section_length' until 'program_info_length'*/ + length += PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH; + + desc_loop_len = vidtv_psi_desc_comp_loop_len(pmt->descriptor); + vidtv_psi_set_desc_loop_len(&pmt->bitfield2, desc_loop_len, 10); + + length += desc_loop_len; + + while (s) { + /* skip both pointers at the end */ + length += sizeof(struct vidtv_psi_table_pmt_stream) - + sizeof(struct vidtv_psi_desc *) - + sizeof(struct vidtv_psi_table_pmt_stream *); + + desc_loop_len = vidtv_psi_desc_comp_loop_len(s->descriptor); + vidtv_psi_set_desc_loop_len(&s->bitfield2, desc_loop_len, 10); + + length += desc_loop_len; + + s = s->next; + } + + length += CRC_SIZE_IN_BYTES; + + vidtv_psi_set_sec_len(&pmt->header, length); +} + +void vidtv_psi_sdt_table_update_sec_len(struct vidtv_psi_table_sdt *sdt) +{ + /* see ETSI EN 300 468 V 1.10.1 p.24 */ + u16 length = 0; + struct vidtv_psi_table_sdt_service *s = sdt->service; + u16 desc_loop_len; + + /* + * from immediately after 'section_length' until + * 'reserved_for_future_use' + */ + length += SDT_LEN_UNTIL_RESERVED_FOR_FUTURE_USE; + + while (s) { + /* skip both pointers at the end */ + length += sizeof(struct vidtv_psi_table_sdt_service) - + sizeof(struct vidtv_psi_desc *) - + sizeof(struct vidtv_psi_table_sdt_service *); + + desc_loop_len = vidtv_psi_desc_comp_loop_len(s->descriptor); + vidtv_psi_set_desc_loop_len(&s->bitfield, desc_loop_len, 12); + + length += desc_loop_len; + + s = s->next; + } + + length += CRC_SIZE_IN_BYTES; + + vidtv_psi_set_sec_len(&sdt->header, length); +} + +struct vidtv_psi_table_pat_program* +vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head, + u16 service_id, + u16 program_map_pid) +{ + struct vidtv_psi_table_pat_program *program; + const u16 RESERVED = 0x07; + + program = kzalloc(sizeof(*program), GFP_KERNEL); + + program->service_id = cpu_to_be16(service_id); + + /* pid for the PMT section in the TS */ + program->bitfield = cpu_to_be16((RESERVED << 13) | program_map_pid); + program->next = NULL; + + if (head) { + while (head->next) + head = head->next; + + head->next = program; + } + + return program; +} + +void +vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p) +{ + struct vidtv_psi_table_pat_program *curr = p; + struct vidtv_psi_table_pat_program *tmp = NULL; + + while (curr) { + tmp = curr; + curr = curr->next; + kfree(tmp); + } +} + +void +vidtv_psi_pat_program_assign(struct vidtv_psi_table_pat *pat, + struct vidtv_psi_table_pat_program *p) +{ + /* This function transfers ownership of p to the table */ + + u16 program_count = 0; + struct vidtv_psi_table_pat_program *program = p; + + if (p == pat->program) + return; + + while (program) { + ++program_count; + program = program->next; + } + + pat->programs = program_count; + pat->program = p; + + /* Recompute section length */ + vidtv_psi_pat_table_update_sec_len(pat); + + if (vidtv_psi_get_sec_len(&pat->header) > MAX_SECTION_LEN) + vidtv_psi_pat_program_assign(pat, NULL); + + vidtv_psi_update_version_num(&pat->header); +} + +struct vidtv_psi_table_pat *vidtv_psi_pat_table_init(u16 transport_stream_id) +{ + struct vidtv_psi_table_pat *pat = kzalloc(sizeof(*pat), GFP_KERNEL); + const u16 SYNTAX = 0x1; + const u16 ZERO = 0x0; + const u16 ONES = 0x03; + + pat->header.table_id = 0x0; + + pat->header.bitfield = cpu_to_be16((SYNTAX << 15) | (ZERO << 14) | (ONES << 12)); + pat->header.id = cpu_to_be16(transport_stream_id); + pat->header.current_next = 0x1; + + pat->header.version = 0x1f; + + pat->header.one2 = 0x03; + pat->header.section_id = 0x0; + pat->header.last_section = 0x0; + + pat->programs = 0; + + vidtv_psi_pat_table_update_sec_len(pat); + + return pat; +} + +u32 vidtv_psi_pat_write_into(struct vidtv_psi_pat_write_args args) +{ + /* the number of bytes written by this function */ + u32 nbytes = 0; + const u16 pat_pid = VIDTV_PAT_PID; + u32 crc = 0xffffffff; + + struct vidtv_psi_table_pat_program *p = args.pat->program; + + struct header_write_args h_args = {}; + struct psi_write_args psi_args = {}; + struct crc32_write_args c_args = {}; + + vidtv_psi_pat_table_update_sec_len(args.pat); + + h_args.dest_buf = args.buf; + h_args.dest_offset = args.offset; + h_args.h = &args.pat->header; + h_args.pid = pat_pid; + h_args.continuity_counter = args.continuity_counter; + h_args.dest_buf_sz = args.buf_sz; + h_args.crc = &crc; + + nbytes += vidtv_psi_table_header_write_into(h_args); + + /* note that the field 'u16 programs' is not really part of the PAT */ + + psi_args.dest_buf = args.buf; + psi_args.pid = pat_pid; + psi_args.new_psi_section = false; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = false; + psi_args.dest_buf_sz = args.buf_sz; + psi_args.crc = &crc; + + while (p) { + /* copy the PAT programs */ + psi_args.from = p; + /* skip the pointer */ + psi_args.len = sizeof(*p) - + sizeof(struct vidtv_psi_table_pat_program *); + psi_args.dest_offset = args.offset + nbytes; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + p = p->next; + } + + c_args.dest_buf = args.buf; + c_args.dest_offset = args.offset + nbytes; + c_args.crc = cpu_to_be32(crc); + c_args.pid = pat_pid; + c_args.continuity_counter = args.continuity_counter; + c_args.dest_buf_sz = args.buf_sz; + + /* Write the CRC32 at the end */ + nbytes += table_section_crc32_write_into(c_args); + + return nbytes; +} + +void +vidtv_psi_pat_table_destroy(struct vidtv_psi_table_pat *p) +{ + vidtv_psi_pat_program_destroy(p->program); + kfree(p); +} + +struct vidtv_psi_table_pmt_stream* +vidtv_psi_pmt_stream_init(struct vidtv_psi_table_pmt_stream *head, + enum vidtv_psi_stream_types stream_type, + u16 es_pid) +{ + struct vidtv_psi_table_pmt_stream *stream; + const u16 RESERVED1 = 0x07; + const u16 RESERVED2 = 0x0f; + const u16 ZERO = 0x0; + u16 desc_loop_len; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + + stream->type = stream_type; + + stream->bitfield = cpu_to_be16((RESERVED1 << 13) | es_pid); + + desc_loop_len = vidtv_psi_desc_comp_loop_len(stream->descriptor); + + stream->bitfield2 = cpu_to_be16((RESERVED2 << 12) | + (ZERO << 10) | + desc_loop_len); + stream->next = NULL; + + if (head) { + while (head->next) + head = head->next; + + head->next = stream; + } + + return stream; +} + +void vidtv_psi_pmt_stream_destroy(struct vidtv_psi_table_pmt_stream *s) +{ + struct vidtv_psi_table_pmt_stream *curr_stream = s; + struct vidtv_psi_table_pmt_stream *tmp_stream = NULL; + + while (curr_stream) { + tmp_stream = curr_stream; + curr_stream = curr_stream->next; + vidtv_psi_desc_destroy(tmp_stream->descriptor); + kfree(tmp_stream); + } +} + +void vidtv_psi_pmt_stream_assign(struct vidtv_psi_table_pmt *pmt, + struct vidtv_psi_table_pmt_stream *s) +{ + /* This function transfers ownership of s to the table */ + if (s == pmt->stream) + return; + + pmt->stream = s; + vidtv_psi_pmt_table_update_sec_len(pmt); + + if (vidtv_psi_get_sec_len(&pmt->header) > MAX_SECTION_LEN) + vidtv_psi_pmt_stream_assign(pmt, NULL); + + vidtv_psi_update_version_num(&pmt->header); +} + +u16 vidtv_psi_pmt_get_pid(struct vidtv_psi_table_pmt *section, + struct vidtv_psi_table_pat *pat) +{ + struct vidtv_psi_table_pat_program *program = pat->program; + + /* + * service_id is the same as program_number in the + * corresponding program_map_section + * see ETSI EN 300 468 v1.15.1 p. 24 + */ + while (program) { + if (program->service_id == section->header.id) + return vidtv_psi_get_pat_program_pid(program); + + program = program->next; + } + + return TS_LAST_VALID_PID + 1; /* not found */ +} + +struct vidtv_psi_table_pmt *vidtv_psi_pmt_table_init(u16 program_number, + u16 pcr_pid) +{ + struct vidtv_psi_table_pmt *pmt = kzalloc(sizeof(*pmt), GFP_KERNEL); + const u16 SYNTAX = 0x1; + const u16 ZERO = 0x0; + const u16 ONES = 0x03; + const u16 RESERVED1 = 0x07; + const u16 RESERVED2 = 0x0f; + u16 desc_loop_len; + + if (!pcr_pid) + pcr_pid = 0x1fff; + + pmt->header.table_id = 0x2; + + pmt->header.bitfield = cpu_to_be16((SYNTAX << 15) | (ZERO << 14) | (ONES << 12)); + + pmt->header.id = cpu_to_be16(program_number); + pmt->header.current_next = 0x1; + + pmt->header.version = 0x1f; + + pmt->header.one2 = ONES; + pmt->header.section_id = 0; + pmt->header.last_section = 0; + + pmt->bitfield = cpu_to_be16((RESERVED1 << 13) | pcr_pid); + + desc_loop_len = vidtv_psi_desc_comp_loop_len(pmt->descriptor); + + pmt->bitfield2 = cpu_to_be16((RESERVED2 << 12) | + (ZERO << 10) | + desc_loop_len); + + vidtv_psi_pmt_table_update_sec_len(pmt); + + return pmt; +} + +u32 vidtv_psi_pmt_write_into(struct vidtv_psi_pmt_write_args args) +{ + /* the number of bytes written by this function */ + u32 nbytes = 0; + u32 crc = 0xffffffff; + + struct vidtv_psi_desc *table_descriptor = args.pmt->descriptor; + struct vidtv_psi_table_pmt_stream *stream = args.pmt->stream; + struct vidtv_psi_desc *stream_descriptor = (stream) ? + args.pmt->stream->descriptor : + NULL; + + struct header_write_args h_args = {}; + struct psi_write_args psi_args = {}; + struct desc_write_args d_args = {}; + struct crc32_write_args c_args = {}; + + vidtv_psi_pmt_table_update_sec_len(args.pmt); + + h_args.dest_buf = args.buf; + h_args.dest_offset = args.offset; + h_args.h = &args.pmt->header; + h_args.pid = args.pid; + h_args.continuity_counter = args.continuity_counter; + h_args.dest_buf_sz = args.buf_sz; + h_args.crc = &crc; + + nbytes += vidtv_psi_table_header_write_into(h_args); + + /* write the two bitfields */ + psi_args.dest_buf = args.buf; + psi_args.from = &args.pmt->bitfield; + psi_args.len = sizeof_field(struct vidtv_psi_table_pmt, bitfield) + + sizeof_field(struct vidtv_psi_table_pmt, bitfield2); + + psi_args.dest_offset = args.offset + nbytes; + psi_args.pid = args.pid; + psi_args.new_psi_section = false; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = false; + psi_args.dest_buf_sz = args.buf_sz; + psi_args.crc = &crc; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + while (table_descriptor) { + /* write the descriptors, if any */ + d_args.dest_buf = args.buf; + d_args.dest_offset = args.offset + nbytes; + d_args.desc = table_descriptor; + d_args.pid = args.pid; + d_args.continuity_counter = args.continuity_counter; + d_args.dest_buf_sz = args.buf_sz; + d_args.crc = &crc; + + nbytes += vidtv_psi_desc_write_into(d_args); + + table_descriptor = table_descriptor->next; + } + + while (stream) { + /* write the streams, if any */ + psi_args.from = stream; + psi_args.len = sizeof_field(struct vidtv_psi_table_pmt_stream, type) + + sizeof_field(struct vidtv_psi_table_pmt_stream, bitfield) + + sizeof_field(struct vidtv_psi_table_pmt_stream, bitfield2); + psi_args.dest_offset = args.offset + nbytes; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + while (stream_descriptor) { + /* write the stream descriptors, if any */ + d_args.dest_buf = args.buf; + d_args.dest_offset = args.offset + nbytes; + d_args.desc = stream_descriptor; + d_args.pid = args.pid; + d_args.continuity_counter = args.continuity_counter; + d_args.dest_buf_sz = args.buf_sz; + d_args.crc = &crc; + + nbytes += vidtv_psi_desc_write_into(d_args); + + stream_descriptor = stream_descriptor->next; + } + + stream = stream->next; + } + + c_args.dest_buf = args.buf; + c_args.dest_offset = args.offset + nbytes; + c_args.crc = cpu_to_be32(crc); + c_args.pid = args.pid; + c_args.continuity_counter = args.continuity_counter; + c_args.dest_buf_sz = args.buf_sz; + + /* Write the CRC32 at the end */ + nbytes += table_section_crc32_write_into(c_args); + + return nbytes; +} + +void vidtv_psi_pmt_table_destroy(struct vidtv_psi_table_pmt *pmt) +{ + vidtv_psi_desc_destroy(pmt->descriptor); + vidtv_psi_pmt_stream_destroy(pmt->stream); + kfree(pmt); +} + +struct vidtv_psi_table_sdt *vidtv_psi_sdt_table_init(u16 transport_stream_id) +{ + struct vidtv_psi_table_sdt *sdt = kzalloc(sizeof(*sdt), GFP_KERNEL); + const u16 SYNTAX = 0x1; + const u16 ONE = 0x1; + const u16 ONES = 0x03; + const u16 RESERVED = 0xff; + + sdt->header.table_id = 0x42; + + sdt->header.bitfield = cpu_to_be16((SYNTAX << 15) | (ONE << 14) | (ONES << 12)); + + /* + * This is a 16-bit field which serves as a label for identification + * of the TS, about which the SDT informs, from any other multiplex + * within the delivery system. + */ + sdt->header.id = cpu_to_be16(transport_stream_id); + sdt->header.current_next = ONE; + + sdt->header.version = 0x1f; + + sdt->header.one2 = ONES; + sdt->header.section_id = 0; + sdt->header.last_section = 0; + + /* + * FIXME: The network_id range from 0xFF01 to 0xFFFF is used to + * indicate temporary private use. For now, let's use the first + * value. + * This can be changed to something more useful, when support for + * NIT gets added + */ + sdt->network_id = cpu_to_be16(0xff01); + sdt->reserved = RESERVED; + + vidtv_psi_sdt_table_update_sec_len(sdt); + + return sdt; +} + +u32 vidtv_psi_sdt_write_into(struct vidtv_psi_sdt_write_args args) +{ + u32 nbytes = 0; + u16 sdt_pid = VIDTV_SDT_PID; /* see ETSI EN 300 468 v1.15.1 p. 11 */ + + u32 crc = 0xffffffff; + + struct vidtv_psi_table_sdt_service *service = args.sdt->service; + struct vidtv_psi_desc *service_desc = (args.sdt->service) ? + args.sdt->service->descriptor : + NULL; + + struct header_write_args h_args = {}; + struct psi_write_args psi_args = {}; + struct desc_write_args d_args = {}; + struct crc32_write_args c_args = {}; + + vidtv_psi_sdt_table_update_sec_len(args.sdt); + + h_args.dest_buf = args.buf; + h_args.dest_offset = args.offset; + h_args.h = &args.sdt->header; + h_args.pid = sdt_pid; + h_args.continuity_counter = args.continuity_counter; + h_args.dest_buf_sz = args.buf_sz; + h_args.crc = &crc; + + nbytes += vidtv_psi_table_header_write_into(h_args); + + psi_args.dest_buf = args.buf; + psi_args.from = &args.sdt->network_id; + + psi_args.len = sizeof_field(struct vidtv_psi_table_sdt, network_id) + + sizeof_field(struct vidtv_psi_table_sdt, reserved); + + psi_args.dest_offset = args.offset + nbytes; + psi_args.pid = sdt_pid; + psi_args.new_psi_section = false; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = false; + psi_args.dest_buf_sz = args.buf_sz; + psi_args.crc = &crc; + + /* copy u16 network_id + u8 reserved)*/ + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + while (service) { + /* copy the services, if any */ + psi_args.from = service; + /* skip both pointers at the end */ + psi_args.len = sizeof(struct vidtv_psi_table_sdt_service) - + sizeof(struct vidtv_psi_desc *) - + sizeof(struct vidtv_psi_table_sdt_service *); + psi_args.dest_offset = args.offset + nbytes; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + while (service_desc) { + /* copy the service descriptors, if any */ + d_args.dest_buf = args.buf; + d_args.dest_offset = args.offset + nbytes; + d_args.desc = service_desc; + d_args.pid = sdt_pid; + d_args.continuity_counter = args.continuity_counter; + d_args.dest_buf_sz = args.buf_sz; + d_args.crc = &crc; + + nbytes += vidtv_psi_desc_write_into(d_args); + + service_desc = service_desc->next; + } + + service = service->next; + } + + c_args.dest_buf = args.buf; + c_args.dest_offset = args.offset + nbytes; + c_args.crc = cpu_to_be32(crc); + c_args.pid = sdt_pid; + c_args.continuity_counter = args.continuity_counter; + c_args.dest_buf_sz = args.buf_sz; + + /* Write the CRC at the end */ + nbytes += table_section_crc32_write_into(c_args); + + return nbytes; +} + +void vidtv_psi_sdt_table_destroy(struct vidtv_psi_table_sdt *sdt) +{ + vidtv_psi_sdt_service_destroy(sdt->service); + kfree(sdt); +} + +struct vidtv_psi_table_sdt_service +*vidtv_psi_sdt_service_init(struct vidtv_psi_table_sdt_service *head, + u16 service_id) +{ + struct vidtv_psi_table_sdt_service *service; + + service = kzalloc(sizeof(*service), GFP_KERNEL); + + /* + * ETSI 300 468: this is a 16bit field which serves as a label to + * identify this service from any other service within the TS. + * The service id is the same as the program number in the + * corresponding program_map_section + */ + service->service_id = cpu_to_be16(service_id); + service->EIT_schedule = 0x0; + service->EIT_present_following = 0x0; + service->reserved = 0x3f; + + service->bitfield = cpu_to_be16(RUNNING << 13); + + if (head) { + while (head->next) + head = head->next; + + head->next = service; + } + + return service; +} + +void +vidtv_psi_sdt_service_destroy(struct vidtv_psi_table_sdt_service *service) +{ + struct vidtv_psi_table_sdt_service *curr = service; + struct vidtv_psi_table_sdt_service *tmp = NULL; + + while (curr) { + tmp = curr; + curr = curr->next; + vidtv_psi_desc_destroy(tmp->descriptor); + kfree(tmp); + } +} + +void +vidtv_psi_sdt_service_assign(struct vidtv_psi_table_sdt *sdt, + struct vidtv_psi_table_sdt_service *service) +{ + if (service == sdt->service) + return; + + sdt->service = service; + + /* recompute section length */ + vidtv_psi_sdt_table_update_sec_len(sdt); + + if (vidtv_psi_get_sec_len(&sdt->header) > MAX_SECTION_LEN) + vidtv_psi_sdt_service_assign(sdt, NULL); + + vidtv_psi_update_version_num(&sdt->header); +} + +struct vidtv_psi_table_pmt** +vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat, u16 pcr_pid) + +{ + /* + * PMTs contain information about programs. For each program, + * there is one PMT section. This function will create a section + * for each program found in the PAT + */ + struct vidtv_psi_table_pat_program *program = pat->program; + struct vidtv_psi_table_pmt **pmt_secs; + u32 i = 0; + + /* a section for each program_id */ + pmt_secs = kcalloc(pat->programs, + sizeof(struct vidtv_psi_table_pmt *), + GFP_KERNEL); + + while (program) { + pmt_secs[i] = vidtv_psi_pmt_table_init(be16_to_cpu(program->service_id), pcr_pid); + ++i; + program = program->next; + } + + return pmt_secs; +} + +struct vidtv_psi_table_pmt +*vidtv_psi_find_pmt_sec(struct vidtv_psi_table_pmt **pmt_sections, + u16 nsections, + u16 program_num) +{ + /* find the PMT section associated with 'program_num' */ + struct vidtv_psi_table_pmt *sec = NULL; + u32 i; + + for (i = 0; i < nsections; ++i) { + sec = pmt_sections[i]; + if (be16_to_cpu(sec->header.id) == program_num) + return sec; + } + + return NULL; /* not found */ +} diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.h b/drivers/media/test-drivers/vidtv/vidtv_psi.h new file mode 100644 index 000000000000..3f962cc78278 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_psi.h @@ -0,0 +1,577 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This file contains the logic to work with MPEG Program-Specific Information. + * These are defined both in ISO/IEC 13818-1 (systems) and ETSI EN 300 468. + * PSI is carried in the form of table structures, and although each table might + * technically be broken into one or more sections, we do not do this here, + * hence 'table' and 'section' are interchangeable for vidtv. + * + * This code currently supports three tables: PAT, PMT and SDT. These are the + * bare minimum to get userspace to recognize our MPEG transport stream. It can + * be extended to support more PSI tables in the future. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#ifndef VIDTV_PSI_H +#define VIDTV_PSI_H + +#include <linux/types.h> +#include <asm/byteorder.h> + +/* + * all section lengths start immediately after the 'section_length' field + * see ISO/IEC 13818-1 : 2000 and ETSI EN 300 468 V 1.10.1 for + * reference + */ +#define PAT_LEN_UNTIL_LAST_SECTION_NUMBER 5 +#define PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH 9 +#define SDT_LEN_UNTIL_RESERVED_FOR_FUTURE_USE 8 +#define MAX_SECTION_LEN 1021 +#define VIDTV_PAT_PID 0 /* mandated by the specs */ +#define VIDTV_SDT_PID 0x0011 /* mandated by the specs */ + +enum vidtv_psi_descriptors { + REGISTRATION_DESCRIPTOR = 0x05, /* See ISO/IEC 13818-1 section 2.6.8 */ + SERVICE_DESCRIPTOR = 0x48, /* See ETSI EN 300 468 section 6.2.33 */ +}; + +enum vidtv_psi_stream_types { + STREAM_PRIVATE_DATA = 0x06, /* see ISO/IEC 13818-1 2000 p. 48 */ +}; + +/** + * struct vidtv_psi_desc - A generic PSI descriptor type. + * The descriptor length is an 8-bit field specifying the total number of bytes of the data portion + * of the descriptor following the byte defining the value of this field. + */ +struct vidtv_psi_desc { + struct vidtv_psi_desc *next; + u8 type; + u8 length; + u8 data[]; +} __packed; + +/** + * struct vidtv_psi_desc_service - Service descriptor. + * See ETSI EN 300 468 section 6.2.33. + */ +struct vidtv_psi_desc_service { + struct vidtv_psi_desc *next; + u8 type; + u8 length; + + u8 service_type; + u8 provider_name_len; + char *provider_name; + u8 service_name_len; + char *service_name; +} __packed; + +/** + * struct vidtv_psi_desc_registration - A registration descriptor. + * See ISO/IEC 13818-1 section 2.6.8 + */ +struct vidtv_psi_desc_registration { + struct vidtv_psi_desc *next; + u8 type; + u8 length; + + /* + * The format_identifier is a 32-bit value obtained from a Registration + * Authority as designated by ISO/IEC JTC 1/SC 29. + */ + __be32 format_id; + /* + * The meaning of additional_identification_info bytes, if any, are + * defined by the assignee of that format_identifier, and once defined + * they shall not change. + */ + u8 additional_identification_info[]; +} __packed; + +/** + * struct vidtv_psi_table_header - A header that is present for all PSI tables. + */ +struct vidtv_psi_table_header { + u8 table_id; + + __be16 bitfield; /* syntax: 1, zero: 1, one: 2, section_length: 13 */ + + __be16 id; /* TS ID */ + u8 current_next:1; + u8 version:5; + u8 one2:2; + u8 section_id; /* section_number */ + u8 last_section; /* last_section_number */ +} __packed; + +/** + * struct vidtv_psi_table_pat_program - A single program in the PAT + * See ISO/IEC 13818-1 : 2000 p.43 + */ +struct vidtv_psi_table_pat_program { + __be16 service_id; + __be16 bitfield; /* reserved: 3, program_map_pid/network_pid: 13 */ + struct vidtv_psi_table_pat_program *next; +} __packed; + +/** + * struct vidtv_psi_table_pat - The Program Allocation Table (PAT) + * See ISO/IEC 13818-1 : 2000 p.43 + */ +struct vidtv_psi_table_pat { + struct vidtv_psi_table_header header; + u16 programs; /* Included by libdvbv5, not part of the table and not actually serialized */ + struct vidtv_psi_table_pat_program *program; +} __packed; + +/** + * struct vidtv_psi_table_sdt_service - Represents a service in the SDT. + * see ETSI EN 300 468 v1.15.1 section 5.2.3. + */ +struct vidtv_psi_table_sdt_service { + __be16 service_id; + u8 EIT_present_following:1; + u8 EIT_schedule:1; + u8 reserved:6; + __be16 bitfield; /* running_status: 3, free_ca:1, desc_loop_len:12 */ + struct vidtv_psi_desc *descriptor; + struct vidtv_psi_table_sdt_service *next; +} __packed; + +/** + * struct vidtv_psi_table_sdt - Represents the Service Description Table + * see ETSI EN 300 468 v1.15.1 section 5.2.3. + */ + +struct vidtv_psi_table_sdt { + struct vidtv_psi_table_header header; + __be16 network_id; /* original_network_id */ + u8 reserved; + struct vidtv_psi_table_sdt_service *service; +} __packed; + +/** + * enum service_running_status - Status of a SDT service. + * see ETSI EN 300 468 v1.15.1 section 5.2.3 table 6. + */ +enum service_running_status { + RUNNING = 0x4, +}; + +/** + * enum service_type - The type of a SDT service. + * see ETSI EN 300 468 v1.15.1 section 6.2.33, table 81. + */ +enum service_type { + /* see ETSI EN 300 468 v1.15.1 p. 77 */ + DIGITAL_TELEVISION_SERVICE = 0x1, +}; + +/** + * struct vidtv_psi_table_pmt_stream - A single stream in the PMT. + * See ISO/IEC 13818-1 : 2000 p.46. + */ +struct vidtv_psi_table_pmt_stream { + u8 type; + __be16 bitfield; /* reserved: 3, elementary_pid: 13 */ + __be16 bitfield2; /*reserved: 4, zero: 2, desc_length: 10 */ + struct vidtv_psi_desc *descriptor; + struct vidtv_psi_table_pmt_stream *next; +} __packed; + +/** + * struct vidtv_psi_table_pmt - The Program Map Table (PMT). + * See ISO/IEC 13818-1 : 2000 p.46. + */ +struct vidtv_psi_table_pmt { + struct vidtv_psi_table_header header; + __be16 bitfield; /* reserved:3, pcr_pid: 13 */ + __be16 bitfield2; /* reserved: 4, zero: 2, desc_len: 10 */ + struct vidtv_psi_desc *descriptor; + struct vidtv_psi_table_pmt_stream *stream; +} __packed; + +/** + * struct psi_write_args - Arguments for the PSI packetizer. + * @dest_buf: The buffer to write into. + * @from: PSI data to be copied. + * @len: How much to write. + * @dest_offset: where to start writing in the dest_buffer. + * @pid: TS packet ID. + * @new_psi_section: Set when starting a table section. + * @continuity_counter: Incremented on every new packet. + * @is_crc: Set when writing the CRC at the end. + * @dest_buf_sz: The size of the dest_buffer + * @crc: a pointer to store the crc for this chunk + */ +struct psi_write_args { + void *dest_buf; + void *from; + size_t len; + u32 dest_offset; + u16 pid; + bool new_psi_section; + u8 *continuity_counter; + bool is_crc; + u32 dest_buf_sz; + u32 *crc; +}; + +/** + * struct desc_write_args - Arguments in order to write a descriptor. + * @dest_buf: The buffer to write into. + * @dest_offset: where to start writing in the dest_buffer. + * @desc: A pointer to the descriptor + * @pid: TS packet ID. + * @continuity_counter: Incremented on every new packet. + * @dest_buf_sz: The size of the dest_buffer + * @crc: a pointer to store the crc for this chunk + */ +struct desc_write_args { + void *dest_buf; + u32 dest_offset; + struct vidtv_psi_desc *desc; + u16 pid; + u8 *continuity_counter; + u32 dest_buf_sz; + u32 *crc; +}; + +/** + * struct crc32_write_args - Arguments in order to write the CRC at the end of + * the PSI tables. + * @dest_buf: The buffer to write into. + * @dest_offset: where to start writing in the dest_buffer. + * @crc: the CRC value to write + * @pid: TS packet ID. + * @continuity_counter: Incremented on every new packet. + * @dest_buf_sz: The size of the dest_buffer + */ +struct crc32_write_args { + void *dest_buf; + u32 dest_offset; + __be32 crc; + u16 pid; + u8 *continuity_counter; + u32 dest_buf_sz; +}; + +/** + * struct header_write_args - Arguments in order to write the common table + * header + * @dest_buf: The buffer to write into. + * @dest_offset: where to start writing in the dest_buffer. + * @h: a pointer to the header. + * @pid: TS packet ID. + * @continuity_counter: Incremented on every new packet. + * @dest_buf_sz: The size of the dest_buffer + * @crc: a pointer to store the crc for this chunk + */ +struct header_write_args { + void *dest_buf; + u32 dest_offset; + struct vidtv_psi_table_header *h; + u16 pid; + u8 *continuity_counter; + u32 dest_buf_sz; + u32 *crc; +}; + +struct vidtv_psi_desc_service *vidtv_psi_service_desc_init(struct vidtv_psi_desc *head, + enum service_type service_type, + char *service_name, + char *provider_name); + +struct vidtv_psi_desc_registration +*vidtv_psi_registration_desc_init(struct vidtv_psi_desc *head, + __be32 format_id, + u8 *additional_ident_info, + u32 additional_info_len); + +struct vidtv_psi_table_pat_program +*vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head, + u16 service_id, + u16 program_map_pid); + +struct vidtv_psi_table_pmt_stream* +vidtv_psi_pmt_stream_init(struct vidtv_psi_table_pmt_stream *head, + enum vidtv_psi_stream_types stream_type, + u16 es_pid); + +struct vidtv_psi_table_pat *vidtv_psi_pat_table_init(u16 transport_stream_id); + +struct vidtv_psi_table_pmt *vidtv_psi_pmt_table_init(u16 program_number, + u16 pcr_pid); + +struct vidtv_psi_table_sdt *vidtv_psi_sdt_table_init(u16 transport_stream_id); + +struct vidtv_psi_table_sdt_service* +vidtv_psi_sdt_service_init(struct vidtv_psi_table_sdt_service *head, + u16 service_id); + +void +vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc); + +void +vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p); + +void +vidtv_psi_pat_table_destroy(struct vidtv_psi_table_pat *p); + +void +vidtv_psi_pmt_stream_destroy(struct vidtv_psi_table_pmt_stream *s); + +void +vidtv_psi_pmt_table_destroy(struct vidtv_psi_table_pmt *pmt); + +void +vidtv_psi_sdt_table_destroy(struct vidtv_psi_table_sdt *sdt); + +void +vidtv_psi_sdt_service_destroy(struct vidtv_psi_table_sdt_service *service); + +/** + * vidtv_psi_sdt_service_assign - Assigns the service loop to the SDT. + * @sdt: The SDT to assign to. + * @service: The service loop (one or more services) + * + * This will free the previous service loop in the table. + * This will assign ownership of the service loop to the table, i.e. the table + * will free this service loop when a call to its destroy function is made. + */ +void +vidtv_psi_sdt_service_assign(struct vidtv_psi_table_sdt *sdt, + struct vidtv_psi_table_sdt_service *service); + +/** + * vidtv_psi_desc_assign - Assigns a descriptor loop at some point + * @to: Where to assign this descriptor loop to + * @desc: The descriptor loop that will be assigned. + * + * This will free the loop in 'to', if any. + */ +void vidtv_psi_desc_assign(struct vidtv_psi_desc **to, + struct vidtv_psi_desc *desc); + +/** + * vidtv_psi_pmt_desc_assign - Assigns a descriptor loop at some point in a PMT section. + * @pmt: The PMT section that will contain the descriptor loop + * @to: Where in the PMT to assign this descriptor loop to + * @desc: The descriptor loop that will be assigned. + * + * This will free the loop in 'to', if any. + * This will assign ownership of the loop to the table, i.e. the table + * will free this loop when a call to its destroy function is made. + */ +void vidtv_pmt_desc_assign(struct vidtv_psi_table_pmt *pmt, + struct vidtv_psi_desc **to, + struct vidtv_psi_desc *desc); + +/** + * vidtv_psi_sdt_desc_assign - Assigns a descriptor loop at some point in a SDT. + * @sdt: The SDT that will contain the descriptor loop + * @to: Where in the PMT to assign this descriptor loop to + * @desc: The descriptor loop that will be assigned. + * + * This will free the loop in 'to', if any. + * This will assign ownership of the loop to the table, i.e. the table + * will free this loop when a call to its destroy function is made. + */ +void vidtv_sdt_desc_assign(struct vidtv_psi_table_sdt *sdt, + struct vidtv_psi_desc **to, + struct vidtv_psi_desc *desc); + +/** + * vidtv_psi_pat_program_assign - Assigns the program loop to the PAT. + * @pat: The PAT to assign to. + * @p: The program loop (one or more programs) + * + * This will free the previous program loop in the table. + * This will assign ownership of the program loop to the table, i.e. the table + * will free this program loop when a call to its destroy function is made. + */ +void vidtv_psi_pat_program_assign(struct vidtv_psi_table_pat *pat, + struct vidtv_psi_table_pat_program *p); + +/** + * vidtv_psi_pmt_stream_assign - Assigns the stream loop to the PAT. + * @pmt: The PMT to assign to. + * @s: The stream loop (one or more streams) + * + * This will free the previous stream loop in the table. + * This will assign ownership of the stream loop to the table, i.e. the table + * will free this stream loop when a call to its destroy function is made. + */ +void vidtv_psi_pmt_stream_assign(struct vidtv_psi_table_pmt *pmt, + struct vidtv_psi_table_pmt_stream *s); + +struct vidtv_psi_desc *vidtv_psi_desc_clone(struct vidtv_psi_desc *desc); + +/** + * vidtv_psi_create_sec_for_each_pat_entry - Create a PMT section for each + * program found in the PAT + * @pat: The PAT to look for programs. + * @s: The stream loop (one or more streams) + * @pcr_pid: packet ID for the PCR to be used for the program described in this + * PMT section + */ +struct vidtv_psi_table_pmt** +vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat, u16 pcr_pid); + +/** + * vidtv_psi_pmt_get_pid - Get the TS PID for a PMT section. + * @section: The PMT section whose PID we want to retrieve. + * @pat: The PAT table to look into. + * + * Returns: the TS PID for 'section' + */ +u16 vidtv_psi_pmt_get_pid(struct vidtv_psi_table_pmt *section, + struct vidtv_psi_table_pat *pat); + +/** + * vidtv_psi_pat_table_update_sec_len - Recompute and update the PAT section length. + * @pat: The PAT whose length is to be updated. + * + * This will traverse the table and accumulate the length of its components, + * which is then used to replace the 'section_length' field. + * + * If section_length > MAX_SECTION_LEN, the operation fails. + */ +void vidtv_psi_pat_table_update_sec_len(struct vidtv_psi_table_pat *pat); + +/** + * vidtv_psi_pmt_table_update_sec_len - Recompute and update the PMT section length. + * @pmt: The PMT whose length is to be updated. + * + * This will traverse the table and accumulate the length of its components, + * which is then used to replace the 'section_length' field. + * + * If section_length > MAX_SECTION_LEN, the operation fails. + */ +void vidtv_psi_pmt_table_update_sec_len(struct vidtv_psi_table_pmt *pmt); + +/** + * vidtv_psi_sdt_table_update_sec_len - Recompute and update the SDT section length. + * @sdt: The SDT whose length is to be updated. + * + * This will traverse the table and accumulate the length of its components, + * which is then used to replace the 'section_length' field. + * + * If section_length > MAX_SECTION_LEN, the operation fails. + */ +void vidtv_psi_sdt_table_update_sec_len(struct vidtv_psi_table_sdt *sdt); + +/** + * struct vidtv_psi_pat_write_args - Arguments for writing a PAT table + * @buf: The destination buffer. + * @offset: The offset into the destination buffer. + * @pat: A pointer to the PAT. + * @buf_sz: The size of the destination buffer. + * @continuity_counter: A pointer to the CC. Incremented on every new packet. + * + */ +struct vidtv_psi_pat_write_args { + char *buf; + u32 offset; + struct vidtv_psi_table_pat *pat; + u32 buf_sz; + u8 *continuity_counter; +}; + +/** + * vidtv_psi_pat_write_into - Write PAT as MPEG-TS packets into a buffer. + * @args: An instance of struct vidtv_psi_pat_write_args + * + * This function writes the MPEG TS packets for a PAT table into a buffer. + * Calling code will usually generate the PAT via a call to its init function + * and thus is responsible for freeing it. + * + * Return: The number of bytes written into the buffer. This is NOT + * equal to the size of the PAT, since more space is needed for TS headers during TS + * encapsulation. + */ +u32 vidtv_psi_pat_write_into(struct vidtv_psi_pat_write_args args); + +/** + * struct vidtv_psi_sdt_write_args - Arguments for writing a SDT table + * @buf: The destination buffer. + * @offset: The offset into the destination buffer. + * @sdt: A pointer to the SDT. + * @buf_sz: The size of the destination buffer. + * @continuity_counter: A pointer to the CC. Incremented on every new packet. + * + */ + +struct vidtv_psi_sdt_write_args { + char *buf; + u32 offset; + struct vidtv_psi_table_sdt *sdt; + u32 buf_sz; + u8 *continuity_counter; +}; + +/** + * vidtv_psi_sdt_write_into - Write SDT as MPEG-TS packets into a buffer. + * @args: an instance of struct vidtv_psi_sdt_write_args + * + * This function writes the MPEG TS packets for a SDT table into a buffer. + * Calling code will usually generate the SDT via a call to its init function + * and thus is responsible for freeing it. + * + * Return: The number of bytes written into the buffer. This is NOT + * equal to the size of the SDT, since more space is needed for TS headers during TS + * encapsulation. + */ +u32 vidtv_psi_sdt_write_into(struct vidtv_psi_sdt_write_args args); + +/** + * struct vidtv_psi_pmt_write_args - Arguments for writing a PMT section + * @buf: The destination buffer. + * @offset: The offset into the destination buffer. + * @pmt: A pointer to the PMT. + * @buf_sz: The size of the destination buffer. + * @continuity_counter: A pointer to the CC. Incremented on every new packet. + * + */ +struct vidtv_psi_pmt_write_args { + char *buf; + u32 offset; + struct vidtv_psi_table_pmt *pmt; + u16 pid; + u32 buf_sz; + u8 *continuity_counter; + u16 pcr_pid; +}; + +/** + * vidtv_psi_pmt_write_into - Write PMT as MPEG-TS packets into a buffer. + * @args: an instance of struct vidtv_psi_pmt_write_args + * + * This function writes the MPEG TS packets for a PMT section into a buffer. + * Calling code will usually generate the PMT section via a call to its init function + * and thus is responsible for freeing it. + * + * Return: The number of bytes written into the buffer. This is NOT + * equal to the size of the PMT section, since more space is needed for TS headers + * during TS encapsulation. + */ +u32 vidtv_psi_pmt_write_into(struct vidtv_psi_pmt_write_args args); + +/** + * vidtv_psi_find_pmt_sec - Finds the PMT section for 'program_num' + * @pmt_sections: The sections to look into. + * @nsections: The number of sections. + * @program_num: The 'program_num' from PAT pointing to a PMT section. + * + * Return: A pointer to the PMT, if found, or NULL. + */ +struct vidtv_psi_table_pmt *vidtv_psi_find_pmt_sec(struct vidtv_psi_table_pmt **pmt_sections, + u16 nsections, + u16 program_num); + +u16 vidtv_psi_get_pat_program_pid(struct vidtv_psi_table_pat_program *p); +u16 vidtv_psi_pmt_stream_get_elem_pid(struct vidtv_psi_table_pmt_stream *s); + +#endif // VIDTV_PSI_H diff --git a/drivers/media/test-drivers/vidtv/vidtv_s302m.c b/drivers/media/test-drivers/vidtv/vidtv_s302m.c new file mode 100644 index 000000000000..a447ccbd68d5 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_s302m.c @@ -0,0 +1,502 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the code for an AES3 (also known as AES/EBU) encoder. + * It is based on EBU Tech 3250 and SMPTE 302M technical documents. + * + * This encoder currently supports 16bit AES3 subframes using 16bit signed + * integers. + * + * Note: AU stands for Access Unit, and AAU stands for Audio Access Unit + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__ + +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/crc32.h> +#include <linux/vmalloc.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/jiffies.h> +#include <linux/printk.h> +#include <linux/ratelimit.h> +#include <linux/fixp-arith.h> + +#include <linux/math64.h> +#include <asm/byteorder.h> + +#include "vidtv_s302m.h" +#include "vidtv_encoder.h" +#include "vidtv_common.h" + +#define S302M_SAMPLING_RATE_HZ 48000 +#define PES_PRIVATE_STREAM_1 0xbd /* PES: private_stream_1 */ +#define S302M_BLOCK_SZ 192 +#define S302M_SIN_LUT_NUM_ELEM 1024 + +/* these are retrieved empirically from ffmpeg/libavcodec */ +#define FF_S302M_DEFAULT_NUM_FRAMES 1115 +#define FF_S302M_DEFAULT_PTS_INCREMENT 2090 +#define FF_S302M_DEFAULT_PTS_OFFSET 100000 + +/* Used by the tone generator: number of samples for PI */ +#define PI 180 + +static const u8 reverse[256] = { + /* from ffmpeg */ + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, + 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, + 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4, + 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, + 0x3C, 0xBC, 0x7C, 0xFC, 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, + 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 0x0A, 0x8A, 0x4A, 0xCA, + 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, + 0x36, 0xB6, 0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, + 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, 0x01, 0x81, 0x41, 0xC1, + 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, + 0x39, 0xB9, 0x79, 0xF9, 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, + 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, 0x0D, 0x8D, 0x4D, 0xCD, + 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, + 0x33, 0xB3, 0x73, 0xF3, 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, + 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, 0x07, 0x87, 0x47, 0xC7, + 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, + 0x3F, 0xBF, 0x7F, 0xFF, +}; + +struct tone_duration { + enum musical_notes note; + int duration; +}; + +#define COMPASS 120 /* beats per minute (Allegro) */ +static const struct tone_duration beethoven_5th_symphony[] = { + { NOTE_E_6, 128}, { NOTE_DS_6, 128}, { NOTE_E_6, 128}, + { NOTE_DS_6, 128}, { NOTE_E_6, 128}, { NOTE_B_5, 128}, + { NOTE_D_6, 128}, { NOTE_C_6, 128}, { NOTE_A_3, 128}, + { NOTE_E_4, 128}, { NOTE_A_4, 128}, { NOTE_C_5, 128}, + { NOTE_E_5, 128}, { NOTE_A_5, 128}, { NOTE_E_3, 128}, + { NOTE_E_4, 128}, { NOTE_GS_4, 128}, { NOTE_E_5, 128}, + { NOTE_GS_5, 128}, { NOTE_B_5, 128}, { NOTE_A_3, 128}, + { NOTE_E_4, 128}, { NOTE_A_4, 128}, { NOTE_E_5, 128}, + { NOTE_E_6, 128}, { NOTE_DS_6, 128}, { NOTE_E_6, 128}, + { NOTE_DS_6, 128}, { NOTE_E_6, 128}, { NOTE_B_5, 128}, + { NOTE_D_6, 128}, { NOTE_C_6, 128}, { NOTE_A_3, 128}, + { NOTE_E_4, 128}, { NOTE_A_4, 128}, { NOTE_C_5, 128}, + { NOTE_E_5, 128}, { NOTE_A_5, 128}, { NOTE_E_3, 128}, + { NOTE_E_4, 128}, { NOTE_GS_4, 128}, { NOTE_E_5, 128}, + { NOTE_C_6, 128}, { NOTE_B_5, 128}, { NOTE_A_3, 128}, + { NOTE_E_4, 128}, { NOTE_A_4, 128}, { NOTE_SILENT, 128}, + + { NOTE_E_6, 128}, { NOTE_DS_6, 128}, { NOTE_E_6, 128}, + { NOTE_DS_6, 128}, { NOTE_E_6, 128}, { NOTE_B_5, 128}, + { NOTE_D_6, 128}, { NOTE_C_6, 128}, { NOTE_A_3, 128}, + { NOTE_E_4, 128}, { NOTE_A_4, 128}, { NOTE_C_5, 128}, + { NOTE_E_5, 128}, { NOTE_A_5, 128}, { NOTE_E_3, 128}, + { NOTE_E_4, 128}, { NOTE_GS_4, 128}, { NOTE_E_5, 128}, + { NOTE_GS_5, 128}, { NOTE_B_5, 128}, { NOTE_A_3, 128}, + { NOTE_E_4, 128}, { NOTE_A_4, 128}, { NOTE_E_5, 128}, + { NOTE_E_6, 128}, { NOTE_DS_6, 128}, { NOTE_E_6, 128}, + { NOTE_DS_6, 128}, { NOTE_E_6, 128}, { NOTE_B_5, 128}, + { NOTE_D_6, 128}, { NOTE_C_6, 128}, { NOTE_A_3, 128}, + { NOTE_E_4, 128}, { NOTE_A_4, 128}, { NOTE_C_5, 128}, + { NOTE_E_5, 128}, { NOTE_A_5, 128}, { NOTE_E_3, 128}, + { NOTE_E_4, 128}, { NOTE_GS_4, 128}, { NOTE_E_5, 128}, + { NOTE_C_6, 128}, { NOTE_B_5, 128}, { NOTE_A_3, 128}, + { NOTE_E_4, 128}, { NOTE_A_4, 128}, { NOTE_B_4, 128}, + { NOTE_C_5, 128}, { NOTE_D_5, 128}, { NOTE_C_4, 128}, + { NOTE_G_4, 128}, { NOTE_C_5, 128}, { NOTE_G_4, 128}, + { NOTE_F_5, 128}, { NOTE_E_5, 128}, { NOTE_G_3, 128}, + { NOTE_G_4, 128}, { NOTE_B_3, 128}, { NOTE_F_4, 128}, + { NOTE_E_5, 128}, { NOTE_D_5, 128}, { NOTE_A_3, 128}, + { NOTE_E_4, 128}, { NOTE_A_4, 128}, { NOTE_E_4, 128}, + { NOTE_D_5, 128}, { NOTE_C_5, 128}, { NOTE_E_3, 128}, + { NOTE_E_4, 128}, { NOTE_E_5, 255}, { NOTE_E_6, 128}, + { NOTE_E_5, 128}, { NOTE_E_6, 128}, { NOTE_E_5, 255}, + { NOTE_DS_5, 128}, { NOTE_E_5, 128}, { NOTE_DS_6, 128}, + { NOTE_E_6, 128}, { NOTE_DS_5, 128}, { NOTE_E_5, 128}, + { NOTE_DS_6, 128}, { NOTE_E_6, 128}, { NOTE_DS_6, 128}, + { NOTE_E_6, 128}, { NOTE_DS_6, 128}, { NOTE_E_6, 128}, + { NOTE_B_5, 128}, { NOTE_D_6, 128}, { NOTE_C_6, 128}, + { NOTE_A_3, 128}, { NOTE_E_4, 128}, { NOTE_A_4, 128}, + { NOTE_C_5, 128}, { NOTE_E_5, 128}, { NOTE_A_5, 128}, + { NOTE_E_3, 128}, { NOTE_E_4, 128}, { NOTE_GS_4, 128}, + { NOTE_E_5, 128}, { NOTE_GS_5, 128}, { NOTE_B_5, 128}, + { NOTE_A_3, 128}, { NOTE_E_4, 128}, { NOTE_A_4, 128}, + { NOTE_E_5, 128}, { NOTE_E_6, 128}, { NOTE_DS_6, 128}, + { NOTE_E_6, 128}, { NOTE_DS_6, 128}, { NOTE_E_6, 128}, + { NOTE_B_5, 128}, { NOTE_D_6, 128}, { NOTE_C_6, 128}, + { NOTE_A_3, 128}, { NOTE_E_4, 128}, { NOTE_A_4, 128}, + { NOTE_C_5, 128}, { NOTE_E_5, 128}, { NOTE_A_5, 128}, + { NOTE_E_3, 128}, { NOTE_E_4, 128}, { NOTE_GS_4, 128}, + { NOTE_E_5, 128}, { NOTE_C_6, 128}, { NOTE_B_5, 128}, + { NOTE_C_5, 255}, { NOTE_C_5, 255}, { NOTE_SILENT, 512}, +}; + +static struct vidtv_access_unit *vidtv_s302m_access_unit_init(struct vidtv_access_unit *head) +{ + struct vidtv_access_unit *au = kzalloc(sizeof(*au), GFP_KERNEL); + + if (head) { + while (head->next) + head = head->next; + + head->next = au; + } + + return au; +} + +static void vidtv_s302m_access_unit_destroy(struct vidtv_encoder *e) +{ + struct vidtv_access_unit *head = e->access_units; + struct vidtv_access_unit *tmp = NULL; + + while (head) { + tmp = head; + head = head->next; + kfree(tmp); + } + + e->access_units = NULL; +} + +static void vidtv_s302m_alloc_au(struct vidtv_encoder *e) +{ + struct vidtv_access_unit *sync_au = NULL; + struct vidtv_access_unit *temp = NULL; + + if (e->sync && e->sync->is_video_encoder) { + sync_au = e->sync->access_units; + + while (sync_au) { + temp = vidtv_s302m_access_unit_init(e->access_units); + if (!e->access_units) + e->access_units = temp; + + sync_au = sync_au->next; + } + + return; + } + + e->access_units = vidtv_s302m_access_unit_init(NULL); +} + +static void +vidtv_s302m_compute_sample_count_from_video(struct vidtv_encoder *e) +{ + struct vidtv_access_unit *au = e->access_units; + struct vidtv_access_unit *sync_au = e->sync->access_units; + u32 vau_duration_usecs; + u32 sample_duration_usecs; + u32 s; + + vau_duration_usecs = USEC_PER_SEC / e->sync->sampling_rate_hz; + sample_duration_usecs = USEC_PER_SEC / e->sampling_rate_hz; + + while (au && sync_au) { + s = DIV_ROUND_UP(vau_duration_usecs, sample_duration_usecs); + au->num_samples = s; + au = au->next; + sync_au = sync_au->next; + } +} + +static void vidtv_s302m_compute_pts_from_video(struct vidtv_encoder *e) +{ + struct vidtv_access_unit *au = e->access_units; + struct vidtv_access_unit *sync_au = e->sync->access_units; + + /* use the same pts from the video access unit*/ + while (au && sync_au) { + au->pts = sync_au->pts; + au = au->next; + sync_au = sync_au->next; + } +} + +static u16 vidtv_s302m_get_sample(struct vidtv_encoder *e) +{ + u16 sample; + int pos; + + if (!e->src_buf) { + /* + * Simple tone generator: play the tones at the + * beethoven_5th_symphony array. + */ + if (e->last_duration <= 0) { + if (e->src_buf_offset >= ARRAY_SIZE(beethoven_5th_symphony)) + e->src_buf_offset = 0; + + e->last_tone = beethoven_5th_symphony[e->src_buf_offset].note; + e->last_duration = beethoven_5th_symphony[e->src_buf_offset].duration * S302M_SAMPLING_RATE_HZ / COMPASS / 5; + e->src_buf_offset++; + e->note_offset = 0; + } else { + e->last_duration--; + } + + /* Handle silent */ + if (!e->last_tone) { + e->src_buf_offset = 0; + return 0x8000; + } + + pos = (2 * PI * e->note_offset * e->last_tone / S302M_SAMPLING_RATE_HZ); + + if (pos == 360) + e->note_offset = 0; + else + e->note_offset++; + + return (fixp_sin32(pos % (2 * PI)) >> 16) + 0x8000; + } + + /* bug somewhere */ + if (e->src_buf_offset > e->src_buf_sz) { + pr_err_ratelimited("overflow detected: %d > %d, wrapping.\n", + e->src_buf_offset, + e->src_buf_sz); + + e->src_buf_offset = 0; + } + + if (e->src_buf_offset >= e->src_buf_sz) { + /* let the source know we are out of data */ + if (e->last_sample_cb) + e->last_sample_cb(e->sample_count); + + e->src_buf_offset = 0; + } + + sample = *(u16 *)(e->src_buf + e->src_buf_offset); + + return sample; +} + +static u32 vidtv_s302m_write_frame(struct vidtv_encoder *e, + u16 sample) +{ + u32 nbytes = 0; + struct vidtv_s302m_frame_16 f = {}; + struct vidtv_s302m_ctx *ctx = e->ctx; + + /* from ffmpeg: see s302enc.c */ + + u8 vucf = ctx->frame_index == 0 ? 0x10 : 0; + + f.data[0] = sample & 0xFF; + f.data[1] = (sample & 0xFF00) >> 8; + f.data[2] = ((sample & 0x0F) << 4) | vucf; + f.data[3] = (sample & 0x0FF0) >> 4; + f.data[4] = (sample & 0xF000) >> 12; + + f.data[0] = reverse[f.data[0]]; + f.data[1] = reverse[f.data[1]]; + f.data[2] = reverse[f.data[2]]; + f.data[3] = reverse[f.data[3]]; + f.data[4] = reverse[f.data[4]]; + + nbytes += vidtv_memcpy(e->encoder_buf, + e->encoder_buf_offset, + VIDTV_S302M_BUF_SZ, + &f, + sizeof(f)); + + e->encoder_buf_offset += nbytes; + + ctx->frame_index++; + if (ctx->frame_index >= S302M_BLOCK_SZ) + ctx->frame_index = 0; + + return nbytes; +} + +static u32 vidtv_s302m_write_h(struct vidtv_encoder *e, u32 p_sz) +{ + struct vidtv_smpte_s302m_es h = {}; + u32 nbytes = 0; + + /* 2 channels, ident: 0, 16 bits per sample */ + h.bitfield = cpu_to_be32((p_sz << 16)); + + nbytes += vidtv_memcpy(e->encoder_buf, + e->encoder_buf_offset, + e->encoder_buf_sz, + &h, + sizeof(h)); + + e->encoder_buf_offset += nbytes; + return nbytes; +} + +static void vidtv_s302m_write_frames(struct vidtv_encoder *e) +{ + struct vidtv_access_unit *au = e->access_units; + struct vidtv_s302m_ctx *ctx = e->ctx; + u32 nbytes_per_unit = 0; + u32 nbytes = 0; + u32 au_sz = 0; + u16 sample; + u32 j; + + while (au) { + au_sz = au->num_samples * + sizeof(struct vidtv_s302m_frame_16); + + nbytes_per_unit = vidtv_s302m_write_h(e, au_sz); + + for (j = 0; j < au->num_samples; ++j) { + sample = vidtv_s302m_get_sample(e); + nbytes_per_unit += vidtv_s302m_write_frame(e, sample); + + if (e->src_buf) + e->src_buf_offset += sizeof(u16); + + e->sample_count++; + } + + au->nbytes = nbytes_per_unit; + + if (au_sz + sizeof(struct vidtv_smpte_s302m_es) != nbytes_per_unit) { + pr_warn_ratelimited("write size was %u, expected %zu\n", + nbytes_per_unit, + au_sz + sizeof(struct vidtv_smpte_s302m_es)); + } + + nbytes += nbytes_per_unit; + au->offset = nbytes - nbytes_per_unit; + + nbytes_per_unit = 0; + ctx->au_count++; + + au = au->next; + } +} + +static void *vidtv_s302m_encode(struct vidtv_encoder *e) +{ + /* + * According to SMPTE 302M, an audio access unit is specified as those + * AES3 words that are associated with a corresponding video frame. + * Therefore, there is one audio access unit for every video access unit + * in the corresponding video encoder ('sync'), using the same values + * for PTS as used by the video encoder. + * + * Assuming that it is also possible to send audio without any + * associated video, as in a radio-like service, a single audio access unit + * is created with values for 'num_samples' and 'pts' taken empirically from + * ffmpeg + */ + + struct vidtv_s302m_ctx *ctx = e->ctx; + + vidtv_s302m_access_unit_destroy(e); + vidtv_s302m_alloc_au(e); + + if (e->sync && e->sync->is_video_encoder) { + vidtv_s302m_compute_sample_count_from_video(e); + vidtv_s302m_compute_pts_from_video(e); + } else { + e->access_units->num_samples = FF_S302M_DEFAULT_NUM_FRAMES; + e->access_units->pts = (ctx->au_count * FF_S302M_DEFAULT_PTS_INCREMENT) + + FF_S302M_DEFAULT_PTS_OFFSET; + } + + vidtv_s302m_write_frames(e); + + return e->encoder_buf; +} + +static u32 vidtv_s302m_clear(struct vidtv_encoder *e) +{ + struct vidtv_access_unit *au = e->access_units; + u32 count = 0; + + while (au) { + count++; + au = au->next; + } + + vidtv_s302m_access_unit_destroy(e); + memset(e->encoder_buf, 0, VIDTV_S302M_BUF_SZ); + e->encoder_buf_offset = 0; + + return count; +} + +struct vidtv_encoder +*vidtv_s302m_encoder_init(struct vidtv_s302m_encoder_init_args args) +{ + struct vidtv_encoder *e = kzalloc(sizeof(*e), GFP_KERNEL); + u32 priv_sz = sizeof(struct vidtv_s302m_ctx); + + e->id = S302M; + + if (args.name) + e->name = kstrdup(args.name, GFP_KERNEL); + + e->encoder_buf = vzalloc(VIDTV_S302M_BUF_SZ); + e->encoder_buf_sz = VIDTV_S302M_BUF_SZ; + e->encoder_buf_offset = 0; + + e->sample_count = 0; + e->last_duration = 0; + + e->src_buf = (args.src_buf) ? args.src_buf : NULL; + e->src_buf_sz = (args.src_buf) ? args.src_buf_sz : 0; + e->src_buf_offset = 0; + + e->is_video_encoder = false; + e->ctx = kzalloc(priv_sz, GFP_KERNEL); + + e->encode = vidtv_s302m_encode; + e->clear = vidtv_s302m_clear; + + e->es_pid = cpu_to_be16(args.es_pid); + e->stream_id = cpu_to_be16(PES_PRIVATE_STREAM_1); + + e->sync = args.sync; + e->sampling_rate_hz = S302M_SAMPLING_RATE_HZ; + + e->last_sample_cb = args.last_sample_cb; + + e->destroy = vidtv_s302m_encoder_destroy; + + if (args.head) { + while (args.head->next) + args.head = args.head->next; + + args.head->next = e; + } + + e->next = NULL; + + return e; +} + +void vidtv_s302m_encoder_destroy(struct vidtv_encoder *e) +{ + if (e->id != S302M) { + pr_err_ratelimited("Encoder type mismatch, skipping.\n"); + return; + } + + vidtv_s302m_access_unit_destroy(e); + kfree(e->name); + vfree(e->encoder_buf); + kfree(e->ctx); + kfree(e); +} diff --git a/drivers/media/test-drivers/vidtv/vidtv_s302m.h b/drivers/media/test-drivers/vidtv/vidtv_s302m.h new file mode 100644 index 000000000000..eca5e3150ede --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_s302m.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the code for an AES3 (also known as AES/EBU) encoder. + * It is based on EBU Tech 3250 and SMPTE 302M technical documents. + * + * This encoder currently supports 16bit AES3 subframes using 16bit signed + * integers. + * + * Note: AU stands for Access Unit, and AAU stands for Audio Access Unit + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#ifndef VIDTV_S302M_H +#define VIDTV_S302M_H + +#include <linux/types.h> +#include <asm/byteorder.h> + +#include "vidtv_encoder.h" + +/* see SMPTE 302M 2007 clause 7.3 */ +#define VIDTV_S302M_BUF_SZ 65024 + +/* see ETSI TS 102 154 v.1.2.1 clause 7.3.5 */ +#define VIDTV_S302M_FORMAT_IDENTIFIER 0x42535344 + +/** + * struct vidtv_s302m_ctx - s302m encoder context. + * @enc: A pointer to the containing encoder structure. + * @frame_index: The current frame in a block + * @au_count: The total number of access units encoded up to now + */ +struct vidtv_s302m_ctx { + struct vidtv_encoder *enc; + u32 frame_index; + u32 au_count; +}; + +/** + * struct vidtv_smpte_s302m_es - s302m MPEG Elementary Stream header. + * + * See SMPTE 302M 2007 table 1. + */ +struct vidtv_smpte_s302m_es { + /* + * + * audio_packet_size:16; + * num_channels:2; + * channel_identification:8; + * bits_per_sample:2; // 0x0 for 16bits + * zero:4; + */ + __be32 bitfield; +} __packed; + +struct vidtv_s302m_frame_16 { + u8 data[5]; +} __packed; + +/** + * struct vidtv_s302m_encoder_init_args - Args for the s302m encoder. + * + * @name: A name to identify this particular instance + * @src_buf: The source buffer, encoder will default to a sine wave if this is NULL. + * @src_buf_sz: The size of the source buffer. + * @es_pid: The MPEG Elementary Stream PID to use. + * @sync: Attempt to synchronize audio with this video encoder, if not NULL. + * @last_sample_cb: A callback called when the encoder runs out of data. + * @head: Add to this chain + */ +struct vidtv_s302m_encoder_init_args { + char *name; + void *src_buf; + u32 src_buf_sz; + u16 es_pid; + struct vidtv_encoder *sync; + void (*last_sample_cb)(u32 sample_no); + + struct vidtv_encoder *head; +}; + +struct vidtv_encoder +*vidtv_s302m_encoder_init(struct vidtv_s302m_encoder_init_args args); + +void vidtv_s302m_encoder_destroy(struct vidtv_encoder *encoder); + +#endif /* VIDTV_S302M_H */ diff --git a/drivers/media/test-drivers/vidtv/vidtv_ts.c b/drivers/media/test-drivers/vidtv/vidtv_ts.c new file mode 100644 index 000000000000..190b9e4438dc --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_ts.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Virtual DVB test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__ + +#include <linux/printk.h> +#include <linux/ratelimit.h> +#include <linux/types.h> +#include <linux/math64.h> +#include <asm/byteorder.h> + +#include "vidtv_ts.h" +#include "vidtv_common.h" + +static u32 vidtv_ts_write_pcr_bits(u8 *to, u32 to_offset, u64 pcr) +{ + /* Exact same from ffmpeg. PCR is a counter driven by a 27Mhz clock */ + u64 div; + u64 rem; + u8 *buf = to + to_offset; + u64 pcr_low; + u64 pcr_high; + + div = div64_u64_rem(pcr, 300, &rem); + + pcr_low = rem; /* pcr_low = pcr % 300 */ + pcr_high = div; /* pcr_high = pcr / 300 */ + + *buf++ = pcr_high >> 25; + *buf++ = pcr_high >> 17; + *buf++ = pcr_high >> 9; + *buf++ = pcr_high >> 1; + *buf++ = pcr_high << 7 | pcr_low >> 8 | 0x7e; + *buf++ = pcr_low; + + return 6; +} + +void vidtv_ts_inc_cc(u8 *continuity_counter) +{ + ++*continuity_counter; + if (*continuity_counter > TS_CC_MAX_VAL) + *continuity_counter = 0; +} + +u32 vidtv_ts_null_write_into(struct null_packet_write_args args) +{ + u32 nbytes = 0; + struct vidtv_mpeg_ts ts_header = {}; + + ts_header.sync_byte = TS_SYNC_BYTE; + ts_header.bitfield = cpu_to_be16(TS_NULL_PACKET_PID); + ts_header.payload = 1; + ts_header.continuity_counter = *args.continuity_counter; + + /* copy TS header */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.buf_sz, + &ts_header, + sizeof(ts_header)); + + vidtv_ts_inc_cc(args.continuity_counter); + + /* fill the rest with empty data */ + nbytes += vidtv_memset(args.dest_buf, + args.dest_offset + nbytes, + args.buf_sz, + TS_FILL_BYTE, + TS_PACKET_LEN - nbytes); + + /* we should have written exactly _one_ 188byte packet */ + if (nbytes != TS_PACKET_LEN) + pr_warn_ratelimited("Expected exactly %d bytes, got %d\n", + TS_PACKET_LEN, + nbytes); + + return nbytes; +} + +u32 vidtv_ts_pcr_write_into(struct pcr_write_args args) +{ + u32 nbytes = 0; + struct vidtv_mpeg_ts ts_header = {}; + struct vidtv_mpeg_ts_adaption ts_adap = {}; + + ts_header.sync_byte = TS_SYNC_BYTE; + ts_header.bitfield = cpu_to_be16(args.pid); + ts_header.scrambling = 0; + /* cc is not incremented, but it is needed. see 13818-1 clause 2.4.3.3 */ + ts_header.continuity_counter = *args.continuity_counter; + ts_header.payload = 0; + ts_header.adaptation_field = 1; + + /* 13818-1 clause 2.4.3.5 */ + ts_adap.length = 183; + ts_adap.PCR = 1; + + /* copy TS header */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.buf_sz, + &ts_header, + sizeof(ts_header)); + + /* write the adap after the TS header */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.buf_sz, + &ts_adap, + sizeof(ts_adap)); + + /* write the PCR optional */ + nbytes += vidtv_ts_write_pcr_bits(args.dest_buf, + args.dest_offset + nbytes, + args.pcr); + + nbytes += vidtv_memset(args.dest_buf, + args.dest_offset + nbytes, + args.buf_sz, + TS_FILL_BYTE, + TS_PACKET_LEN - nbytes); + + /* we should have written exactly _one_ 188byte packet */ + if (nbytes != TS_PACKET_LEN) + pr_warn_ratelimited("Expected exactly %d bytes, got %d\n", + TS_PACKET_LEN, + nbytes); + + return nbytes; +} diff --git a/drivers/media/test-drivers/vidtv/vidtv_ts.h b/drivers/media/test-drivers/vidtv/vidtv_ts.h new file mode 100644 index 000000000000..83dcc9183b45 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_ts.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * The Virtual DVB test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#ifndef VIDTV_TS_H +#define VIDTV_TS_H + +#include <linux/types.h> +#include <asm/byteorder.h> + +#define TS_SYNC_BYTE 0x47 +#define TS_PACKET_LEN 188 +#define TS_PAYLOAD_LEN 184 +#define TS_NULL_PACKET_PID 0x1fff +#define TS_CC_MAX_VAL 0x0f /* 4 bits */ +#define TS_LAST_VALID_PID 8191 +#define TS_FILL_BYTE 0xff /* the byte used in packet stuffing */ + +struct vidtv_mpeg_ts_adaption { + u8 length; + struct { + u8 extension:1; + u8 private_data:1; + u8 splicing_point:1; + u8 OPCR:1; + u8 PCR:1; + u8 priority:1; + u8 random_access:1; + u8 discontinued:1; + } __packed; + u8 data[]; +} __packed; + +struct vidtv_mpeg_ts { + u8 sync_byte; + __be16 bitfield; /* tei: 1, payload_start:1 priority: 1, pid:13 */ + struct { + u8 continuity_counter:4; + u8 payload:1; + u8 adaptation_field:1; + u8 scrambling:2; + } __packed; + struct vidtv_mpeg_ts_adaption adaption[]; +} __packed; + +/** + * struct pcr_write_args - Arguments for the pcr_write_into function. + * @dest_buf: The buffer to write into. + * @dest_offset: The byte offset into the buffer. + * @pid: The TS PID for the PCR packets. + * @buf_sz: The size of the buffer in bytes. + * @countinuity_counter: The TS continuity_counter. + * @pcr: A sample from the system clock. + */ +struct pcr_write_args { + void *dest_buf; + u32 dest_offset; + u16 pid; + u32 buf_sz; + u8 *continuity_counter; + u64 pcr; +}; + +/** + * struct null_packet_write_args - Arguments for the null_write_into function + * @dest_buf: The buffer to write into. + * @dest_offset: The byte offset into the buffer. + * @buf_sz: The size of the buffer in bytes. + * @countinuity_counter: The TS continuity_counter. + */ +struct null_packet_write_args { + void *dest_buf; + u32 dest_offset; + u32 buf_sz; + u8 *continuity_counter; +}; + +/* Increment the continuity counter */ +void vidtv_ts_inc_cc(u8 *continuity_counter); + +/** + * vidtv_ts_null_write_into - Write a TS null packet into a buffer. + * @args: the arguments to use when writing. + * + * This function will write a null packet into a buffer. This is usually used to + * pad TS streams. + * + * Return: The number of bytes written into the buffer. + */ +u32 vidtv_ts_null_write_into(struct null_packet_write_args args); + +/** + * vidtv_ts_pcr_write_into - Write a PCR packet into a buffer. + * @args: the arguments to use when writing. + * + * This function will write a PCR packet into a buffer. This is used to + * synchronize the clocks between encoders and decoders. + * + * Return: The number of bytes written into the buffer. + */ +u32 vidtv_ts_pcr_write_into(struct pcr_write_args args); + +#endif //VIDTV_TS_H diff --git a/drivers/media/test-drivers/vidtv/vidtv_tuner.c b/drivers/media/test-drivers/vidtv/vidtv_tuner.c new file mode 100644 index 000000000000..9bc49e099f65 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_tuner.c @@ -0,0 +1,438 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Virtual DVB test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * The vidtv tuner should support common TV standards such as + * DVB-T/T2/S/S2, ISDB-T and ATSC when completed. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <media/dvb_frontend.h> +#include <linux/printk.h> +#include <linux/ratelimit.h> + +#include "vidtv_tuner.h" + +struct vidtv_tuner_cnr_to_qual_s { + /* attempt to use the same values as libdvbv5 */ + u32 modulation; + u32 fec; + u32 cnr_ok; + u32 cnr_good; +}; + +static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_c_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QAM_256, FEC_NONE, 34000, 38000}, + { QAM_64, FEC_NONE, 30000, 34000}, +}; + +static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QPSK, FEC_1_2, 7000, 10000}, + { QPSK, FEC_2_3, 9000, 12000}, + { QPSK, FEC_3_4, 10000, 13000}, + { QPSK, FEC_5_6, 11000, 14000}, + { QPSK, FEC_7_8, 12000, 15000}, +}; + +static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s2_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QPSK, FEC_1_2, 9000, 12000}, + { QPSK, FEC_2_3, 11000, 14000}, + { QPSK, FEC_3_4, 12000, 15000}, + { QPSK, FEC_5_6, 12000, 15000}, + { QPSK, FEC_8_9, 13000, 16000}, + { QPSK, FEC_9_10, 13500, 16500}, + { PSK_8, FEC_2_3, 14500, 17500}, + { PSK_8, FEC_3_4, 16000, 19000}, + { PSK_8, FEC_5_6, 17500, 20500}, + { PSK_8, FEC_8_9, 19000, 22000}, +}; + +static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_t_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db*/ + { QPSK, FEC_1_2, 4100, 5900}, + { QPSK, FEC_2_3, 6100, 9600}, + { QPSK, FEC_3_4, 7200, 12400}, + { QPSK, FEC_5_6, 8500, 15600}, + { QPSK, FEC_7_8, 9200, 17500}, + { QAM_16, FEC_1_2, 9800, 11800}, + { QAM_16, FEC_2_3, 12100, 15300}, + { QAM_16, FEC_3_4, 13400, 18100}, + { QAM_16, FEC_5_6, 14800, 21300}, + { QAM_16, FEC_7_8, 15700, 23600}, + { QAM_64, FEC_1_2, 14000, 16000}, + { QAM_64, FEC_2_3, 19900, 25400}, + { QAM_64, FEC_3_4, 24900, 27900}, + { QAM_64, FEC_5_6, 21300, 23300}, + { QAM_64, FEC_7_8, 22000, 24000}, +}; + +/** + * struct vidtv_tuner_hardware_state - Simulate the tuner hardware status + * @asleep: whether the tuner is asleep, i.e whether _sleep() or _suspend() was + * called. + * @lock_status: Whether the tuner has managed to lock on the requested + * frequency. + * @if_frequency: The tuner's intermediate frequency. Hardcoded for the purposes + * of simulation. + * @tuned_frequency: The actual tuned frequency. + * @bandwidth: The actual bandwidth. + * + * This structure is meant to simulate the status of the tuner hardware, as if + * we had a physical tuner hardware. + */ +struct vidtv_tuner_hardware_state { + bool asleep; + u32 lock_status; + u32 if_frequency; + u32 tuned_frequency; + u32 bandwidth; +}; + +/** + * struct vidtv_tuner_dev - The tuner struct + * @fe: A pointer to the dvb_frontend structure allocated by vidtv_demod + * @hw_state: A struct to simulate the tuner's hardware state as if we had a + * physical tuner hardware. + * @config: The configuration used to start the tuner module, usually filled + * by a bridge driver. For vidtv, this is filled by vidtv_bridge before the + * tuner module is probed. + */ +struct vidtv_tuner_dev { + struct dvb_frontend *fe; + struct vidtv_tuner_hardware_state hw_state; + struct vidtv_tuner_config config; +}; + +static struct vidtv_tuner_dev* +vidtv_tuner_get_dev(struct dvb_frontend *fe) +{ + return i2c_get_clientdata(fe->tuner_priv); +} + +static int vidtv_tuner_check_frequency_shift(struct dvb_frontend *fe) +{ + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct vidtv_tuner_config config = tuner_dev->config; + u32 *valid_freqs = NULL; + u32 array_sz = 0; + u32 i; + u32 shift; + + switch (c->delivery_system) { + case SYS_DVBT: + case SYS_DVBT2: + valid_freqs = config.vidtv_valid_dvb_t_freqs; + array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_t_freqs); + break; + case SYS_DVBS: + case SYS_DVBS2: + valid_freqs = config.vidtv_valid_dvb_s_freqs; + array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_s_freqs); + break; + case SYS_DVBC_ANNEX_A: + valid_freqs = config.vidtv_valid_dvb_c_freqs; + array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_c_freqs); + break; + + default: + dev_warn(fe->dvb->device, + "%s: unsupported delivery system: %u\n", + __func__, + c->delivery_system); + + return -EINVAL; + } + + for (i = 0; i < array_sz; i++) { + if (!valid_freqs[i]) + break; + shift = abs(c->frequency - valid_freqs[i]); + + if (!shift) + return 0; + + /* + * This will provide a value from 0 to 100 that would + * indicate how far is the tuned frequency from the + * right one. + */ + if (shift < config.max_frequency_shift_hz) + return shift * 100 / config.max_frequency_shift_hz; + } + + return -EINVAL; +} + +static int +vidtv_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + const struct vidtv_tuner_cnr_to_qual_s *cnr2qual = NULL; + struct device *dev = fe->dvb->device; + u32 array_size = 0; + s32 shift; + u32 i; + + shift = vidtv_tuner_check_frequency_shift(fe); + if (shift < 0) { + tuner_dev->hw_state.lock_status = 0; + *strength = 0; + return 0; + } + + switch (c->delivery_system) { + case SYS_DVBT: + case SYS_DVBT2: + cnr2qual = vidtv_tuner_t_cnr_2_qual; + array_size = ARRAY_SIZE(vidtv_tuner_t_cnr_2_qual); + break; + + case SYS_DVBS: + cnr2qual = vidtv_tuner_s_cnr_2_qual; + array_size = ARRAY_SIZE(vidtv_tuner_s_cnr_2_qual); + break; + + case SYS_DVBS2: + cnr2qual = vidtv_tuner_s2_cnr_2_qual; + array_size = ARRAY_SIZE(vidtv_tuner_s2_cnr_2_qual); + break; + + case SYS_DVBC_ANNEX_A: + cnr2qual = vidtv_tuner_c_cnr_2_qual; + array_size = ARRAY_SIZE(vidtv_tuner_c_cnr_2_qual); + break; + + default: + dev_warn_ratelimited(dev, + "%s: unsupported delivery system: %u\n", + __func__, + c->delivery_system); + return -EINVAL; + } + + for (i = 0; i < array_size; i++) { + if (cnr2qual[i].modulation != c->modulation || + cnr2qual[i].fec != c->fec_inner) + continue; + + if (!shift) { + *strength = cnr2qual[i].cnr_good; + return 0; + } + /* + * Channel tuned at wrong frequency. Simulate that the + * Carrier S/N ratio is not too good. + */ + + *strength = cnr2qual[i].cnr_ok - + (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok); + return 0; + } + + /* + * do a linear interpolation between 34dB and 10dB if we can't + * match against the table + */ + *strength = 34000 - 24000 * shift / 100; + return 0; +} + +static int vidtv_tuner_init(struct dvb_frontend *fe) +{ + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + struct vidtv_tuner_config config = tuner_dev->config; + + msleep_interruptible(config.mock_power_up_delay_msec); + + tuner_dev->hw_state.asleep = false; + tuner_dev->hw_state.if_frequency = 5000; + + return 0; +} + +static int vidtv_tuner_sleep(struct dvb_frontend *fe) +{ + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + + tuner_dev->hw_state.asleep = true; + return 0; +} + +static int vidtv_tuner_suspend(struct dvb_frontend *fe) +{ + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + + tuner_dev->hw_state.asleep = true; + return 0; +} + +static int vidtv_tuner_resume(struct dvb_frontend *fe) +{ + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + + tuner_dev->hw_state.asleep = false; + return 0; +} + +static int vidtv_tuner_set_params(struct dvb_frontend *fe) +{ + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + struct vidtv_tuner_config config = tuner_dev->config; + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + s32 shift; + + u32 min_freq = fe->ops.tuner_ops.info.frequency_min_hz; + u32 max_freq = fe->ops.tuner_ops.info.frequency_max_hz; + u32 min_bw = fe->ops.tuner_ops.info.bandwidth_min; + u32 max_bw = fe->ops.tuner_ops.info.bandwidth_max; + + if (c->frequency < min_freq || c->frequency > max_freq || + c->bandwidth_hz < min_bw || c->bandwidth_hz > max_bw) { + tuner_dev->hw_state.lock_status = 0; + return -EINVAL; + } + + tuner_dev->hw_state.tuned_frequency = c->frequency; + tuner_dev->hw_state.bandwidth = c->bandwidth_hz; + tuner_dev->hw_state.lock_status = TUNER_STATUS_LOCKED; + + msleep_interruptible(config.mock_tune_delay_msec); + + shift = vidtv_tuner_check_frequency_shift(fe); + if (shift < 0) { + tuner_dev->hw_state.lock_status = 0; + return shift; + } + + return 0; +} + +static int vidtv_tuner_set_config(struct dvb_frontend *fe, + void *priv_cfg) +{ + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + + memcpy(&tuner_dev->config, priv_cfg, sizeof(tuner_dev->config)); + + return 0; +} + +static int vidtv_tuner_get_frequency(struct dvb_frontend *fe, + u32 *frequency) +{ + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + + *frequency = tuner_dev->hw_state.tuned_frequency; + + return 0; +} + +static int vidtv_tuner_get_bandwidth(struct dvb_frontend *fe, + u32 *bandwidth) +{ + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + + *bandwidth = tuner_dev->hw_state.bandwidth; + + return 0; +} + +static int vidtv_tuner_get_if_frequency(struct dvb_frontend *fe, + u32 *frequency) +{ + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + + *frequency = tuner_dev->hw_state.if_frequency; + + return 0; +} + +static int vidtv_tuner_get_status(struct dvb_frontend *fe, u32 *status) +{ + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe); + + *status = tuner_dev->hw_state.lock_status; + + return 0; +} + +static const struct dvb_tuner_ops vidtv_tuner_ops = { + .init = vidtv_tuner_init, + .sleep = vidtv_tuner_sleep, + .suspend = vidtv_tuner_suspend, + .resume = vidtv_tuner_resume, + .set_params = vidtv_tuner_set_params, + .set_config = vidtv_tuner_set_config, + .get_bandwidth = vidtv_tuner_get_bandwidth, + .get_frequency = vidtv_tuner_get_frequency, + .get_if_frequency = vidtv_tuner_get_if_frequency, + .get_status = vidtv_tuner_get_status, + .get_rf_strength = vidtv_tuner_get_signal_strength +}; + +static const struct i2c_device_id vidtv_tuner_i2c_id_table[] = { + {"dvb_vidtv_tuner", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, vidtv_tuner_i2c_id_table); + +static int vidtv_tuner_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct vidtv_tuner_config *config = client->dev.platform_data; + struct dvb_frontend *fe = config->fe; + struct vidtv_tuner_dev *tuner_dev = NULL; + + tuner_dev = kzalloc(sizeof(*tuner_dev), GFP_KERNEL); + if (!tuner_dev) + return -ENOMEM; + + tuner_dev->fe = config->fe; + i2c_set_clientdata(client, tuner_dev); + + memcpy(&fe->ops.tuner_ops, + &vidtv_tuner_ops, + sizeof(struct dvb_tuner_ops)); + + memcpy(&tuner_dev->config, config, sizeof(tuner_dev->config)); + fe->tuner_priv = client; + + return 0; +} + +static int vidtv_tuner_i2c_remove(struct i2c_client *client) +{ + struct vidtv_tuner_dev *tuner_dev = i2c_get_clientdata(client); + + kfree(tuner_dev); + + return 0; +} + +static struct i2c_driver vidtv_tuner_i2c_driver = { + .driver = { + .name = "dvb_vidtv_tuner", + .suppress_bind_attrs = true, + }, + .probe = vidtv_tuner_i2c_probe, + .remove = vidtv_tuner_i2c_remove, + .id_table = vidtv_tuner_i2c_id_table, +}; +module_i2c_driver(vidtv_tuner_i2c_driver); + +MODULE_DESCRIPTION("Virtual DVB Tuner"); +MODULE_AUTHOR("Daniel W. S. Almeida"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/test-drivers/vidtv/vidtv_tuner.h b/drivers/media/test-drivers/vidtv/vidtv_tuner.h new file mode 100644 index 000000000000..8455b2d564b3 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_tuner.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * The Virtual DTV test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Copyright (C) 2020 Daniel W. S. Almeida + */ + +#ifndef VIDTV_TUNER_H +#define VIDTV_TUNER_H + +#include <linux/types.h> +#include <media/dvb_frontend.h> + +#define NUM_VALID_TUNER_FREQS 8 + +/** + * struct vidtv_tuner_config - Configuration used to init the tuner. + * @fe: A pointer to the dvb_frontend structure allocated by vidtv_demod. + * @mock_power_up_delay_msec: Simulate a power-up delay. + * @mock_tune_delay_msec: Simulate a tune delay. + * @vidtv_valid_dvb_t_freqs: The valid DVB-T frequencies to simulate. + * @vidtv_valid_dvb_c_freqs: The valid DVB-C frequencies to simulate. + * @vidtv_valid_dvb_s_freqs: The valid DVB-S frequencies to simulate. + * @max_frequency_shift_hz: The maximum frequency shift in HZ allowed when + * tuning in a channel + * + * The configuration used to init the tuner module, usually filled + * by a bridge driver. For vidtv, this is filled by vidtv_bridge before the + * tuner module is probed. + */ +struct vidtv_tuner_config { + struct dvb_frontend *fe; + u32 mock_power_up_delay_msec; + u32 mock_tune_delay_msec; + u32 vidtv_valid_dvb_t_freqs[NUM_VALID_TUNER_FREQS]; + u32 vidtv_valid_dvb_c_freqs[NUM_VALID_TUNER_FREQS]; + u32 vidtv_valid_dvb_s_freqs[NUM_VALID_TUNER_FREQS]; + u8 max_frequency_shift_hz; +}; + +#endif //VIDTV_TUNER_H diff --git a/drivers/media/test-drivers/vimc/vimc-capture.c b/drivers/media/test-drivers/vimc/vimc-capture.c index c63496b17b9a..5e9fd902cd37 100644 --- a/drivers/media/test-drivers/vimc/vimc-capture.c +++ b/drivers/media/test-drivers/vimc/vimc-capture.c @@ -351,8 +351,7 @@ static void vimc_cap_unregister(struct vimc_ent_device *ved) struct vimc_cap_device *vcap = container_of(ved, struct vimc_cap_device, ved); - vb2_queue_release(&vcap->queue); - video_unregister_device(&vcap->vdev); + vb2_video_unregister_device(&vcap->vdev); } static void *vimc_cap_process_frame(struct vimc_ent_device *ved, @@ -477,13 +476,11 @@ static struct vimc_ent_device *vimc_cap_add(struct vimc_device *vimc, if (ret) { dev_err(vimc->mdev.dev, "%s: video register failed (err=%d)\n", vcap->vdev.name, ret); - goto err_release_queue; + goto err_clean_m_ent; } return &vcap->ved; -err_release_queue: - vb2_queue_release(q); err_clean_m_ent: media_entity_cleanup(&vcap->vdev.entity); err_free_vcap: diff --git a/drivers/media/test-drivers/vivid/vivid-core.c b/drivers/media/test-drivers/vivid/vivid-core.c index f7ee37e9508d..aa8d350fd682 100644 --- a/drivers/media/test-drivers/vivid/vivid-core.c +++ b/drivers/media/test-drivers/vivid/vivid-core.c @@ -832,56 +832,16 @@ static int vivid_create_queue(struct vivid_dev *dev, return vb2_queue_init(q); } -static int vivid_create_instance(struct platform_device *pdev, int inst) +static int vivid_detect_feature_set(struct vivid_dev *dev, int inst, + unsigned node_type, + bool *has_tuner, + bool *has_modulator, + int *ccs_cap, + int *ccs_out, + unsigned in_type_counter[4], + unsigned out_type_counter[4]) { - static const struct v4l2_dv_timings def_dv_timings = - V4L2_DV_BT_CEA_1280X720P60; - unsigned in_type_counter[4] = { 0, 0, 0, 0 }; - unsigned out_type_counter[4] = { 0, 0, 0, 0 }; - int ccs_cap = ccs_cap_mode[inst]; - int ccs_out = ccs_out_mode[inst]; - bool has_tuner; - bool has_modulator; - struct vivid_dev *dev; - struct video_device *vfd; - unsigned node_type = node_types[inst]; - v4l2_std_id tvnorms_cap = 0, tvnorms_out = 0; - int ret; int i; -#ifdef CONFIG_VIDEO_VIVID_CEC - unsigned int cec_tx_bus_cnt = 0; -#endif - - /* allocate main vivid state structure */ - dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (!dev) - return -ENOMEM; - - dev->inst = inst; - -#ifdef CONFIG_MEDIA_CONTROLLER - dev->v4l2_dev.mdev = &dev->mdev; - - /* Initialize media device */ - strscpy(dev->mdev.model, VIVID_MODULE_NAME, sizeof(dev->mdev.model)); - snprintf(dev->mdev.bus_info, sizeof(dev->mdev.bus_info), - "platform:%s-%03d", VIVID_MODULE_NAME, inst); - dev->mdev.dev = &pdev->dev; - media_device_init(&dev->mdev); - dev->mdev.ops = &vivid_media_ops; -#endif - - /* register v4l2_device */ - snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), - "%s-%03d", VIVID_MODULE_NAME, inst); - ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); - if (ret) { - kfree(dev); - return ret; - } - dev->v4l2_dev.release = vivid_dev_release; - - /* start detecting feature set */ /* do we use single- or multi-planar? */ dev->multiplanar = multiplanar[inst] > 1; @@ -947,14 +907,12 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) !dev->has_vid_cap && !dev->has_meta_cap) { v4l2_warn(&dev->v4l2_dev, "Webcam or HDMI input without video or metadata nodes\n"); - kfree(dev); return -EINVAL; } if ((in_type_counter[TV] || in_type_counter[SVID]) && !dev->has_vid_cap && !dev->has_vbi_cap && !dev->has_meta_cap) { v4l2_warn(&dev->v4l2_dev, "TV or S-Video input without video, VBI or metadata nodes\n"); - kfree(dev); return -EINVAL; } @@ -976,13 +934,11 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) !dev->has_vid_out && !dev->has_vbi_out && !dev->has_meta_out) { v4l2_warn(&dev->v4l2_dev, "S-Video output without video, VBI or metadata nodes\n"); - kfree(dev); return -EINVAL; } if (out_type_counter[HDMI] && !dev->has_vid_out && !dev->has_meta_out) { v4l2_warn(&dev->v4l2_dev, "HDMI output without video or metadata nodes\n"); - kfree(dev); return -EINVAL; } @@ -999,25 +955,25 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) dev->has_tv_tuner = in_type_counter[TV]; /* do we have a tuner? */ - has_tuner = ((dev->has_vid_cap || dev->has_vbi_cap) && in_type_counter[TV]) || - dev->has_radio_rx || dev->has_sdr_cap; + *has_tuner = ((dev->has_vid_cap || dev->has_vbi_cap) && in_type_counter[TV]) || + dev->has_radio_rx || dev->has_sdr_cap; /* do we have a modulator? */ - has_modulator = dev->has_radio_tx; + *has_modulator = dev->has_radio_tx; if (dev->has_vid_cap) /* do we have a framebuffer for overlay testing? */ dev->has_fb = node_type & 0x10000; /* can we do crop/compose/scaling while capturing? */ - if (no_error_inj && ccs_cap == -1) - ccs_cap = 7; + if (no_error_inj && *ccs_cap == -1) + *ccs_cap = 7; /* if ccs_cap == -1, then the user can select it using controls */ - if (ccs_cap != -1) { - dev->has_crop_cap = ccs_cap & 1; - dev->has_compose_cap = ccs_cap & 2; - dev->has_scaler_cap = ccs_cap & 4; + if (*ccs_cap != -1) { + dev->has_crop_cap = *ccs_cap & 1; + dev->has_compose_cap = *ccs_cap & 2; + dev->has_scaler_cap = *ccs_cap & 4; v4l2_info(&dev->v4l2_dev, "Capture Crop: %c Compose: %c Scaler: %c\n", dev->has_crop_cap ? 'Y' : 'N', dev->has_compose_cap ? 'Y' : 'N', @@ -1025,14 +981,14 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) } /* can we do crop/compose/scaling with video output? */ - if (no_error_inj && ccs_out == -1) - ccs_out = 7; + if (no_error_inj && *ccs_out == -1) + *ccs_out = 7; /* if ccs_out == -1, then the user can select it using controls */ - if (ccs_out != -1) { - dev->has_crop_out = ccs_out & 1; - dev->has_compose_out = ccs_out & 2; - dev->has_scaler_out = ccs_out & 4; + if (*ccs_out != -1) { + dev->has_crop_out = *ccs_out & 1; + dev->has_compose_out = *ccs_out & 2; + dev->has_scaler_out = *ccs_out & 4; v4l2_info(&dev->v4l2_dev, "Output Crop: %c Compose: %c Scaler: %c\n", dev->has_crop_out ? 'Y' : 'N', dev->has_compose_out ? 'Y' : 'N', @@ -1042,8 +998,11 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) /* do we create a touch capture device */ dev->has_touch_cap = node_type & 0x80000; - /* end detecting feature set */ + return 0; +} +static void vivid_set_capabilities(struct vivid_dev *dev) +{ if (dev->has_vid_cap) { /* set up the capabilities of the video capture device */ dev->vid_cap_caps = dev->multiplanar ? @@ -1122,58 +1081,14 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) dev->touch_cap_caps |= dev->multiplanar ? V4L2_CAP_VIDEO_CAPTURE_MPLANE : V4L2_CAP_VIDEO_CAPTURE; } +} - ret = -ENOMEM; - /* initialize the test pattern generator */ - tpg_init(&dev->tpg, 640, 360); - if (tpg_alloc(&dev->tpg, array_size(MAX_WIDTH, MAX_ZOOM))) - goto free_dev; - dev->scaled_line = vzalloc(array_size(MAX_WIDTH, MAX_ZOOM)); - if (!dev->scaled_line) - goto free_dev; - dev->blended_line = vzalloc(array_size(MAX_WIDTH, MAX_ZOOM)); - if (!dev->blended_line) - goto free_dev; - - /* load the edid */ - dev->edid = vmalloc(array_size(256, 128)); - if (!dev->edid) - goto free_dev; - - while (v4l2_dv_timings_presets[dev->query_dv_timings_size].bt.width) - dev->query_dv_timings_size++; - - /* - * Create a char pointer array that points to the names of all the - * preset timings - */ - dev->query_dv_timings_qmenu = kmalloc_array(dev->query_dv_timings_size, - sizeof(char *), GFP_KERNEL); - /* - * Create a string array containing the names of all the preset - * timings. Each name is max 31 chars long (+ terminating 0). - */ - dev->query_dv_timings_qmenu_strings = - kmalloc_array(dev->query_dv_timings_size, 32, GFP_KERNEL); - - if (!dev->query_dv_timings_qmenu || - !dev->query_dv_timings_qmenu_strings) - goto free_dev; - - for (i = 0; i < dev->query_dv_timings_size; i++) { - const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[i].bt; - char *p = dev->query_dv_timings_qmenu_strings + i * 32; - u32 htot, vtot; - - dev->query_dv_timings_qmenu[i] = p; - - htot = V4L2_DV_BT_FRAME_WIDTH(bt); - vtot = V4L2_DV_BT_FRAME_HEIGHT(bt); - snprintf(p, 32, "%ux%u%s%u", - bt->width, bt->height, bt->interlaced ? "i" : "p", - (u32)bt->pixelclock / (htot * vtot)); - } - +static void vivid_disable_unused_ioctls(struct vivid_dev *dev, + bool has_tuner, + bool has_modulator, + unsigned in_type_counter[4], + unsigned out_type_counter[4]) +{ /* disable invalid ioctls based on the feature set */ if (!dev->has_audio_inputs) { v4l2_disable_ioctl(&dev->vid_cap_dev, VIDIOC_S_AUDIO); @@ -1260,112 +1175,52 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) v4l2_disable_ioctl(&dev->touch_cap_dev, VIDIOC_S_PARM); v4l2_disable_ioctl(&dev->touch_cap_dev, VIDIOC_ENUM_FRAMESIZES); v4l2_disable_ioctl(&dev->touch_cap_dev, VIDIOC_ENUM_FRAMEINTERVALS); +} - /* configure internal data */ - dev->fmt_cap = &vivid_formats[0]; - dev->fmt_out = &vivid_formats[0]; - if (!dev->multiplanar) - vivid_formats[0].data_offset[0] = 0; - dev->webcam_size_idx = 1; - dev->webcam_ival_idx = 3; - tpg_s_fourcc(&dev->tpg, dev->fmt_cap->fourcc); - dev->std_out = V4L2_STD_PAL; - if (dev->input_type[0] == TV || dev->input_type[0] == SVID) - tvnorms_cap = V4L2_STD_ALL; - if (dev->output_type[0] == SVID) - tvnorms_out = V4L2_STD_ALL; - for (i = 0; i < MAX_INPUTS; i++) { - dev->dv_timings_cap[i] = def_dv_timings; - dev->std_cap[i] = V4L2_STD_PAL; - } - dev->dv_timings_out = def_dv_timings; - dev->tv_freq = 2804 /* 175.25 * 16 */; - dev->tv_audmode = V4L2_TUNER_MODE_STEREO; - dev->tv_field_cap = V4L2_FIELD_INTERLACED; - dev->tv_field_out = V4L2_FIELD_INTERLACED; - dev->radio_rx_freq = 95000 * 16; - dev->radio_rx_audmode = V4L2_TUNER_MODE_STEREO; - if (dev->has_radio_tx) { - dev->radio_tx_freq = 95500 * 16; - dev->radio_rds_loop = false; - } - dev->radio_tx_subchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_RDS; - dev->sdr_adc_freq = 300000; - dev->sdr_fm_freq = 50000000; - dev->sdr_pixelformat = V4L2_SDR_FMT_CU8; - dev->sdr_buffersize = SDR_CAP_SAMPLES_PER_BUF * 2; - - dev->edid_max_blocks = dev->edid_blocks = 2; - memcpy(dev->edid, vivid_hdmi_edid, sizeof(vivid_hdmi_edid)); - dev->radio_rds_init_time = ktime_get(); - - /* create all controls */ - ret = vivid_create_controls(dev, ccs_cap == -1, ccs_out == -1, no_error_inj, - in_type_counter[TV] || in_type_counter[SVID] || - out_type_counter[SVID], - in_type_counter[HDMI] || out_type_counter[HDMI]); - if (ret) - goto unreg_dev; +static int vivid_init_dv_timings(struct vivid_dev *dev) +{ + int i; - /* enable/disable interface specific controls */ - if (dev->num_outputs && dev->output_type[0] != HDMI) - v4l2_ctrl_activate(dev->ctrl_display_present, false); - if (dev->num_inputs && dev->input_type[0] != HDMI) { - v4l2_ctrl_activate(dev->ctrl_dv_timings_signal_mode, false); - v4l2_ctrl_activate(dev->ctrl_dv_timings, false); - } else if (dev->num_inputs && dev->input_type[0] == HDMI) { - v4l2_ctrl_activate(dev->ctrl_std_signal_mode, false); - v4l2_ctrl_activate(dev->ctrl_standard, false); - } + while (v4l2_dv_timings_presets[dev->query_dv_timings_size].bt.width) + dev->query_dv_timings_size++; /* - * update the capture and output formats to do a proper initial - * configuration. + * Create a char pointer array that points to the names of all the + * preset timings */ - vivid_update_format_cap(dev, false); - vivid_update_format_out(dev); - - /* initialize overlay */ - dev->fb_cap.fmt.width = dev->src_rect.width; - dev->fb_cap.fmt.height = dev->src_rect.height; - dev->fb_cap.fmt.pixelformat = dev->fmt_cap->fourcc; - dev->fb_cap.fmt.bytesperline = dev->src_rect.width * tpg_g_twopixelsize(&dev->tpg, 0) / 2; - dev->fb_cap.fmt.sizeimage = dev->src_rect.height * dev->fb_cap.fmt.bytesperline; + dev->query_dv_timings_qmenu = kmalloc_array(dev->query_dv_timings_size, + sizeof(char *), GFP_KERNEL); + /* + * Create a string array containing the names of all the preset + * timings. Each name is max 31 chars long (+ terminating 0). + */ + dev->query_dv_timings_qmenu_strings = + kmalloc_array(dev->query_dv_timings_size, 32, GFP_KERNEL); - /* update touch configuration */ - dev->timeperframe_tch_cap.numerator = 1; - dev->timeperframe_tch_cap.denominator = 10; - vivid_set_touch(dev, 0); + if (!dev->query_dv_timings_qmenu || + !dev->query_dv_timings_qmenu_strings) + return -ENOMEM; - /* initialize locks */ - spin_lock_init(&dev->slock); - mutex_init(&dev->mutex); + for (i = 0; i < dev->query_dv_timings_size; i++) { + const struct v4l2_bt_timings *bt = &v4l2_dv_timings_presets[i].bt; + char *p = dev->query_dv_timings_qmenu_strings + i * 32; + u32 htot, vtot; - /* init dma queues */ - INIT_LIST_HEAD(&dev->vid_cap_active); - INIT_LIST_HEAD(&dev->vid_out_active); - INIT_LIST_HEAD(&dev->vbi_cap_active); - INIT_LIST_HEAD(&dev->vbi_out_active); - INIT_LIST_HEAD(&dev->sdr_cap_active); - INIT_LIST_HEAD(&dev->meta_cap_active); - INIT_LIST_HEAD(&dev->meta_out_active); - INIT_LIST_HEAD(&dev->touch_cap_active); + dev->query_dv_timings_qmenu[i] = p; - INIT_LIST_HEAD(&dev->cec_work_list); - spin_lock_init(&dev->cec_slock); - /* - * Same as create_singlethread_workqueue, but now I can use the - * string formatting of alloc_ordered_workqueue. - */ - dev->cec_workqueue = - alloc_ordered_workqueue("vivid-%03d-cec", WQ_MEM_RECLAIM, inst); - if (!dev->cec_workqueue) { - ret = -ENOMEM; - goto unreg_dev; + htot = V4L2_DV_BT_FRAME_WIDTH(bt); + vtot = V4L2_DV_BT_FRAME_HEIGHT(bt); + snprintf(p, 32, "%ux%u%s%u", + bt->width, bt->height, bt->interlaced ? "i" : "p", + (u32)bt->pixelclock / (htot * vtot)); } - if (allocators[inst] == 1) - dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + return 0; +} + +static int vivid_create_queues(struct vivid_dev *dev) +{ + int ret; /* start creating the vb2 queues */ if (dev->has_vid_cap) { @@ -1374,7 +1229,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) V4L2_BUF_TYPE_VIDEO_CAPTURE, 2, &vivid_vid_cap_qops); if (ret) - goto unreg_dev; + return ret; } if (dev->has_vid_out) { @@ -1383,7 +1238,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) V4L2_BUF_TYPE_VIDEO_OUTPUT, 2, &vivid_vid_out_qops); if (ret) - goto unreg_dev; + return ret; } if (dev->has_vbi_cap) { @@ -1392,7 +1247,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) V4L2_BUF_TYPE_VBI_CAPTURE, 2, &vivid_vbi_cap_qops); if (ret) - goto unreg_dev; + return ret; } if (dev->has_vbi_out) { @@ -1401,7 +1256,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) V4L2_BUF_TYPE_VBI_OUTPUT, 2, &vivid_vbi_out_qops); if (ret) - goto unreg_dev; + return ret; } if (dev->has_sdr_cap) { @@ -1410,7 +1265,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) V4L2_BUF_TYPE_SDR_CAPTURE, 8, &vivid_sdr_cap_qops); if (ret) - goto unreg_dev; + return ret; } if (dev->has_meta_cap) { @@ -1419,7 +1274,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) V4L2_BUF_TYPE_META_CAPTURE, 2, &vivid_meta_cap_qops); if (ret) - goto unreg_dev; + return ret; } if (dev->has_meta_out) { @@ -1428,7 +1283,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) V4L2_BUF_TYPE_META_OUTPUT, 1, &vivid_meta_out_qops); if (ret) - goto unreg_dev; + return ret; } if (dev->has_touch_cap) { @@ -1437,63 +1292,31 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) V4L2_BUF_TYPE_VIDEO_CAPTURE, 1, &vivid_touch_cap_qops); if (ret) - goto unreg_dev; + return ret; } if (dev->has_fb) { /* Create framebuffer for testing capture/output overlay */ ret = vivid_fb_init(dev); if (ret) - goto unreg_dev; + return ret; v4l2_info(&dev->v4l2_dev, "Framebuffer device registered as fb%d\n", dev->fb_info.node); } + return 0; +} -#ifdef CONFIG_VIDEO_VIVID_CEC - if (dev->has_vid_cap && in_type_counter[HDMI]) { - struct cec_adapter *adap; - - adap = vivid_cec_alloc_adap(dev, 0, false); - ret = PTR_ERR_OR_ZERO(adap); - if (ret < 0) - goto unreg_dev; - dev->cec_rx_adap = adap; - } - - if (dev->has_vid_out) { - for (i = 0; i < dev->num_outputs; i++) { - struct cec_adapter *adap; - - if (dev->output_type[i] != HDMI) - continue; - - dev->cec_output2bus_map[i] = cec_tx_bus_cnt; - adap = vivid_cec_alloc_adap(dev, cec_tx_bus_cnt, true); - ret = PTR_ERR_OR_ZERO(adap); - if (ret < 0) { - for (i = 0; i < dev->num_outputs; i++) - cec_delete_adapter(dev->cec_tx_adap[i]); - goto unreg_dev; - } - - dev->cec_tx_adap[cec_tx_bus_cnt] = adap; - cec_tx_bus_cnt++; - } - } -#endif - - v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vid_cap); - v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vid_out); - v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vbi_cap); - v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vbi_out); - v4l2_ctrl_handler_setup(&dev->ctrl_hdl_radio_rx); - v4l2_ctrl_handler_setup(&dev->ctrl_hdl_radio_tx); - v4l2_ctrl_handler_setup(&dev->ctrl_hdl_sdr_cap); - v4l2_ctrl_handler_setup(&dev->ctrl_hdl_meta_cap); - v4l2_ctrl_handler_setup(&dev->ctrl_hdl_meta_out); - v4l2_ctrl_handler_setup(&dev->ctrl_hdl_touch_cap); +static int vivid_create_devnodes(struct platform_device *pdev, + struct vivid_dev *dev, int inst, + unsigned int cec_tx_bus_cnt, + v4l2_std_id tvnorms_cap, + v4l2_std_id tvnorms_out, + unsigned in_type_counter[4], + unsigned out_type_counter[4]) +{ + struct video_device *vfd; + int ret; - /* finally start creating the device nodes */ if (dev->has_vid_cap) { vfd = &dev->vid_cap_dev; snprintf(vfd->name, sizeof(vfd->name), @@ -1517,7 +1340,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) dev->vid_cap_pad.flags = MEDIA_PAD_FL_SINK; ret = media_entity_pads_init(&vfd->entity, 1, &dev->vid_cap_pad); if (ret) - goto unreg_dev; + return ret; #endif #ifdef CONFIG_VIDEO_VIVID_CEC @@ -1526,7 +1349,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) if (ret < 0) { cec_delete_adapter(dev->cec_rx_adap); dev->cec_rx_adap = NULL; - goto unreg_dev; + return ret; } cec_s_phys_addr(dev->cec_rx_adap, 0, false); v4l2_info(&dev->v4l2_dev, "CEC adapter %s registered for HDMI input 0\n", @@ -1536,12 +1359,15 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) ret = video_register_device(vfd, VFL_TYPE_VIDEO, vid_cap_nr[inst]); if (ret < 0) - goto unreg_dev; + return ret; v4l2_info(&dev->v4l2_dev, "V4L2 capture device registered as %s\n", video_device_node_name(vfd)); } if (dev->has_vid_out) { +#ifdef CONFIG_VIDEO_VIVID_CEC + int i; +#endif vfd = &dev->vid_out_dev; snprintf(vfd->name, sizeof(vfd->name), "vivid-%03d-vid-out", inst); @@ -1565,7 +1391,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) dev->vid_out_pad.flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&vfd->entity, 1, &dev->vid_out_pad); if (ret) - goto unreg_dev; + return ret; #endif #ifdef CONFIG_VIDEO_VIVID_CEC @@ -1576,7 +1402,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) cec_delete_adapter(dev->cec_tx_adap[i]); dev->cec_tx_adap[i] = NULL; } - goto unreg_dev; + return ret; } v4l2_info(&dev->v4l2_dev, "CEC adapter %s registered for HDMI output %d\n", dev_name(&dev->cec_tx_adap[i]->devnode.dev), i); @@ -1589,7 +1415,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) ret = video_register_device(vfd, VFL_TYPE_VIDEO, vid_out_nr[inst]); if (ret < 0) - goto unreg_dev; + return ret; v4l2_info(&dev->v4l2_dev, "V4L2 output device registered as %s\n", video_device_node_name(vfd)); } @@ -1612,12 +1438,12 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) dev->vbi_cap_pad.flags = MEDIA_PAD_FL_SINK; ret = media_entity_pads_init(&vfd->entity, 1, &dev->vbi_cap_pad); if (ret) - goto unreg_dev; + return ret; #endif ret = video_register_device(vfd, VFL_TYPE_VBI, vbi_cap_nr[inst]); if (ret < 0) - goto unreg_dev; + return ret; v4l2_info(&dev->v4l2_dev, "V4L2 capture device registered as %s, supports %s VBI\n", video_device_node_name(vfd), (dev->has_raw_vbi_cap && dev->has_sliced_vbi_cap) ? @@ -1644,12 +1470,12 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) dev->vbi_out_pad.flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&vfd->entity, 1, &dev->vbi_out_pad); if (ret) - goto unreg_dev; + return ret; #endif ret = video_register_device(vfd, VFL_TYPE_VBI, vbi_out_nr[inst]); if (ret < 0) - goto unreg_dev; + return ret; v4l2_info(&dev->v4l2_dev, "V4L2 output device registered as %s, supports %s VBI\n", video_device_node_name(vfd), (dev->has_raw_vbi_out && dev->has_sliced_vbi_out) ? @@ -1674,12 +1500,12 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) dev->sdr_cap_pad.flags = MEDIA_PAD_FL_SINK; ret = media_entity_pads_init(&vfd->entity, 1, &dev->sdr_cap_pad); if (ret) - goto unreg_dev; + return ret; #endif ret = video_register_device(vfd, VFL_TYPE_SDR, sdr_cap_nr[inst]); if (ret < 0) - goto unreg_dev; + return ret; v4l2_info(&dev->v4l2_dev, "V4L2 capture device registered as %s\n", video_device_node_name(vfd)); } @@ -1698,7 +1524,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) ret = video_register_device(vfd, VFL_TYPE_RADIO, radio_rx_nr[inst]); if (ret < 0) - goto unreg_dev; + return ret; v4l2_info(&dev->v4l2_dev, "V4L2 receiver device registered as %s\n", video_device_node_name(vfd)); } @@ -1718,7 +1544,7 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) ret = video_register_device(vfd, VFL_TYPE_RADIO, radio_tx_nr[inst]); if (ret < 0) - goto unreg_dev; + return ret; v4l2_info(&dev->v4l2_dev, "V4L2 transmitter device registered as %s\n", video_device_node_name(vfd)); } @@ -1741,12 +1567,12 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) ret = media_entity_pads_init(&vfd->entity, 1, &dev->meta_cap_pad); if (ret) - goto unreg_dev; + return ret; #endif ret = video_register_device(vfd, VFL_TYPE_VIDEO, meta_cap_nr[inst]); if (ret < 0) - goto unreg_dev; + return ret; v4l2_info(&dev->v4l2_dev, "V4L2 metadata capture device registered as %s\n", video_device_node_name(vfd)); @@ -1771,12 +1597,12 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) ret = media_entity_pads_init(&vfd->entity, 1, &dev->meta_out_pad); if (ret) - goto unreg_dev; + return ret; #endif ret = video_register_device(vfd, VFL_TYPE_VIDEO, meta_out_nr[inst]); if (ret < 0) - goto unreg_dev; + return ret; v4l2_info(&dev->v4l2_dev, "V4L2 metadata output device registered as %s\n", video_device_node_name(vfd)); @@ -1800,12 +1626,12 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) ret = media_entity_pads_init(&vfd->entity, 1, &dev->touch_cap_pad); if (ret) - goto unreg_dev; + return ret; #endif ret = video_register_device(vfd, VFL_TYPE_TOUCH, touch_cap_nr[inst]); if (ret < 0) - goto unreg_dev; + return ret; v4l2_info(&dev->v4l2_dev, "V4L2 touch capture device registered as %s\n", video_device_node_name(vfd)); @@ -1817,26 +1643,268 @@ static int vivid_create_instance(struct platform_device *pdev, int inst) if (ret) { dev_err(dev->mdev.dev, "media device register failed (err=%d)\n", ret); + return ret; + } +#endif + return 0; +} + +static int vivid_create_instance(struct platform_device *pdev, int inst) +{ + static const struct v4l2_dv_timings def_dv_timings = + V4L2_DV_BT_CEA_1280X720P60; + unsigned in_type_counter[4] = { 0, 0, 0, 0 }; + unsigned out_type_counter[4] = { 0, 0, 0, 0 }; + int ccs_cap = ccs_cap_mode[inst]; + int ccs_out = ccs_out_mode[inst]; + bool has_tuner; + bool has_modulator; + struct vivid_dev *dev; + unsigned node_type = node_types[inst]; + v4l2_std_id tvnorms_cap = 0, tvnorms_out = 0; + unsigned int cec_tx_bus_cnt = 0; + int ret; + int i; + + /* allocate main vivid state structure */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->inst = inst; + +#ifdef CONFIG_MEDIA_CONTROLLER + dev->v4l2_dev.mdev = &dev->mdev; + + /* Initialize media device */ + strscpy(dev->mdev.model, VIVID_MODULE_NAME, sizeof(dev->mdev.model)); + snprintf(dev->mdev.bus_info, sizeof(dev->mdev.bus_info), + "platform:%s-%03d", VIVID_MODULE_NAME, inst); + dev->mdev.dev = &pdev->dev; + media_device_init(&dev->mdev); + dev->mdev.ops = &vivid_media_ops; +#endif + + /* register v4l2_device */ + snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), + "%s-%03d", VIVID_MODULE_NAME, inst); + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) { + kfree(dev); + return ret; + } + dev->v4l2_dev.release = vivid_dev_release; + + ret = vivid_detect_feature_set(dev, inst, node_type, + &has_tuner, &has_modulator, + &ccs_cap, &ccs_out, + in_type_counter, out_type_counter); + if (ret) + goto free_dev; + + vivid_set_capabilities(dev); + + ret = -ENOMEM; + /* initialize the test pattern generator */ + tpg_init(&dev->tpg, 640, 360); + if (tpg_alloc(&dev->tpg, array_size(MAX_WIDTH, MAX_ZOOM))) + goto free_dev; + dev->scaled_line = vzalloc(array_size(MAX_WIDTH, MAX_ZOOM)); + if (!dev->scaled_line) + goto free_dev; + dev->blended_line = vzalloc(array_size(MAX_WIDTH, MAX_ZOOM)); + if (!dev->blended_line) + goto free_dev; + + /* load the edid */ + dev->edid = vmalloc(array_size(256, 128)); + if (!dev->edid) + goto free_dev; + + ret = vivid_init_dv_timings(dev); + if (ret < 0) + goto free_dev; + + vivid_disable_unused_ioctls(dev, has_tuner, has_modulator, + in_type_counter, out_type_counter); + + /* configure internal data */ + dev->fmt_cap = &vivid_formats[0]; + dev->fmt_out = &vivid_formats[0]; + if (!dev->multiplanar) + vivid_formats[0].data_offset[0] = 0; + dev->webcam_size_idx = 1; + dev->webcam_ival_idx = 3; + tpg_s_fourcc(&dev->tpg, dev->fmt_cap->fourcc); + dev->std_out = V4L2_STD_PAL; + if (dev->input_type[0] == TV || dev->input_type[0] == SVID) + tvnorms_cap = V4L2_STD_ALL; + if (dev->output_type[0] == SVID) + tvnorms_out = V4L2_STD_ALL; + for (i = 0; i < MAX_INPUTS; i++) { + dev->dv_timings_cap[i] = def_dv_timings; + dev->std_cap[i] = V4L2_STD_PAL; + } + dev->dv_timings_out = def_dv_timings; + dev->tv_freq = 2804 /* 175.25 * 16 */; + dev->tv_audmode = V4L2_TUNER_MODE_STEREO; + dev->tv_field_cap = V4L2_FIELD_INTERLACED; + dev->tv_field_out = V4L2_FIELD_INTERLACED; + dev->radio_rx_freq = 95000 * 16; + dev->radio_rx_audmode = V4L2_TUNER_MODE_STEREO; + if (dev->has_radio_tx) { + dev->radio_tx_freq = 95500 * 16; + dev->radio_rds_loop = false; + } + dev->radio_tx_subchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_RDS; + dev->sdr_adc_freq = 300000; + dev->sdr_fm_freq = 50000000; + dev->sdr_pixelformat = V4L2_SDR_FMT_CU8; + dev->sdr_buffersize = SDR_CAP_SAMPLES_PER_BUF * 2; + + dev->edid_max_blocks = dev->edid_blocks = 2; + memcpy(dev->edid, vivid_hdmi_edid, sizeof(vivid_hdmi_edid)); + dev->radio_rds_init_time = ktime_get(); + + /* create all controls */ + ret = vivid_create_controls(dev, ccs_cap == -1, ccs_out == -1, no_error_inj, + in_type_counter[TV] || in_type_counter[SVID] || + out_type_counter[SVID], + in_type_counter[HDMI] || out_type_counter[HDMI]); + if (ret) goto unreg_dev; + + /* enable/disable interface specific controls */ + if (dev->num_outputs && dev->output_type[0] != HDMI) + v4l2_ctrl_activate(dev->ctrl_display_present, false); + if (dev->num_inputs && dev->input_type[0] != HDMI) { + v4l2_ctrl_activate(dev->ctrl_dv_timings_signal_mode, false); + v4l2_ctrl_activate(dev->ctrl_dv_timings, false); + } else if (dev->num_inputs && dev->input_type[0] == HDMI) { + v4l2_ctrl_activate(dev->ctrl_std_signal_mode, false); + v4l2_ctrl_activate(dev->ctrl_standard, false); + } + + /* + * update the capture and output formats to do a proper initial + * configuration. + */ + vivid_update_format_cap(dev, false); + vivid_update_format_out(dev); + + /* initialize overlay */ + dev->fb_cap.fmt.width = dev->src_rect.width; + dev->fb_cap.fmt.height = dev->src_rect.height; + dev->fb_cap.fmt.pixelformat = dev->fmt_cap->fourcc; + dev->fb_cap.fmt.bytesperline = dev->src_rect.width * tpg_g_twopixelsize(&dev->tpg, 0) / 2; + dev->fb_cap.fmt.sizeimage = dev->src_rect.height * dev->fb_cap.fmt.bytesperline; + + /* update touch configuration */ + dev->timeperframe_tch_cap.numerator = 1; + dev->timeperframe_tch_cap.denominator = 10; + vivid_set_touch(dev, 0); + + /* initialize locks */ + spin_lock_init(&dev->slock); + mutex_init(&dev->mutex); + + /* init dma queues */ + INIT_LIST_HEAD(&dev->vid_cap_active); + INIT_LIST_HEAD(&dev->vid_out_active); + INIT_LIST_HEAD(&dev->vbi_cap_active); + INIT_LIST_HEAD(&dev->vbi_out_active); + INIT_LIST_HEAD(&dev->sdr_cap_active); + INIT_LIST_HEAD(&dev->meta_cap_active); + INIT_LIST_HEAD(&dev->meta_out_active); + INIT_LIST_HEAD(&dev->touch_cap_active); + + INIT_LIST_HEAD(&dev->cec_work_list); + spin_lock_init(&dev->cec_slock); + /* + * Same as create_singlethread_workqueue, but now I can use the + * string formatting of alloc_ordered_workqueue. + */ + dev->cec_workqueue = alloc_ordered_workqueue("vivid-%03d-cec", + WQ_MEM_RECLAIM, inst); + if (!dev->cec_workqueue) { + ret = -ENOMEM; + goto unreg_dev; + } + + if (allocators[inst] == 1) + dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + + ret = vivid_create_queues(dev); + if (ret) + goto unreg_dev; + +#ifdef CONFIG_VIDEO_VIVID_CEC + if (dev->has_vid_cap && in_type_counter[HDMI]) { + struct cec_adapter *adap; + + adap = vivid_cec_alloc_adap(dev, 0, false); + ret = PTR_ERR_OR_ZERO(adap); + if (ret < 0) + goto unreg_dev; + dev->cec_rx_adap = adap; + } + + if (dev->has_vid_out) { + for (i = 0; i < dev->num_outputs; i++) { + struct cec_adapter *adap; + + if (dev->output_type[i] != HDMI) + continue; + + dev->cec_output2bus_map[i] = cec_tx_bus_cnt; + adap = vivid_cec_alloc_adap(dev, cec_tx_bus_cnt, true); + ret = PTR_ERR_OR_ZERO(adap); + if (ret < 0) { + for (i = 0; i < dev->num_outputs; i++) + cec_delete_adapter(dev->cec_tx_adap[i]); + goto unreg_dev; + } + + dev->cec_tx_adap[cec_tx_bus_cnt] = adap; + cec_tx_bus_cnt++; + } } #endif + v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vid_cap); + v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vid_out); + v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vbi_cap); + v4l2_ctrl_handler_setup(&dev->ctrl_hdl_vbi_out); + v4l2_ctrl_handler_setup(&dev->ctrl_hdl_radio_rx); + v4l2_ctrl_handler_setup(&dev->ctrl_hdl_radio_tx); + v4l2_ctrl_handler_setup(&dev->ctrl_hdl_sdr_cap); + v4l2_ctrl_handler_setup(&dev->ctrl_hdl_meta_cap); + v4l2_ctrl_handler_setup(&dev->ctrl_hdl_meta_out); + v4l2_ctrl_handler_setup(&dev->ctrl_hdl_touch_cap); + + /* finally start creating the device nodes */ + ret = vivid_create_devnodes(pdev, dev, inst, cec_tx_bus_cnt, + tvnorms_cap, tvnorms_out, + in_type_counter, out_type_counter); + if (ret) + goto unreg_dev; + /* Now that everything is fine, let's add it to device list */ vivid_devs[inst] = dev; return 0; unreg_dev: - video_unregister_device(&dev->touch_cap_dev); - video_unregister_device(&dev->meta_out_dev); - video_unregister_device(&dev->meta_cap_dev); + vb2_video_unregister_device(&dev->touch_cap_dev); + vb2_video_unregister_device(&dev->meta_out_dev); + vb2_video_unregister_device(&dev->meta_cap_dev); video_unregister_device(&dev->radio_tx_dev); video_unregister_device(&dev->radio_rx_dev); - video_unregister_device(&dev->sdr_cap_dev); - video_unregister_device(&dev->vbi_out_dev); - video_unregister_device(&dev->vbi_cap_dev); - video_unregister_device(&dev->vid_out_dev); - video_unregister_device(&dev->vid_cap_dev); + vb2_video_unregister_device(&dev->sdr_cap_dev); + vb2_video_unregister_device(&dev->vbi_out_dev); + vb2_video_unregister_device(&dev->vbi_cap_dev); + vb2_video_unregister_device(&dev->vid_out_dev); + vb2_video_unregister_device(&dev->vid_cap_dev); cec_unregister_adapter(dev->cec_rx_adap); for (i = 0; i < MAX_OUTPUTS; i++) cec_unregister_adapter(dev->cec_tx_adap[i]); @@ -1907,27 +1975,27 @@ static int vivid_remove(struct platform_device *pdev) if (dev->has_vid_cap) { v4l2_info(&dev->v4l2_dev, "unregistering %s\n", video_device_node_name(&dev->vid_cap_dev)); - video_unregister_device(&dev->vid_cap_dev); + vb2_video_unregister_device(&dev->vid_cap_dev); } if (dev->has_vid_out) { v4l2_info(&dev->v4l2_dev, "unregistering %s\n", video_device_node_name(&dev->vid_out_dev)); - video_unregister_device(&dev->vid_out_dev); + vb2_video_unregister_device(&dev->vid_out_dev); } if (dev->has_vbi_cap) { v4l2_info(&dev->v4l2_dev, "unregistering %s\n", video_device_node_name(&dev->vbi_cap_dev)); - video_unregister_device(&dev->vbi_cap_dev); + vb2_video_unregister_device(&dev->vbi_cap_dev); } if (dev->has_vbi_out) { v4l2_info(&dev->v4l2_dev, "unregistering %s\n", video_device_node_name(&dev->vbi_out_dev)); - video_unregister_device(&dev->vbi_out_dev); + vb2_video_unregister_device(&dev->vbi_out_dev); } if (dev->has_sdr_cap) { v4l2_info(&dev->v4l2_dev, "unregistering %s\n", video_device_node_name(&dev->sdr_cap_dev)); - video_unregister_device(&dev->sdr_cap_dev); + vb2_video_unregister_device(&dev->sdr_cap_dev); } if (dev->has_radio_rx) { v4l2_info(&dev->v4l2_dev, "unregistering %s\n", @@ -1948,17 +2016,17 @@ static int vivid_remove(struct platform_device *pdev) if (dev->has_meta_cap) { v4l2_info(&dev->v4l2_dev, "unregistering %s\n", video_device_node_name(&dev->meta_cap_dev)); - video_unregister_device(&dev->meta_cap_dev); + vb2_video_unregister_device(&dev->meta_cap_dev); } if (dev->has_meta_out) { v4l2_info(&dev->v4l2_dev, "unregistering %s\n", video_device_node_name(&dev->meta_out_dev)); - video_unregister_device(&dev->meta_out_dev); + vb2_video_unregister_device(&dev->meta_out_dev); } if (dev->has_touch_cap) { v4l2_info(&dev->v4l2_dev, "unregistering %s\n", video_device_node_name(&dev->touch_cap_dev)); - video_unregister_device(&dev->touch_cap_dev); + vb2_video_unregister_device(&dev->touch_cap_dev); } cec_unregister_adapter(dev->cec_rx_adap); for (j = 0; j < MAX_OUTPUTS; j++) diff --git a/drivers/media/test-drivers/vivid/vivid-meta-out.c b/drivers/media/test-drivers/vivid/vivid-meta-out.c index ff8a039aba72..95835b52b58f 100644 --- a/drivers/media/test-drivers/vivid/vivid-meta-out.c +++ b/drivers/media/test-drivers/vivid/vivid-meta-out.c @@ -164,10 +164,11 @@ void vivid_meta_out_process(struct vivid_dev *dev, { struct vivid_meta_out_buf *meta = vb2_plane_vaddr(&buf->vb.vb2_buf, 0); - tpg_s_brightness(&dev->tpg, meta->brightness); - tpg_s_contrast(&dev->tpg, meta->contrast); - tpg_s_saturation(&dev->tpg, meta->saturation); - tpg_s_hue(&dev->tpg, meta->hue); + v4l2_ctrl_s_ctrl(dev->brightness, meta->brightness); + v4l2_ctrl_s_ctrl(dev->contrast, meta->contrast); + v4l2_ctrl_s_ctrl(dev->saturation, meta->saturation); + v4l2_ctrl_s_ctrl(dev->hue, meta->hue); + dprintk(dev, 2, " %s brightness %u contrast %u saturation %u hue %d\n", __func__, meta->brightness, meta->contrast, meta->saturation, meta->hue); diff --git a/drivers/media/test-drivers/vivid/vivid-vbi-gen.c b/drivers/media/test-drivers/vivid/vivid-vbi-gen.c index acc98445a1fa..a141369a7a63 100644 --- a/drivers/media/test-drivers/vivid/vivid-vbi-gen.c +++ b/drivers/media/test-drivers/vivid/vivid-vbi-gen.c @@ -298,7 +298,7 @@ void vivid_vbi_gen_sliced(struct vivid_vbi_gen_data *vbi, switch (frame) { case 0: vivid_vbi_gen_set_time_of_day(vbi->time_of_day_packet); - /* fall through */ + fallthrough; case 1 ... 7: data1->data[0] = vbi->time_of_day_packet[frame * 2]; data1->data[1] = vbi->time_of_day_packet[frame * 2 + 1]; diff --git a/drivers/media/test-drivers/vivid/vivid-vid-cap.c b/drivers/media/test-drivers/vivid/vivid-vid-cap.c index e94beef008c8..eadf28ab1e39 100644 --- a/drivers/media/test-drivers/vivid/vivid-vid-cap.c +++ b/drivers/media/test-drivers/vivid/vivid-vid-cap.c @@ -560,6 +560,7 @@ int vivid_try_fmt_vid_cap(struct file *file, void *priv, unsigned factor = 1; unsigned w, h; unsigned p; + bool user_set_csc = !!(mp->flags & V4L2_PIX_FMT_FLAG_SET_CSC); fmt = vivid_get_format(dev, mp->pixelformat); if (!fmt) { @@ -633,13 +634,30 @@ int vivid_try_fmt_vid_cap(struct file *file, void *priv, (fmt->bit_depth[p] / fmt->vdownsampling[p])) / (fmt->bit_depth[0] / fmt->vdownsampling[0]); - mp->colorspace = vivid_colorspace_cap(dev); - if (fmt->color_enc == TGP_COLOR_ENC_HSV) - mp->hsv_enc = vivid_hsv_enc_cap(dev); - else + if (!user_set_csc || !v4l2_is_colorspace_valid(mp->colorspace)) + mp->colorspace = vivid_colorspace_cap(dev); + + if (!user_set_csc || !v4l2_is_xfer_func_valid(mp->xfer_func)) + mp->xfer_func = vivid_xfer_func_cap(dev); + + if (fmt->color_enc == TGP_COLOR_ENC_HSV) { + if (!user_set_csc || !v4l2_is_hsv_enc_valid(mp->hsv_enc)) + mp->hsv_enc = vivid_hsv_enc_cap(dev); + } else if (fmt->color_enc == TGP_COLOR_ENC_YCBCR) { + if (!user_set_csc || !v4l2_is_ycbcr_enc_valid(mp->ycbcr_enc)) + mp->ycbcr_enc = vivid_ycbcr_enc_cap(dev); + } else { mp->ycbcr_enc = vivid_ycbcr_enc_cap(dev); - mp->xfer_func = vivid_xfer_func_cap(dev); - mp->quantization = vivid_quantization_cap(dev); + } + + if (fmt->color_enc == TGP_COLOR_ENC_YCBCR || + fmt->color_enc == TGP_COLOR_ENC_RGB) { + if (!user_set_csc || !v4l2_is_quant_valid(mp->quantization)) + mp->quantization = vivid_quantization_cap(dev); + } else { + mp->quantization = vivid_quantization_cap(dev); + } + memset(mp->reserved, 0, sizeof(mp->reserved)); return 0; } @@ -769,6 +787,14 @@ int vivid_s_fmt_vid_cap(struct file *file, void *priv, if (vivid_is_sdtv_cap(dev)) dev->tv_field_cap = mp->field; tpg_update_mv_step(&dev->tpg); + dev->tpg.colorspace = mp->colorspace; + dev->tpg.xfer_func = mp->xfer_func; + if (dev->fmt_cap->color_enc == TGP_COLOR_ENC_YCBCR) + dev->tpg.ycbcr_enc = mp->ycbcr_enc; + else + dev->tpg.hsv_enc = mp->hsv_enc; + dev->tpg.quantization = mp->quantization; + return 0; } diff --git a/drivers/media/test-drivers/vivid/vivid-vid-common.c b/drivers/media/test-drivers/vivid/vivid-vid-common.c index 76b0be670ebb..19701fe72030 100644 --- a/drivers/media/test-drivers/vivid/vivid-vid-common.c +++ b/drivers/media/test-drivers/vivid/vivid-vid-common.c @@ -920,6 +920,31 @@ int vivid_enum_fmt_vid(struct file *file, void *priv, fmt = &vivid_formats[f->index]; f->pixelformat = fmt->fourcc; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return 0; + /* + * For capture devices, we support the CSC API. + * We allow userspace to: + * 1. set the colorspace + * 2. set the xfer_func + * 3. set the ycbcr_enc on YUV formats + * 4. set the hsv_enc on HSV formats + * 5. set the quantization on YUV and RGB formats + */ + f->flags |= V4L2_FMT_FLAG_CSC_COLORSPACE; + f->flags |= V4L2_FMT_FLAG_CSC_XFER_FUNC; + + if (fmt->color_enc == TGP_COLOR_ENC_YCBCR) { + f->flags |= V4L2_FMT_FLAG_CSC_YCBCR_ENC; + f->flags |= V4L2_FMT_FLAG_CSC_QUANTIZATION; + } else if (fmt->color_enc == TGP_COLOR_ENC_HSV) { + f->flags |= V4L2_FMT_FLAG_CSC_HSV_ENC; + } else if (fmt->color_enc == TGP_COLOR_ENC_RGB) { + f->flags |= V4L2_FMT_FLAG_CSC_QUANTIZATION; + } + return 0; } |