2020-04-06 23:21:59

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v3 0/6] media: vidtv: implement a virtual DVB driver

From: "Daniel W. S. Almeida" <[email protected]>

This series is work in progress. It represents the current work done on a
virtual DVB driver for the Linux media subsystem. I am new to the media
subsystem and to kernel development in general.

This applies on top of this branch
https://git.linuxtv.org/mchehab/experimental.git/log/?h=media-kconfig
for now, as these changes have not been applied to the media tree yet.
The changes in this branch have reorganized where test drivers go in
the subsystem. The dummy driver written by Emard was left untouched.

As for vidtv, we currently have a tuner driver, a demodulator driver, a
bridge driver and some MPEG PSI code. This should be, therefore, a
minimum viable example for what this driver intends to become in the
future.

I appreciate any feedback given. Thank you.


Changes in v3:
Added a bridge driver
Renamed the driver to vidtv
Renamed/reworked commits into smaller pieces
Moved the driver into its own directory
Fixed the code for the signal strength in the tuner
Removed useless enums in the tuner driver (e.g. lock_status, power_status...)
Reworked the logic for the poll_snr thread in the demodulator driver
Moved MPEG related code to the bridge driver, as it controls the demux logic
Changed literals to #defines, used sizeof in place of integer literals when
computing the size of PSI structs
Moved the MPEG PSI tables to the heap to reduce stack space usage
Now using usleep_range in place of msleep_interruptible in the MPEG TS thread
Wrapped memcpy and memset to protect against buffer overflow when writing to the
MPEG TS buffer.

Changes in v2:
Attempted not to break assignments into multiple lines as much as possible.
Code now passes checkpatch strict mode

media: dvb_dummy_tuner: implement driver skeleton
Changed snr values to mili db
Return value from 0-100 to indicate how far off the requested
frequency is from a valid one

Use the frequency shift to interpolate between 34dB and 10dB if
we can not match against the SNR lookup table
Remove sleep calls for suspend/resume

Fix memcpy call for the config struct

media: dvb_dummy_fe.c: lose TS lock on bad snr
Randomly recover the TS lock if the signal quality improves

media: dvb_dummy_fe.c: write PSI information into DMX buffer
Split the patch into multiple header/source files

Hexadecimal literals are now lower case

Prefer short function names / reduce function signatures

Add #defines for constants when computing section lengths

Change signature for functions that take a dummy channel as
argument (i.e. channels* is now channels[NUM_CHANNELS])

Daniel W. S. Almeida (6):
media: vidtv: add Kconfig entry
media: vidtv: implement a tuner driver
media: vidtv: implement a demodulator driver
media: vidtv: implement a PSI generator
media: vidtv: move config structs into a common header
media: vidtv: add a bridge driver

drivers/media/test_drivers/Kconfig | 10 +
drivers/media/test_drivers/Makefile | 1 +
drivers/media/test_drivers/vidtv/Kconfig | 11 +
drivers/media/test_drivers/vidtv/Makefile | 6 +
.../media/test_drivers/vidtv/vidtv_bridge.c | 736 ++++++++++++++
.../media/test_drivers/vidtv/vidtv_bridge.h | 51 +
.../media/test_drivers/vidtv/vidtv_common.c | 44 +
.../media/test_drivers/vidtv/vidtv_common.h | 105 ++
.../media/test_drivers/vidtv/vidtv_demod.c | 493 +++++++++
.../media/test_drivers/vidtv/vidtv_demod.h | 34 +
drivers/media/test_drivers/vidtv/vidtv_psi.c | 960 ++++++++++++++++++
drivers/media/test_drivers/vidtv/vidtv_psi.h | 294 ++++++
.../media/test_drivers/vidtv/vidtv_tuner.c | 403 ++++++++
13 files changed, 3148 insertions(+)
create mode 100644 drivers/media/test_drivers/vidtv/Kconfig
create mode 100644 drivers/media/test_drivers/vidtv/Makefile
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_bridge.c
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_bridge.h
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_common.c
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_common.h
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_demod.c
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_demod.h
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_psi.c
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_psi.h
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_tuner.c

--
2.26.0


2020-04-06 23:22:04

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v3 1/6] media: vidtv: add Kconfig entry

From: "Daniel W. S. Almeida" <[email protected]>

Add the necessary Kconfig entries and a dummy Makefile to compile the new
virtual DVB test driver (vidtv).

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test_drivers/Kconfig | 10 ++++++++++
drivers/media/test_drivers/Makefile | 1 +
drivers/media/test_drivers/vidtv/Kconfig | 11 +++++++++++
drivers/media/test_drivers/vidtv/Makefile | 2 ++
4 files changed, 24 insertions(+)
create mode 100644 drivers/media/test_drivers/vidtv/Kconfig
create mode 100644 drivers/media/test_drivers/vidtv/Makefile

diff --git a/drivers/media/test_drivers/Kconfig b/drivers/media/test_drivers/Kconfig
index 9f4a9cfbacc9c..997594ab3842a 100644
--- a/drivers/media/test_drivers/Kconfig
+++ b/drivers/media/test_drivers/Kconfig
@@ -6,6 +6,10 @@ menuconfig V4L_TEST_DRIVERS
bool "V4L test drivers"
depends on VIDEO_DEV

+menuconfig DVB_TEST_DRIVERS
+ bool "DVB test drivers"
+ depends on DVB_CORE && MEDIA_SUPPORT && I2C
+
if V4L_TEST_DRIVERS

source "drivers/media/test_drivers/vimc/Kconfig"
@@ -25,4 +29,10 @@ source "drivers/media/test_drivers/vicodec/Kconfig"

endif #V4L_TEST_DRIVERS

+if DVB_TEST_DRIVERS
+
+source "drivers/media/test_drivers/vidtv/Kconfig"
+
+endif #DVB_TEST_DRIVERS
+
endif #MEDIA_TEST_SUPPORT
diff --git a/drivers/media/test_drivers/Makefile b/drivers/media/test_drivers/Makefile
index 74410d3a9f2d2..9f0e4ebb2efe7 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/vidtv/Kconfig b/drivers/media/test_drivers/vidtv/Kconfig
new file mode 100644
index 0000000000000..22c4fd39461f1
--- /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 0000000000000..d1558d84eeaed
--- /dev/null
+++ b/drivers/media/test_drivers/vidtv/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+
--
2.26.0

2020-04-06 23:22:14

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v3 2/6] media: vidtv: implement a tuner driver

From: "Daniel W. S. Almeida" <[email protected]>

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.

This dummy tuner should support common TV standards such as DVB-T/T2/S/S2,
ISDB-T and ATSC when completed.

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test_drivers/vidtv/Makefile | 1 +
.../media/test_drivers/vidtv/vidtv_tuner.c | 411 ++++++++++++++++++
2 files changed, 412 insertions(+)
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_tuner.c

diff --git a/drivers/media/test_drivers/vidtv/Makefile b/drivers/media/test_drivers/vidtv/Makefile
index d1558d84eeaed..e625810a82603 100644
--- a/drivers/media/test_drivers/vidtv/Makefile
+++ b/drivers/media/test_drivers/vidtv/Makefile
@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0

+obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o
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 0000000000000..c948daa66ec73
--- /dev/null
+++ b/drivers/media/test_drivers/vidtv/vidtv_tuner.c
@@ -0,0 +1,411 @@
+// 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.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#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>
+
+MODULE_DESCRIPTION("Virtual DTV Tuner");
+MODULE_AUTHOR("Daniel W. S. Almeida");
+MODULE_LICENSE("GPL");
+
+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[8];
+ u32 vidtv_valid_dvb_c_freqs[8];
+ u32 vidtv_valid_dvb_s_freqs[8];
+ u8 max_frequency_shift_hz;
+};
+
+struct vidtv_tuner_cnr_to_qual_s {
+ /* attempt to use the same values as libdvbv5 */
+ u32 modulation;
+ u32 fec;
+ u32 cnr_ok, cnr_good;
+};
+
+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},
+};
+
+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},
+};
+
+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 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 {
+ bool asleep;
+ u32 lock_status;
+ u32 if_frequency;
+ u32 tuned_frequency;
+ u32 bandwidth;
+};
+
+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)
+{
+ struct i2c_client *client = fe->tuner_priv;
+
+ return i2c_get_clientdata(client);
+}
+
+static s32 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:
+ pr_warn("%s: unsupported delivery system: %u\n",
+ __func__,
+ c->delivery_system);
+ break;
+ }
+
+ for (i = 0; i < array_sz; i++) {
+ 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 -1;
+}
+
+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_cnr_to_qual_s *cnr2qual = NULL;
+ u32 array_size = 0;
+ s32 shift;
+ u32 i;
+
+ shift = vidtv_tuner_check_frequency_shift(fe);
+
+ 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:
+ pr_warn("%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;
+ }
+ if (shift < 0) { /* Channel not tuned */
+ *strength = 0;
+ 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 = 34 - 24 * 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;
+ 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);
+ 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[] = {
+ {"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));
+
+ 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);
+ struct dvb_frontend *fe = tuner_dev->fe;
+
+ memset(&fe->ops.tuner_ops, 0, sizeof(struct dvb_tuner_ops));
+ fe->tuner_priv = NULL;
+ kfree(tuner_dev);
+
+ return 0;
+}
+
+static struct i2c_driver vidtv_tuner_i2c_driver = {
+ .driver = {
+ .name = "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);
--
2.26.0

2020-04-06 23:22:58

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v3 5/6] media: vidtv: move config structs into a common header

From: "Daniel W. S. Almeida" <[email protected]>

Move config structs to a common header so they can be used by the bridge
driver and by their respective drivers.

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
.../media/test_drivers/vidtv/vidtv_common.h | 19 +++++++++++++++++++
.../media/test_drivers/vidtv/vidtv_demod.h | 9 ---------
.../media/test_drivers/vidtv/vidtv_tuner.c | 12 ++----------
3 files changed, 21 insertions(+), 19 deletions(-)

diff --git a/drivers/media/test_drivers/vidtv/vidtv_common.h b/drivers/media/test_drivers/vidtv/vidtv_common.h
index 43269833ee866..e6b36429cc8de 100644
--- a/drivers/media/test_drivers/vidtv/vidtv_common.h
+++ b/drivers/media/test_drivers/vidtv/vidtv_common.h
@@ -56,6 +56,25 @@ struct vidtv_mpeg_ts {
struct vidtv_mpeg_ts_adaption adaption[];
} __packed;

+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[8];
+ u32 vidtv_valid_dvb_c_freqs[8];
+ u32 vidtv_valid_dvb_s_freqs[8];
+ u8 max_frequency_shift_hz;
+};
+
+struct vidtv_demod_config {
+ struct dvb_frontend *frontend;
+ /* probability of losing the lock due to low snr */
+ u8 drop_tslock_prob_on_low_snr;
+ /* probability of recovering when the signal improves */
+ u8 recover_tslock_prob_on_good_snr;
+ u8 chosen_delsys;
+};
+
u32 vidtv_memcpy(void *to,
const void *from,
size_t len,
diff --git a/drivers/media/test_drivers/vidtv/vidtv_demod.h b/drivers/media/test_drivers/vidtv/vidtv_demod.h
index 49c2a43f71661..269855efb77f3 100644
--- a/drivers/media/test_drivers/vidtv/vidtv_demod.h
+++ b/drivers/media/test_drivers/vidtv/vidtv_demod.h
@@ -21,15 +21,6 @@ struct vidtv_demod_cnr_to_qual_s {
u32 cnr_ok, cnr_good;
};

-struct vidtv_demod_config {
- struct dvb_frontend *frontend;
- /* probability of losing the lock due to low snr */
- u8 drop_tslock_prob_on_low_snr;
- /* probability of recovering when the signal improves */
- u8 recover_tslock_prob_on_good_snr;
- u8 chosen_delsys;
-};
-
struct vidtv_demod_state {
struct dvb_frontend frontend;
struct vidtv_demod_config config;
diff --git a/drivers/media/test_drivers/vidtv/vidtv_tuner.c b/drivers/media/test_drivers/vidtv/vidtv_tuner.c
index c948daa66ec73..8b1befc861e33 100644
--- a/drivers/media/test_drivers/vidtv/vidtv_tuner.c
+++ b/drivers/media/test_drivers/vidtv/vidtv_tuner.c
@@ -17,20 +17,12 @@
#include <linux/types.h>
#include <media/dvb_frontend.h>

+#include "vidtv_common.h"
+
MODULE_DESCRIPTION("Virtual DTV Tuner");
MODULE_AUTHOR("Daniel W. S. Almeida");
MODULE_LICENSE("GPL");

-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[8];
- u32 vidtv_valid_dvb_c_freqs[8];
- u32 vidtv_valid_dvb_s_freqs[8];
- u8 max_frequency_shift_hz;
-};
-
struct vidtv_tuner_cnr_to_qual_s {
/* attempt to use the same values as libdvbv5 */
u32 modulation;
--
2.26.0

2020-04-06 23:22:58

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v3 4/6] media: vidtv: implement a PSI generator

From: "Daniel W. S. Almeida" <[email protected]>

PSI packets contain general information about a MPEG Transport Stream.
A PSI generator is needed so userspace apps can retrieve information
about the Transport Stream and eventually tune into a (dummy) channel.

Because the generator is implemented in a separate file, it can be
reused elsewhere in the media subsystem.

Currently this commit adds support for working with 3 PSI tables:
PAT, PMT and SDT.

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test_drivers/vidtv/Makefile | 4 +-
.../media/test_drivers/vidtv/vidtv_common.c | 44 +
.../media/test_drivers/vidtv/vidtv_common.h | 71 ++
drivers/media/test_drivers/vidtv/vidtv_psi.c | 960 ++++++++++++++++++
drivers/media/test_drivers/vidtv/vidtv_psi.h | 294 ++++++
5 files changed, 1372 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_common.c
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_common.h
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_psi.c
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_psi.h

diff --git a/drivers/media/test_drivers/vidtv/Makefile b/drivers/media/test_drivers/vidtv/Makefile
index 36ba00ddc0d1e..690420a7c904b 100644
--- a/drivers/media/test_drivers/vidtv/Makefile
+++ b/drivers/media/test_drivers/vidtv/Makefile
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-2.0

-obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o
+vidtv_demod-objs := vidtv_common.o vidtv_psi.o
+
+obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o
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 0000000000000..62713284e14d9
--- /dev/null
+++ b/drivers/media/test_drivers/vidtv/vidtv_common.c
@@ -0,0 +1,44 @@
+// 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.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/printk.h>
+
+u32 vidtv_memcpy(void *to,
+ const void *from,
+ size_t len,
+ u32 offset,
+ u32 buf_sz)
+{
+ if (buf_sz && offset + len > buf_sz) {
+ pr_err("%s: overflow detected, skipping. Try increasing the buffer size",
+ __func__);
+ return 0;
+ }
+
+ memcpy(to, from, len);
+ return len;
+}
+
+u32 vidtv_memset(void *to,
+ int c,
+ size_t len,
+ u32 offset,
+ u32 buf_sz)
+{
+ if (buf_sz && offset + len > buf_sz) {
+ pr_err("%s: overflow detected, skipping. Try increasing the buffer size",
+ __func__);
+ return 0;
+ }
+
+ memset(to, 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 0000000000000..43269833ee866
--- /dev/null
+++ b/drivers/media/test_drivers/vidtv/vidtv_common.h
@@ -0,0 +1,71 @@
+/* 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.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_COMMON_H
+#define VIDTV_COMMON_H
+
+#include <linux/types.h>
+#include <media/dvb_frontend.h>
+
+#define CRC_SIZE_IN_BYTES 32
+#define TS_SYNC_BYTE 0x47
+#define TS_PACKET_LEN 188
+#define TS_PAYLOAD_LEN 184
+#define LAST_VALID_TS_PID 8191
+
+/* to be used by both PSI and ES */
+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;
+
+/* to be used by both PSI and ES */
+struct vidtv_mpeg_ts {
+ u8 sync_byte;
+ union {
+ u16 bitfield;
+ struct {
+ u16 pid:13;
+ u16 priority:1;
+ u16 payload_start:1;
+ u16 tei:1;
+ } __packed;
+ } __packed;
+ struct {
+ u8 continuity_counter:4;
+ u8 payload:1;
+ u8 adaptation_field:1;
+ u8 scrambling:2;
+ } __packed;
+ struct vidtv_mpeg_ts_adaption adaption[];
+} __packed;
+
+u32 vidtv_memcpy(void *to,
+ const void *from,
+ size_t len,
+ u32 offset,
+ u32 buf_sz);
+
+u32 vidtv_memset(void *to,
+ int c,
+ size_t len,
+ u32 offset,
+ u32 buf_sz);
+
+#endif // VIDTV_COMMON_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 0000000000000..70fc6289407a0
--- /dev/null
+++ b/drivers/media/test_drivers/vidtv/vidtv_psi.c
@@ -0,0 +1,960 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#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 "vidtv_common.h"
+#include "vidtv_psi.h"
+
+static u32 vidtv_psi_ts_psi_write_stuffing(void *to,
+ u32 len,
+ u32 offset,
+ u32 buf_sz)
+{
+ return vidtv_memset(to, 0xff, len, offset, buf_sz);
+}
+
+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 after the CRC
+ */
+
+ u32 nbytes_past_boundary = (args.offset % TS_PACKET_LEN);
+ bool aligned = nbytes_past_boundary == 0;
+ bool split = args.len > TS_PAYLOAD_LEN;
+ u32 payload_write_len = (split) ? TS_PAYLOAD_LEN : args.len;
+
+ struct psi_write_args new_args = {0};
+ struct vidtv_mpeg_ts ts_header = {0};
+
+ u32 nbytes = 0; /* number of bytes written by this function */
+ u32 temp;
+
+ if (args.new_psi_section && !aligned) {
+ /*
+ * must pad the buffer with the complement to get a
+ * multiple of 188
+ */
+ nbytes += vidtv_psi_ts_psi_write_stuffing(args.to +
+ args.offset +
+ nbytes,
+ TS_PACKET_LEN -
+ nbytes_past_boundary,
+ args.offset + nbytes,
+ args.buf_sz);
+
+ /*
+ * if we were not at a packet boundary, we are now after
+ * stuffing the buffer with 0xff
+ */
+ aligned = true;
+ }
+
+ if (aligned) {
+ /* if at a packet boundary, write a new TS header */
+ ts_header.sync_byte = TS_SYNC_BYTE;
+ ts_header.tei = 0;
+ ts_header.payload_start = 1;
+ ts_header.pid = args.pid;
+ ts_header.priority = 0;
+ ts_header.scrambling = 0; /* not scrambled */
+ ts_header.continuity_counter = *args.continuity_counter;
+ ts_header.payload_start = 0; /* no adaption for now */
+
+ /* copy the header minus the adaption pointer*/
+ nbytes += vidtv_memcpy(args.to + args.offset + nbytes,
+ &ts_header,
+ sizeof(ts_header),
+ args.offset + nbytes,
+ args.buf_sz);
+ }
+
+ if (args.new_psi_section) {
+ /* write the pointer_field in the first byte of the payload */
+ temp = vidtv_memset(args.to + args.offset + nbytes,
+ 0x0,
+ 1,
+ args.offset + nbytes,
+ args.buf_sz);
+ /* one byte was used by the pointer field*/
+ nbytes += temp;
+ payload_write_len -= temp;
+ }
+
+ /* write as much of the payload as we possibly can */
+ nbytes += vidtv_memcpy(args.to + args.offset + nbytes,
+ args.from,
+ payload_write_len,
+ args.offset + nbytes,
+ args.buf_sz);
+
+ if (split) {
+ /*
+ * next TS packet keeps the same PID, but increments the
+ * counter
+ */
+ ++(*args.continuity_counter);
+ /* 'nbytes' written from a total of 'len' requested*/
+ args.len -= nbytes;
+ /*
+ * recursively write the rest of the data until we do not
+ * need to split it anymore
+ */
+ memcpy(&new_args, &args, sizeof(struct psi_write_args));
+ new_args.from = args.from + nbytes;
+ new_args.offset = args.offset + nbytes;
+ new_args.new_psi_section = false;
+
+ nbytes += vidtv_psi_ts_psi_write_into(new_args);
+ }
+
+ /*
+ * as the CRC is last in the section, stuff the rest of the
+ * packet if there is any remaining space in there
+ */
+ if (args.is_crc)
+ nbytes += vidtv_psi_ts_psi_write_stuffing(args.to + nbytes,
+ TS_PAYLOAD_LEN -
+ nbytes,
+ args.offset + nbytes,
+ args.buf_sz);
+
+ 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;
+ u32 crc;
+ struct psi_write_args psi_args = {0};
+
+ crc = crc32(0, args.to, args.offset);
+
+ psi_args.to = args.to;
+ psi_args.from = &crc;
+ psi_args.len = CRC_SIZE_IN_BYTES;
+ psi_args.offset = args.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.buf_sz = args.buf_sz;
+
+ nbytes += vidtv_psi_ts_psi_write_into(psi_args);
+
+ return nbytes;
+}
+
+struct vidtv_psi_desc *vidtv_psi_desc_init(struct vidtv_psi_desc *head,
+ u8 type,
+ u8 length)
+{
+ struct vidtv_psi_desc *desc;
+
+ /* alloc enough memory for the flexible array too */
+ desc = kzalloc(sizeof(*desc) + length, GFP_KERNEL);
+
+ desc->type = type;
+ desc->length = length;
+
+ if (head) {
+ while (head->next)
+ head = head->next;
+
+ head->next = desc;
+ }
+
+ return desc;
+}
+
+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;
+ kfree(tmp);
+ }
+}
+
+static u32
+vidtv_psi_desc_comp_len(struct vidtv_psi_desc *desc)
+{
+ u32 length = 0;
+
+ if (!desc)
+ return 0;
+
+ while (desc) {
+ length += desc->length;
+ desc = desc->next;
+ }
+
+ return length;
+}
+
+void vidtv_psi_desc_assign(struct vidtv_psi_desc **to,
+ struct vidtv_psi_desc *desc,
+ u16 *desc_length)
+{
+ /*
+ * This function transfers ownedship of desc.
+ * Start by cleaning the old data
+ */
+ if (*to)
+ vidtv_psi_desc_destroy(*to);
+
+ *desc_length = vidtv_psi_desc_comp_len(desc);
+ *to = desc; /* reassign pointer */
+}
+
+static u32 vidtv_psi_desc_write_into(struct desc_write_args args)
+{
+ u32 nbytes = 0; /* the number of bytes written by this function */
+ struct psi_write_args psi_args = {0};
+
+ psi_args.to = args.to;
+ psi_args.from = args.desc;
+ psi_args.len = sizeof_field(struct vidtv_psi_desc, type) +
+ sizeof_field(struct vidtv_psi_desc, length);
+ psi_args.offset = args.offset;
+ 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.buf_sz = args.buf_sz;
+
+ nbytes += vidtv_psi_ts_psi_write_into(psi_args);
+
+ /* move 'from' pointer to point to u8 data[] */
+ psi_args.from = args.desc + nbytes + sizeof(struct vidtv_psi_desc *);
+ psi_args.len = args.desc->length;
+ psi_args.offset = args.offset + nbytes;
+
+ nbytes += vidtv_psi_ts_psi_write_into(psi_args);
+
+ 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 = {0};
+
+ psi_args.to = args.to;
+ psi_args.from = args.h;
+ psi_args.len = sizeof(struct vidtv_psi_table_header);
+ psi_args.offset = args.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.buf_sz = args.buf_sz;
+
+ nbytes += vidtv_psi_ts_psi_write_into(psi_args);
+
+ return nbytes;
+}
+
+static u16
+vidtv_psi_pat_table_comp_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;
+
+ WARN_ON(length > PAT_MAX_SECTION_LEN);
+ return length;
+}
+
+static u16
+vidtv_psi_pmt_table_comp_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;
+
+ /* from immediately after 'section_length' until 'program_info_length'*/
+ length += PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH;
+
+ /* do not fail if 'desc_length' has not been computed yet */
+ length += vidtv_psi_desc_comp_len(pmt->descriptor);
+ length += pmt->desc_length;
+
+ 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 *);
+
+ length += vidtv_psi_desc_comp_len(s->descriptor);
+ s = s->next;
+ }
+
+ length += CRC_SIZE_IN_BYTES;
+
+ WARN_ON(length > PMT_MAX_SECTION_LEN);
+ return length;
+}
+
+static u16
+vidtv_psi_sdt_table_comp_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;
+
+ /*
+ * 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_pmt_stream) -
+ sizeof(struct vidtv_psi_desc *) -
+ sizeof(struct vidtv_psi_table_pmt_stream *);
+ /* do not fail if 'desc_length' has not been computed yet */
+ length += vidtv_psi_desc_comp_len(s->descriptor);
+
+ s = s->next;
+ }
+
+ length += CRC_SIZE_IN_BYTES;
+
+ WARN_ON(length > SDT_MAX_SECTION_LEN);
+ return length;
+}
+
+struct vidtv_psi_table_pat_program*
+vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head,
+ u16 service_id,
+ u16 pid)
+{
+ struct vidtv_psi_table_pat_program *program;
+
+ program = kzalloc(sizeof(*program), GFP_KERNEL);
+
+ program->service_id = service_id;
+ program->pid = pid; /* pid for the PMT section in the TS */
+ program->next = NULL;
+ program->reserved = 0x7;
+
+ 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;
+ struct vidtv_psi_table_pat_program *temp = pat->program;
+
+ while (program) {
+ ++program_count;
+ program = program->next;
+ }
+
+ pat->programs = program_count;
+ pat->program = p;
+
+ /* Recompute section length */
+ pat->header.section_length = vidtv_psi_pat_table_comp_sec_len(pat);
+
+ /* do not break userspace: reassign if the new size is too big */
+ if (pat->header.section_length > PAT_MAX_SECTION_LEN)
+ vidtv_psi_pat_program_assign(pat, temp);
+ else
+ vidtv_psi_pat_program_destroy(temp);
+}
+
+void vidtv_psi_pat_table_init(struct vidtv_psi_table_pat *pat,
+ bool update_version_num,
+ u16 transport_stream_id)
+{
+ static u8 pat_version;
+
+ pat->header.table_id = 0x0;
+ pat->header.syntax = 0x1;
+ pat->header.zero = 0x0;
+ pat->header.one = 0x03;
+
+ pat->header.id = transport_stream_id; /* transport stream ID, at will */
+ pat->header.current_next = 0x1;
+
+ /* ETSI 300 468: indicates changes in the TS described by this table*/
+ if (update_version_num)
+ ++pat_version;
+
+ pat->header.version = pat_version;
+
+ pat->header.one2 = 0x03;
+ pat->header.section_id = 0x0;
+ pat->header.last_section = 0x0;
+
+ pat->programs = 0;
+
+ pat->header.section_length = vidtv_psi_pat_table_comp_sec_len(pat);
+}
+
+u32 vidtv_psi_pat_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_pat *pat,
+ u32 buf_sz)
+{
+ u32 nbytes = 0; /* the number of bytes written by this function */
+ u8 continuity_counter = 0;
+ const u16 pat_pid = pat->header.table_id; /* always 0x0 */
+
+ struct vidtv_psi_table_pat_program *p = pat->program;
+ struct header_write_args h_args = {0};
+ struct psi_write_args args = {0};
+ struct crc32_write_args c_args = {0};
+
+ h_args.to = buf;
+ h_args.offset = offset;
+ h_args.h = &pat->header;
+ h_args.pid = pat_pid;
+ h_args.continuity_counter = &continuity_counter;
+ h_args.buf_sz = buf_sz;
+
+ nbytes += vidtv_psi_table_header_write_into(h_args);
+
+ args.to = buf;
+ args.from = pat + sizeof(struct vidtv_psi_table_header),
+ args.len = sizeof(pat->programs);
+ args.offset = offset + nbytes;
+ args.pid = pat_pid;
+ args.new_psi_section = false;
+ args.continuity_counter = &continuity_counter;
+ args.is_crc = false;
+ args.buf_sz = buf_sz;
+
+ nbytes += vidtv_psi_ts_psi_write_into(args);
+
+ while (p) {
+ args.from = p;
+ /* skip the pointer */
+ args. len = sizeof(*p) -
+ sizeof(struct vidtv_psi_table_pat_program *);
+ args.offset = offset + nbytes;
+
+ nbytes += vidtv_psi_ts_psi_write_into(args);
+ p = p->next;
+ }
+
+ c_args.to = buf;
+ c_args.offset = offset + nbytes;
+ c_args.pid = pat_pid;
+ c_args.continuity_counter = &continuity_counter;
+ c_args.buf_sz = buf_sz;
+
+ 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);
+}
+
+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;
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+
+ stream->type = stream_type;
+ stream->elementary_pid = es_pid;
+ stream->reserved = 0x07;
+
+ stream->desc_length = vidtv_psi_desc_comp_len(stream->descriptor);
+
+ stream->zero = 0x0;
+ stream->reserved2 = 0x0f;
+
+ 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;
+ 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 */
+ struct vidtv_psi_table_pmt_stream *stream = s;
+ struct vidtv_psi_desc *desc = s->descriptor;
+ struct vidtv_psi_table_pmt_stream *temp = pmt->stream;
+
+ while (stream)
+ stream = stream->next;
+
+ while (desc)
+ desc = desc->next;
+
+ pmt->stream = s;
+ /* Recompute section length */
+ pmt->header.section_length = vidtv_psi_pmt_table_comp_sec_len(pmt);
+
+ /* do not break userspace: reassign if the new size is too big */
+ if (pmt->header.section_length > PMT_MAX_SECTION_LEN)
+ vidtv_psi_pmt_stream_assign(pmt, temp);
+ else
+ vidtv_psi_pmt_stream_destroy(temp);
+}
+
+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 pat->program->pid;
+
+ return LAST_VALID_TS_PID + 1; /* not found */
+}
+
+void vidtv_psi_pmt_table_init(struct vidtv_psi_table_pmt *pmt,
+ bool update_version_num,
+ u16 program_number,
+ u16 pcr_pid)
+{
+ static u8 pmt_version;
+
+ pmt->header.table_id = 0x2;
+ pmt->header.syntax = 0x1;
+ pmt->header.zero = 0x0;
+ pmt->header.one = 0x3;
+
+ pmt->header.id = program_number;
+ pmt->header.current_next = 0x1;
+
+ /* ETSI 300 468: indicates changes in the TS described by this table*/
+ if (update_version_num)
+ ++pmt_version;
+
+ pmt->header.version = pmt_version;
+
+ pmt->header.one2 = 0x3;
+ pmt->header.section_id = 0;
+ pmt->header.last_section = 0;
+
+ pmt->pcr_pid = (pcr_pid) ? pcr_pid : 0x1fff;
+ pmt->reserved2 = 0x03;
+
+ pmt->reserved3 = 0x0f;
+ pmt->zero3 = 0x0;
+
+ pmt->desc_length = vidtv_psi_desc_comp_len(pmt->descriptor);
+
+ pmt->header.section_length = vidtv_psi_pmt_table_comp_sec_len(pmt);
+}
+
+u32 vidtv_psi_pmt_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_pmt *pmt,
+ u16 pid,
+ u32 buf_sz)
+{
+ u32 nbytes = 0; /* the number of bytes written by this function */
+ u8 continuity_counter = 0;
+ struct vidtv_psi_desc *table_descriptor = pmt->descriptor;
+ struct vidtv_psi_table_pmt_stream *stream = pmt->stream;
+ struct vidtv_psi_desc *stream_descriptor = (stream) ?
+ pmt->stream->descriptor :
+ NULL;
+
+ struct header_write_args h_args = {0};
+ struct psi_write_args args = {0};
+ struct desc_write_args d_args = {0};
+ struct crc32_write_args c_args = {0};
+
+ h_args.to = buf;
+ h_args.offset = offset;
+ h_args.h = &pmt->header;
+ h_args.pid = pid;
+ h_args.continuity_counter = &continuity_counter;
+ h_args.buf_sz = buf_sz;
+
+ nbytes += vidtv_psi_table_header_write_into(h_args);
+
+ args.to = buf;
+ args.from = pmt + sizeof(struct vidtv_psi_table_header);
+ args.len = sizeof_field(struct vidtv_psi_table_pmt, bitfield) +
+ sizeof_field(struct vidtv_psi_table_pmt, bitfield2);
+ args.offset = offset + nbytes;
+ args.pid = pid;
+ args.new_psi_section = false;
+ args.continuity_counter = &continuity_counter;
+ args.is_crc = false;
+ args.buf_sz = buf_sz;
+
+ nbytes += vidtv_psi_ts_psi_write_into(args);
+
+ while (table_descriptor) {
+ d_args.to = buf;
+ d_args.offset = offset + nbytes;
+ d_args.desc = table_descriptor;
+ d_args.pid = pid;
+ d_args.continuity_counter = &continuity_counter;
+ d_args.buf_sz = buf_sz;
+
+ nbytes += vidtv_psi_desc_write_into(d_args);
+
+ table_descriptor = table_descriptor->next;
+ }
+
+ while (stream) {
+ args.from = stream;
+ 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);
+ args.offset = offset + nbytes;
+
+ nbytes += vidtv_psi_ts_psi_write_into(args);
+
+ while (stream_descriptor) {
+ d_args.desc = stream_descriptor;
+ d_args.offset = offset + nbytes;
+ nbytes += vidtv_psi_desc_write_into(d_args);
+
+ stream_descriptor = stream_descriptor->next;
+ }
+
+ stream = stream->next;
+ }
+
+ c_args.to = buf;
+ c_args.offset = offset + nbytes;
+ c_args.pid = pid;
+ c_args.continuity_counter = &continuity_counter;
+ c_args.buf_sz = buf_sz;
+
+ nbytes += table_section_crc32_write_into(c_args);
+
+ return nbytes;
+}
+
+void vidtv_psi_pmt_table_destroy(struct vidtv_psi_table_pmt *pmt)
+{
+ struct vidtv_psi_desc *curr_desc = pmt->descriptor;
+ struct vidtv_psi_desc *tmp_desc = NULL;
+
+ while (curr_desc) {
+ tmp_desc = curr_desc;
+ curr_desc = curr_desc->next;
+ vidtv_psi_desc_destroy(tmp_desc);
+ kfree(tmp_desc);
+ }
+
+ vidtv_psi_pmt_stream_destroy(pmt->stream);
+}
+
+void vidtv_psi_sdt_table_init(struct vidtv_psi_table_sdt *sdt,
+ bool update_version_num,
+ u16 transport_stream_id)
+{
+ static u8 sdt_version;
+
+ sdt->header.table_id = 0x42;
+
+ sdt->header.one = 0x3;
+ sdt->header.zero = 0x1;
+ /*
+ * The PAT, PMT, and CAT all set this to 0.
+ * Other tables set this to 1.
+ */
+ sdt->header.syntax = 0x1;
+
+ /*
+ * 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 = transport_stream_id;
+ sdt->header.current_next = 0x1;
+
+ /* ETSI 300 468: indicates changes in the TS described by this table*/
+ if (update_version_num)
+ ++sdt_version;
+
+ sdt->header.version = sdt_version;
+
+ sdt->header.one2 = 0x3;
+ sdt->header.section_id = 0;
+ sdt->header.last_section = 0;
+
+ sdt->network_id = transport_stream_id;
+ sdt->reserved = 0xff;
+
+ sdt->header.section_length = vidtv_psi_sdt_table_comp_sec_len(sdt);
+}
+
+u32 vidtv_psi_sdt_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_sdt *sdt,
+ u32 buf_sz)
+{
+ u32 nbytes = 0; /* the number of bytes written */
+ u16 sdt_pid = 0x11; /* see ETSI EN 300 468 v1.15.1 p. 11 */
+ u8 continuity_counter = 0;
+
+ struct vidtv_psi_table_sdt_service *service = sdt->service;
+ struct vidtv_psi_desc *service_desc = (sdt->service) ?
+ sdt->service->descriptor :
+ NULL;
+
+ struct header_write_args h_args = {0};
+ struct psi_write_args args = {0};
+ struct desc_write_args d_args = {0};
+ struct crc32_write_args c_args = {0};
+
+ h_args.to = buf;
+ h_args.offset = offset;
+ h_args.h = &sdt->header;
+ h_args.pid = sdt_pid;
+ h_args.continuity_counter = &continuity_counter;
+ h_args.buf_sz = buf_sz;
+
+ nbytes += vidtv_psi_table_header_write_into(h_args);
+
+ args.to = buf;
+ args.from = sdt + sizeof(struct vidtv_psi_table_header);
+ args.len = sizeof_field(struct vidtv_psi_table_sdt, network_id) +
+ sizeof_field(struct vidtv_psi_table_sdt, reserved);
+ args.offset = offset + nbytes;
+ args.pid = sdt_pid;
+ args.new_psi_section = false;
+ args.continuity_counter = &continuity_counter;
+ args.is_crc = false;
+ args.buf_sz = buf_sz;
+
+ /* copy u16 network_id + u8 reserved)*/
+ nbytes += vidtv_psi_ts_psi_write_into(args);
+
+ while (service) {
+ args.from = service;
+ /* skip both pointers at the end */
+ args.len = sizeof(struct vidtv_psi_table_sdt_service) -
+ sizeof(struct vidtv_psi_desc *) -
+ sizeof(struct vidtv_psi_table_sdt_service *);
+ args.offset = offset + nbytes;
+
+ nbytes += vidtv_psi_ts_psi_write_into(args);
+
+ while (service_desc) {
+ d_args.to = buf;
+ d_args.offset = offset + nbytes;
+ d_args.desc = service_desc;
+ d_args.pid = sdt_pid;
+ d_args.continuity_counter = &continuity_counter;
+ d_args.buf_sz = buf_sz;
+
+ nbytes += vidtv_psi_desc_write_into(d_args);
+
+ service_desc = service_desc->next;
+ }
+
+ service = service->next;
+ }
+
+ c_args.to = buf;
+ c_args.offset = offset + nbytes;
+ c_args.pid = sdt_pid;
+ c_args.continuity_counter = &continuity_counter;
+ c_args.buf_sz = buf_sz;
+
+ nbytes += table_section_crc32_write_into(c_args);
+
+ return nbytes;
+}
+
+void vidtv_psi_sdt_table_destroy(struct vidtv_psi_table_sdt *sdt)
+{
+ struct vidtv_psi_table_sdt_service *curr_service = sdt->service;
+ struct vidtv_psi_table_sdt_service *tmp_service = NULL;
+ struct vidtv_psi_desc *curr_desc = (sdt->service) ?
+ sdt->service->descriptor : NULL;
+ struct vidtv_psi_desc *tmp_desc = NULL;
+
+ while (curr_service) {
+ curr_desc = curr_service->descriptor;
+
+ while (curr_desc) {
+ /* clear all descriptors for the service */
+ tmp_desc = curr_desc;
+ curr_desc = curr_desc->next;
+ vidtv_psi_desc_destroy(tmp_desc);
+ kfree(tmp_desc);
+ }
+
+ /* then clear the current service */
+ tmp_service = curr_service;
+ curr_service = curr_service->next;
+ kfree(tmp_service);
+ }
+}
+
+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 = service_id;
+ service->EIT_schedule = 0x0; /* TODO */
+ service->EIT_present_following = 0x0; /* TODO */
+ service->reserved = 0x3f; /* all bits on */
+ service->free_CA_mode = 0x0; /* not scrambled */
+ service->running_status = RUNNING;
+
+ 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;
+ kfree(tmp);
+ }
+}
+
+void
+vidtv_psi_sdt_service_assign(struct vidtv_psi_table_sdt *sdt,
+ struct vidtv_psi_table_sdt_service *service)
+{
+ struct vidtv_psi_table_sdt_service *temp = sdt->service;
+
+ sdt->service = service;
+
+ sdt->header.section_length = vidtv_psi_sdt_table_comp_sec_len(sdt);
+
+ /* do not break userspace: reassign if the new size is too big */
+ if (sdt->header.section_length > SDT_MAX_SECTION_LEN)
+ vidtv_psi_sdt_service_assign(sdt, temp);
+ else
+ vidtv_psi_sdt_service_destroy(temp);
+}
+
+void
+vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat,
+ struct vidtv_psi_table_pmt *sec)
+
+{
+ /*
+ * PMTs contain information about programs. For each program,
+ * there is one PMT
+ */
+ struct vidtv_psi_table_pat_program *program = pat->program;
+ u32 i = 0;
+
+ while (program) {
+ vidtv_psi_pmt_table_init(&sec[i],
+ false,
+ sec[i].header.id,
+ 0);
+
+ ++i;
+ program = program->next;
+ }
+}
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 0000000000000..0336934b3aba2
--- /dev/null
+++ b/drivers/media/test_drivers/vidtv/vidtv_psi.h
@@ -0,0 +1,294 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef VIDTV_PSI_H
+#define VIDTV_PSI_H
+
+#include <linux/types.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 PAT_MAX_SECTION_LEN 1021
+#define PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH 9
+#define PMT_MAX_SECTION_LEN 1021
+#define SDT_LEN_UNTIL_RESERVED_FOR_FUTURE_USE 8
+#define SDT_MAX_SECTION_LEN 1021
+
+enum vidtv_psi_descriptors {
+ SERVICE_DESCRIPTOR = 0x48,
+};
+
+enum vidtv_psi_stream_types {
+ ISO_IEC_13818_3_AUDIO = 0x4,
+};
+
+struct vidtv_psi_desc {
+ u8 type;
+ u8 length;
+ struct vidtv_psi_desc *next;
+ u8 data[];
+} __packed;
+
+struct vidtv_psi_desc_service {
+ u8 type;
+ u8 length;
+ struct dvb_desc *next;
+
+ u8 service_type;
+ char *name;
+ char *name_emph;
+ char *provider;
+ char *provider_emph;
+} __packed;
+
+struct vidtv_psi_table_header {
+ u8 table_id;
+ union {
+ u16 bitfield;
+ struct {
+ u16 section_length:12;
+ u8 one:2;
+ u8 zero:1;
+ u8 syntax:1;
+ } __packed;
+ } __packed;
+ u16 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 {
+ u16 service_id;
+ union {
+ u16 bitfield;
+ struct {
+ u16 pid:13;
+ u8 reserved:3;
+ } __packed;
+ } __packed;
+ struct vidtv_psi_table_pat_program *next;
+} __packed;
+
+struct vidtv_psi_table_pat {
+ struct vidtv_psi_table_header header;
+ u16 programs;
+ struct vidtv_psi_table_pat_program *program;
+} __packed;
+
+struct vidtv_psi_table_sdt_service {
+ u16 service_id;
+ u8 EIT_present_following:1;
+ u8 EIT_schedule:1;
+ u8 reserved:6;
+ union {
+ u16 bitfield;
+ struct {
+ u16 desc_length:12;
+ u16 free_CA_mode:1;
+ u16 running_status:3;
+ } __packed;
+ } __packed;
+ struct vidtv_psi_desc *descriptor;
+ struct vidtv_psi_table_sdt_service *next;
+} __packed;
+
+struct vidtv_psi_table_sdt {
+ struct vidtv_psi_table_header header;
+ u16 network_id;
+ u8 reserved;
+ struct vidtv_psi_table_sdt_service *service;
+} __packed;
+
+enum service_running_status {
+ RUNNING,
+};
+
+enum service_type {
+ /* see ETSI EN 300 468 v1.15.1 p. 77 */
+ DIGITAL_TELEVISION_SERVICE = 0x1,
+};
+
+struct vidtv_psi_table_pmt_stream {
+ u8 type;
+ union {
+ u16 bitfield;
+ struct {
+ u16 elementary_pid:13;
+ u16 reserved:3;
+ } __packed;
+ } __packed;
+ union {
+ u16 bitfield2;
+ struct {
+ u16 desc_length:10;
+ u16 zero:2;
+ u16 reserved2:4;
+ } __packed;
+ } __packed;
+ struct vidtv_psi_desc *descriptor;
+ struct vidtv_psi_table_pmt_stream *next;
+} __packed;
+
+struct vidtv_psi_table_pmt {
+ struct vidtv_psi_table_header header;
+ union {
+ u16 bitfield;
+ struct {
+ u16 pcr_pid:13;
+ u16 reserved2:3;
+ } __packed;
+ } __packed;
+
+ union {
+ u16 bitfield2;
+ struct {
+ u16 desc_length:10;
+ u16 zero3:2;
+ u16 reserved3:4;
+ } __packed;
+ } __packed;
+ struct vidtv_psi_desc *descriptor;
+ struct vidtv_psi_table_pmt_stream *stream;
+} __packed;
+
+struct psi_write_args {
+ void *to;
+ void *from;
+ size_t len; /* how much to write */
+ u32 offset; /* where to start writing in the buffer */
+ u16 pid; /* TS packet ID */
+ bool new_psi_section; /* set when starting a table section */
+ u8 *continuity_counter; /* TS: incremented when section gets split */
+ bool is_crc; /* set when writing the CRC at the end */
+ u32 buf_sz; /* protect against overflow when this field is not zero */
+};
+
+struct desc_write_args {
+ void *to;
+ u32 offset;
+ struct vidtv_psi_desc *desc;
+ u16 pid;
+ u8 *continuity_counter;
+ u32 buf_sz; /* protect against overflow when this field is not zero */
+};
+
+struct crc32_write_args {
+ void *to;
+ u32 offset;
+ u16 pid;
+ u8 *continuity_counter;
+ u32 buf_sz; /* protect against overflow when this field is not zero */
+};
+
+struct header_write_args {
+ void *to;
+ u32 offset;
+ struct vidtv_psi_table_header *h;
+ u16 pid;
+ u8 *continuity_counter;
+ u32 buf_sz; /* protect against overflow when this field is not zero */
+};
+
+struct vidtv_psi_desc *vidtv_psi_desc_init(struct vidtv_psi_desc *head,
+ u8 type,
+ u8 length);
+
+void vidtv_psi_pat_table_init(struct vidtv_psi_table_pat *pat,
+ bool update_version_num,
+ u16 transport_stream_id);
+
+struct vidtv_psi_table_pat_program*
+vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head,
+ u16 service_id,
+ u16 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);
+
+void vidtv_psi_pmt_table_init(struct vidtv_psi_table_pmt *pmt,
+ bool update_version_num,
+ u16 program_number,
+ u16 pcr_pid);
+
+void
+vidtv_psi_sdt_table_init(struct vidtv_psi_table_sdt *sdt,
+ bool update_version_num,
+ 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);
+
+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_sdt_service_assign(struct vidtv_psi_table_sdt *sdt,
+ struct vidtv_psi_table_sdt_service *service);
+
+void vidtv_psi_desc_assign(struct vidtv_psi_desc **to,
+ struct vidtv_psi_desc *desc,
+ u16 *desc_length);
+
+void vidtv_psi_pat_program_assign(struct vidtv_psi_table_pat *pat,
+ struct vidtv_psi_table_pat_program *p);
+
+void vidtv_psi_pmt_stream_assign(struct vidtv_psi_table_pmt *pmt,
+ struct vidtv_psi_table_pmt_stream *s);
+void
+vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat,
+ struct vidtv_psi_table_pmt *sec);
+
+u16 vidtv_psi_pmt_get_pid(struct vidtv_psi_table_pmt *section,
+ struct vidtv_psi_table_pat *pat);
+
+u32 vidtv_psi_pat_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_pat *pat,
+ u32 buf_sz);
+
+u32 vidtv_psi_sdt_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_sdt *sdt,
+ u32 buf_sz);
+
+u32 vidtv_psi_pmt_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_pmt *pmt,
+ u16 pid,
+ u32 buf_sz);
+
+#endif // VIDTV_PSI_H
--
2.26.0

2020-04-06 23:23:30

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v3 6/6] media: vidtv: add a bridge driver

From: "Daniel W. S. Almeida" <[email protected]>

Digital TV devices consist of several independent hardware components which
are controlled by different drivers.
Each media device is controlled by a group of cooperating drivers with the
bridge driver as the main driver.

This patch adds a bridge driver for the Virtual Digital TV driver [vidtv].

The bridge driver binds to the other drivers, that is, vidtv_tuner and
vidtv_demod and implements the digital demux logic, providing userspace
with a MPEG Transport Stream.

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test_drivers/vidtv/Makefile | 3 +-
.../media/test_drivers/vidtv/vidtv_bridge.c | 736 ++++++++++++++++++
.../media/test_drivers/vidtv/vidtv_bridge.h | 51 ++
.../media/test_drivers/vidtv/vidtv_common.h | 15 +
4 files changed, 804 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_bridge.c
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_bridge.h

diff --git a/drivers/media/test_drivers/vidtv/Makefile b/drivers/media/test_drivers/vidtv/Makefile
index 690420a7c904b..296e591883c5e 100644
--- a/drivers/media/test_drivers/vidtv/Makefile
+++ b/drivers/media/test_drivers/vidtv/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0

vidtv_demod-objs := vidtv_common.o vidtv_psi.o
+vidtv_bridge-objs := vidtv_common.o vidtv_psi.o

-obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o
+obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o 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 0000000000000..a3dfd913d1e83
--- /dev/null
+++ b/drivers/media/test_drivers/vidtv/vidtv_bridge.c
@@ -0,0 +1,736 @@
+// 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.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#include <linux/types.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/time.h>
+#include "vidtv_bridge.h"
+#include "vidtv_common.h"
+#include "vidtv_psi.h"
+
+#define TS_BUF_MAX_SZ (128 * TS_PACKET_LEN)
+
+MODULE_AUTHOR("Daniel W. S. Almeida");
+MODULE_LICENSE("GPL");
+
+static unsigned int drop_tslock_prob_on_low_snr;
+module_param(drop_tslock_prob_on_low_snr, uint, 0644);
+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, 0644);
+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, 0644);
+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, 0644);
+MODULE_PARM_DESC(mock_tune_delay_msec, "Simulate a tune delay");
+
+static unsigned int vidtv_valid_dvb_t_freqs[8];
+module_param_array(vidtv_valid_dvb_t_freqs, uint, NULL, 0644);
+MODULE_PARM_DESC(vidtv_valid_dvb_t_freqs,
+ "Valid DVB-T frequencies to simulate");
+
+static unsigned int vidtv_valid_dvb_c_freqs[8];
+module_param_array(vidtv_valid_dvb_c_freqs, uint, NULL, 0644);
+MODULE_PARM_DESC(vidtv_valid_dvb_c_freqs,
+ "Valid DVB-C frequencies to simulate");
+
+static unsigned int vidtv_valid_dvb_s_freqs[8];
+module_param_array(vidtv_valid_dvb_s_freqs, uint, NULL, 0644);
+MODULE_PARM_DESC(vidtv_valid_dvb_s_freqs,
+ "Valid DVB-C frequencies to simulate");
+
+static unsigned int max_frequency_shift_hz;
+module_param(max_frequency_shift_hz, uint, 0644);
+MODULE_PARM_DESC(max_frequency_shift_hz,
+ "Maximum shift in HZ allowed when tuning in a channel");
+
+static unsigned int chosen_delsys = SYS_DVBT;
+module_param(chosen_delsys, uint, 0644);
+MODULE_PARM_DESC(chosen_delsys,
+ "The delivery system to simulate. Currently supported: DVB-T, DVB-C, DVB-S");
+
+static unsigned int ts_buf_sz = 20 * TS_PACKET_LEN;
+module_param(ts_buf_sz, uint, 0644);
+MODULE_PARM_DESC(ts_buf_sz, "Optional size for the TS buffer");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nums);
+
+/*
+ * Influences the signal acquisition time. See ISO/IEC 13818-1 : 2000. p. 113.
+ */
+static unsigned int psi_freq_hz = 25;
+module_param(psi_freq_hz, uint, 0644);
+MODULE_PARM_DESC(psi_freq_hz, "Simulate a given PSI frequency");
+
+static unsigned int mpeg_thread_freq_hz = 100;
+module_param(mpeg_thread_freq_hz, uint, 0644);
+MODULE_PARM_DESC(mpeg_thread_freq_hz,
+ "Simulate a given loop frequency for the MPEG thread");
+
+static int vidtv_start_streaming(struct vidtv_dvb *dvb)
+{
+ /* if already streaming, then this call is probably a mistake */
+ WARN_ON(dvb->streaming);
+
+ dvb->streaming = true;
+ schedule_work(&dvb->mpeg_thread);
+
+ return 0;
+}
+
+static int vidtv_stop_streaming(struct vidtv_dvb *dvb)
+{
+ /* mpeg thread will quit */
+ dvb->streaming = false;
+
+ 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, 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 void vidtv_bridge_channels_init(struct vidtv_dvb *dvb)
+{
+ /* these channels will be used to populate the MPEG PSI tables */
+
+ const u16 pac_service_id = 0x880;
+ const u16 pac_program_num = 0x880;
+ const u16 pac_program_pid = 0x101; /* packet id for PMT*/
+ const u16 pac_audio_stream_id = 0x111;
+
+ struct vidtv_channel pac; /* PCM Audio Channel */
+ struct vidtv_psi_table_sdt_service *pac_service;
+ struct vidtv_psi_desc_service *pac_s_desc;
+ u16 desc_length;
+
+ pac_service = vidtv_psi_sdt_service_init(NULL, pac_service_id);
+
+ pac_s_desc = (struct vidtv_psi_desc_service *)
+ vidtv_psi_desc_init(NULL,
+ SERVICE_DESCRIPTOR,
+ sizeof(*pac_s_desc));
+
+ pac_s_desc->name = "Sine Wave PCM Audio";
+ pac_s_desc->service_type = DIGITAL_TELEVISION_SERVICE;
+
+ pac_s_desc->length = sizeof(pac_s_desc->service_type)
+ + strlen(pac_s_desc->name)
+ + strlen(pac_s_desc->name_emph)
+ + strlen(pac_s_desc->provider)
+ + strlen(pac_s_desc->provider_emph);
+
+ vidtv_psi_desc_assign(&pac_service->descriptor,
+ (struct vidtv_psi_desc *)
+ pac_s_desc,
+ &desc_length);
+
+ pac_service->desc_length = desc_length;
+
+ pac.transport_stream_id = TRANSPORT_STREAM_ID;
+
+ pac.program = vidtv_psi_pat_program_init(NULL,
+ pac_service_id,
+ pac_program_pid);
+
+ pac.program_num = pac_program_num;
+ pac.streams = vidtv_psi_pmt_stream_init(NULL,
+ ISO_IEC_13818_3_AUDIO,
+ pac_audio_stream_id);
+
+ dvb->channels[0] = pac;
+}
+
+static void
+vidtv_bridge_channels_destroy(struct vidtv_dvb *dvb)
+{
+ u32 i;
+ struct vidtv_channel *curr;
+
+ for (i = 0; i < NUM_CHANNELS; ++i) {
+ curr = &dvb->channels[i];
+ vidtv_psi_sdt_service_destroy(curr->service);
+ vidtv_psi_pat_program_destroy(curr->program);
+ vidtv_psi_pmt_stream_destroy(curr->streams);
+ }
+}
+
+static struct vidtv_psi_table_sdt_service*
+vidtv_bridge_sdt_serv_cat_into_new(struct vidtv_channel chnls[NUM_CHANNELS])
+{
+ u32 i;
+ struct vidtv_psi_table_sdt_service *curr = NULL;
+ struct vidtv_psi_table_sdt_service *head = NULL;
+ struct vidtv_psi_table_sdt_service *tail = NULL;
+ u16 service_id;
+
+ for (i = 0; i < NUM_CHANNELS; ++i) {
+ curr = chnls[i].service;
+ service_id = curr->service_id;
+
+ if (!curr)
+ continue;
+
+ while (curr->next) {
+ tail = vidtv_psi_sdt_service_init(tail, service_id);
+
+ if (!head)
+ head = tail;
+
+ curr = curr->next;
+ }
+ }
+
+ return head;
+}
+
+static struct vidtv_psi_table_pat_program*
+vidtv_bridge_pat_prog_cat_into_new(struct vidtv_channel chnls[NUM_CHANNELS])
+{
+ u32 i;
+ struct vidtv_psi_table_pat_program *curr = NULL;
+ struct vidtv_psi_table_pat_program *head = NULL;
+ struct vidtv_psi_table_pat_program *tail = NULL;
+
+ for (i = 0; i < NUM_CHANNELS; ++i) {
+ curr = chnls[i].program;
+
+ if (!curr)
+ continue;
+
+ while (curr->next) {
+ tail = vidtv_psi_pat_program_init(tail,
+ curr->service_id,
+ curr->pid);
+
+ if (!head)
+ head = tail;
+
+ curr = curr->next;
+ }
+ }
+
+ return head;
+}
+
+static void
+vidtv_bridge_pmt_match_sections(struct vidtv_channel chnls[NUM_CHANNELS],
+ struct vidtv_psi_table_pmt *sections,
+ u32 nsections)
+{
+ struct vidtv_psi_table_pmt *curr_section = NULL;
+ u32 i, j;
+
+ for (i = 0; i < NUM_CHANNELS; ++i) {
+ for (j = 0; j < nsections; ++j) {
+ curr_section = &sections[j];
+
+ if (!curr_section)
+ continue;
+
+ /* we got a match */
+ if (curr_section->header.id ==
+ chnls[i].program_num) {
+ vidtv_psi_pmt_stream_assign(curr_section,
+ chnls[i].streams);
+ break;
+ }
+ }
+ }
+}
+
+static void vidtv_bridge_mpeg_tables_init(struct vidtv_dvb *dvb)
+{
+ struct vidtv_psi_table_pat *pat = dvb->pat;
+ struct vidtv_psi_table_sdt *sdt = dvb->sdt;
+
+ struct vidtv_psi_table_pmt *pmt_sections = dvb->pmt_sections;
+
+ struct vidtv_psi_table_pat_program *programs = NULL;
+ struct vidtv_psi_table_sdt_service *services = NULL;
+
+ bool update_version_num = false;
+
+ vidtv_psi_pat_table_init(pat,
+ update_version_num,
+ TRANSPORT_STREAM_ID);
+
+ vidtv_psi_sdt_table_init(sdt,
+ update_version_num,
+ TRANSPORT_STREAM_ID);
+
+ programs = vidtv_bridge_pat_prog_cat_into_new(dvb->channels);
+ services = vidtv_bridge_sdt_serv_cat_into_new(dvb->channels);
+
+ /* assemble all programs and assign to PAT */
+ vidtv_psi_pat_program_assign(pat, programs);
+
+ /* assemble all services and assign to SDT */
+ vidtv_psi_sdt_service_assign(sdt, services);
+
+ /* a section for each program_id */
+ pmt_sections = kcalloc(pat->programs,
+ sizeof(struct vidtv_psi_table_pmt),
+ GFP_KERNEL);
+
+ vidtv_psi_pmt_create_sec_for_each_pat_entry(pat,
+ pmt_sections);
+
+ vidtv_bridge_pmt_match_sections(dvb->channels,
+ pmt_sections,
+ pat->programs);
+}
+
+static void vidtv_bridge_mpeg_tables_destroy(struct vidtv_dvb *dvb)
+{
+ u32 i;
+
+ vidtv_psi_pat_table_destroy(dvb->pat);
+
+ for (i = 0; i < dvb->num_pmt_sections; ++i)
+ vidtv_psi_pmt_table_destroy(&dvb->pmt_sections[i]);
+
+ vidtv_psi_sdt_table_destroy(dvb->sdt);
+}
+
+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_LOCK; /* all other flags will be set */
+}
+
+static bool vidtv_bridge_should_push_psi(struct vidtv_dvb *dvb,
+ unsigned long elapsed_time)
+{
+ unsigned long psi_period_in_jiffies;
+ unsigned long next_psi_at;
+
+ psi_period_in_jiffies = usecs_to_jiffies(USECS_IN_SEC / psi_freq_hz);
+ next_psi_at = dvb->stream_start_jiffies +
+ (dvb->num_streamed_psi * psi_period_in_jiffies);
+
+ /* if this is in the past, it is time to push PSI packets again */
+ return elapsed_time > next_psi_at;
+}
+
+static void vidtv_bridge_mpeg_push_psi(struct vidtv_dvb *dvb,
+ u8 *buf,
+ u32 *buffer_offset)
+{
+ u16 pmt_pid;
+ u32 i;
+
+ *buffer_offset += vidtv_psi_pat_write_into(buf,
+ *buffer_offset,
+ dvb->pat,
+ dvb->ts_buffer_sz);
+
+ for (i = 0; i < dvb->num_pmt_sections; ++i) {
+ pmt_pid = vidtv_psi_pmt_get_pid(&dvb->pmt_sections[i],
+ dvb->pat);
+
+ /* not found */
+ WARN_ON(pmt_pid > LAST_VALID_TS_PID);
+
+ /* write each section into buffer */
+ *buffer_offset +=
+ vidtv_psi_pmt_write_into(buf,
+ *buffer_offset,
+ &dvb->pmt_sections[i],
+ pmt_pid,
+ dvb->ts_buffer_sz);
+ }
+
+ *buffer_offset += vidtv_psi_sdt_write_into(buf,
+ *buffer_offset,
+ dvb->sdt,
+ dvb->ts_buffer_sz);
+
+ dvb->num_streamed_psi++;
+}
+
+static void vidtv_bridge_mpeg_tick(struct work_struct *work)
+{
+ struct vidtv_dvb *dvb = container_of(work,
+ struct vidtv_dvb,
+ mpeg_thread);
+
+ const int SLEEP_USECS = USECS_IN_SEC / mpeg_thread_freq_hz;
+ char *buf = dvb->ts_buffer;
+ u32 buffer_offset;
+ unsigned long elapsed_time = 0;
+
+ dvb->stream_start_jiffies = jiffies;
+
+ while (dvb->streaming && vidtv_bridge_check_demod_lock(dvb, 0)) {
+ memset(buf, 0, dvb->ts_buffer_sz);
+ buffer_offset = 0;
+
+ if (vidtv_bridge_should_push_psi(dvb, elapsed_time))
+ vidtv_bridge_mpeg_push_psi(dvb, buf, &buffer_offset);
+
+ /*
+ * just a sanity check, should not happen because we check
+ * for overflow before writing
+ */
+ WARN_ON(buffer_offset > dvb->ts_buffer_sz);
+
+ /* if packets are not aligned by now, something is also wrong */
+ WARN_ON(buffer_offset % TS_PACKET_LEN);
+
+ dvb_dmx_swfilter_packets(&dvb->demux,
+ buf,
+ buffer_offset / TS_PACKET_LEN);
+
+ usleep_range(SLEEP_USECS, SLEEP_USECS + (SLEEP_USECS / 10));
+ elapsed_time = dvb->stream_start_jiffies - jiffies;
+ }
+
+ dvb->streaming = false; /* demod lost the lock */
+ dvb->stream_start_jiffies = 0;
+ dvb->num_streamed_psi = 0;
+}
+
+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;
+}
+
+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 = NULL;
+
+ 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 void vidtv_bridge_probe_demod(struct vidtv_dvb *dvb, u32 n)
+{
+ struct vidtv_demod_config cfg = {0};
+
+ 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;
+ cfg.chosen_delsys = chosen_delsys;
+
+ dvb->i2c_client_demod[n] = dvb_module_probe("vidtv_demod",
+ NULL,
+ dvb->i2c_adapter,
+ DEMOD_DEFAULT_ADDR,
+ &cfg);
+
+ /* retrieve a pointer to struct dvb_frontend */
+ dvb->fe[n] = cfg.frontend;
+}
+
+static void vidtv_bridge_probe_tuner(struct vidtv_dvb *dvb, u32 n)
+{
+ struct vidtv_tuner_config cfg = {0};
+
+ 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;
+
+ 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));
+
+ memcpy(cfg.vidtv_valid_dvb_s_freqs,
+ vidtv_valid_dvb_s_freqs,
+ sizeof(vidtv_valid_dvb_s_freqs));
+
+ cfg.max_frequency_shift_hz = max_frequency_shift_hz;
+
+ dvb->i2c_client_tuner[n] = dvb_module_probe("vidtv_tuner",
+ NULL,
+ dvb->i2c_adapter,
+ TUNER_DEFAULT_ADDR,
+ &cfg);
+}
+
+static int vidtv_bridge_dvb_init(struct vidtv_dvb *dvb)
+{
+ int ret;
+ int i, 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;
+
+ vidtv_bridge_probe_demod(dvb, 0);
+ vidtv_bridge_probe_tuner(dvb, 0);
+
+ for (i = 0; i < NUM_FE; ++i) {
+ 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 (i = i - 1; i >= 0; --i)
+ dvb_unregister_frontend(dvb->fe[i]);
+
+fail_adapter:
+ dvb_unregister_adapter(&dvb->adapter);
+
+fail_i2c:
+ i2c_del_adapter(dvb->i2c_adapter);
+
+ return ret;
+}
+
+static int vidtv_bridge_ts_buffer_init(struct vidtv_dvb *dvb)
+{
+ u32 nbytes_past_boundary;
+
+ if (ts_buf_sz > TS_BUF_MAX_SZ)
+ ts_buf_sz = TS_BUF_MAX_SZ;
+
+ nbytes_past_boundary = ts_buf_sz % TS_PACKET_LEN;
+
+ /* round to the nearest multiple of 188 */
+ if (nbytes_past_boundary)
+ ts_buf_sz += TS_PACKET_LEN - nbytes_past_boundary;
+
+ dvb->ts_buffer = kzalloc(ts_buf_sz, GFP_KERNEL);
+ if (!dvb->ts_buffer)
+ return -ENOMEM;
+
+ dvb->ts_buffer_sz = ts_buf_sz;
+
+ return 0;
+}
+
+static void vidtv_bridge_ts_buffer_destroy(struct vidtv_dvb *dvb)
+{
+ kfree(dvb->ts_buffer);
+ dvb->ts_buffer_sz = 0;
+}
+
+static int vidtv_bridge_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret;
+ struct vidtv_dvb *dvb;
+
+ dvb = kzalloc(sizeof(*dvb), GFP_KERNEL);
+ if (!dvb)
+ return -ENOMEM;
+
+ ret = vidtv_bridge_dvb_init(dvb);
+ if (ret < 0)
+ goto err_dvb;
+
+ ret = vidtv_bridge_ts_buffer_init(dvb);
+ if (ret < 0)
+ goto err_ts_buf;
+
+ vidtv_bridge_channels_init(dvb);
+ vidtv_bridge_mpeg_tables_init(dvb);
+
+ mutex_init(&dvb->feed_lock);
+
+ INIT_WORK(&dvb->mpeg_thread, vidtv_bridge_mpeg_tick);
+ i2c_set_clientdata(client, dvb);
+ return ret;
+
+err_ts_buf:
+err_dvb:
+ kfree(dvb);
+ return ret;
+}
+
+static int vidtv_bridge_i2c_remove(struct i2c_client *client)
+{
+ struct vidtv_dvb *dvb;
+ u32 i;
+
+ dvb = i2c_get_clientdata(client);
+
+ for (i = 0; i < NUM_FE; ++i)
+ dvb->demux.dmx.remove_frontend(&dvb->demux.dmx,
+ &dvb->dmx_fe[i]);
+
+ dvb_dmxdev_release(&dvb->dmx_dev);
+ dvb_dmx_release(&dvb->demux);
+
+ for (i = 0; i < NUM_FE; ++i) {
+ dvb_unregister_frontend(dvb->fe[i]);
+ dvb_frontend_detach(dvb->fe[i]);
+ }
+
+ dvb_unregister_adapter(&dvb->adapter);
+
+ vidtv_bridge_mpeg_tables_destroy(dvb);
+ vidtv_bridge_channels_destroy(dvb);
+ vidtv_bridge_ts_buffer_destroy(dvb);
+
+ return 0;
+}
+
+static const struct i2c_device_id vidtv_bridge_id_table[] = {
+ {"vidtv_bridge", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, vidtv_bridge_id_table);
+
+static struct i2c_driver vidtv_bridge_driver = {
+ .driver = {
+ .name = "vidtv_bridge",
+ .suppress_bind_attrs = true,
+ },
+ .probe = vidtv_bridge_i2c_probe,
+ .remove = vidtv_bridge_i2c_remove,
+ .id_table = vidtv_bridge_id_table,
+};
+
+module_i2c_driver(vidtv_bridge_driver);
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 0000000000000..4958e5c73e512
--- /dev/null
+++ b/drivers/media/test_drivers/vidtv/vidtv_bridge.h
@@ -0,0 +1,51 @@
+/* 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.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_BRIDGE_H
+#define VIDTV_BRIDGE_H
+
+#define NUM_FE 1
+
+#include <linux/types.h>
+#include <media/dvb_frontend.h>
+#include <media/dvb_demux.h>
+#include <media/dmxdev.h>
+#include <linux/i2c.h>
+#include "vidtv_common.h"
+
+struct vidtv_dvb {
+ 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];
+
+ struct vidtv_psi_table_pat *pat;
+ struct vidtv_psi_table_pmt *pmt_sections;
+ u16 num_pmt_sections; /* as many sections as programs in the PAT */
+ struct vidtv_psi_table_sdt *sdt;
+
+ struct vidtv_channel channels[NUM_CHANNELS];
+
+ u32 nfeeds;
+ struct mutex feed_lock; /* start/stop feed */
+
+ u8 *ts_buffer;
+ u32 ts_buffer_sz;
+
+ struct work_struct mpeg_thread;
+ bool streaming;
+ unsigned long stream_start_jiffies;
+ u64 num_streamed_psi;
+};
+
+#endif // VIDTV_BRIDGE_H
diff --git a/drivers/media/test_drivers/vidtv/vidtv_common.h b/drivers/media/test_drivers/vidtv/vidtv_common.h
index e6b36429cc8de..d2c869cffe35b 100644
--- a/drivers/media/test_drivers/vidtv/vidtv_common.h
+++ b/drivers/media/test_drivers/vidtv/vidtv_common.h
@@ -18,6 +18,12 @@
#define TS_PACKET_LEN 188
#define TS_PAYLOAD_LEN 184
#define LAST_VALID_TS_PID 8191
+#define USECS_IN_SEC 1000000
+#define TUNER_DEFAULT_ADDR 0x68
+#define DEMOD_DEFAULT_ADDR 0x60
+#define NUM_CHANNELS 1
+#define TRANSPORT_STREAM_ID 0x744 /* a single stream */
+

/* to be used by both PSI and ES */
struct vidtv_mpeg_ts_adaption {
@@ -75,6 +81,15 @@ struct vidtv_demod_config {
u8 chosen_delsys;
};

+struct vidtv_channel {
+ u16 transport_stream_id;
+ struct vidtv_psi_table_sdt_service *service;
+ u16 program_num;
+ /* a single program with one or more streams associated with it */
+ struct vidtv_psi_table_pat_program *program;
+ struct vidtv_psi_table_pmt_stream *streams;
+};
+
u32 vidtv_memcpy(void *to,
const void *from,
size_t len,
--
2.26.0

2020-04-06 23:24:01

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v3 3/6] media: vidtv: implement a demodulator driver

From: "Daniel W. S. Almeida" <[email protected]>

Implement a I2C demodulator driver, simulating support for DVB-T, DVB-C
and DVB-S.

This demodulator will periodically check the signal quality against a table
and drop the TS lock if it drops below a threshold value, regaining it in
the event that the signal improves.

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test_drivers/vidtv/Makefile | 2 +-
.../media/test_drivers/vidtv/vidtv_demod.c | 493 ++++++++++++++++++
.../media/test_drivers/vidtv/vidtv_demod.h | 43 ++
3 files changed, 537 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_demod.c
create mode 100644 drivers/media/test_drivers/vidtv/vidtv_demod.h

diff --git a/drivers/media/test_drivers/vidtv/Makefile b/drivers/media/test_drivers/vidtv/Makefile
index e625810a82603..36ba00ddc0d1e 100644
--- a/drivers/media/test_drivers/vidtv/Makefile
+++ b/drivers/media/test_drivers/vidtv/Makefile
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0

-obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o
+obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o
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 0000000000000..803fe1a89b7ec
--- /dev/null
+++ b/drivers/media/test_drivers/vidtv/vidtv_demod.c
@@ -0,0 +1,493 @@
+// 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.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ * Based on the example driver written by Emard <[email protected]>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/random.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <media/dvb_frontend.h>
+#include "vidtv_demod.h"
+
+MODULE_DESCRIPTION("Virtual DVB Demodulator Driver");
+MODULE_AUTHOR("Daniel W. S. Almeida");
+MODULE_LICENSE("GPL");
+
+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},
+};
+
+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},
+};
+
+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 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 struct vidtv_demod_cnr_to_qual_s
+*vidtv_match_cnr_s(struct dvb_frontend *fe)
+{
+ struct dtv_frontend_properties *c;
+ struct vidtv_demod_cnr_to_qual_s *cnr2qual = NULL;
+ 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:
+ pr_warn("%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_demod_poll_snr_handler(struct work_struct *work)
+{
+ /*
+ * periodically check the signal quality and eventually
+ * lose the TS lock if it dips too low
+ */
+ struct vidtv_demod_state *state;
+ struct dtv_frontend_properties *c;
+ struct vidtv_demod_cnr_to_qual_s *cnr2qual = NULL;
+ struct vidtv_demod_config *config;
+ u16 snr = 0;
+
+ state = container_of(work, struct vidtv_demod_state, poll_snr.work);
+ c = &state->frontend.dtv_property_cache;
+ config = &state->config;
+
+ if (!state->frontend.ops.tuner_ops.get_rf_strength)
+ return;
+
+ state->frontend.ops.tuner_ops.get_rf_strength(&state->frontend, &snr);
+
+ cnr2qual = vidtv_match_cnr_s(&state->frontend);
+ if (!cnr2qual)
+ return;
+
+ 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;
+ }
+
+ schedule_delayed_work(&state->poll_snr, msecs_to_jiffies(2000));
+}
+
+static int vidtv_demod_read_status(struct dvb_frontend *fe,
+ enum fe_status *status)
+{
+ struct vidtv_demod_state *state = fe->demodulator_priv;
+
+ *status = state->status;
+
+ return 0;
+}
+
+static int vidtv_demod_read_ber(struct dvb_frontend *fe, u32 *ber)
+{
+ *ber = 0;
+ return 0;
+}
+
+static int vidtv_demod_read_signal_strength(struct dvb_frontend *fe,
+ u16 *strength)
+{
+ *strength = 0;
+ return 0;
+}
+
+static int vidtv_demod_read_snr(struct dvb_frontend *fe, u16 *snr)
+{
+ *snr = 0;
+ return 0;
+}
+
+static int vidtv_demod_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
+{
+ *ucblocks = 0;
+ return 0;
+}
+
+/*
+ * 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;
+ struct vidtv_demod_cnr_to_qual_s *cnr2qual = NULL;
+ u32 tuner_status = 0;
+
+ if (fe->ops.tuner_ops.set_params) {
+ fe->ops.tuner_ops.set_params(fe);
+
+ /* store the CNR returned by the tuner */
+ fe->ops.tuner_ops.get_rf_strength(fe, &state->tuner_cnr);
+
+ 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;
+ cnr2qual = vidtv_match_cnr_s(fe);
+
+ /* signal isn't good: might lose the lock eventually */
+ if (tuner_status == TUNER_STATUS_LOCKED &&
+ state->tuner_cnr < cnr2qual->cnr_good) {
+ schedule_delayed_work(&state->poll_snr,
+ msecs_to_jiffies(2000));
+
+ state->poll_snr_thread_running = true;
+ }
+
+ if (fe->ops.i2c_gate_ctrl)
+ fe->ops.i2c_gate_ctrl(fe, 0);
+ }
+
+ return 0;
+}
+
+static int vidtv_demod_sleep(struct dvb_frontend *fe)
+{
+ struct vidtv_demod_state *state = fe->demodulator_priv;
+
+ if (state->poll_snr_thread_running) {
+ cancel_delayed_work_sync(&state->poll_snr);
+ state->poll_snr_thread_running = false;
+ state->poll_snr_thread_restart = true;
+ }
+ return 0;
+}
+
+static int vidtv_demod_init(struct dvb_frontend *fe)
+{
+ struct vidtv_demod_state *state = fe->demodulator_priv;
+ u32 tuner_status = 0;
+
+ if (state->cold_start)
+ INIT_DELAYED_WORK(&state->poll_snr,
+ &vidtv_demod_poll_snr_handler);
+
+ /*
+ * At resume, start the snr poll thread only if it was suspended with
+ * the thread running. Extra care should be taken here, as some tuner
+ * status change might happen at resume time (for example, due to a
+ * ioctl syscall to set_frontend, or due to a release syscall).
+ */
+ fe->ops.tuner_ops.get_status(fe, &tuner_status);
+
+ if (tuner_status == TUNER_STATUS_LOCKED &&
+ state->poll_snr_thread_restart) {
+ schedule_delayed_work(&state->poll_snr,
+ msecs_to_jiffies(2000));
+
+ state->poll_snr_thread_restart = false;
+ }
+
+ state->cold_start = false;
+ return 0;
+}
+
+static int vidtv_demod_set_tone(struct dvb_frontend *fe,
+ enum fe_sec_tone_mode tone)
+{
+ return 0;
+}
+
+static int vidtv_demod_set_voltage(struct dvb_frontend *fe,
+ enum fe_sec_voltage voltage)
+{
+ return 0;
+}
+
+static void vidtv_demod_release(struct dvb_frontend *fe)
+{
+ struct vidtv_demod_state *state = fe->demodulator_priv;
+
+ if (state->poll_snr_thread_running)
+ cancel_delayed_work_sync(&state->poll_snr);
+
+ kfree(state);
+}
+
+static const struct dvb_frontend_ops vidtv_demod_ofdm_ops = {
+ .delsys = { SYS_DVBT },
+ .info = {
+ .name = "Dummy DVB-T",
+ .frequency_min_hz = 0,
+ .frequency_max_hz = 863250 * kHz,
+ .frequency_stepsize_hz = 62500,
+ .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_FEC_AUTO |
+ FE_CAN_QAM_16 |
+ FE_CAN_QAM_64 |
+ FE_CAN_QAM_AUTO |
+ FE_CAN_TRANSMISSION_MODE_AUTO |
+ FE_CAN_GUARD_INTERVAL_AUTO |
+ FE_CAN_HIERARCHY_AUTO,
+ },
+
+ .release = vidtv_demod_release,
+
+ .init = vidtv_demod_init,
+ .sleep = vidtv_demod_sleep,
+
+ .set_frontend = vidtv_demod_set_frontend,
+ .get_frontend = vidtv_demod_get_frontend,
+
+ .read_status = vidtv_demod_read_status,
+ .read_ber = vidtv_demod_read_ber,
+ .read_signal_strength = vidtv_demod_read_signal_strength,
+ .read_snr = vidtv_demod_read_snr,
+ .read_ucblocks = vidtv_demod_read_ucblocks,
+};
+
+static const struct dvb_frontend_ops vidtv_demod_qam_ops = {
+ .delsys = { SYS_DVBC_ANNEX_A },
+ .info = {
+ .name = "Dummy DVB-C",
+ .frequency_min_hz = 51 * MHz,
+ .frequency_max_hz = 858 * MHz,
+ .frequency_stepsize_hz = 62500,
+ /* symbol_rate_min: SACLK/64 == (XIN/2)/64 */
+ .symbol_rate_min = (57840000 / 2) / 64,
+ .symbol_rate_max = (57840000 / 2) / 4, /* SACLK/4 */
+ .caps = FE_CAN_QAM_16 |
+ FE_CAN_QAM_32 |
+ FE_CAN_QAM_64 |
+ FE_CAN_QAM_128 |
+ FE_CAN_QAM_256 |
+ FE_CAN_FEC_AUTO |
+ FE_CAN_INVERSION_AUTO
+ },
+
+ .release = vidtv_demod_release,
+
+ .init = vidtv_demod_init,
+ .sleep = vidtv_demod_sleep,
+
+ .set_frontend = vidtv_demod_set_frontend,
+ .get_frontend = vidtv_demod_get_frontend,
+
+ .read_status = vidtv_demod_read_status,
+ .read_ber = vidtv_demod_read_ber,
+ .read_signal_strength = vidtv_demod_read_signal_strength,
+ .read_snr = vidtv_demod_read_snr,
+ .read_ucblocks = vidtv_demod_read_ucblocks,
+};
+
+static const struct dvb_frontend_ops vidtv_demod_qpsk_ops = {
+ .delsys = { SYS_DVBS },
+ .info = {
+ .name = "Dummy DVB-S",
+ .frequency_min_hz = 950 * MHz,
+ .frequency_max_hz = 2150 * MHz,
+ .frequency_stepsize_hz = 250 * kHz,
+ .frequency_tolerance_hz = 29500 * kHz,
+ .symbol_rate_min = 1000000,
+ .symbol_rate_max = 45000000,
+ .caps = FE_CAN_INVERSION_AUTO |
+ FE_CAN_FEC_1_2 |
+ FE_CAN_FEC_2_3 |
+ FE_CAN_FEC_3_4 |
+ FE_CAN_FEC_5_6 |
+ FE_CAN_FEC_7_8 |
+ FE_CAN_FEC_AUTO |
+ FE_CAN_QPSK
+ },
+
+ .release = vidtv_demod_release,
+
+ .init = vidtv_demod_init,
+ .sleep = vidtv_demod_sleep,
+
+ .set_frontend = vidtv_demod_set_frontend,
+ .get_frontend = vidtv_demod_get_frontend,
+
+ .read_status = vidtv_demod_read_status,
+ .read_ber = vidtv_demod_read_ber,
+ .read_signal_strength = vidtv_demod_read_signal_strength,
+ .read_snr = vidtv_demod_read_snr,
+ .read_ucblocks = vidtv_demod_read_ucblocks,
+
+ .set_voltage = vidtv_demod_set_voltage,
+ .set_tone = vidtv_demod_set_tone,
+};
+
+static const struct i2c_device_id vidtv_demod_i2c_id_table[] = {
+ {"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_demod_config *config = client->dev.platform_data;
+ struct vidtv_demod_state *state;
+ const struct dvb_frontend_ops *ops;
+
+ /* allocate memory for the internal state */
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ switch (config->chosen_delsys) {
+ case SYS_DVBT:
+ ops = &vidtv_demod_ofdm_ops;
+ break;
+ case SYS_DVBC_ANNEX_A:
+ ops = &vidtv_demod_qam_ops;
+ break;
+ case SYS_DVBS:
+ ops = &vidtv_demod_qpsk_ops;
+ break;
+ default:
+ pr_err("%s: Unsupported delivery system. Falling back to DVB-T",
+ __func__);
+ ops = &vidtv_demod_ofdm_ops;
+ break;
+ }
+
+ /* create dvb_frontend */
+ memcpy(&state->frontend.ops,
+ ops,
+ sizeof(struct dvb_frontend_ops));
+
+ state->frontend.demodulator_priv = state;
+ /* return the pointer to the bridge driver */
+ config->frontend = &state->frontend;
+ i2c_set_clientdata(client, state);
+
+ return 0;
+}
+
+static int vidtv_demod_i2c_remove(struct i2c_client *client)
+{
+ struct vidtv_demod_state *state = i2c_get_clientdata(client);
+
+ memset(&state->frontend.ops, 0, sizeof(struct dvb_frontend_ops));
+ state->frontend.demodulator_priv = NULL;
+ kfree(state);
+
+ return 0;
+}
+
+static struct i2c_driver vidtv_demod_i2c_driver = {
+ .driver = {
+ .name = "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);
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 0000000000000..49c2a43f71661
--- /dev/null
+++ b/drivers/media/test_drivers/vidtv/vidtv_demod.h
@@ -0,0 +1,43 @@
+/* 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.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ * Based on the example driver written by Emard <[email protected]>
+ */
+
+#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 {
+ /* attempt to use the same values as libdvbv5 */
+ u32 modulation;
+ u32 fec;
+ u32 cnr_ok, cnr_good;
+};
+
+struct vidtv_demod_config {
+ struct dvb_frontend *frontend;
+ /* probability of losing the lock due to low snr */
+ u8 drop_tslock_prob_on_low_snr;
+ /* probability of recovering when the signal improves */
+ u8 recover_tslock_prob_on_good_snr;
+ u8 chosen_delsys;
+};
+
+struct vidtv_demod_state {
+ struct dvb_frontend frontend;
+ struct vidtv_demod_config config;
+ struct delayed_work poll_snr;
+ enum fe_status status;
+ u16 tuner_cnr;
+ bool cold_start;
+ bool poll_snr_thread_running;
+ bool poll_snr_thread_restart;
+};
+#endif // VIDTV_DEMOD_H
--
2.26.0