All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC 0/3] Implement a virtual DVB driver
@ 2020-03-18  6:00 ` Daniel W. S. Almeida
  0 siblings, 0 replies; 21+ messages in thread
From: Daniel W. S. Almeida @ 2020-03-18  6:00 UTC (permalink / raw)
  To: mchehab, sean, kstewart, allison, tglx
  Cc: Daniel W. S. Almeida, linux-media, skhan, linux-kernel-mentees

From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>

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 subsystem and to DTV in general, please take that into account.

Currently I have written a simple fake tuner and I have been working on
getting PSI packets into a TS stream and then feeding the stream to the
hardware-independent demux layer. I have been basing my work on ISO 13818,
ETSI EN 300 468 and on a real MPEG Transport Stream excerpt.

I am aware that the first patch generates plenty of warnings due to floating
point to integer implicit conversions. I still have to rework that.

The last patch in the series is also WIP, it still misses a set_frontend()
implementation among other things. This will be sorted soon.

A few bits of code were taken from libdvbv5, mostly the structs for the
PSI tables.

I appreciate any comments to improve this.

Thank you.

Daniel W. S. Almeida (3):
  media: dvb_dummy_tuner: implement driver skeleton
  media: dvb_dummy_fe.c: lose TS lock on bad snr
  media: dvb_dummy_fe.c: write PSI information into DMX buffer

 drivers/media/dvb-frontends/dvb_dummy_fe.c | 1538 +++++++++++++++++++-
 drivers/media/tuners/Kconfig               |    9 +
 drivers/media/tuners/Makefile              |    1 +
 drivers/media/tuners/dvb_dummy_tuner.c     |  421 ++++++
 4 files changed, 1963 insertions(+), 6 deletions(-)
 create mode 100644 drivers/media/tuners/dvb_dummy_tuner.c

-- 
2.25.1


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [Linux-kernel-mentees] [RFC 0/3] Implement a virtual DVB driver
@ 2020-03-18  6:00 ` Daniel W. S. Almeida
  0 siblings, 0 replies; 21+ messages in thread
From: Daniel W. S. Almeida @ 2020-03-18  6:00 UTC (permalink / raw)
  To: mchehab, sean, kstewart, allison, tglx
  Cc: linux-kernel-mentees, Daniel W. S. Almeida, linux-media

From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>

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 subsystem and to DTV in general, please take that into account.

Currently I have written a simple fake tuner and I have been working on
getting PSI packets into a TS stream and then feeding the stream to the
hardware-independent demux layer. I have been basing my work on ISO 13818,
ETSI EN 300 468 and on a real MPEG Transport Stream excerpt.

I am aware that the first patch generates plenty of warnings due to floating
point to integer implicit conversions. I still have to rework that.

The last patch in the series is also WIP, it still misses a set_frontend()
implementation among other things. This will be sorted soon.

A few bits of code were taken from libdvbv5, mostly the structs for the
PSI tables.

I appreciate any comments to improve this.

Thank you.

Daniel W. S. Almeida (3):
  media: dvb_dummy_tuner: implement driver skeleton
  media: dvb_dummy_fe.c: lose TS lock on bad snr
  media: dvb_dummy_fe.c: write PSI information into DMX buffer

 drivers/media/dvb-frontends/dvb_dummy_fe.c | 1538 +++++++++++++++++++-
 drivers/media/tuners/Kconfig               |    9 +
 drivers/media/tuners/Makefile              |    1 +
 drivers/media/tuners/dvb_dummy_tuner.c     |  421 ++++++
 4 files changed, 1963 insertions(+), 6 deletions(-)
 create mode 100644 drivers/media/tuners/dvb_dummy_tuner.c

-- 
2.25.1

_______________________________________________
Linux-kernel-mentees mailing list
Linux-kernel-mentees@lists.linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/linux-kernel-mentees

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC 1/3] media: dvb_dummy_tuner: implement driver skeleton
  2020-03-18  6:00 ` [Linux-kernel-mentees] " Daniel W. S. Almeida
@ 2020-03-18  6:00   ` Daniel W. S. Almeida
  -1 siblings, 0 replies; 21+ messages in thread
From: Daniel W. S. Almeida @ 2020-03-18  6:00 UTC (permalink / raw)
  To: mchehab, sean, kstewart, allison, tglx
  Cc: Daniel W. S. Almeida, linux-media, skhan, linux-kernel-mentees

From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>

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 <dwlsalmeida@gmail.com>
---
 drivers/media/tuners/Kconfig           |   9 +
 drivers/media/tuners/Makefile          |   1 +
 drivers/media/tuners/dvb_dummy_tuner.c | 421 +++++++++++++++++++++++++
 3 files changed, 431 insertions(+)
 create mode 100644 drivers/media/tuners/dvb_dummy_tuner.c

diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index e104bb7766e1..8ad54339ceee 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -296,4 +296,13 @@ config MEDIA_TUNER_QM1D1B0004
 	default m if !MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Sharp QM1D1B0004 ISDB-S tuner driver.
+
+config MEDIA_TUNER_DVB_DUMMY_TUNER
+	tristate "Dummy DVB Media Tuner"
+	depends on MEDIA_SUPPORT && I2C
+	help
+	  Dummy DVB media tuner driver
+	  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.
 endmenu
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index 7b4f8423501e..f98de1cf2e19 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -44,5 +44,6 @@ obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
 obj-$(CONFIG_MEDIA_TUNER_QM1D1B0004) += qm1d1b0004.o
 obj-$(CONFIG_MEDIA_TUNER_M88RS6000T) += m88rs6000t.o
 obj-$(CONFIG_MEDIA_TUNER_TDA18250) += tda18250.o
+obj-$(CONFIG_MEDIA_TUNER_DVB_DUMMY_TUNER) += dvb_dummy_tuner.o
 
 ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
diff --git a/drivers/media/tuners/dvb_dummy_tuner.c b/drivers/media/tuners/dvb_dummy_tuner.c
new file mode 100644
index 000000000000..1408a8c84be2
--- /dev/null
+++ b/drivers/media/tuners/dvb_dummy_tuner.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Daniel W. S. Almeida <dwlsalmeida@gmail.com>
+ *
+ * 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.
+ *
+ */
+
+#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>
+
+struct dvb_dummy_tuner_config {
+	struct dvb_frontend *fe;
+	u32 mock_power_up_delay_msec;
+	u32 mock_tune_delay_msec;
+	u32 mock_sleep_delay_msec;
+	u32 mock_resume_delay_msec;
+	u32 dummy_valid_dvb_t_frequencies[8];
+	u32 dummy_valid_dvb_c_frequencies[8];
+	u32 dummy_valid_dvb_s_frequencies[8];
+	u8  max_frequency_shift_hz;
+};
+
+struct dvb_dummy_tuner_cnr_to_qual_s {
+	/* attempt to use the same values as libdvbv5 */
+	u32 modulation;
+	u32 fec;
+	u32 cnr_ok, cnr_good;
+};
+
+struct dvb_dummy_tuner_cnr_to_qual_s dvb_c_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QAM_256, FEC_NONE,  34., 38.},
+	{ QAM_64,  FEC_NONE,  30., 34.},
+};
+
+struct dvb_dummy_tuner_cnr_to_qual_s dvb_s_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QPSK, FEC_1_2,  7., 10.},
+
+	{ QPSK, FEC_2_3,  9., 12.},
+	{ QPSK, FEC_3_4, 10., 13.},
+	{ QPSK, FEC_5_6, 11., 14.},
+
+	{ QPSK, FEC_7_8, 12., 15.},
+};
+
+struct dvb_dummy_tuner_cnr_to_qual_s dvb_s2_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QPSK,  FEC_1_2,   9.,  12.},
+	{ QPSK,  FEC_2_3,  11.,  14.},
+	{ QPSK,  FEC_3_4,  12.,  15.},
+	{ QPSK,  FEC_5_6,  12.,  15.},
+	{ QPSK,  FEC_8_9,  13.,  16.},
+	{ QPSK,  FEC_9_10, 13.5, 16.5},
+	{ PSK_8, FEC_2_3,  14.5, 17.5},
+	{ PSK_8, FEC_3_4,  16.,  19.},
+	{ PSK_8, FEC_5_6,  17.5, 20.5},
+	{ PSK_8, FEC_8_9,  19.,  22.},
+};
+
+static struct dvb_dummy_tuner_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{   QPSK, FEC_1_2,  4.1,  5.9},
+	{   QPSK, FEC_2_3,  6.1,  9.6},
+	{   QPSK, FEC_3_4,  7.2, 12.4},
+	{   QPSK, FEC_5_6,  8.5, 15.6},
+	{   QPSK, FEC_7_8,  9.2, 17.5},
+
+	{ QAM_16, FEC_1_2,  9.8, 11.8},
+	{ QAM_16, FEC_2_3, 12.1, 15.3},
+	{ QAM_16, FEC_3_4, 13.4, 18.1},
+	{ QAM_16, FEC_5_6, 14.8, 21.3},
+	{ QAM_16, FEC_7_8, 15.7, 23.6},
+
+	{ QAM_64, FEC_1_2, 14.0, 16.0},
+	{ QAM_64, FEC_2_3, 19.9, 25.4},
+	{ QAM_64, FEC_3_4, 24.9, 27.9},
+	{ QAM_64, FEC_5_6, 21.3, 23.3},
+	{ QAM_64, FEC_7_8, 22.0, 24.0},
+};
+
+enum dvb_dummy_tuner_lock_status {
+	STATUS_NO_LOCK = 0,
+	STATUS_LOCKED = TUNER_STATUS_LOCKED,
+};
+
+enum dvb_dummy_tuner_power_status {
+	STATUS_UNKNOWN,
+	STATUS_ASLEEP,
+	STATUS_ACTIVE
+};
+
+enum dvb_dummy_tuner_frequency_shift_status {
+	EXACT_MATCH,
+	CLOSE_MATCH,
+	TOO_FAR_OFF
+};
+
+struct dvb_dummy_tuner_hardware_state {
+	enum dvb_dummy_tuner_power_status power_status;
+	enum dvb_dummy_tuner_lock_status lock_status;
+	u32 if_frequency;
+	u32 tuned_frequency;
+	u32 bandwidth;
+};
+
+struct dvb_dummy_tuner_dev {
+	struct dvb_frontend *fe;
+	struct dvb_dummy_tuner_hardware_state hw_state;
+	struct dvb_dummy_tuner_config config;
+};
+
+static struct dvb_dummy_tuner_dev*
+dvb_dummy_tuner_get_dev(struct dvb_frontend *fe)
+{
+	struct i2c_client *client = fe->tuner_priv;
+
+	return (struct dvb_dummy_tuner_dev *)i2c_get_clientdata(client);
+}
+
+static bool dvb_dummy_tuner_check_frequency_shift(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	struct dvb_dummy_tuner_config config = tuner_dev->config;
+	u32 *valid_frequencies = NULL;
+	u32 array_sz = 0;
+	u32 i;
+
+	switch (c->delivery_system) {
+	case SYS_DVBT:
+	case SYS_DVBT2:
+		valid_frequencies =
+			config.dummy_valid_dvb_t_frequencies;
+		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_t_frequencies);
+		break;
+	case SYS_DVBS:
+	case SYS_DVBS2:
+		valid_frequencies =
+			config.dummy_valid_dvb_s_frequencies;
+		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_s_frequencies);
+		break;
+	case SYS_DVBC_ANNEX_A:
+		valid_frequencies =
+			config.dummy_valid_dvb_c_frequencies;
+		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_c_frequencies);
+		break;
+
+	default:
+		pr_warn("%s: unsupported delivery system: %u\n",
+			__func__,
+			c->delivery_system);
+		break;
+	}
+
+	for (i = 0; i < array_sz; i++) {
+		if (c->frequency == valid_frequencies[i])
+			return EXACT_MATCH;
+		else if (c->frequency >= valid_frequencies[i] -
+			 config.max_frequency_shift_hz &&
+			 c->frequency <= valid_frequencies[i] +
+			 config.max_frequency_shift_hz)
+			return CLOSE_MATCH;
+	}
+
+	return TOO_FAR_OFF;
+}
+
+static int
+dvb_dummy_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength)
+{
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	struct dvb_dummy_tuner_cnr_to_qual_s *cnr2qual = NULL;
+	u32 array_size = 0;
+	enum dvb_dummy_tuner_frequency_shift_status shift_status;
+	u32 i;
+
+	shift_status = dvb_dummy_tuner_check_frequency_shift(fe);
+
+	switch (c->delivery_system) {
+	case SYS_DVBT:
+	case SYS_DVBT2:
+		cnr2qual = dvb_t_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_t_cnr_2_qual);
+		break;
+
+	case SYS_DVBS:
+		cnr2qual = dvb_s_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_s_cnr_2_qual);
+		break;
+
+	case SYS_DVBS2:
+		cnr2qual = dvb_s2_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_s2_cnr_2_qual);
+		break;
+
+	case SYS_DVBC_ANNEX_A:
+		cnr2qual = dvb_c_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_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) {
+			*strength = (shift_status == EXACT_MATCH) ?
+				    cnr2qual[i].cnr_good :
+				    (shift_status == CLOSE_MATCH) ?
+				    cnr2qual[i].cnr_ok :
+				    cnr2qual[i].cnr_ok -
+				    (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok);
+			return 0;
+		}
+
+	}
+
+	/* default to some random values if we can't match against the table */
+	*strength = (shift_status == EXACT_MATCH) ? 34 : 10;
+	return 0;
+}
+
+static int dvb_dummy_tuner_init(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+	struct dvb_dummy_tuner_config config = tuner_dev->config;
+
+	msleep_interruptible(config.mock_power_up_delay_msec);
+
+	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
+	tuner_dev->hw_state.if_frequency = 5000;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_sleep(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+	struct dvb_dummy_tuner_config config = tuner_dev->config;
+
+	msleep_interruptible(config.mock_sleep_delay_msec);
+	tuner_dev->hw_state.power_status = STATUS_ASLEEP;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_suspend(struct dvb_frontend *fe)
+{
+	return dvb_dummy_tuner_sleep(fe);
+}
+
+static int dvb_dummy_tuner_resume(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+	struct dvb_dummy_tuner_config config = tuner_dev->config;
+
+	msleep_interruptible(config.mock_resume_delay_msec);
+	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_set_params(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+	struct dvb_dummy_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;
+
+	msleep_interruptible(config.mock_tune_delay_msec);
+
+	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 = STATUS_NO_LOCK;
+		return -EINVAL;
+	}
+
+	tuner_dev->hw_state.tuned_frequency = c->frequency;
+	tuner_dev->hw_state.bandwidth = c->bandwidth_hz;
+	tuner_dev->hw_state.lock_status = STATUS_LOCKED;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_set_config(struct dvb_frontend *fe,
+				      void *priv_cfg)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+
+	WARN_ON(sizeof(tuner_dev->config) != sizeof(*priv_cfg));
+	memcpy(&tuner_dev->config, priv_cfg, sizeof(*priv_cfg));
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_get_frequency(struct dvb_frontend *fe,
+					 u32 *frequency)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+
+	*frequency = tuner_dev->hw_state.tuned_frequency;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_get_bandwidth(struct dvb_frontend *fe,
+					 u32 *bandwidth)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+
+	*bandwidth = tuner_dev->hw_state.bandwidth;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_get_if_frequency(struct dvb_frontend *fe,
+					    u32 *frequency)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+
+	*frequency = tuner_dev->hw_state.if_frequency;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_get_status(struct dvb_frontend *fe, u32 *status)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+
+	*status = tuner_dev->hw_state.lock_status;
+
+	return 0;
+}
+
+static const struct dvb_tuner_ops dvb_dummy_tuner_ops = {
+	.init = dvb_dummy_tuner_init,
+	.sleep = dvb_dummy_tuner_sleep,
+	.suspend = dvb_dummy_tuner_suspend,
+	.resume = dvb_dummy_tuner_resume,
+	.set_params = dvb_dummy_tuner_set_params,
+	.set_config = dvb_dummy_tuner_set_config,
+	.get_bandwidth = dvb_dummy_tuner_get_bandwidth,
+	.get_frequency = dvb_dummy_tuner_get_frequency,
+	.get_if_frequency = dvb_dummy_tuner_get_if_frequency,
+	.get_status = dvb_dummy_tuner_get_status,
+	.get_rf_strength = dvb_dummy_tuner_get_signal_strength
+};
+
+static const struct i2c_device_id dvb_dummy_tuner_i2c_id_table[] = {
+	{"dvb_dummy_tuner", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, dvb_dummy_tuner_i2c_id_table);
+
+static int dvb_dummy_tuner_i2c_probe(struct i2c_client *client,
+				     const struct i2c_device_id *id)
+{
+	struct dvb_dummy_tuner_config *config = client->dev.platform_data;
+	struct dvb_frontend *fe = config->fe;
+	struct dvb_dummy_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,
+	       &dvb_dummy_tuner_ops,
+	       sizeof(struct dvb_tuner_ops));
+
+	fe->tuner_priv = client;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_i2c_remove(struct i2c_client *client)
+{
+	struct dvb_dummy_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 dvb_dummy_tuner_i2c_driver = {
+	.driver = {
+		.name = "dvb_dummy_tuner",
+		.suppress_bind_attrs = true,
+	},
+	.probe		= dvb_dummy_tuner_i2c_probe,
+	.remove		= dvb_dummy_tuner_i2c_remove,
+	.id_table	= dvb_dummy_tuner_i2c_id_table,
+};
+module_i2c_driver(dvb_dummy_tuner_i2c_driver);
+
+MODULE_DESCRIPTION("DVB Dummy Tuner");
+MODULE_AUTHOR("Daniel W. S. Almeida");
+MODULE_LICENSE("GPL");
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [Linux-kernel-mentees] [RFC 1/3] media: dvb_dummy_tuner: implement driver skeleton
@ 2020-03-18  6:00   ` Daniel W. S. Almeida
  0 siblings, 0 replies; 21+ messages in thread
From: Daniel W. S. Almeida @ 2020-03-18  6:00 UTC (permalink / raw)
  To: mchehab, sean, kstewart, allison, tglx
  Cc: linux-kernel-mentees, Daniel W. S. Almeida, linux-media

From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>

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 <dwlsalmeida@gmail.com>
---
 drivers/media/tuners/Kconfig           |   9 +
 drivers/media/tuners/Makefile          |   1 +
 drivers/media/tuners/dvb_dummy_tuner.c | 421 +++++++++++++++++++++++++
 3 files changed, 431 insertions(+)
 create mode 100644 drivers/media/tuners/dvb_dummy_tuner.c

diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
index e104bb7766e1..8ad54339ceee 100644
--- a/drivers/media/tuners/Kconfig
+++ b/drivers/media/tuners/Kconfig
@@ -296,4 +296,13 @@ config MEDIA_TUNER_QM1D1B0004
 	default m if !MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Sharp QM1D1B0004 ISDB-S tuner driver.
+
+config MEDIA_TUNER_DVB_DUMMY_TUNER
+	tristate "Dummy DVB Media Tuner"
+	depends on MEDIA_SUPPORT && I2C
+	help
+	  Dummy DVB media tuner driver
+	  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.
 endmenu
diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
index 7b4f8423501e..f98de1cf2e19 100644
--- a/drivers/media/tuners/Makefile
+++ b/drivers/media/tuners/Makefile
@@ -44,5 +44,6 @@ obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
 obj-$(CONFIG_MEDIA_TUNER_QM1D1B0004) += qm1d1b0004.o
 obj-$(CONFIG_MEDIA_TUNER_M88RS6000T) += m88rs6000t.o
 obj-$(CONFIG_MEDIA_TUNER_TDA18250) += tda18250.o
+obj-$(CONFIG_MEDIA_TUNER_DVB_DUMMY_TUNER) += dvb_dummy_tuner.o
 
 ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
diff --git a/drivers/media/tuners/dvb_dummy_tuner.c b/drivers/media/tuners/dvb_dummy_tuner.c
new file mode 100644
index 000000000000..1408a8c84be2
--- /dev/null
+++ b/drivers/media/tuners/dvb_dummy_tuner.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Daniel W. S. Almeida <dwlsalmeida@gmail.com>
+ *
+ * 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.
+ *
+ */
+
+#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>
+
+struct dvb_dummy_tuner_config {
+	struct dvb_frontend *fe;
+	u32 mock_power_up_delay_msec;
+	u32 mock_tune_delay_msec;
+	u32 mock_sleep_delay_msec;
+	u32 mock_resume_delay_msec;
+	u32 dummy_valid_dvb_t_frequencies[8];
+	u32 dummy_valid_dvb_c_frequencies[8];
+	u32 dummy_valid_dvb_s_frequencies[8];
+	u8  max_frequency_shift_hz;
+};
+
+struct dvb_dummy_tuner_cnr_to_qual_s {
+	/* attempt to use the same values as libdvbv5 */
+	u32 modulation;
+	u32 fec;
+	u32 cnr_ok, cnr_good;
+};
+
+struct dvb_dummy_tuner_cnr_to_qual_s dvb_c_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QAM_256, FEC_NONE,  34., 38.},
+	{ QAM_64,  FEC_NONE,  30., 34.},
+};
+
+struct dvb_dummy_tuner_cnr_to_qual_s dvb_s_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QPSK, FEC_1_2,  7., 10.},
+
+	{ QPSK, FEC_2_3,  9., 12.},
+	{ QPSK, FEC_3_4, 10., 13.},
+	{ QPSK, FEC_5_6, 11., 14.},
+
+	{ QPSK, FEC_7_8, 12., 15.},
+};
+
+struct dvb_dummy_tuner_cnr_to_qual_s dvb_s2_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QPSK,  FEC_1_2,   9.,  12.},
+	{ QPSK,  FEC_2_3,  11.,  14.},
+	{ QPSK,  FEC_3_4,  12.,  15.},
+	{ QPSK,  FEC_5_6,  12.,  15.},
+	{ QPSK,  FEC_8_9,  13.,  16.},
+	{ QPSK,  FEC_9_10, 13.5, 16.5},
+	{ PSK_8, FEC_2_3,  14.5, 17.5},
+	{ PSK_8, FEC_3_4,  16.,  19.},
+	{ PSK_8, FEC_5_6,  17.5, 20.5},
+	{ PSK_8, FEC_8_9,  19.,  22.},
+};
+
+static struct dvb_dummy_tuner_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{   QPSK, FEC_1_2,  4.1,  5.9},
+	{   QPSK, FEC_2_3,  6.1,  9.6},
+	{   QPSK, FEC_3_4,  7.2, 12.4},
+	{   QPSK, FEC_5_6,  8.5, 15.6},
+	{   QPSK, FEC_7_8,  9.2, 17.5},
+
+	{ QAM_16, FEC_1_2,  9.8, 11.8},
+	{ QAM_16, FEC_2_3, 12.1, 15.3},
+	{ QAM_16, FEC_3_4, 13.4, 18.1},
+	{ QAM_16, FEC_5_6, 14.8, 21.3},
+	{ QAM_16, FEC_7_8, 15.7, 23.6},
+
+	{ QAM_64, FEC_1_2, 14.0, 16.0},
+	{ QAM_64, FEC_2_3, 19.9, 25.4},
+	{ QAM_64, FEC_3_4, 24.9, 27.9},
+	{ QAM_64, FEC_5_6, 21.3, 23.3},
+	{ QAM_64, FEC_7_8, 22.0, 24.0},
+};
+
+enum dvb_dummy_tuner_lock_status {
+	STATUS_NO_LOCK = 0,
+	STATUS_LOCKED = TUNER_STATUS_LOCKED,
+};
+
+enum dvb_dummy_tuner_power_status {
+	STATUS_UNKNOWN,
+	STATUS_ASLEEP,
+	STATUS_ACTIVE
+};
+
+enum dvb_dummy_tuner_frequency_shift_status {
+	EXACT_MATCH,
+	CLOSE_MATCH,
+	TOO_FAR_OFF
+};
+
+struct dvb_dummy_tuner_hardware_state {
+	enum dvb_dummy_tuner_power_status power_status;
+	enum dvb_dummy_tuner_lock_status lock_status;
+	u32 if_frequency;
+	u32 tuned_frequency;
+	u32 bandwidth;
+};
+
+struct dvb_dummy_tuner_dev {
+	struct dvb_frontend *fe;
+	struct dvb_dummy_tuner_hardware_state hw_state;
+	struct dvb_dummy_tuner_config config;
+};
+
+static struct dvb_dummy_tuner_dev*
+dvb_dummy_tuner_get_dev(struct dvb_frontend *fe)
+{
+	struct i2c_client *client = fe->tuner_priv;
+
+	return (struct dvb_dummy_tuner_dev *)i2c_get_clientdata(client);
+}
+
+static bool dvb_dummy_tuner_check_frequency_shift(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	struct dvb_dummy_tuner_config config = tuner_dev->config;
+	u32 *valid_frequencies = NULL;
+	u32 array_sz = 0;
+	u32 i;
+
+	switch (c->delivery_system) {
+	case SYS_DVBT:
+	case SYS_DVBT2:
+		valid_frequencies =
+			config.dummy_valid_dvb_t_frequencies;
+		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_t_frequencies);
+		break;
+	case SYS_DVBS:
+	case SYS_DVBS2:
+		valid_frequencies =
+			config.dummy_valid_dvb_s_frequencies;
+		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_s_frequencies);
+		break;
+	case SYS_DVBC_ANNEX_A:
+		valid_frequencies =
+			config.dummy_valid_dvb_c_frequencies;
+		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_c_frequencies);
+		break;
+
+	default:
+		pr_warn("%s: unsupported delivery system: %u\n",
+			__func__,
+			c->delivery_system);
+		break;
+	}
+
+	for (i = 0; i < array_sz; i++) {
+		if (c->frequency == valid_frequencies[i])
+			return EXACT_MATCH;
+		else if (c->frequency >= valid_frequencies[i] -
+			 config.max_frequency_shift_hz &&
+			 c->frequency <= valid_frequencies[i] +
+			 config.max_frequency_shift_hz)
+			return CLOSE_MATCH;
+	}
+
+	return TOO_FAR_OFF;
+}
+
+static int
+dvb_dummy_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength)
+{
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	struct dvb_dummy_tuner_cnr_to_qual_s *cnr2qual = NULL;
+	u32 array_size = 0;
+	enum dvb_dummy_tuner_frequency_shift_status shift_status;
+	u32 i;
+
+	shift_status = dvb_dummy_tuner_check_frequency_shift(fe);
+
+	switch (c->delivery_system) {
+	case SYS_DVBT:
+	case SYS_DVBT2:
+		cnr2qual = dvb_t_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_t_cnr_2_qual);
+		break;
+
+	case SYS_DVBS:
+		cnr2qual = dvb_s_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_s_cnr_2_qual);
+		break;
+
+	case SYS_DVBS2:
+		cnr2qual = dvb_s2_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_s2_cnr_2_qual);
+		break;
+
+	case SYS_DVBC_ANNEX_A:
+		cnr2qual = dvb_c_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_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) {
+			*strength = (shift_status == EXACT_MATCH) ?
+				    cnr2qual[i].cnr_good :
+				    (shift_status == CLOSE_MATCH) ?
+				    cnr2qual[i].cnr_ok :
+				    cnr2qual[i].cnr_ok -
+				    (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok);
+			return 0;
+		}
+
+	}
+
+	/* default to some random values if we can't match against the table */
+	*strength = (shift_status == EXACT_MATCH) ? 34 : 10;
+	return 0;
+}
+
+static int dvb_dummy_tuner_init(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+	struct dvb_dummy_tuner_config config = tuner_dev->config;
+
+	msleep_interruptible(config.mock_power_up_delay_msec);
+
+	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
+	tuner_dev->hw_state.if_frequency = 5000;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_sleep(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+	struct dvb_dummy_tuner_config config = tuner_dev->config;
+
+	msleep_interruptible(config.mock_sleep_delay_msec);
+	tuner_dev->hw_state.power_status = STATUS_ASLEEP;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_suspend(struct dvb_frontend *fe)
+{
+	return dvb_dummy_tuner_sleep(fe);
+}
+
+static int dvb_dummy_tuner_resume(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+	struct dvb_dummy_tuner_config config = tuner_dev->config;
+
+	msleep_interruptible(config.mock_resume_delay_msec);
+	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_set_params(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+	struct dvb_dummy_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;
+
+	msleep_interruptible(config.mock_tune_delay_msec);
+
+	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 = STATUS_NO_LOCK;
+		return -EINVAL;
+	}
+
+	tuner_dev->hw_state.tuned_frequency = c->frequency;
+	tuner_dev->hw_state.bandwidth = c->bandwidth_hz;
+	tuner_dev->hw_state.lock_status = STATUS_LOCKED;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_set_config(struct dvb_frontend *fe,
+				      void *priv_cfg)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+
+	WARN_ON(sizeof(tuner_dev->config) != sizeof(*priv_cfg));
+	memcpy(&tuner_dev->config, priv_cfg, sizeof(*priv_cfg));
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_get_frequency(struct dvb_frontend *fe,
+					 u32 *frequency)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+
+	*frequency = tuner_dev->hw_state.tuned_frequency;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_get_bandwidth(struct dvb_frontend *fe,
+					 u32 *bandwidth)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+
+	*bandwidth = tuner_dev->hw_state.bandwidth;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_get_if_frequency(struct dvb_frontend *fe,
+					    u32 *frequency)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+
+	*frequency = tuner_dev->hw_state.if_frequency;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_get_status(struct dvb_frontend *fe, u32 *status)
+{
+	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
+
+	*status = tuner_dev->hw_state.lock_status;
+
+	return 0;
+}
+
+static const struct dvb_tuner_ops dvb_dummy_tuner_ops = {
+	.init = dvb_dummy_tuner_init,
+	.sleep = dvb_dummy_tuner_sleep,
+	.suspend = dvb_dummy_tuner_suspend,
+	.resume = dvb_dummy_tuner_resume,
+	.set_params = dvb_dummy_tuner_set_params,
+	.set_config = dvb_dummy_tuner_set_config,
+	.get_bandwidth = dvb_dummy_tuner_get_bandwidth,
+	.get_frequency = dvb_dummy_tuner_get_frequency,
+	.get_if_frequency = dvb_dummy_tuner_get_if_frequency,
+	.get_status = dvb_dummy_tuner_get_status,
+	.get_rf_strength = dvb_dummy_tuner_get_signal_strength
+};
+
+static const struct i2c_device_id dvb_dummy_tuner_i2c_id_table[] = {
+	{"dvb_dummy_tuner", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, dvb_dummy_tuner_i2c_id_table);
+
+static int dvb_dummy_tuner_i2c_probe(struct i2c_client *client,
+				     const struct i2c_device_id *id)
+{
+	struct dvb_dummy_tuner_config *config = client->dev.platform_data;
+	struct dvb_frontend *fe = config->fe;
+	struct dvb_dummy_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,
+	       &dvb_dummy_tuner_ops,
+	       sizeof(struct dvb_tuner_ops));
+
+	fe->tuner_priv = client;
+
+	return 0;
+}
+
+static int dvb_dummy_tuner_i2c_remove(struct i2c_client *client)
+{
+	struct dvb_dummy_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 dvb_dummy_tuner_i2c_driver = {
+	.driver = {
+		.name = "dvb_dummy_tuner",
+		.suppress_bind_attrs = true,
+	},
+	.probe		= dvb_dummy_tuner_i2c_probe,
+	.remove		= dvb_dummy_tuner_i2c_remove,
+	.id_table	= dvb_dummy_tuner_i2c_id_table,
+};
+module_i2c_driver(dvb_dummy_tuner_i2c_driver);
+
+MODULE_DESCRIPTION("DVB Dummy Tuner");
+MODULE_AUTHOR("Daniel W. S. Almeida");
+MODULE_LICENSE("GPL");
-- 
2.25.1

_______________________________________________
Linux-kernel-mentees mailing list
Linux-kernel-mentees@lists.linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/linux-kernel-mentees

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [RFC 2/3] media: dvb_dummy_fe.c: lose TS lock on bad snr
  2020-03-18  6:00 ` [Linux-kernel-mentees] " Daniel W. S. Almeida
@ 2020-03-18  6:00   ` Daniel W. S. Almeida
  -1 siblings, 0 replies; 21+ messages in thread
From: Daniel W. S. Almeida @ 2020-03-18  6:00 UTC (permalink / raw)
  To: mchehab, sean, kstewart, allison, tglx
  Cc: Daniel W. S. Almeida, linux-media, skhan, linux-kernel-mentees

From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>

Periodically check the signal quality and eventually lose the lock if
the quality is sub-par. A fake tuner can return a bad quality signal to
the demod if the frequency is too far off from a valid frequency.

Signed-off-by: Daniel W. S. Almeida <dwlsalmeida@gmail.com>
---
 drivers/media/dvb-frontends/dvb_dummy_fe.c | 149 ++++++++++++++++++++-
 1 file changed, 144 insertions(+), 5 deletions(-)

diff --git a/drivers/media/dvb-frontends/dvb_dummy_fe.c b/drivers/media/dvb-frontends/dvb_dummy_fe.c
index 9ff1ebaa5e04..726c964a523d 100644
--- a/drivers/media/dvb-frontends/dvb_dummy_fe.c
+++ b/drivers/media/dvb-frontends/dvb_dummy_fe.c
@@ -9,24 +9,155 @@
 #include <linux/init.h>
 #include <linux/string.h>
 #include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/random.h>
 
 #include <media/dvb_frontend.h>
 #include "dvb_dummy_fe.h"
 
 
+struct dvb_dummy_fe_cnr_to_qual_s {
+	/* attempt to use the same values as libdvbv5 */
+	u32 modulation;
+	u32 fec;
+	u32 cnr_ok, cnr_good;
+};
+
+struct dvb_dummy_fe_cnr_to_qual_s dvb_c_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QAM_256, FEC_NONE,  34., 38.},
+	{ QAM_64,  FEC_NONE,  30., 34.},
+};
+
+struct dvb_dummy_fe_cnr_to_qual_s dvb_s_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QPSK, FEC_1_2,  7., 10.},
+
+	{ QPSK, FEC_2_3,  9., 12.},
+	{ QPSK, FEC_3_4, 10., 13.},
+	{ QPSK, FEC_5_6, 11., 14.},
+
+	{ QPSK, FEC_7_8, 12., 15.},
+};
+
+struct dvb_dummy_fe_cnr_to_qual_s dvb_s2_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QPSK,  FEC_1_2,   9.,  12.},
+	{ QPSK,  FEC_2_3,  11.,  14.},
+	{ QPSK,  FEC_3_4,  12.,  15.},
+	{ QPSK,  FEC_5_6,  12.,  15.},
+	{ QPSK,  FEC_8_9,  13.,  16.},
+	{ QPSK,  FEC_9_10, 13.5, 16.5},
+	{ PSK_8, FEC_2_3,  14.5, 17.5},
+	{ PSK_8, FEC_3_4,  16.,  19.},
+	{ PSK_8, FEC_5_6,  17.5, 20.5},
+	{ PSK_8, FEC_8_9,  19.,  22.},
+};
+
+static struct dvb_dummy_fe_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{   QPSK, FEC_1_2,  4.1,  5.9},
+	{   QPSK, FEC_2_3,  6.1,  9.6},
+	{   QPSK, FEC_3_4,  7.2, 12.4},
+	{   QPSK, FEC_5_6,  8.5, 15.6},
+	{   QPSK, FEC_7_8,  9.2, 17.5},
+
+	{ QAM_16, FEC_1_2,  9.8, 11.8},
+	{ QAM_16, FEC_2_3, 12.1, 15.3},
+	{ QAM_16, FEC_3_4, 13.4, 18.1},
+	{ QAM_16, FEC_5_6, 14.8, 21.3},
+	{ QAM_16, FEC_7_8, 15.7, 23.6},
+
+	{ QAM_64, FEC_1_2, 14.0, 16.0},
+	{ QAM_64, FEC_2_3, 19.9, 25.4},
+	{ QAM_64, FEC_3_4, 24.9, 27.9},
+	{ QAM_64, FEC_5_6, 21.3, 23.3},
+	{ QAM_64, FEC_7_8, 22.0, 24.0},
+};
+
+struct dvb_dummy_fe_config {
+	/* probability of losing the lock due to low snr */
+	u8 drop_tslock_probability_on_low_snr;
+};
+
 struct dvb_dummy_fe_state {
 	struct dvb_frontend frontend;
+	struct dvb_dummy_fe_config config;
+	struct delayed_work poll_snr;
+	enum fe_status status;
 };
 
+void poll_snr_handler(struct work_struct *work)
+{
+	/* periodically check the signal quality and eventually
+	 * lose the TS lock if it dips too low
+	 */
+	struct dvb_dummy_fe_state *state =
+		container_of(work, struct dvb_dummy_fe_state, poll_snr.work);
+	struct dtv_frontend_properties *c = &state->frontend.dtv_property_cache;
+	struct dvb_dummy_fe_cnr_to_qual_s *cnr2qual = NULL;
+	struct dvb_dummy_fe_config *config = &state->config;
+	u32 array_size = 0;
+	u16 snr = 0;
+	u32 i;
+
+	if (!state->frontend.ops.tuner_ops.get_rf_strength)
+		return;
+
+	state->frontend.ops.tuner_ops.get_rf_strength(&state->frontend, &snr);
+
+	switch (c->delivery_system) {
+	case SYS_DVBT:
+	case SYS_DVBT2:
+		cnr2qual = dvb_t_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_t_cnr_2_qual);
+		break;
+
+	case SYS_DVBS:
+		cnr2qual = dvb_s_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_s_cnr_2_qual);
+		break;
+
+	case SYS_DVBS2:
+		cnr2qual = dvb_s2_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_s2_cnr_2_qual);
+		break;
+
+	case SYS_DVBC_ANNEX_A:
+		cnr2qual = dvb_c_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_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) {
+
+			if (snr < cnr2qual[i].cnr_ok) {
+				/* eventually lose the TS lock */
+				if (prandom_u32_max(100) <
+				    config->drop_tslock_probability_on_low_snr)
+					state->status = 0;
+			}
+		}
+	}
+
+	schedule_delayed_work(&(state->poll_snr), msecs_to_jiffies(2000));
+}
 
 static int dvb_dummy_fe_read_status(struct dvb_frontend *fe,
 				    enum fe_status *status)
 {
-	*status = FE_HAS_SIGNAL
-		| FE_HAS_CARRIER
-		| FE_HAS_VITERBI
-		| FE_HAS_SYNC
-		| FE_HAS_LOCK;
+
+	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
+
+	*status = state->status;
 
 	return 0;
 }
@@ -80,11 +211,18 @@ static int dvb_dummy_fe_set_frontend(struct dvb_frontend *fe)
 
 static int dvb_dummy_fe_sleep(struct dvb_frontend *fe)
 {
+	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
+
+	cancel_delayed_work_sync(&(state->poll_snr));
 	return 0;
 }
 
 static int dvb_dummy_fe_init(struct dvb_frontend *fe)
 {
+	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
+
+	INIT_DELAYED_WORK(&(state->poll_snr), &poll_snr_handler);
+	schedule_delayed_work(&(state->poll_snr), msecs_to_jiffies(2000));
 	return 0;
 }
 
@@ -104,6 +242,7 @@ static void dvb_dummy_fe_release(struct dvb_frontend *fe)
 {
 	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
 
+	cancel_delayed_work_sync(&(state->poll_snr));
 	kfree(state);
 }
 
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [Linux-kernel-mentees] [RFC 2/3] media: dvb_dummy_fe.c: lose TS lock on bad snr
@ 2020-03-18  6:00   ` Daniel W. S. Almeida
  0 siblings, 0 replies; 21+ messages in thread
From: Daniel W. S. Almeida @ 2020-03-18  6:00 UTC (permalink / raw)
  To: mchehab, sean, kstewart, allison, tglx
  Cc: linux-kernel-mentees, Daniel W. S. Almeida, linux-media

From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>

Periodically check the signal quality and eventually lose the lock if
the quality is sub-par. A fake tuner can return a bad quality signal to
the demod if the frequency is too far off from a valid frequency.

Signed-off-by: Daniel W. S. Almeida <dwlsalmeida@gmail.com>
---
 drivers/media/dvb-frontends/dvb_dummy_fe.c | 149 ++++++++++++++++++++-
 1 file changed, 144 insertions(+), 5 deletions(-)

diff --git a/drivers/media/dvb-frontends/dvb_dummy_fe.c b/drivers/media/dvb-frontends/dvb_dummy_fe.c
index 9ff1ebaa5e04..726c964a523d 100644
--- a/drivers/media/dvb-frontends/dvb_dummy_fe.c
+++ b/drivers/media/dvb-frontends/dvb_dummy_fe.c
@@ -9,24 +9,155 @@
 #include <linux/init.h>
 #include <linux/string.h>
 #include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/random.h>
 
 #include <media/dvb_frontend.h>
 #include "dvb_dummy_fe.h"
 
 
+struct dvb_dummy_fe_cnr_to_qual_s {
+	/* attempt to use the same values as libdvbv5 */
+	u32 modulation;
+	u32 fec;
+	u32 cnr_ok, cnr_good;
+};
+
+struct dvb_dummy_fe_cnr_to_qual_s dvb_c_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QAM_256, FEC_NONE,  34., 38.},
+	{ QAM_64,  FEC_NONE,  30., 34.},
+};
+
+struct dvb_dummy_fe_cnr_to_qual_s dvb_s_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QPSK, FEC_1_2,  7., 10.},
+
+	{ QPSK, FEC_2_3,  9., 12.},
+	{ QPSK, FEC_3_4, 10., 13.},
+	{ QPSK, FEC_5_6, 11., 14.},
+
+	{ QPSK, FEC_7_8, 12., 15.},
+};
+
+struct dvb_dummy_fe_cnr_to_qual_s dvb_s2_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{ QPSK,  FEC_1_2,   9.,  12.},
+	{ QPSK,  FEC_2_3,  11.,  14.},
+	{ QPSK,  FEC_3_4,  12.,  15.},
+	{ QPSK,  FEC_5_6,  12.,  15.},
+	{ QPSK,  FEC_8_9,  13.,  16.},
+	{ QPSK,  FEC_9_10, 13.5, 16.5},
+	{ PSK_8, FEC_2_3,  14.5, 17.5},
+	{ PSK_8, FEC_3_4,  16.,  19.},
+	{ PSK_8, FEC_5_6,  17.5, 20.5},
+	{ PSK_8, FEC_8_9,  19.,  22.},
+};
+
+static struct dvb_dummy_fe_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
+	/* from libdvbv5 source code */
+	{   QPSK, FEC_1_2,  4.1,  5.9},
+	{   QPSK, FEC_2_3,  6.1,  9.6},
+	{   QPSK, FEC_3_4,  7.2, 12.4},
+	{   QPSK, FEC_5_6,  8.5, 15.6},
+	{   QPSK, FEC_7_8,  9.2, 17.5},
+
+	{ QAM_16, FEC_1_2,  9.8, 11.8},
+	{ QAM_16, FEC_2_3, 12.1, 15.3},
+	{ QAM_16, FEC_3_4, 13.4, 18.1},
+	{ QAM_16, FEC_5_6, 14.8, 21.3},
+	{ QAM_16, FEC_7_8, 15.7, 23.6},
+
+	{ QAM_64, FEC_1_2, 14.0, 16.0},
+	{ QAM_64, FEC_2_3, 19.9, 25.4},
+	{ QAM_64, FEC_3_4, 24.9, 27.9},
+	{ QAM_64, FEC_5_6, 21.3, 23.3},
+	{ QAM_64, FEC_7_8, 22.0, 24.0},
+};
+
+struct dvb_dummy_fe_config {
+	/* probability of losing the lock due to low snr */
+	u8 drop_tslock_probability_on_low_snr;
+};
+
 struct dvb_dummy_fe_state {
 	struct dvb_frontend frontend;
+	struct dvb_dummy_fe_config config;
+	struct delayed_work poll_snr;
+	enum fe_status status;
 };
 
+void poll_snr_handler(struct work_struct *work)
+{
+	/* periodically check the signal quality and eventually
+	 * lose the TS lock if it dips too low
+	 */
+	struct dvb_dummy_fe_state *state =
+		container_of(work, struct dvb_dummy_fe_state, poll_snr.work);
+	struct dtv_frontend_properties *c = &state->frontend.dtv_property_cache;
+	struct dvb_dummy_fe_cnr_to_qual_s *cnr2qual = NULL;
+	struct dvb_dummy_fe_config *config = &state->config;
+	u32 array_size = 0;
+	u16 snr = 0;
+	u32 i;
+
+	if (!state->frontend.ops.tuner_ops.get_rf_strength)
+		return;
+
+	state->frontend.ops.tuner_ops.get_rf_strength(&state->frontend, &snr);
+
+	switch (c->delivery_system) {
+	case SYS_DVBT:
+	case SYS_DVBT2:
+		cnr2qual = dvb_t_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_t_cnr_2_qual);
+		break;
+
+	case SYS_DVBS:
+		cnr2qual = dvb_s_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_s_cnr_2_qual);
+		break;
+
+	case SYS_DVBS2:
+		cnr2qual = dvb_s2_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_s2_cnr_2_qual);
+		break;
+
+	case SYS_DVBC_ANNEX_A:
+		cnr2qual = dvb_c_cnr_2_qual;
+		array_size = ARRAY_SIZE(dvb_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) {
+
+			if (snr < cnr2qual[i].cnr_ok) {
+				/* eventually lose the TS lock */
+				if (prandom_u32_max(100) <
+				    config->drop_tslock_probability_on_low_snr)
+					state->status = 0;
+			}
+		}
+	}
+
+	schedule_delayed_work(&(state->poll_snr), msecs_to_jiffies(2000));
+}
 
 static int dvb_dummy_fe_read_status(struct dvb_frontend *fe,
 				    enum fe_status *status)
 {
-	*status = FE_HAS_SIGNAL
-		| FE_HAS_CARRIER
-		| FE_HAS_VITERBI
-		| FE_HAS_SYNC
-		| FE_HAS_LOCK;
+
+	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
+
+	*status = state->status;
 
 	return 0;
 }
@@ -80,11 +211,18 @@ static int dvb_dummy_fe_set_frontend(struct dvb_frontend *fe)
 
 static int dvb_dummy_fe_sleep(struct dvb_frontend *fe)
 {
+	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
+
+	cancel_delayed_work_sync(&(state->poll_snr));
 	return 0;
 }
 
 static int dvb_dummy_fe_init(struct dvb_frontend *fe)
 {
+	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
+
+	INIT_DELAYED_WORK(&(state->poll_snr), &poll_snr_handler);
+	schedule_delayed_work(&(state->poll_snr), msecs_to_jiffies(2000));
 	return 0;
 }
 
@@ -104,6 +242,7 @@ static void dvb_dummy_fe_release(struct dvb_frontend *fe)
 {
 	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
 
+	cancel_delayed_work_sync(&(state->poll_snr));
 	kfree(state);
 }
 
-- 
2.25.1

_______________________________________________
Linux-kernel-mentees mailing list
Linux-kernel-mentees@lists.linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/linux-kernel-mentees

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [RFC 3/3] media: dvb_dummy_fe.c: write PSI information into DMX buffer
  2020-03-18  6:00 ` [Linux-kernel-mentees] " Daniel W. S. Almeida
@ 2020-03-18  6:00   ` Daniel W. S. Almeida
  -1 siblings, 0 replies; 21+ messages in thread
From: Daniel W. S. Almeida @ 2020-03-18  6:00 UTC (permalink / raw)
  To: mchehab, sean, kstewart, allison, tglx
  Cc: Daniel W. S. Almeida, linux-media, skhan, linux-kernel-mentees

From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>

Periodically feed the demux with PSI packets. This is needed so userspace
apps can retrieve information about the Transport Stream and eventually
tune into a (dummy) channel.

Currently this commit adds support for working with 3 PSI tables:
PAT, PMT and SDT. A dummy service with a dummy program is hardcoded
in the driver code.
---
 drivers/media/dvb-frontends/dvb_dummy_fe.c | 1409 +++++++++++++++++++-
 1 file changed, 1398 insertions(+), 11 deletions(-)

diff --git a/drivers/media/dvb-frontends/dvb_dummy_fe.c b/drivers/media/dvb-frontends/dvb_dummy_fe.c
index 726c964a523d..10b1bd22c15d 100644
--- a/drivers/media/dvb-frontends/dvb_dummy_fe.c
+++ b/drivers/media/dvb-frontends/dvb_dummy_fe.c
@@ -11,11 +11,1408 @@
 #include <linux/slab.h>
 #include <linux/workqueue.h>
 #include <linux/random.h>
+#include <linux/crc32.h>
+#include <linux/string.h>
 
 #include <media/dvb_frontend.h>
-#include "dvb_dummy_fe.h"
 
 
+#define CRC_SIZE_IN_BYTES 32
+#define NUM_CHANNELS 1
+#define TRANSPORT_STREAM_ID 0x744 /* a single stream */
+#define TS_SYNC_BYTE 0x47
+#define TS_PACKET_LEN 188
+#define TS_PAYLOAD_LEN 184
+#define DMX_BUF_LEN (20 * TS_PACKET_LEN)
+
+
+enum dvb_dummy_descriptors {
+	SERVICE_DESCRIPTOR = 0x48,
+};
+
+enum dvb_dummy_stream_types {
+	ISO_IEC_13818_3_Audio = 0x4,
+};
+
+struct dvb_dummy_channel {
+	u16 transport_stream_id;
+	struct dvb_dummy_table_sdt_service *service;
+	u16 program_num;
+	/* a single program with one or more streams associated with it */
+	struct dvb_dummy_table_pat_program *program;
+	struct dvb_dummy_table_pmt_stream *streams;
+};
+
+struct dvb_dummy_desc {
+	u8 type;
+	u8 length;
+	struct dvb_dummy_desc *next;
+	u8 *data;
+} __packed;
+
+struct dvb_dummy_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 dvb_dummy_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 dvb_dummy_table_pat_program {
+	u16 service_id;
+	union {
+		u16 bitfield;
+		struct {
+			u16 pid:13;
+			u8  reserved:3;
+		} __packed;
+	} __packed;
+	struct dvb_dummy_table_pat_program *next;
+} __packed;
+
+struct dvb_dummy_table_pat {
+	struct dvb_dummy_table_header header;
+	u16 programs;
+	struct dvb_dummy_table_pat_program *program;
+} __packed;
+
+struct dvb_dummy_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 dvb_dummy_desc *descriptor;
+	struct dvb_dummy_table_sdt_service *next;
+} __packed;
+
+struct dvb_dummy_table_sdt {
+	struct dvb_dummy_table_header header;
+	u16 network_id;
+	u8  reserved;
+	struct dvb_dummy_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 dvb_dummy_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 dvb_dummy_desc *descriptor;
+	struct dvb_dummy_table_pmt_stream *next;
+} __packed;
+
+struct dvb_dummy_table_pmt {
+	struct dvb_dummy_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 dvb_dummy_desc *descriptor;
+	struct dvb_dummy_table_pmt_stream *stream;
+} __packed;
+
+struct dvb_dummy_mpeg_ts_adaption {
+	u8 length;
+	struct {
+		u8 extension:1;
+		u8 private_data:1;
+		u8 splicing_point:1;
+		u8 OPCR:1;
+		u8 PCR:1;
+		u8 priority:1;
+		u8 random_access:1;
+		u8 discontinued:1;
+	} __packed;
+	u8 data[];
+} __packed;
+
+
+struct dvb_dummy_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 dvb_dummy_mpeg_ts_adaption adaption[];
+} __packed;
+
+struct dvb_dummy_fe_config {
+	/* probability of losing the lock due to low snr */
+	u8 drop_tslock_probability_on_low_snr;
+};
+
+struct dvb_dummy_fe_state {
+	struct dvb_frontend frontend;
+	struct dvb_dummy_fe_config config;
+	struct delayed_work poll_snr;
+	enum fe_status status;
+	struct dvb_dummy_channel *channels;
+};
+
+static u32 dvb_dummy_fe_mpeg_ts_psi_write_stuffing(void *to, u32 len)
+{
+	memset(to, 0xFF, len);
+	return len;
+}
+
+static u32 dvb_dummy_fe_mpeg_ts_psi_write_into(void *to,
+					    const void *from,
+					    size_t len,
+					    u32 offset,
+					    u16 pid,
+					    bool new_psi_section,
+					    u8 *continuity_counter,
+					    bool is_crc)
+{
+	/*   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 = (offset % TS_PACKET_LEN);
+	bool aligned = nbytes_past_boundary == 0;
+	bool split = len > TS_PAYLOAD_LEN;
+	u32 payload_write_len = (split) ? TS_PAYLOAD_LEN : len;
+
+	struct dvb_dummy_mpeg_ts ts_header = {0};
+
+	u32 nbytes = 0; /* number of bytes written by this function */
+
+	if (new_psi_section && !aligned) {
+		/* must pad the buffer with the complement to get a
+		 * multiple of 188
+		 */
+		nbytes +=
+			dvb_dummy_fe_mpeg_ts_psi_write_stuffing(
+						to + offset + nbytes,
+						TS_PACKET_LEN -
+						nbytes_past_boundary);
+
+		/* 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 = pid;
+		ts_header.priority = 0;
+		ts_header.scrambling = 0; /* not scrambled */
+		ts_header.continuity_counter = *continuity_counter;
+		ts_header.payload_start = 0; /* no adaption for now */
+
+
+		/* copy the header minus the adaption pointer*/
+		memcpy(to + offset + nbytes,
+		&ts_header,
+		sizeof(ts_header) -
+		sizeof(struct dvb_dummy_mpeg_ts_adaption *));
+
+		nbytes +=
+		sizeof(ts_header) -
+		sizeof(struct dvb_dummy_mpeg_ts_adaption *);
+	}
+
+	if (new_psi_section) {
+		/* write the pointer_field in the first byte of the payload */
+		memset(to + offset + nbytes, 0x0, 1);
+		nbytes += 1;
+		--payload_write_len; /* one byte was used by the pointer field*/
+	}
+
+	/* write as much of the payload as we possibly can */
+	memcpy(to + offset + nbytes, from, payload_write_len);
+	nbytes += payload_write_len;
+
+	if (split) {
+		/* next TS packet keeps the same PID, but increments the
+		 * counter
+		 */
+		++(*continuity_counter);
+		/* 'nbytes' written from a total of 'len' requested*/
+		len -= nbytes;
+		/* recursively write the rest of the data until we do not
+		 * need to split it anymore
+		 */
+		nbytes +=
+		dvb_dummy_fe_mpeg_ts_psi_write_into(to,
+						    from + nbytes,
+						    len,
+						    offset + nbytes,
+						    pid,
+						    false,
+						    continuity_counter,
+						    is_crc);
+	}
+
+	if (is_crc)
+		/* as the CRC is last in the section, stuff the rest of the
+		 * packet if there is any remaining space in there
+		 */
+
+		nbytes +=
+		dvb_dummy_fe_mpeg_ts_psi_write_stuffing(to + nbytes,
+							TS_PAYLOAD_LEN -
+							nbytes);
+
+	return nbytes;
+}
+
+static u32 table_section_crc32_write_into(char *buf,
+					  u32 offset,
+					  u16 pid,
+					  u8 *continuity_counter)
+{
+	/* the CRC is the last entry in the section */
+	u32 nbytes = 0;
+	u32 crc;
+
+	crc = crc32(0, buf, offset);
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+					    &crc,
+					    CRC_SIZE_IN_BYTES,
+					    offset,
+					    pid,
+					    false,
+					    continuity_counter,
+					    true);
+
+
+	return nbytes;
+}
+
+static struct dvb_dummy_desc*
+dvb_dummy_fe_mpeg_descriptor_init(struct dvb_dummy_desc *head,
+				  u8 type,
+				  u8 length)
+{
+	struct dvb_dummy_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+
+	desc->type = type;
+	desc->length = length;
+	desc->data = kzalloc(length, GFP_KERNEL);
+
+	if (head) {
+		while (head->next)
+			head = head->next;
+
+		head->next = desc;
+	}
+
+	return desc;
+}
+
+static void
+dvb_dummy_fe_mpeg_descriptor_destroy(struct dvb_dummy_desc *desc)
+{
+
+	struct dvb_dummy_desc *curr = desc;
+	struct dvb_dummy_desc *tmp = NULL;
+
+	while (curr) {
+		tmp = curr;
+		curr = curr->next;
+		kfree(tmp->data);
+		kfree(tmp);
+	}
+
+}
+
+static u32
+dvb_dummy_fe_mpeg_descriptor_compute_length(struct dvb_dummy_desc *desc)
+{
+	u32 length = 0;
+
+	if (!desc)
+		return 0;
+
+	while (desc) {
+		length += desc->length;
+		desc = desc->next;
+	}
+
+	return length;
+}
+
+static void
+dvb_dummy_fe_mpeg_descriptor_assign(struct dvb_dummy_desc *desc,
+				    struct dvb_dummy_desc *desc_ptr,
+				    u16 *desc_length)
+{
+
+	if (desc_ptr)
+		/* clean the old data */
+		dvb_dummy_fe_mpeg_descriptor_destroy(desc_ptr);
+
+	*desc_length = dvb_dummy_fe_mpeg_descriptor_compute_length(desc);
+	desc_ptr = desc;
+}
+
+static u32 dvb_dummy_fe_mpeg_desc_write_into(char *buf,
+					     u32 offset,
+					     struct dvb_dummy_desc *desc,
+					     u16 pid,
+					     u8 *continuity_counter)
+{
+	u32 nbytes = 0; /* the number of bytes written by this function */
+
+	/* u8 type + u8 length */
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+					    desc,
+					    16,
+					    offset,
+					    pid,
+					    false,
+					    continuity_counter,
+					    false);
+
+	/* move 'from' pointer to point to u8 data[] */
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+				desc + nbytes + sizeof(struct dvb_dummy_desc *),
+				desc->length,
+				offset + nbytes,
+				pid,
+				false,
+				continuity_counter,
+				false);
+
+	return nbytes;
+}
+
+static u32
+dvb_dummy_fe_mpeg_table_header_write_into(char *buf,
+					  u32 offset,
+					  struct dvb_dummy_table_header *h,
+					  u16 pid,
+					  u8 *continuity_counter)
+{
+	/* the number of bytes written by this function */
+	u32 nbytes = 0;
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+					h,
+					sizeof(struct dvb_dummy_table_header),
+					offset,
+					pid,
+					true,
+					continuity_counter,
+					false);
+
+	return nbytes;
+}
+
+static u16
+dvb_dummy_fe_mpeg_pat_table_compute_section_length
+(struct dvb_dummy_table_pat *pat)
+{
+	/* see ISO/IEC 13818-1 : 2000 p.43 */
+	u16 length = 0;
+	u32 i;
+
+	length += 40;
+
+	for (i = 0; i < pat->programs; ++i)
+		length += 32;
+
+	length += CRC_SIZE_IN_BYTES;
+
+	WARN_ON(length > 1021);
+	return length;
+}
+
+static u16
+dvb_dummy_fe_mpeg_pmt_table_compute_section_length
+(struct dvb_dummy_table_pmt *pmt)
+{
+	/* see ISO/IEC 13818-1 : 2000 p.46 */
+	u16 length = 0;
+	struct dvb_dummy_table_pmt_stream *s = pmt->stream;
+
+	length += 72;
+	length += /* do not fail if 'desc_length' has not been computed yet */
+		dvb_dummy_fe_mpeg_descriptor_compute_length(pmt->descriptor);
+	length += pmt->desc_length;
+
+	while (s) {
+		length += 40;
+		length +=
+			dvb_dummy_fe_mpeg_descriptor_compute_length
+			(s->descriptor);
+		s = s->next;
+	}
+
+	length += CRC_SIZE_IN_BYTES;
+
+	WARN_ON(length > 1021);
+	return length;
+}
+
+static u16
+dvb_dummy_fe_mpeg_sdt_table_compute_section_length
+(struct dvb_dummy_table_sdt *sdt)
+{
+	/* see ETSI EN 300 468 V 1.10.1 p.24 */
+	u16 length = 0;
+	struct dvb_dummy_table_sdt_service *s = sdt->service;
+
+	length += 64;
+	while (s) {
+		length += 40;
+		length +=
+		/* do not fail if 'desc_length' has not been computed yet */
+			dvb_dummy_fe_mpeg_descriptor_compute_length
+			(s->descriptor);
+	}
+
+	length += CRC_SIZE_IN_BYTES;
+
+	WARN_ON(length > 1021);
+	return length;
+}
+
+static struct dvb_dummy_table_pat_program*
+dvb_dummy_fe_mpeg_pat_program_init(struct dvb_dummy_table_pat_program *head,
+				   u16 service_id,
+				   u16 pid)
+{
+
+	struct dvb_dummy_table_pat_program *program;
+
+	program = kzalloc(sizeof(struct dvb_dummy_table_pat_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;
+}
+
+static void
+dvb_dummy_fe_mpeg_pat_program_destroy(struct dvb_dummy_table_pat_program *p)
+{
+	struct dvb_dummy_table_pat_program *curr = p;
+	struct dvb_dummy_table_pat_program *tmp = NULL;
+
+	while (curr) {
+		tmp = curr;
+		curr = curr->next;
+		kfree(tmp);
+	}
+}
+
+static void
+dvb_dummy_fe_mpeg_pat_program_assign(struct dvb_dummy_table_pat *pat,
+				     struct dvb_dummy_table_pat_program *p)
+{
+	u16 program_count = 0;
+	struct dvb_dummy_table_pat_program *program = p;
+
+	if (pat->program)
+		dvb_dummy_fe_mpeg_pat_program_destroy(pat->program);
+
+	while (program) {
+		++program_count;
+		program = program->next;
+	}
+
+	pat->programs = program_count;
+
+	/* Recompute section length */
+	pat->header.section_length =
+		dvb_dummy_fe_mpeg_pat_table_compute_section_length(pat);
+
+	pat->program = p;
+}
+
+static struct dvb_dummy_table_pat_program*
+dvb_dummy_fe_mpeg_pat_program_concat_into_new
+(struct dvb_dummy_channel *channels)
+{
+	u32 i;
+	struct dvb_dummy_table_pat_program *curr = NULL;
+	struct dvb_dummy_table_pat_program *head = NULL;
+	struct dvb_dummy_table_pat_program *tail = NULL;
+
+	for (i = 0; i < NUM_CHANNELS; ++i) {
+		curr = channels[i].program;
+
+		if (!curr)
+			continue;
+
+		while (curr->next) {
+			tail = dvb_dummy_fe_mpeg_pat_program_init
+			       (tail, curr->service_id, curr->pid);
+
+			if (!head)
+				head = tail;
+
+			curr = curr->next;
+		}
+	}
+
+	return head;
+}
+
+
+static void dvb_dummy_fe_mpeg_pat_table_init(struct dvb_dummy_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;
+
+	if (update_version_num)
+	/* ETSI 300 468: indicates changes in the TS described by this table*/
+		++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 =
+		dvb_dummy_fe_mpeg_pat_table_compute_section_length(pat);
+
+}
+
+static u32 dvb_dummy_fe_mpeg_pat_write_into(char *buf,
+					    u32 offset,
+					    struct dvb_dummy_table_pat *pat)
+{
+
+	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 dvb_dummy_table_pat_program *p = pat->program;
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_table_header_write_into(buf,
+						  offset,
+						  &pat->header,
+						  pat_pid,
+						  &continuity_counter);
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+				pat + sizeof(struct dvb_dummy_table_header),
+				sizeof(pat->programs),
+				offset + nbytes,
+				pat_pid,
+				false,
+				&continuity_counter,
+				false);
+
+
+	while (p) {
+		/* skip the pointer */
+		nbytes +=
+			dvb_dummy_fe_mpeg_ts_psi_write_into
+				(buf,
+				 p,
+				 sizeof(*p) -
+				 sizeof(struct dvb_dummy_table_pat_program *),
+				 offset + nbytes,
+				 pat_pid,
+				 false,
+				 &continuity_counter,
+				 false);
+
+		p = p->next;
+	}
+
+	nbytes += table_section_crc32_write_into(buf,
+						 offset + nbytes,
+						 pat_pid,
+						 &continuity_counter);
+	return nbytes;
+}
+
+static void
+dvb_dummy_fe_mpeg_pat_table_destroy(struct dvb_dummy_table_pat *p)
+{
+	dvb_dummy_fe_mpeg_pat_program_destroy(p->program);
+}
+
+static struct dvb_dummy_table_pmt_stream*
+dvb_dummy_fe_mpeg_pmt_stream_init(struct dvb_dummy_table_pmt_stream *head,
+				  enum dvb_dummy_stream_types stream_type,
+				  u16 es_pid)
+{
+
+	struct dvb_dummy_table_pmt_stream *stream =
+		kzalloc(sizeof(struct dvb_dummy_table_pmt_stream),
+			GFP_KERNEL);
+
+	stream->type = stream_type;
+	stream->elementary_pid = es_pid;
+	stream->reserved = 0x07;
+
+	stream->desc_length =
+		dvb_dummy_fe_mpeg_descriptor_compute_length(stream->descriptor);
+
+	stream->zero = 0x0;
+	stream->reserved2 = 0x0F;
+
+	if (head) {
+		while (head->next)
+			head = head->next;
+
+		head->next = stream;
+	}
+
+	return stream;
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_stream_destroy(struct dvb_dummy_table_pmt_stream *s)
+{
+	struct dvb_dummy_table_pmt_stream *curr_stream = s;
+	struct dvb_dummy_table_pmt_stream *tmp_stream = NULL;
+
+	while (curr_stream) {
+		tmp_stream = curr_stream;
+		curr_stream = curr_stream->next;
+		kfree(tmp_stream);
+	}
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_stream_assign(struct dvb_dummy_table_pmt *pmt,
+				    struct dvb_dummy_table_pmt_stream *s)
+{
+	u16 stream_count = 0;
+	u16 table_descriptor_count = 0;
+	struct dvb_dummy_table_pmt_stream *stream = s;
+	struct dvb_dummy_desc *desc = s->descriptor;
+
+	if (pmt->stream)
+		dvb_dummy_fe_mpeg_pmt_stream_destroy(pmt->stream);
+
+	while (stream) {
+		++stream_count;
+		stream = stream->next;
+	}
+
+	while (desc) {
+		++table_descriptor_count;
+		desc = desc->next;
+	}
+
+	/* Recompute section length */
+	pmt->header.section_length =
+		dvb_dummy_fe_mpeg_pmt_table_compute_section_length(pmt);
+
+	pmt->stream = s;
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_stream_match_with_sections
+(struct dvb_dummy_channel *channels,
+struct dvb_dummy_table_pmt *sections, u32 nsections)
+{
+	struct dvb_dummy_table_pmt *curr_section = NULL;
+	u32 i, j;
+	bool match;
+
+	for (i = 0; i < NUM_CHANNELS; ++i) {
+
+		for (j = 0; j < nsections; ++j) {
+			curr_section = &sections[j];
+
+			if (!curr_section)
+				continue;
+
+			match = curr_section->header.id ==
+					channels[i].program_num;
+
+			if (match) {
+				dvb_dummy_fe_mpeg_pmt_stream_assign
+				(curr_section, channels[i].streams);
+				break;
+			}
+		}
+	}
+}
+
+static u16
+dvb_dummy_fe_mpeg_pmt_get_pid(struct dvb_dummy_table_pmt *section,
+			      struct dvb_dummy_table_pat *pat)
+{
+	struct dvb_dummy_table_pat_program *program = pat->program;
+
+	while (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
+		 */
+		if (program->service_id == section->header.id)
+			return pat->program->pid;
+
+	return -1;
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_table_init(struct dvb_dummy_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;
+
+	if (update_version_num)
+	/* ETSI 300 468: indicates changes in the TS described by this table*/
+		++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 =
+		dvb_dummy_fe_mpeg_descriptor_compute_length(pmt->descriptor);
+
+	pmt->header.section_length =
+		dvb_dummy_fe_mpeg_pmt_table_compute_section_length(pmt);
+
+}
+
+static u32 dvb_dummy_fe_mpeg_pmt_write_into(char *buf,
+					    u32 offset,
+					    struct dvb_dummy_table_pmt *pmt,
+					    u16 pid)
+{
+	u32 nbytes = 0; /* the number of bytes written */
+	u8 continuity_counter = 0;
+	struct dvb_dummy_desc *table_descriptor = pmt->descriptor;
+	struct dvb_dummy_table_pmt_stream *stream = pmt->stream;
+	struct dvb_dummy_desc *stream_descriptor = (stream) ?
+						    pmt->stream->descriptor :
+						    NULL;
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_table_header_write_into(buf,
+						  offset,
+						  &pmt->header,
+						  pid,
+						  &continuity_counter);
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+				pmt + sizeof(struct dvb_dummy_table_header *),
+				32,
+				offset + nbytes,
+				pid,
+				false,
+				&continuity_counter,
+				false);
+
+
+	while (table_descriptor) {
+		nbytes +=
+			dvb_dummy_fe_mpeg_desc_write_into(buf,
+							  offset + nbytes,
+							  table_descriptor,
+							  pid,
+							  &continuity_counter);
+
+		table_descriptor = table_descriptor->next;
+	}
+
+	while (stream) {
+		/* u8 type + (2* u16) bitfields */
+		nbytes +=
+		dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+						    stream,
+						    40,
+						    offset + nbytes,
+						    pid,
+						    false,
+						    &continuity_counter,
+						    false);
+
+		while (stream_descriptor) {
+			nbytes +=
+				dvb_dummy_fe_mpeg_desc_write_into(buf,
+							offset + nbytes,
+							stream_descriptor,
+							pid,
+							&continuity_counter);
+
+			stream_descriptor = stream_descriptor->next;
+		}
+
+		stream = stream->next;
+	}
+
+	nbytes += table_section_crc32_write_into(buf,
+						 offset + nbytes,
+						 pid,
+						 &continuity_counter);
+	return nbytes;
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_table_destroy(struct dvb_dummy_table_pmt *pmt)
+{
+
+	struct dvb_dummy_desc *curr_desc = pmt->descriptor;
+	struct dvb_dummy_desc *tmp_desc = NULL;
+
+	while (curr_desc) {
+		tmp_desc = curr_desc;
+		curr_desc = curr_desc->next;
+		dvb_dummy_fe_mpeg_descriptor_destroy(tmp_desc);
+		kfree(tmp_desc);
+	}
+
+	dvb_dummy_fe_mpeg_pmt_stream_destroy(pmt->stream);
+}
+
+static void
+dvb_dummy_fe_mpeg_sdt_table_init(struct dvb_dummy_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;
+
+	if (update_version_num)
+	/* ETSI 300 468: indicates changes in the TS described by this table*/
+		++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 =
+		dvb_dummy_fe_mpeg_sdt_table_compute_section_length(sdt);
+}
+
+static u32 dvb_dummy_fe_mpeg_sdt_write_into(char *buf,
+					    u32 offset,
+					    struct dvb_dummy_table_sdt *sdt)
+{
+	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 dvb_dummy_table_sdt_service *service = sdt->service;
+	struct dvb_dummy_desc *service_desc = (sdt->service) ?
+					       sdt->service->descriptor :
+					       NULL;
+
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_table_header_write_into(buf,
+						  offset,
+						  &sdt->header,
+						  sdt_pid,
+						  &continuity_counter);
+	/* copy u16 network_id + u8 reserved)*/
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+				sdt + sizeof(struct dvb_dummy_table_header),
+				16,
+				offset + nbytes,
+				sdt_pid,
+				false,
+				&continuity_counter,
+				false);
+
+
+	while (service) {
+		/* u16 service_id + u8 + u16 bitfield */
+		nbytes +=
+		dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+						    service,
+						    40,
+						    offset + nbytes,
+						    sdt_pid,
+						    false,
+						    &continuity_counter,
+						    false);
+
+		while (service_desc) {
+			nbytes +=
+				dvb_dummy_fe_mpeg_desc_write_into(buf,
+							  offset + nbytes,
+							  service_desc,
+							  sdt_pid,
+							  &continuity_counter);
+			service_desc = service_desc->next;
+		}
+
+		service = service->next;
+	}
+
+	nbytes += table_section_crc32_write_into(buf,
+						 offset + nbytes,
+						 sdt_pid,
+						 &continuity_counter);
+	return nbytes;
+}
+
+static void
+dvb_dummy_fe_mpeg_sdt_table_destroy(struct dvb_dummy_table_sdt *sdt)
+{
+	struct dvb_dummy_table_sdt_service *curr_service = sdt->service;
+	struct dvb_dummy_table_sdt_service *tmp_service = NULL;
+	struct dvb_dummy_desc *curr_desc = (sdt->service) ?
+					   sdt->service->descriptor : NULL;
+	struct dvb_dummy_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;
+			dvb_dummy_fe_mpeg_descriptor_destroy(tmp_desc);
+			kfree(tmp_desc);
+		}
+
+		/* then clear the current service */
+		tmp_service = curr_service;
+		curr_service = curr_service->next;
+		kfree(tmp_service);
+	}
+}
+
+static struct dvb_dummy_table_sdt_service*
+dvb_dummy_fe_mpeg_sdt_service_init(struct dvb_dummy_table_sdt_service *head,
+				   u16 service_id)
+{
+	struct dvb_dummy_table_sdt_service *service;
+
+	service = kzalloc(sizeof(struct dvb_dummy_table_sdt_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;
+
+}
+
+static void
+dvb_dummy_fe_mpeg_sdt_service_destroy
+(struct dvb_dummy_table_sdt_service *service)
+{
+
+	struct dvb_dummy_table_sdt_service *curr = service;
+	struct dvb_dummy_table_sdt_service *tmp = NULL;
+
+	while (curr) {
+		tmp = curr;
+		curr = curr->next;
+		kfree(tmp);
+	}
+}
+
+static void
+dvb_dummy_fe_mpeg_sdt_service_assign
+(struct dvb_dummy_table_sdt *sdt,
+struct dvb_dummy_table_sdt_service *service)
+{
+	if (sdt->service)
+		/* clean up old services */
+		dvb_dummy_fe_mpeg_sdt_service_destroy(sdt->service);
+
+	sdt->service = service;
+
+	sdt->header.section_length =
+		dvb_dummy_fe_mpeg_sdt_table_compute_section_length(sdt);
+}
+
+static struct dvb_dummy_table_sdt_service*
+dvb_dummy_fe_mpeg_sdt_service_concat_into_new
+(struct dvb_dummy_channel *channels)
+{
+	u32 i = 0;
+	struct dvb_dummy_table_sdt_service *curr = NULL;
+	struct dvb_dummy_table_sdt_service *head = NULL;
+	struct dvb_dummy_table_sdt_service *tail = NULL;
+
+	for (; i < NUM_CHANNELS; ++i) {
+		curr = channels[i].service;
+
+		if (!curr)
+			continue;
+
+		while (curr->next) {
+			tail = dvb_dummy_fe_mpeg_sdt_service_init(tail,
+				channels[i].service->service_id);
+
+			if (!head)
+				head = tail;
+
+			curr = curr->next;
+		}
+	}
+
+	return head;
+}
+
+static void dvb_dummy_fe_init_channels(struct dvb_dummy_channel *channels)
+{
+
+	const u16 pcm_audio_channel_service_id = 0x880;
+	const u16 pcm_audio_channel_program_num = 0x880;
+	const u16 pcm_audio_channel_program_pid = 0x101; /* packet id for PMT*/
+	const u16 pcm_audio_channel_audio_stream_id = 0x111;
+
+	struct dvb_dummy_channel pcm_audio_channel;
+	struct dvb_dummy_table_sdt_service *pcm_audio_channel_service;
+	struct dvb_dummy_desc_service *pcm_audio_channel_service_descriptor;
+	u16 desc_length;
+
+	channels = kcalloc(NUM_CHANNELS,
+			   sizeof(struct dvb_dummy_channel),
+			   GFP_KERNEL);
+
+
+	pcm_audio_channel_service =
+		dvb_dummy_fe_mpeg_sdt_service_init
+			(NULL, pcm_audio_channel_service_id);
+
+	pcm_audio_channel_service_descriptor =
+		(struct dvb_dummy_desc_service *)
+		dvb_dummy_fe_mpeg_descriptor_init(NULL,
+					SERVICE_DESCRIPTOR,
+					sizeof(struct dvb_dummy_desc_service));
+
+	pcm_audio_channel_service_descriptor->name = "Sine Wave PCM Audio";
+	pcm_audio_channel_service_descriptor->service_type
+		= DIGITAL_TELEVISION_SERVICE;
+	pcm_audio_channel_service_descriptor->length = 8 /* u8 service_type */
+		+ strlen(pcm_audio_channel_service_descriptor->name)
+		+ strlen(pcm_audio_channel_service_descriptor->name_emph)
+		+ strlen(pcm_audio_channel_service_descriptor->provider)
+		+ strlen(pcm_audio_channel_service_descriptor->provider_emph);
+
+	dvb_dummy_fe_mpeg_descriptor_assign(
+					(struct dvb_dummy_desc *)
+					pcm_audio_channel_service_descriptor,
+					(struct dvb_dummy_desc *)
+					pcm_audio_channel_service->descriptor,
+					&desc_length);
+
+	pcm_audio_channel_service->desc_length = desc_length;
+
+	pcm_audio_channel.transport_stream_id = TRANSPORT_STREAM_ID;
+
+	pcm_audio_channel.program =
+		dvb_dummy_fe_mpeg_pat_program_init(NULL,
+					pcm_audio_channel_service_id,
+					pcm_audio_channel_program_pid);
+
+	pcm_audio_channel.program_num = pcm_audio_channel_program_num;
+	pcm_audio_channel.streams =
+		dvb_dummy_fe_mpeg_pmt_stream_init(NULL,
+					ISO_IEC_13818_3_Audio,
+					pcm_audio_channel_audio_stream_id);
+
+	memcpy(channels, &pcm_audio_channel, sizeof(struct dvb_dummy_channel));
+}
+
+static void dvb_dummy_fe_channels_destroy(struct dvb_dummy_channel *channels)
+{
+	u32 i;
+	struct dvb_dummy_channel *curr;
+
+	for (i = 0; i < NUM_CHANNELS; ++i) {
+		curr = &channels[i];
+		dvb_dummy_fe_mpeg_sdt_service_destroy(curr->service);
+		dvb_dummy_fe_mpeg_pat_program_destroy(curr->program);
+		dvb_dummy_fe_mpeg_pmt_stream_destroy(curr->streams);
+		kfree(curr);
+	}
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_create_section_for_each_pat_entry
+(struct dvb_dummy_table_pat *pat, struct dvb_dummy_table_pmt *sections)
+{
+	/* PMTs contain information about programs. For each program,
+	 * there is one PMT
+	 */
+
+	struct dvb_dummy_table_pat_program *program = pat->program;
+	u32 i = 0;
+
+	while (program) {
+		dvb_dummy_fe_mpeg_pmt_table_init(&sections[i],
+						 false,
+						 sections[i].header.id,
+						 0);
+
+		++i;
+		program = program->next;
+	}
+}
+
+static void dvb_dummy_fe_thread_mpeg_ts_tick(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
+	const unsigned int SLEEP_MSECS = 10;
+	u32 ticks = 0;
+	u32 i;
+	char *buf = kzalloc(DMX_BUF_LEN, GFP_KERNEL);
+	u32 buffer_offset;
+
+	struct dvb_dummy_table_pat pat = {0};
+	struct dvb_dummy_table_sdt sdt = {0};
+
+	struct dvb_dummy_table_pmt *pmt_sections;
+
+	struct dvb_dummy_table_pat_program *programs = NULL;
+	struct dvb_dummy_table_sdt_service *services = NULL;
+
+	bool update_version_num = false;
+	u16 pmt_pid;
+
+	dvb_dummy_fe_init_channels(state->channels);
+
+	programs =
+		dvb_dummy_fe_mpeg_pat_program_concat_into_new(state->channels);
+	services =
+		dvb_dummy_fe_mpeg_sdt_service_concat_into_new(state->channels);
+
+	/* assemble all programs and assign to PAT */
+	dvb_dummy_fe_mpeg_pat_program_assign(&pat, programs);
+
+	/* assemble all services and assign to SDT */
+	dvb_dummy_fe_mpeg_sdt_service_assign(&sdt, services);
+
+	/* a section for each program_id */
+	pmt_sections = kcalloc(pat.programs,
+			       sizeof(struct dvb_dummy_table_pmt),
+			       GFP_KERNEL);
+
+	dvb_dummy_fe_mpeg_pmt_create_section_for_each_pat_entry(&pat,
+								pmt_sections);
+
+	dvb_dummy_fe_mpeg_pmt_stream_match_with_sections(state->channels,
+							 pmt_sections,
+							 pat.programs);
+
+	dvb_dummy_fe_mpeg_pat_table_init(&pat,
+					 update_version_num,
+					 TRANSPORT_STREAM_ID);
+	dvb_dummy_fe_mpeg_sdt_table_init(&sdt,
+				    update_version_num,
+				    TRANSPORT_STREAM_ID);
+	while (true) {
+
+		memset(buf, 0, DMX_BUF_LEN);
+		buffer_offset = 0;
+
+		if (!ticks || (ticks % 50) == 0) {
+			/* push PSI packets into the buffer */
+
+			buffer_offset +=
+				dvb_dummy_fe_mpeg_pat_write_into(buf,
+								 buffer_offset,
+								 &pat);
+			for (i = 0; i < pat.programs; ++i) {
+
+				pmt_pid =
+				dvb_dummy_fe_mpeg_pmt_get_pid(&pmt_sections[i],
+							      &pat);
+
+				WARN_ON(pmt_pid < 0); /* not found */
+
+				buffer_offset +=
+					/* write each section into buffer */
+					dvb_dummy_fe_mpeg_pmt_write_into(buf,
+							buffer_offset,
+							&pmt_sections[i],
+							pmt_pid);
+			}
+
+			buffer_offset +=
+				dvb_dummy_fe_mpeg_sdt_write_into(buf,
+								 buffer_offset,
+								 &sdt);
+
+
+			WARN_ON(buffer_offset > DMX_BUF_LEN); /* overflow */
+			msleep_interruptible(SLEEP_MSECS);
+		}
+	}
+
+	dvb_dummy_fe_mpeg_pat_table_destroy(&pat);
+	dvb_dummy_fe_mpeg_sdt_table_destroy(&sdt);
+
+	for (i = 0; i < pat.programs; ++i) {
+		/* destroy all PMT sections */
+		dvb_dummy_fe_mpeg_pmt_table_destroy(&pmt_sections[i]);
+		kfree(&pmt_sections[i]);
+	}
+
+	dvb_dummy_fe_channels_destroy(state->channels);
+	kfree(buf);
+}
+
 struct dvb_dummy_fe_cnr_to_qual_s {
 	/* attempt to use the same values as libdvbv5 */
 	u32 modulation;
@@ -75,17 +1472,7 @@ static struct dvb_dummy_fe_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
 	{ QAM_64, FEC_7_8, 22.0, 24.0},
 };
 
-struct dvb_dummy_fe_config {
-	/* probability of losing the lock due to low snr */
-	u8 drop_tslock_probability_on_low_snr;
-};
 
-struct dvb_dummy_fe_state {
-	struct dvb_frontend frontend;
-	struct dvb_dummy_fe_config config;
-	struct delayed_work poll_snr;
-	enum fe_status status;
-};
 
 void poll_snr_handler(struct work_struct *work)
 {
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [Linux-kernel-mentees] [RFC 3/3] media: dvb_dummy_fe.c: write PSI information into DMX buffer
@ 2020-03-18  6:00   ` Daniel W. S. Almeida
  0 siblings, 0 replies; 21+ messages in thread
From: Daniel W. S. Almeida @ 2020-03-18  6:00 UTC (permalink / raw)
  To: mchehab, sean, kstewart, allison, tglx
  Cc: linux-kernel-mentees, Daniel W. S. Almeida, linux-media

From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>

Periodically feed the demux with PSI packets. This is needed so userspace
apps can retrieve information about the Transport Stream and eventually
tune into a (dummy) channel.

Currently this commit adds support for working with 3 PSI tables:
PAT, PMT and SDT. A dummy service with a dummy program is hardcoded
in the driver code.
---
 drivers/media/dvb-frontends/dvb_dummy_fe.c | 1409 +++++++++++++++++++-
 1 file changed, 1398 insertions(+), 11 deletions(-)

diff --git a/drivers/media/dvb-frontends/dvb_dummy_fe.c b/drivers/media/dvb-frontends/dvb_dummy_fe.c
index 726c964a523d..10b1bd22c15d 100644
--- a/drivers/media/dvb-frontends/dvb_dummy_fe.c
+++ b/drivers/media/dvb-frontends/dvb_dummy_fe.c
@@ -11,11 +11,1408 @@
 #include <linux/slab.h>
 #include <linux/workqueue.h>
 #include <linux/random.h>
+#include <linux/crc32.h>
+#include <linux/string.h>
 
 #include <media/dvb_frontend.h>
-#include "dvb_dummy_fe.h"
 
 
+#define CRC_SIZE_IN_BYTES 32
+#define NUM_CHANNELS 1
+#define TRANSPORT_STREAM_ID 0x744 /* a single stream */
+#define TS_SYNC_BYTE 0x47
+#define TS_PACKET_LEN 188
+#define TS_PAYLOAD_LEN 184
+#define DMX_BUF_LEN (20 * TS_PACKET_LEN)
+
+
+enum dvb_dummy_descriptors {
+	SERVICE_DESCRIPTOR = 0x48,
+};
+
+enum dvb_dummy_stream_types {
+	ISO_IEC_13818_3_Audio = 0x4,
+};
+
+struct dvb_dummy_channel {
+	u16 transport_stream_id;
+	struct dvb_dummy_table_sdt_service *service;
+	u16 program_num;
+	/* a single program with one or more streams associated with it */
+	struct dvb_dummy_table_pat_program *program;
+	struct dvb_dummy_table_pmt_stream *streams;
+};
+
+struct dvb_dummy_desc {
+	u8 type;
+	u8 length;
+	struct dvb_dummy_desc *next;
+	u8 *data;
+} __packed;
+
+struct dvb_dummy_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 dvb_dummy_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 dvb_dummy_table_pat_program {
+	u16 service_id;
+	union {
+		u16 bitfield;
+		struct {
+			u16 pid:13;
+			u8  reserved:3;
+		} __packed;
+	} __packed;
+	struct dvb_dummy_table_pat_program *next;
+} __packed;
+
+struct dvb_dummy_table_pat {
+	struct dvb_dummy_table_header header;
+	u16 programs;
+	struct dvb_dummy_table_pat_program *program;
+} __packed;
+
+struct dvb_dummy_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 dvb_dummy_desc *descriptor;
+	struct dvb_dummy_table_sdt_service *next;
+} __packed;
+
+struct dvb_dummy_table_sdt {
+	struct dvb_dummy_table_header header;
+	u16 network_id;
+	u8  reserved;
+	struct dvb_dummy_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 dvb_dummy_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 dvb_dummy_desc *descriptor;
+	struct dvb_dummy_table_pmt_stream *next;
+} __packed;
+
+struct dvb_dummy_table_pmt {
+	struct dvb_dummy_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 dvb_dummy_desc *descriptor;
+	struct dvb_dummy_table_pmt_stream *stream;
+} __packed;
+
+struct dvb_dummy_mpeg_ts_adaption {
+	u8 length;
+	struct {
+		u8 extension:1;
+		u8 private_data:1;
+		u8 splicing_point:1;
+		u8 OPCR:1;
+		u8 PCR:1;
+		u8 priority:1;
+		u8 random_access:1;
+		u8 discontinued:1;
+	} __packed;
+	u8 data[];
+} __packed;
+
+
+struct dvb_dummy_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 dvb_dummy_mpeg_ts_adaption adaption[];
+} __packed;
+
+struct dvb_dummy_fe_config {
+	/* probability of losing the lock due to low snr */
+	u8 drop_tslock_probability_on_low_snr;
+};
+
+struct dvb_dummy_fe_state {
+	struct dvb_frontend frontend;
+	struct dvb_dummy_fe_config config;
+	struct delayed_work poll_snr;
+	enum fe_status status;
+	struct dvb_dummy_channel *channels;
+};
+
+static u32 dvb_dummy_fe_mpeg_ts_psi_write_stuffing(void *to, u32 len)
+{
+	memset(to, 0xFF, len);
+	return len;
+}
+
+static u32 dvb_dummy_fe_mpeg_ts_psi_write_into(void *to,
+					    const void *from,
+					    size_t len,
+					    u32 offset,
+					    u16 pid,
+					    bool new_psi_section,
+					    u8 *continuity_counter,
+					    bool is_crc)
+{
+	/*   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 = (offset % TS_PACKET_LEN);
+	bool aligned = nbytes_past_boundary == 0;
+	bool split = len > TS_PAYLOAD_LEN;
+	u32 payload_write_len = (split) ? TS_PAYLOAD_LEN : len;
+
+	struct dvb_dummy_mpeg_ts ts_header = {0};
+
+	u32 nbytes = 0; /* number of bytes written by this function */
+
+	if (new_psi_section && !aligned) {
+		/* must pad the buffer with the complement to get a
+		 * multiple of 188
+		 */
+		nbytes +=
+			dvb_dummy_fe_mpeg_ts_psi_write_stuffing(
+						to + offset + nbytes,
+						TS_PACKET_LEN -
+						nbytes_past_boundary);
+
+		/* 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 = pid;
+		ts_header.priority = 0;
+		ts_header.scrambling = 0; /* not scrambled */
+		ts_header.continuity_counter = *continuity_counter;
+		ts_header.payload_start = 0; /* no adaption for now */
+
+
+		/* copy the header minus the adaption pointer*/
+		memcpy(to + offset + nbytes,
+		&ts_header,
+		sizeof(ts_header) -
+		sizeof(struct dvb_dummy_mpeg_ts_adaption *));
+
+		nbytes +=
+		sizeof(ts_header) -
+		sizeof(struct dvb_dummy_mpeg_ts_adaption *);
+	}
+
+	if (new_psi_section) {
+		/* write the pointer_field in the first byte of the payload */
+		memset(to + offset + nbytes, 0x0, 1);
+		nbytes += 1;
+		--payload_write_len; /* one byte was used by the pointer field*/
+	}
+
+	/* write as much of the payload as we possibly can */
+	memcpy(to + offset + nbytes, from, payload_write_len);
+	nbytes += payload_write_len;
+
+	if (split) {
+		/* next TS packet keeps the same PID, but increments the
+		 * counter
+		 */
+		++(*continuity_counter);
+		/* 'nbytes' written from a total of 'len' requested*/
+		len -= nbytes;
+		/* recursively write the rest of the data until we do not
+		 * need to split it anymore
+		 */
+		nbytes +=
+		dvb_dummy_fe_mpeg_ts_psi_write_into(to,
+						    from + nbytes,
+						    len,
+						    offset + nbytes,
+						    pid,
+						    false,
+						    continuity_counter,
+						    is_crc);
+	}
+
+	if (is_crc)
+		/* as the CRC is last in the section, stuff the rest of the
+		 * packet if there is any remaining space in there
+		 */
+
+		nbytes +=
+		dvb_dummy_fe_mpeg_ts_psi_write_stuffing(to + nbytes,
+							TS_PAYLOAD_LEN -
+							nbytes);
+
+	return nbytes;
+}
+
+static u32 table_section_crc32_write_into(char *buf,
+					  u32 offset,
+					  u16 pid,
+					  u8 *continuity_counter)
+{
+	/* the CRC is the last entry in the section */
+	u32 nbytes = 0;
+	u32 crc;
+
+	crc = crc32(0, buf, offset);
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+					    &crc,
+					    CRC_SIZE_IN_BYTES,
+					    offset,
+					    pid,
+					    false,
+					    continuity_counter,
+					    true);
+
+
+	return nbytes;
+}
+
+static struct dvb_dummy_desc*
+dvb_dummy_fe_mpeg_descriptor_init(struct dvb_dummy_desc *head,
+				  u8 type,
+				  u8 length)
+{
+	struct dvb_dummy_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+
+	desc->type = type;
+	desc->length = length;
+	desc->data = kzalloc(length, GFP_KERNEL);
+
+	if (head) {
+		while (head->next)
+			head = head->next;
+
+		head->next = desc;
+	}
+
+	return desc;
+}
+
+static void
+dvb_dummy_fe_mpeg_descriptor_destroy(struct dvb_dummy_desc *desc)
+{
+
+	struct dvb_dummy_desc *curr = desc;
+	struct dvb_dummy_desc *tmp = NULL;
+
+	while (curr) {
+		tmp = curr;
+		curr = curr->next;
+		kfree(tmp->data);
+		kfree(tmp);
+	}
+
+}
+
+static u32
+dvb_dummy_fe_mpeg_descriptor_compute_length(struct dvb_dummy_desc *desc)
+{
+	u32 length = 0;
+
+	if (!desc)
+		return 0;
+
+	while (desc) {
+		length += desc->length;
+		desc = desc->next;
+	}
+
+	return length;
+}
+
+static void
+dvb_dummy_fe_mpeg_descriptor_assign(struct dvb_dummy_desc *desc,
+				    struct dvb_dummy_desc *desc_ptr,
+				    u16 *desc_length)
+{
+
+	if (desc_ptr)
+		/* clean the old data */
+		dvb_dummy_fe_mpeg_descriptor_destroy(desc_ptr);
+
+	*desc_length = dvb_dummy_fe_mpeg_descriptor_compute_length(desc);
+	desc_ptr = desc;
+}
+
+static u32 dvb_dummy_fe_mpeg_desc_write_into(char *buf,
+					     u32 offset,
+					     struct dvb_dummy_desc *desc,
+					     u16 pid,
+					     u8 *continuity_counter)
+{
+	u32 nbytes = 0; /* the number of bytes written by this function */
+
+	/* u8 type + u8 length */
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+					    desc,
+					    16,
+					    offset,
+					    pid,
+					    false,
+					    continuity_counter,
+					    false);
+
+	/* move 'from' pointer to point to u8 data[] */
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+				desc + nbytes + sizeof(struct dvb_dummy_desc *),
+				desc->length,
+				offset + nbytes,
+				pid,
+				false,
+				continuity_counter,
+				false);
+
+	return nbytes;
+}
+
+static u32
+dvb_dummy_fe_mpeg_table_header_write_into(char *buf,
+					  u32 offset,
+					  struct dvb_dummy_table_header *h,
+					  u16 pid,
+					  u8 *continuity_counter)
+{
+	/* the number of bytes written by this function */
+	u32 nbytes = 0;
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+					h,
+					sizeof(struct dvb_dummy_table_header),
+					offset,
+					pid,
+					true,
+					continuity_counter,
+					false);
+
+	return nbytes;
+}
+
+static u16
+dvb_dummy_fe_mpeg_pat_table_compute_section_length
+(struct dvb_dummy_table_pat *pat)
+{
+	/* see ISO/IEC 13818-1 : 2000 p.43 */
+	u16 length = 0;
+	u32 i;
+
+	length += 40;
+
+	for (i = 0; i < pat->programs; ++i)
+		length += 32;
+
+	length += CRC_SIZE_IN_BYTES;
+
+	WARN_ON(length > 1021);
+	return length;
+}
+
+static u16
+dvb_dummy_fe_mpeg_pmt_table_compute_section_length
+(struct dvb_dummy_table_pmt *pmt)
+{
+	/* see ISO/IEC 13818-1 : 2000 p.46 */
+	u16 length = 0;
+	struct dvb_dummy_table_pmt_stream *s = pmt->stream;
+
+	length += 72;
+	length += /* do not fail if 'desc_length' has not been computed yet */
+		dvb_dummy_fe_mpeg_descriptor_compute_length(pmt->descriptor);
+	length += pmt->desc_length;
+
+	while (s) {
+		length += 40;
+		length +=
+			dvb_dummy_fe_mpeg_descriptor_compute_length
+			(s->descriptor);
+		s = s->next;
+	}
+
+	length += CRC_SIZE_IN_BYTES;
+
+	WARN_ON(length > 1021);
+	return length;
+}
+
+static u16
+dvb_dummy_fe_mpeg_sdt_table_compute_section_length
+(struct dvb_dummy_table_sdt *sdt)
+{
+	/* see ETSI EN 300 468 V 1.10.1 p.24 */
+	u16 length = 0;
+	struct dvb_dummy_table_sdt_service *s = sdt->service;
+
+	length += 64;
+	while (s) {
+		length += 40;
+		length +=
+		/* do not fail if 'desc_length' has not been computed yet */
+			dvb_dummy_fe_mpeg_descriptor_compute_length
+			(s->descriptor);
+	}
+
+	length += CRC_SIZE_IN_BYTES;
+
+	WARN_ON(length > 1021);
+	return length;
+}
+
+static struct dvb_dummy_table_pat_program*
+dvb_dummy_fe_mpeg_pat_program_init(struct dvb_dummy_table_pat_program *head,
+				   u16 service_id,
+				   u16 pid)
+{
+
+	struct dvb_dummy_table_pat_program *program;
+
+	program = kzalloc(sizeof(struct dvb_dummy_table_pat_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;
+}
+
+static void
+dvb_dummy_fe_mpeg_pat_program_destroy(struct dvb_dummy_table_pat_program *p)
+{
+	struct dvb_dummy_table_pat_program *curr = p;
+	struct dvb_dummy_table_pat_program *tmp = NULL;
+
+	while (curr) {
+		tmp = curr;
+		curr = curr->next;
+		kfree(tmp);
+	}
+}
+
+static void
+dvb_dummy_fe_mpeg_pat_program_assign(struct dvb_dummy_table_pat *pat,
+				     struct dvb_dummy_table_pat_program *p)
+{
+	u16 program_count = 0;
+	struct dvb_dummy_table_pat_program *program = p;
+
+	if (pat->program)
+		dvb_dummy_fe_mpeg_pat_program_destroy(pat->program);
+
+	while (program) {
+		++program_count;
+		program = program->next;
+	}
+
+	pat->programs = program_count;
+
+	/* Recompute section length */
+	pat->header.section_length =
+		dvb_dummy_fe_mpeg_pat_table_compute_section_length(pat);
+
+	pat->program = p;
+}
+
+static struct dvb_dummy_table_pat_program*
+dvb_dummy_fe_mpeg_pat_program_concat_into_new
+(struct dvb_dummy_channel *channels)
+{
+	u32 i;
+	struct dvb_dummy_table_pat_program *curr = NULL;
+	struct dvb_dummy_table_pat_program *head = NULL;
+	struct dvb_dummy_table_pat_program *tail = NULL;
+
+	for (i = 0; i < NUM_CHANNELS; ++i) {
+		curr = channels[i].program;
+
+		if (!curr)
+			continue;
+
+		while (curr->next) {
+			tail = dvb_dummy_fe_mpeg_pat_program_init
+			       (tail, curr->service_id, curr->pid);
+
+			if (!head)
+				head = tail;
+
+			curr = curr->next;
+		}
+	}
+
+	return head;
+}
+
+
+static void dvb_dummy_fe_mpeg_pat_table_init(struct dvb_dummy_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;
+
+	if (update_version_num)
+	/* ETSI 300 468: indicates changes in the TS described by this table*/
+		++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 =
+		dvb_dummy_fe_mpeg_pat_table_compute_section_length(pat);
+
+}
+
+static u32 dvb_dummy_fe_mpeg_pat_write_into(char *buf,
+					    u32 offset,
+					    struct dvb_dummy_table_pat *pat)
+{
+
+	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 dvb_dummy_table_pat_program *p = pat->program;
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_table_header_write_into(buf,
+						  offset,
+						  &pat->header,
+						  pat_pid,
+						  &continuity_counter);
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+				pat + sizeof(struct dvb_dummy_table_header),
+				sizeof(pat->programs),
+				offset + nbytes,
+				pat_pid,
+				false,
+				&continuity_counter,
+				false);
+
+
+	while (p) {
+		/* skip the pointer */
+		nbytes +=
+			dvb_dummy_fe_mpeg_ts_psi_write_into
+				(buf,
+				 p,
+				 sizeof(*p) -
+				 sizeof(struct dvb_dummy_table_pat_program *),
+				 offset + nbytes,
+				 pat_pid,
+				 false,
+				 &continuity_counter,
+				 false);
+
+		p = p->next;
+	}
+
+	nbytes += table_section_crc32_write_into(buf,
+						 offset + nbytes,
+						 pat_pid,
+						 &continuity_counter);
+	return nbytes;
+}
+
+static void
+dvb_dummy_fe_mpeg_pat_table_destroy(struct dvb_dummy_table_pat *p)
+{
+	dvb_dummy_fe_mpeg_pat_program_destroy(p->program);
+}
+
+static struct dvb_dummy_table_pmt_stream*
+dvb_dummy_fe_mpeg_pmt_stream_init(struct dvb_dummy_table_pmt_stream *head,
+				  enum dvb_dummy_stream_types stream_type,
+				  u16 es_pid)
+{
+
+	struct dvb_dummy_table_pmt_stream *stream =
+		kzalloc(sizeof(struct dvb_dummy_table_pmt_stream),
+			GFP_KERNEL);
+
+	stream->type = stream_type;
+	stream->elementary_pid = es_pid;
+	stream->reserved = 0x07;
+
+	stream->desc_length =
+		dvb_dummy_fe_mpeg_descriptor_compute_length(stream->descriptor);
+
+	stream->zero = 0x0;
+	stream->reserved2 = 0x0F;
+
+	if (head) {
+		while (head->next)
+			head = head->next;
+
+		head->next = stream;
+	}
+
+	return stream;
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_stream_destroy(struct dvb_dummy_table_pmt_stream *s)
+{
+	struct dvb_dummy_table_pmt_stream *curr_stream = s;
+	struct dvb_dummy_table_pmt_stream *tmp_stream = NULL;
+
+	while (curr_stream) {
+		tmp_stream = curr_stream;
+		curr_stream = curr_stream->next;
+		kfree(tmp_stream);
+	}
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_stream_assign(struct dvb_dummy_table_pmt *pmt,
+				    struct dvb_dummy_table_pmt_stream *s)
+{
+	u16 stream_count = 0;
+	u16 table_descriptor_count = 0;
+	struct dvb_dummy_table_pmt_stream *stream = s;
+	struct dvb_dummy_desc *desc = s->descriptor;
+
+	if (pmt->stream)
+		dvb_dummy_fe_mpeg_pmt_stream_destroy(pmt->stream);
+
+	while (stream) {
+		++stream_count;
+		stream = stream->next;
+	}
+
+	while (desc) {
+		++table_descriptor_count;
+		desc = desc->next;
+	}
+
+	/* Recompute section length */
+	pmt->header.section_length =
+		dvb_dummy_fe_mpeg_pmt_table_compute_section_length(pmt);
+
+	pmt->stream = s;
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_stream_match_with_sections
+(struct dvb_dummy_channel *channels,
+struct dvb_dummy_table_pmt *sections, u32 nsections)
+{
+	struct dvb_dummy_table_pmt *curr_section = NULL;
+	u32 i, j;
+	bool match;
+
+	for (i = 0; i < NUM_CHANNELS; ++i) {
+
+		for (j = 0; j < nsections; ++j) {
+			curr_section = &sections[j];
+
+			if (!curr_section)
+				continue;
+
+			match = curr_section->header.id ==
+					channels[i].program_num;
+
+			if (match) {
+				dvb_dummy_fe_mpeg_pmt_stream_assign
+				(curr_section, channels[i].streams);
+				break;
+			}
+		}
+	}
+}
+
+static u16
+dvb_dummy_fe_mpeg_pmt_get_pid(struct dvb_dummy_table_pmt *section,
+			      struct dvb_dummy_table_pat *pat)
+{
+	struct dvb_dummy_table_pat_program *program = pat->program;
+
+	while (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
+		 */
+		if (program->service_id == section->header.id)
+			return pat->program->pid;
+
+	return -1;
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_table_init(struct dvb_dummy_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;
+
+	if (update_version_num)
+	/* ETSI 300 468: indicates changes in the TS described by this table*/
+		++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 =
+		dvb_dummy_fe_mpeg_descriptor_compute_length(pmt->descriptor);
+
+	pmt->header.section_length =
+		dvb_dummy_fe_mpeg_pmt_table_compute_section_length(pmt);
+
+}
+
+static u32 dvb_dummy_fe_mpeg_pmt_write_into(char *buf,
+					    u32 offset,
+					    struct dvb_dummy_table_pmt *pmt,
+					    u16 pid)
+{
+	u32 nbytes = 0; /* the number of bytes written */
+	u8 continuity_counter = 0;
+	struct dvb_dummy_desc *table_descriptor = pmt->descriptor;
+	struct dvb_dummy_table_pmt_stream *stream = pmt->stream;
+	struct dvb_dummy_desc *stream_descriptor = (stream) ?
+						    pmt->stream->descriptor :
+						    NULL;
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_table_header_write_into(buf,
+						  offset,
+						  &pmt->header,
+						  pid,
+						  &continuity_counter);
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+				pmt + sizeof(struct dvb_dummy_table_header *),
+				32,
+				offset + nbytes,
+				pid,
+				false,
+				&continuity_counter,
+				false);
+
+
+	while (table_descriptor) {
+		nbytes +=
+			dvb_dummy_fe_mpeg_desc_write_into(buf,
+							  offset + nbytes,
+							  table_descriptor,
+							  pid,
+							  &continuity_counter);
+
+		table_descriptor = table_descriptor->next;
+	}
+
+	while (stream) {
+		/* u8 type + (2* u16) bitfields */
+		nbytes +=
+		dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+						    stream,
+						    40,
+						    offset + nbytes,
+						    pid,
+						    false,
+						    &continuity_counter,
+						    false);
+
+		while (stream_descriptor) {
+			nbytes +=
+				dvb_dummy_fe_mpeg_desc_write_into(buf,
+							offset + nbytes,
+							stream_descriptor,
+							pid,
+							&continuity_counter);
+
+			stream_descriptor = stream_descriptor->next;
+		}
+
+		stream = stream->next;
+	}
+
+	nbytes += table_section_crc32_write_into(buf,
+						 offset + nbytes,
+						 pid,
+						 &continuity_counter);
+	return nbytes;
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_table_destroy(struct dvb_dummy_table_pmt *pmt)
+{
+
+	struct dvb_dummy_desc *curr_desc = pmt->descriptor;
+	struct dvb_dummy_desc *tmp_desc = NULL;
+
+	while (curr_desc) {
+		tmp_desc = curr_desc;
+		curr_desc = curr_desc->next;
+		dvb_dummy_fe_mpeg_descriptor_destroy(tmp_desc);
+		kfree(tmp_desc);
+	}
+
+	dvb_dummy_fe_mpeg_pmt_stream_destroy(pmt->stream);
+}
+
+static void
+dvb_dummy_fe_mpeg_sdt_table_init(struct dvb_dummy_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;
+
+	if (update_version_num)
+	/* ETSI 300 468: indicates changes in the TS described by this table*/
+		++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 =
+		dvb_dummy_fe_mpeg_sdt_table_compute_section_length(sdt);
+}
+
+static u32 dvb_dummy_fe_mpeg_sdt_write_into(char *buf,
+					    u32 offset,
+					    struct dvb_dummy_table_sdt *sdt)
+{
+	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 dvb_dummy_table_sdt_service *service = sdt->service;
+	struct dvb_dummy_desc *service_desc = (sdt->service) ?
+					       sdt->service->descriptor :
+					       NULL;
+
+
+	nbytes +=
+	dvb_dummy_fe_mpeg_table_header_write_into(buf,
+						  offset,
+						  &sdt->header,
+						  sdt_pid,
+						  &continuity_counter);
+	/* copy u16 network_id + u8 reserved)*/
+	nbytes +=
+	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+				sdt + sizeof(struct dvb_dummy_table_header),
+				16,
+				offset + nbytes,
+				sdt_pid,
+				false,
+				&continuity_counter,
+				false);
+
+
+	while (service) {
+		/* u16 service_id + u8 + u16 bitfield */
+		nbytes +=
+		dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
+						    service,
+						    40,
+						    offset + nbytes,
+						    sdt_pid,
+						    false,
+						    &continuity_counter,
+						    false);
+
+		while (service_desc) {
+			nbytes +=
+				dvb_dummy_fe_mpeg_desc_write_into(buf,
+							  offset + nbytes,
+							  service_desc,
+							  sdt_pid,
+							  &continuity_counter);
+			service_desc = service_desc->next;
+		}
+
+		service = service->next;
+	}
+
+	nbytes += table_section_crc32_write_into(buf,
+						 offset + nbytes,
+						 sdt_pid,
+						 &continuity_counter);
+	return nbytes;
+}
+
+static void
+dvb_dummy_fe_mpeg_sdt_table_destroy(struct dvb_dummy_table_sdt *sdt)
+{
+	struct dvb_dummy_table_sdt_service *curr_service = sdt->service;
+	struct dvb_dummy_table_sdt_service *tmp_service = NULL;
+	struct dvb_dummy_desc *curr_desc = (sdt->service) ?
+					   sdt->service->descriptor : NULL;
+	struct dvb_dummy_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;
+			dvb_dummy_fe_mpeg_descriptor_destroy(tmp_desc);
+			kfree(tmp_desc);
+		}
+
+		/* then clear the current service */
+		tmp_service = curr_service;
+		curr_service = curr_service->next;
+		kfree(tmp_service);
+	}
+}
+
+static struct dvb_dummy_table_sdt_service*
+dvb_dummy_fe_mpeg_sdt_service_init(struct dvb_dummy_table_sdt_service *head,
+				   u16 service_id)
+{
+	struct dvb_dummy_table_sdt_service *service;
+
+	service = kzalloc(sizeof(struct dvb_dummy_table_sdt_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;
+
+}
+
+static void
+dvb_dummy_fe_mpeg_sdt_service_destroy
+(struct dvb_dummy_table_sdt_service *service)
+{
+
+	struct dvb_dummy_table_sdt_service *curr = service;
+	struct dvb_dummy_table_sdt_service *tmp = NULL;
+
+	while (curr) {
+		tmp = curr;
+		curr = curr->next;
+		kfree(tmp);
+	}
+}
+
+static void
+dvb_dummy_fe_mpeg_sdt_service_assign
+(struct dvb_dummy_table_sdt *sdt,
+struct dvb_dummy_table_sdt_service *service)
+{
+	if (sdt->service)
+		/* clean up old services */
+		dvb_dummy_fe_mpeg_sdt_service_destroy(sdt->service);
+
+	sdt->service = service;
+
+	sdt->header.section_length =
+		dvb_dummy_fe_mpeg_sdt_table_compute_section_length(sdt);
+}
+
+static struct dvb_dummy_table_sdt_service*
+dvb_dummy_fe_mpeg_sdt_service_concat_into_new
+(struct dvb_dummy_channel *channels)
+{
+	u32 i = 0;
+	struct dvb_dummy_table_sdt_service *curr = NULL;
+	struct dvb_dummy_table_sdt_service *head = NULL;
+	struct dvb_dummy_table_sdt_service *tail = NULL;
+
+	for (; i < NUM_CHANNELS; ++i) {
+		curr = channels[i].service;
+
+		if (!curr)
+			continue;
+
+		while (curr->next) {
+			tail = dvb_dummy_fe_mpeg_sdt_service_init(tail,
+				channels[i].service->service_id);
+
+			if (!head)
+				head = tail;
+
+			curr = curr->next;
+		}
+	}
+
+	return head;
+}
+
+static void dvb_dummy_fe_init_channels(struct dvb_dummy_channel *channels)
+{
+
+	const u16 pcm_audio_channel_service_id = 0x880;
+	const u16 pcm_audio_channel_program_num = 0x880;
+	const u16 pcm_audio_channel_program_pid = 0x101; /* packet id for PMT*/
+	const u16 pcm_audio_channel_audio_stream_id = 0x111;
+
+	struct dvb_dummy_channel pcm_audio_channel;
+	struct dvb_dummy_table_sdt_service *pcm_audio_channel_service;
+	struct dvb_dummy_desc_service *pcm_audio_channel_service_descriptor;
+	u16 desc_length;
+
+	channels = kcalloc(NUM_CHANNELS,
+			   sizeof(struct dvb_dummy_channel),
+			   GFP_KERNEL);
+
+
+	pcm_audio_channel_service =
+		dvb_dummy_fe_mpeg_sdt_service_init
+			(NULL, pcm_audio_channel_service_id);
+
+	pcm_audio_channel_service_descriptor =
+		(struct dvb_dummy_desc_service *)
+		dvb_dummy_fe_mpeg_descriptor_init(NULL,
+					SERVICE_DESCRIPTOR,
+					sizeof(struct dvb_dummy_desc_service));
+
+	pcm_audio_channel_service_descriptor->name = "Sine Wave PCM Audio";
+	pcm_audio_channel_service_descriptor->service_type
+		= DIGITAL_TELEVISION_SERVICE;
+	pcm_audio_channel_service_descriptor->length = 8 /* u8 service_type */
+		+ strlen(pcm_audio_channel_service_descriptor->name)
+		+ strlen(pcm_audio_channel_service_descriptor->name_emph)
+		+ strlen(pcm_audio_channel_service_descriptor->provider)
+		+ strlen(pcm_audio_channel_service_descriptor->provider_emph);
+
+	dvb_dummy_fe_mpeg_descriptor_assign(
+					(struct dvb_dummy_desc *)
+					pcm_audio_channel_service_descriptor,
+					(struct dvb_dummy_desc *)
+					pcm_audio_channel_service->descriptor,
+					&desc_length);
+
+	pcm_audio_channel_service->desc_length = desc_length;
+
+	pcm_audio_channel.transport_stream_id = TRANSPORT_STREAM_ID;
+
+	pcm_audio_channel.program =
+		dvb_dummy_fe_mpeg_pat_program_init(NULL,
+					pcm_audio_channel_service_id,
+					pcm_audio_channel_program_pid);
+
+	pcm_audio_channel.program_num = pcm_audio_channel_program_num;
+	pcm_audio_channel.streams =
+		dvb_dummy_fe_mpeg_pmt_stream_init(NULL,
+					ISO_IEC_13818_3_Audio,
+					pcm_audio_channel_audio_stream_id);
+
+	memcpy(channels, &pcm_audio_channel, sizeof(struct dvb_dummy_channel));
+}
+
+static void dvb_dummy_fe_channels_destroy(struct dvb_dummy_channel *channels)
+{
+	u32 i;
+	struct dvb_dummy_channel *curr;
+
+	for (i = 0; i < NUM_CHANNELS; ++i) {
+		curr = &channels[i];
+		dvb_dummy_fe_mpeg_sdt_service_destroy(curr->service);
+		dvb_dummy_fe_mpeg_pat_program_destroy(curr->program);
+		dvb_dummy_fe_mpeg_pmt_stream_destroy(curr->streams);
+		kfree(curr);
+	}
+}
+
+static void
+dvb_dummy_fe_mpeg_pmt_create_section_for_each_pat_entry
+(struct dvb_dummy_table_pat *pat, struct dvb_dummy_table_pmt *sections)
+{
+	/* PMTs contain information about programs. For each program,
+	 * there is one PMT
+	 */
+
+	struct dvb_dummy_table_pat_program *program = pat->program;
+	u32 i = 0;
+
+	while (program) {
+		dvb_dummy_fe_mpeg_pmt_table_init(&sections[i],
+						 false,
+						 sections[i].header.id,
+						 0);
+
+		++i;
+		program = program->next;
+	}
+}
+
+static void dvb_dummy_fe_thread_mpeg_ts_tick(struct dvb_frontend *fe)
+{
+	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
+	const unsigned int SLEEP_MSECS = 10;
+	u32 ticks = 0;
+	u32 i;
+	char *buf = kzalloc(DMX_BUF_LEN, GFP_KERNEL);
+	u32 buffer_offset;
+
+	struct dvb_dummy_table_pat pat = {0};
+	struct dvb_dummy_table_sdt sdt = {0};
+
+	struct dvb_dummy_table_pmt *pmt_sections;
+
+	struct dvb_dummy_table_pat_program *programs = NULL;
+	struct dvb_dummy_table_sdt_service *services = NULL;
+
+	bool update_version_num = false;
+	u16 pmt_pid;
+
+	dvb_dummy_fe_init_channels(state->channels);
+
+	programs =
+		dvb_dummy_fe_mpeg_pat_program_concat_into_new(state->channels);
+	services =
+		dvb_dummy_fe_mpeg_sdt_service_concat_into_new(state->channels);
+
+	/* assemble all programs and assign to PAT */
+	dvb_dummy_fe_mpeg_pat_program_assign(&pat, programs);
+
+	/* assemble all services and assign to SDT */
+	dvb_dummy_fe_mpeg_sdt_service_assign(&sdt, services);
+
+	/* a section for each program_id */
+	pmt_sections = kcalloc(pat.programs,
+			       sizeof(struct dvb_dummy_table_pmt),
+			       GFP_KERNEL);
+
+	dvb_dummy_fe_mpeg_pmt_create_section_for_each_pat_entry(&pat,
+								pmt_sections);
+
+	dvb_dummy_fe_mpeg_pmt_stream_match_with_sections(state->channels,
+							 pmt_sections,
+							 pat.programs);
+
+	dvb_dummy_fe_mpeg_pat_table_init(&pat,
+					 update_version_num,
+					 TRANSPORT_STREAM_ID);
+	dvb_dummy_fe_mpeg_sdt_table_init(&sdt,
+				    update_version_num,
+				    TRANSPORT_STREAM_ID);
+	while (true) {
+
+		memset(buf, 0, DMX_BUF_LEN);
+		buffer_offset = 0;
+
+		if (!ticks || (ticks % 50) == 0) {
+			/* push PSI packets into the buffer */
+
+			buffer_offset +=
+				dvb_dummy_fe_mpeg_pat_write_into(buf,
+								 buffer_offset,
+								 &pat);
+			for (i = 0; i < pat.programs; ++i) {
+
+				pmt_pid =
+				dvb_dummy_fe_mpeg_pmt_get_pid(&pmt_sections[i],
+							      &pat);
+
+				WARN_ON(pmt_pid < 0); /* not found */
+
+				buffer_offset +=
+					/* write each section into buffer */
+					dvb_dummy_fe_mpeg_pmt_write_into(buf,
+							buffer_offset,
+							&pmt_sections[i],
+							pmt_pid);
+			}
+
+			buffer_offset +=
+				dvb_dummy_fe_mpeg_sdt_write_into(buf,
+								 buffer_offset,
+								 &sdt);
+
+
+			WARN_ON(buffer_offset > DMX_BUF_LEN); /* overflow */
+			msleep_interruptible(SLEEP_MSECS);
+		}
+	}
+
+	dvb_dummy_fe_mpeg_pat_table_destroy(&pat);
+	dvb_dummy_fe_mpeg_sdt_table_destroy(&sdt);
+
+	for (i = 0; i < pat.programs; ++i) {
+		/* destroy all PMT sections */
+		dvb_dummy_fe_mpeg_pmt_table_destroy(&pmt_sections[i]);
+		kfree(&pmt_sections[i]);
+	}
+
+	dvb_dummy_fe_channels_destroy(state->channels);
+	kfree(buf);
+}
+
 struct dvb_dummy_fe_cnr_to_qual_s {
 	/* attempt to use the same values as libdvbv5 */
 	u32 modulation;
@@ -75,17 +1472,7 @@ static struct dvb_dummy_fe_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
 	{ QAM_64, FEC_7_8, 22.0, 24.0},
 };
 
-struct dvb_dummy_fe_config {
-	/* probability of losing the lock due to low snr */
-	u8 drop_tslock_probability_on_low_snr;
-};
 
-struct dvb_dummy_fe_state {
-	struct dvb_frontend frontend;
-	struct dvb_dummy_fe_config config;
-	struct delayed_work poll_snr;
-	enum fe_status status;
-};
 
 void poll_snr_handler(struct work_struct *work)
 {
-- 
2.25.1

_______________________________________________
Linux-kernel-mentees mailing list
Linux-kernel-mentees@lists.linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/linux-kernel-mentees

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [RFC 1/3] media: dvb_dummy_tuner: implement driver skeleton
  2020-03-18  6:00   ` [Linux-kernel-mentees] " Daniel W. S. Almeida
@ 2020-03-18  8:17     ` Mauro Carvalho Chehab
  -1 siblings, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2020-03-18  8:17 UTC (permalink / raw)
  To: Daniel W. S. Almeida
  Cc: sean, kstewart, allison, tglx, linux-media, skhan, linux-kernel-mentees

Em Wed, 18 Mar 2020 03:00:16 -0300
"Daniel W. S. Almeida" <dwlsalmeida@gmail.com> escreveu:

> From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
> 
> 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 <dwlsalmeida@gmail.com>
> ---
>  drivers/media/tuners/Kconfig           |   9 +
>  drivers/media/tuners/Makefile          |   1 +
>  drivers/media/tuners/dvb_dummy_tuner.c | 421 +++++++++++++++++++++++++
>  3 files changed, 431 insertions(+)
>  create mode 100644 drivers/media/tuners/dvb_dummy_tuner.c
> 
> diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
> index e104bb7766e1..8ad54339ceee 100644
> --- a/drivers/media/tuners/Kconfig
> +++ b/drivers/media/tuners/Kconfig
> @@ -296,4 +296,13 @@ config MEDIA_TUNER_QM1D1B0004
>  	default m if !MEDIA_SUBDRV_AUTOSELECT
>  	help
>  	  Sharp QM1D1B0004 ISDB-S tuner driver.
> +
> +config MEDIA_TUNER_DVB_DUMMY_TUNER
> +	tristate "Dummy DVB Media Tuner"
> +	depends on MEDIA_SUPPORT && I2C
> +	help
> +	  Dummy DVB media tuner driver
> +	  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.
>  endmenu
> diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
> index 7b4f8423501e..f98de1cf2e19 100644
> --- a/drivers/media/tuners/Makefile
> +++ b/drivers/media/tuners/Makefile
> @@ -44,5 +44,6 @@ obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
>  obj-$(CONFIG_MEDIA_TUNER_QM1D1B0004) += qm1d1b0004.o
>  obj-$(CONFIG_MEDIA_TUNER_M88RS6000T) += m88rs6000t.o
>  obj-$(CONFIG_MEDIA_TUNER_TDA18250) += tda18250.o
> +obj-$(CONFIG_MEDIA_TUNER_DVB_DUMMY_TUNER) += dvb_dummy_tuner.o
>  
>  ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
> diff --git a/drivers/media/tuners/dvb_dummy_tuner.c b/drivers/media/tuners/dvb_dummy_tuner.c
> new file mode 100644
> index 000000000000..1408a8c84be2
> --- /dev/null
> +++ b/drivers/media/tuners/dvb_dummy_tuner.c
> @@ -0,0 +1,421 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2020 Daniel W. S. Almeida <dwlsalmeida@gmail.com>
> + *
> + * 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.
> + *
> + */
> +
> +#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>
> +
> +struct dvb_dummy_tuner_config {
> +	struct dvb_frontend *fe;
> +	u32 mock_power_up_delay_msec;
> +	u32 mock_tune_delay_msec;
> +	u32 mock_sleep_delay_msec;
> +	u32 mock_resume_delay_msec;
> +	u32 dummy_valid_dvb_t_frequencies[8];
> +	u32 dummy_valid_dvb_c_frequencies[8];
> +	u32 dummy_valid_dvb_s_frequencies[8];
> +	u8  max_frequency_shift_hz;
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s {
> +	/* attempt to use the same values as libdvbv5 */
> +	u32 modulation;
> +	u32 fec;
> +	u32 cnr_ok, cnr_good;
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_c_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QAM_256, FEC_NONE,  34., 38.},
> +	{ QAM_64,  FEC_NONE,  30., 34.},

Instead of using float point, you should multiply those by some value,
like 1000. So, 34.0 dB would become 34000. Btw, that's the way the DVBv5
API expects it: as mili-dB. See FE_SCALE_DECIBEL at:

	https://linuxtv.org/downloads/v4l-dvb-apis-new/uapi/dvb/frontend-stat-properties.html#dtv-stat-cnr

> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_s_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK, FEC_1_2,  7., 10.},
> +
> +	{ QPSK, FEC_2_3,  9., 12.},
> +	{ QPSK, FEC_3_4, 10., 13.},
> +	{ QPSK, FEC_5_6, 11., 14.},
> +
> +	{ QPSK, FEC_7_8, 12., 15.},
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_s2_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK,  FEC_1_2,   9.,  12.},
> +	{ QPSK,  FEC_2_3,  11.,  14.},
> +	{ QPSK,  FEC_3_4,  12.,  15.},
> +	{ QPSK,  FEC_5_6,  12.,  15.},
> +	{ QPSK,  FEC_8_9,  13.,  16.},
> +	{ QPSK,  FEC_9_10, 13.5, 16.5},
> +	{ PSK_8, FEC_2_3,  14.5, 17.5},
> +	{ PSK_8, FEC_3_4,  16.,  19.},
> +	{ PSK_8, FEC_5_6,  17.5, 20.5},
> +	{ PSK_8, FEC_8_9,  19.,  22.},
> +};
> +
> +static struct dvb_dummy_tuner_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{   QPSK, FEC_1_2,  4.1,  5.9},
> +	{   QPSK, FEC_2_3,  6.1,  9.6},
> +	{   QPSK, FEC_3_4,  7.2, 12.4},
> +	{   QPSK, FEC_5_6,  8.5, 15.6},
> +	{   QPSK, FEC_7_8,  9.2, 17.5},
> +
> +	{ QAM_16, FEC_1_2,  9.8, 11.8},
> +	{ QAM_16, FEC_2_3, 12.1, 15.3},
> +	{ QAM_16, FEC_3_4, 13.4, 18.1},
> +	{ QAM_16, FEC_5_6, 14.8, 21.3},
> +	{ QAM_16, FEC_7_8, 15.7, 23.6},
> +
> +	{ QAM_64, FEC_1_2, 14.0, 16.0},
> +	{ QAM_64, FEC_2_3, 19.9, 25.4},
> +	{ QAM_64, FEC_3_4, 24.9, 27.9},
> +	{ QAM_64, FEC_5_6, 21.3, 23.3},
> +	{ QAM_64, FEC_7_8, 22.0, 24.0},
> +};
> +
> +enum dvb_dummy_tuner_lock_status {
> +	STATUS_NO_LOCK = 0,
> +	STATUS_LOCKED = TUNER_STATUS_LOCKED,
> +};
> +
> +enum dvb_dummy_tuner_power_status {
> +	STATUS_UNKNOWN,
> +	STATUS_ASLEEP,
> +	STATUS_ACTIVE
> +};
> +
> +enum dvb_dummy_tuner_frequency_shift_status {
> +	EXACT_MATCH,
> +	CLOSE_MATCH,
> +	TOO_FAR_OFF
> +};
> +
> +struct dvb_dummy_tuner_hardware_state {
> +	enum dvb_dummy_tuner_power_status power_status;
> +	enum dvb_dummy_tuner_lock_status lock_status;
> +	u32 if_frequency;
> +	u32 tuned_frequency;
> +	u32 bandwidth;
> +};
> +
> +struct dvb_dummy_tuner_dev {
> +	struct dvb_frontend *fe;
> +	struct dvb_dummy_tuner_hardware_state hw_state;
> +	struct dvb_dummy_tuner_config config;
> +};
> +
> +static struct dvb_dummy_tuner_dev*
> +dvb_dummy_tuner_get_dev(struct dvb_frontend *fe)
> +{
> +	struct i2c_client *client = fe->tuner_priv;
> +
> +	return (struct dvb_dummy_tuner_dev *)i2c_get_clientdata(client);
> +}
> +
> +static bool dvb_dummy_tuner_check_frequency_shift(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +	u32 *valid_frequencies = NULL;
> +	u32 array_sz = 0;
> +	u32 i;
> +
> +	switch (c->delivery_system) {
> +	case SYS_DVBT:
> +	case SYS_DVBT2:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_t_frequencies;

Please avoid splitting assignments like that on multiple lines.

Btw, I would just do a s/frequencies/freqs/. So, that would be:

		valid_freqs = config.dummy_valid_dvb_t_freqs;

> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_t_frequencies);
> +		break;
> +	case SYS_DVBS:
> +	case SYS_DVBS2:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_s_frequencies;
> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_s_frequencies);
> +		break;
> +	case SYS_DVBC_ANNEX_A:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_c_frequencies;
> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_c_frequencies);
> +		break;
> +
> +	default:
> +		pr_warn("%s: unsupported delivery system: %u\n",
> +			__func__,
> +			c->delivery_system);
> +		break;
> +	}
> +
> +	for (i = 0; i < array_sz; i++) {
> +		if (c->frequency == valid_frequencies[i])
> +			return EXACT_MATCH;

I would, instead, return 0 here.

> +		else if (c->frequency >= ) -
> +			 config.max_frequency_shift_hz &&
> +			 c->frequency <= valid_frequencies[i] +
> +			 config.max_frequency_shift_hz)
> +			return CLOSE_MATCH;

I would, instead, do:

	return abs((c->frequency - valid_frequencies[i]) * 100 / config.max_frequency_shift_hz);

This will provide a value from 0 to 100 that would indicate how far is the
tuned frequency from the right one.

> +	}
> +
> +	return TOO_FAR_OFF;

I would return -1.

> +}
> +
> +static int
> +dvb_dummy_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength)
> +{
> +	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +	struct dvb_dummy_tuner_cnr_to_qual_s *cnr2qual = NULL;
> +	u32 array_size = 0;
> +	enum dvb_dummy_tuner_frequency_shift_status shift_status;
> +	u32 i;
> +
> +	shift_status = dvb_dummy_tuner_check_frequency_shift(fe);
> +
> +	switch (c->delivery_system) {
> +	case SYS_DVBT:
> +	case SYS_DVBT2:
> +		cnr2qual = dvb_t_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_t_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS:
> +		cnr2qual = dvb_s_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS2:
> +		cnr2qual = dvb_s2_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s2_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBC_ANNEX_A:
> +		cnr2qual = dvb_c_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_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) {
> +			*strength = (shift_status == EXACT_MATCH) ?
> +				    cnr2qual[i].cnr_good :
> +				    (shift_status == CLOSE_MATCH) ?
> +				    cnr2qual[i].cnr_ok :
> +				    cnr2qual[i].cnr_ok -
> +				    (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok);
> +			return 0;
> +		}
> +
> +	}
> +
> +	/* default to some random values if we can't match against the table */
> +	*strength = (shift_status == EXACT_MATCH) ? 34 : 10;

Perhaps you could, instead, use the frequency shift to calculate the
strength between 34 and 10. 

So, assuming that you take my suggestion of returning a value from 0 to 100,
and such value is stored at a "shift" var, you could do:

	*strength = 34 - 24 * shift / 100;

(ok, a linear interpolation is not what would happen on a real case, but
as the goal of a virtual driver is to validate if userspace is doing the 
right thing, this works just fine).

> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_init(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_power_up_delay_msec);
> +
> +	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
> +	tuner_dev->hw_state.if_frequency = 5000;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_sleep(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_sleep_delay_msec);
> +	tuner_dev->hw_state.power_status = STATUS_ASLEEP;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_suspend(struct dvb_frontend *fe)
> +{
> +	return dvb_dummy_tuner_sleep(fe);
> +}
> +
> +static int dvb_dummy_tuner_resume(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_resume_delay_msec);
> +	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
> +
> +	return 0;
> +}

For suspend/resume, I would not do any msleep(). Just set the status.

> +
> +static int dvb_dummy_tuner_set_params(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_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;
> +
> +	msleep_interruptible(config.mock_tune_delay_msec);
> +
> +	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 = STATUS_NO_LOCK;
> +		return -EINVAL;
> +	}

You can move the msleep() to be after checking the parameters.
This is what happens on other drivers.

> +
> +	tuner_dev->hw_state.tuned_frequency = c->frequency;
> +	tuner_dev->hw_state.bandwidth = c->bandwidth_hz;
> +	tuner_dev->hw_state.lock_status = STATUS_LOCKED;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_set_config(struct dvb_frontend *fe,
> +				      void *priv_cfg)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	WARN_ON(sizeof(tuner_dev->config) != sizeof(*priv_cfg));
> +	memcpy(&tuner_dev->config, priv_cfg, sizeof(*priv_cfg));

Huh? sizeof(void *priv_config) seems plain wrong to me. The above
shold be, instead:

	memcpy(&tuner_dev->config, priv_cfg, sizeof(tuner_dev->config));

but you should ensure that the caller driver will be filling it
properly there.

> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_frequency(struct dvb_frontend *fe,
> +					 u32 *frequency)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*frequency = tuner_dev->hw_state.tuned_frequency;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_bandwidth(struct dvb_frontend *fe,
> +					 u32 *bandwidth)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*bandwidth = tuner_dev->hw_state.bandwidth;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_if_frequency(struct dvb_frontend *fe,
> +					    u32 *frequency)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*frequency = tuner_dev->hw_state.if_frequency;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_status(struct dvb_frontend *fe, u32 *status)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*status = tuner_dev->hw_state.lock_status;
> +
> +	return 0;
> +}
> +
> +static const struct dvb_tuner_ops dvb_dummy_tuner_ops = {
> +	.init = dvb_dummy_tuner_init,
> +	.sleep = dvb_dummy_tuner_sleep,
> +	.suspend = dvb_dummy_tuner_suspend,
> +	.resume = dvb_dummy_tuner_resume,
> +	.set_params = dvb_dummy_tuner_set_params,
> +	.set_config = dvb_dummy_tuner_set_config,
> +	.get_bandwidth = dvb_dummy_tuner_get_bandwidth,
> +	.get_frequency = dvb_dummy_tuner_get_frequency,
> +	.get_if_frequency = dvb_dummy_tuner_get_if_frequency,
> +	.get_status = dvb_dummy_tuner_get_status,
> +	.get_rf_strength = dvb_dummy_tuner_get_signal_strength
> +};
> +
> +static const struct i2c_device_id dvb_dummy_tuner_i2c_id_table[] = {
> +	{"dvb_dummy_tuner", 0},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, dvb_dummy_tuner_i2c_id_table);
> +
> +static int dvb_dummy_tuner_i2c_probe(struct i2c_client *client,
> +				     const struct i2c_device_id *id)
> +{
> +	struct dvb_dummy_tuner_config *config = client->dev.platform_data;
> +	struct dvb_frontend *fe = config->fe;
> +	struct dvb_dummy_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,
> +	       &dvb_dummy_tuner_ops,
> +	       sizeof(struct dvb_tuner_ops));
> +
> +	fe->tuner_priv = client;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_i2c_remove(struct i2c_client *client)
> +{
> +	struct dvb_dummy_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 dvb_dummy_tuner_i2c_driver = {
> +	.driver = {
> +		.name = "dvb_dummy_tuner",
> +		.suppress_bind_attrs = true,
> +	},
> +	.probe		= dvb_dummy_tuner_i2c_probe,
> +	.remove		= dvb_dummy_tuner_i2c_remove,
> +	.id_table	= dvb_dummy_tuner_i2c_id_table,
> +};
> +module_i2c_driver(dvb_dummy_tuner_i2c_driver);
> +
> +MODULE_DESCRIPTION("DVB Dummy Tuner");
> +MODULE_AUTHOR("Daniel W. S. Almeida");
> +MODULE_LICENSE("GPL");



Thanks,
Mauro

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [Linux-kernel-mentees] [RFC 1/3] media: dvb_dummy_tuner: implement driver skeleton
@ 2020-03-18  8:17     ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2020-03-18  8:17 UTC (permalink / raw)
  To: Daniel W. S. Almeida
  Cc: kstewart, sean, tglx, linux-kernel-mentees, allison, linux-media

Em Wed, 18 Mar 2020 03:00:16 -0300
"Daniel W. S. Almeida" <dwlsalmeida@gmail.com> escreveu:

> From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
> 
> 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 <dwlsalmeida@gmail.com>
> ---
>  drivers/media/tuners/Kconfig           |   9 +
>  drivers/media/tuners/Makefile          |   1 +
>  drivers/media/tuners/dvb_dummy_tuner.c | 421 +++++++++++++++++++++++++
>  3 files changed, 431 insertions(+)
>  create mode 100644 drivers/media/tuners/dvb_dummy_tuner.c
> 
> diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
> index e104bb7766e1..8ad54339ceee 100644
> --- a/drivers/media/tuners/Kconfig
> +++ b/drivers/media/tuners/Kconfig
> @@ -296,4 +296,13 @@ config MEDIA_TUNER_QM1D1B0004
>  	default m if !MEDIA_SUBDRV_AUTOSELECT
>  	help
>  	  Sharp QM1D1B0004 ISDB-S tuner driver.
> +
> +config MEDIA_TUNER_DVB_DUMMY_TUNER
> +	tristate "Dummy DVB Media Tuner"
> +	depends on MEDIA_SUPPORT && I2C
> +	help
> +	  Dummy DVB media tuner driver
> +	  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.
>  endmenu
> diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
> index 7b4f8423501e..f98de1cf2e19 100644
> --- a/drivers/media/tuners/Makefile
> +++ b/drivers/media/tuners/Makefile
> @@ -44,5 +44,6 @@ obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
>  obj-$(CONFIG_MEDIA_TUNER_QM1D1B0004) += qm1d1b0004.o
>  obj-$(CONFIG_MEDIA_TUNER_M88RS6000T) += m88rs6000t.o
>  obj-$(CONFIG_MEDIA_TUNER_TDA18250) += tda18250.o
> +obj-$(CONFIG_MEDIA_TUNER_DVB_DUMMY_TUNER) += dvb_dummy_tuner.o
>  
>  ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
> diff --git a/drivers/media/tuners/dvb_dummy_tuner.c b/drivers/media/tuners/dvb_dummy_tuner.c
> new file mode 100644
> index 000000000000..1408a8c84be2
> --- /dev/null
> +++ b/drivers/media/tuners/dvb_dummy_tuner.c
> @@ -0,0 +1,421 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2020 Daniel W. S. Almeida <dwlsalmeida@gmail.com>
> + *
> + * 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.
> + *
> + */
> +
> +#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>
> +
> +struct dvb_dummy_tuner_config {
> +	struct dvb_frontend *fe;
> +	u32 mock_power_up_delay_msec;
> +	u32 mock_tune_delay_msec;
> +	u32 mock_sleep_delay_msec;
> +	u32 mock_resume_delay_msec;
> +	u32 dummy_valid_dvb_t_frequencies[8];
> +	u32 dummy_valid_dvb_c_frequencies[8];
> +	u32 dummy_valid_dvb_s_frequencies[8];
> +	u8  max_frequency_shift_hz;
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s {
> +	/* attempt to use the same values as libdvbv5 */
> +	u32 modulation;
> +	u32 fec;
> +	u32 cnr_ok, cnr_good;
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_c_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QAM_256, FEC_NONE,  34., 38.},
> +	{ QAM_64,  FEC_NONE,  30., 34.},

Instead of using float point, you should multiply those by some value,
like 1000. So, 34.0 dB would become 34000. Btw, that's the way the DVBv5
API expects it: as mili-dB. See FE_SCALE_DECIBEL at:

	https://linuxtv.org/downloads/v4l-dvb-apis-new/uapi/dvb/frontend-stat-properties.html#dtv-stat-cnr

> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_s_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK, FEC_1_2,  7., 10.},
> +
> +	{ QPSK, FEC_2_3,  9., 12.},
> +	{ QPSK, FEC_3_4, 10., 13.},
> +	{ QPSK, FEC_5_6, 11., 14.},
> +
> +	{ QPSK, FEC_7_8, 12., 15.},
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_s2_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK,  FEC_1_2,   9.,  12.},
> +	{ QPSK,  FEC_2_3,  11.,  14.},
> +	{ QPSK,  FEC_3_4,  12.,  15.},
> +	{ QPSK,  FEC_5_6,  12.,  15.},
> +	{ QPSK,  FEC_8_9,  13.,  16.},
> +	{ QPSK,  FEC_9_10, 13.5, 16.5},
> +	{ PSK_8, FEC_2_3,  14.5, 17.5},
> +	{ PSK_8, FEC_3_4,  16.,  19.},
> +	{ PSK_8, FEC_5_6,  17.5, 20.5},
> +	{ PSK_8, FEC_8_9,  19.,  22.},
> +};
> +
> +static struct dvb_dummy_tuner_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{   QPSK, FEC_1_2,  4.1,  5.9},
> +	{   QPSK, FEC_2_3,  6.1,  9.6},
> +	{   QPSK, FEC_3_4,  7.2, 12.4},
> +	{   QPSK, FEC_5_6,  8.5, 15.6},
> +	{   QPSK, FEC_7_8,  9.2, 17.5},
> +
> +	{ QAM_16, FEC_1_2,  9.8, 11.8},
> +	{ QAM_16, FEC_2_3, 12.1, 15.3},
> +	{ QAM_16, FEC_3_4, 13.4, 18.1},
> +	{ QAM_16, FEC_5_6, 14.8, 21.3},
> +	{ QAM_16, FEC_7_8, 15.7, 23.6},
> +
> +	{ QAM_64, FEC_1_2, 14.0, 16.0},
> +	{ QAM_64, FEC_2_3, 19.9, 25.4},
> +	{ QAM_64, FEC_3_4, 24.9, 27.9},
> +	{ QAM_64, FEC_5_6, 21.3, 23.3},
> +	{ QAM_64, FEC_7_8, 22.0, 24.0},
> +};
> +
> +enum dvb_dummy_tuner_lock_status {
> +	STATUS_NO_LOCK = 0,
> +	STATUS_LOCKED = TUNER_STATUS_LOCKED,
> +};
> +
> +enum dvb_dummy_tuner_power_status {
> +	STATUS_UNKNOWN,
> +	STATUS_ASLEEP,
> +	STATUS_ACTIVE
> +};
> +
> +enum dvb_dummy_tuner_frequency_shift_status {
> +	EXACT_MATCH,
> +	CLOSE_MATCH,
> +	TOO_FAR_OFF
> +};
> +
> +struct dvb_dummy_tuner_hardware_state {
> +	enum dvb_dummy_tuner_power_status power_status;
> +	enum dvb_dummy_tuner_lock_status lock_status;
> +	u32 if_frequency;
> +	u32 tuned_frequency;
> +	u32 bandwidth;
> +};
> +
> +struct dvb_dummy_tuner_dev {
> +	struct dvb_frontend *fe;
> +	struct dvb_dummy_tuner_hardware_state hw_state;
> +	struct dvb_dummy_tuner_config config;
> +};
> +
> +static struct dvb_dummy_tuner_dev*
> +dvb_dummy_tuner_get_dev(struct dvb_frontend *fe)
> +{
> +	struct i2c_client *client = fe->tuner_priv;
> +
> +	return (struct dvb_dummy_tuner_dev *)i2c_get_clientdata(client);
> +}
> +
> +static bool dvb_dummy_tuner_check_frequency_shift(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +	u32 *valid_frequencies = NULL;
> +	u32 array_sz = 0;
> +	u32 i;
> +
> +	switch (c->delivery_system) {
> +	case SYS_DVBT:
> +	case SYS_DVBT2:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_t_frequencies;

Please avoid splitting assignments like that on multiple lines.

Btw, I would just do a s/frequencies/freqs/. So, that would be:

		valid_freqs = config.dummy_valid_dvb_t_freqs;

> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_t_frequencies);
> +		break;
> +	case SYS_DVBS:
> +	case SYS_DVBS2:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_s_frequencies;
> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_s_frequencies);
> +		break;
> +	case SYS_DVBC_ANNEX_A:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_c_frequencies;
> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_c_frequencies);
> +		break;
> +
> +	default:
> +		pr_warn("%s: unsupported delivery system: %u\n",
> +			__func__,
> +			c->delivery_system);
> +		break;
> +	}
> +
> +	for (i = 0; i < array_sz; i++) {
> +		if (c->frequency == valid_frequencies[i])
> +			return EXACT_MATCH;

I would, instead, return 0 here.

> +		else if (c->frequency >= ) -
> +			 config.max_frequency_shift_hz &&
> +			 c->frequency <= valid_frequencies[i] +
> +			 config.max_frequency_shift_hz)
> +			return CLOSE_MATCH;

I would, instead, do:

	return abs((c->frequency - valid_frequencies[i]) * 100 / config.max_frequency_shift_hz);

This will provide a value from 0 to 100 that would indicate how far is the
tuned frequency from the right one.

> +	}
> +
> +	return TOO_FAR_OFF;

I would return -1.

> +}
> +
> +static int
> +dvb_dummy_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength)
> +{
> +	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +	struct dvb_dummy_tuner_cnr_to_qual_s *cnr2qual = NULL;
> +	u32 array_size = 0;
> +	enum dvb_dummy_tuner_frequency_shift_status shift_status;
> +	u32 i;
> +
> +	shift_status = dvb_dummy_tuner_check_frequency_shift(fe);
> +
> +	switch (c->delivery_system) {
> +	case SYS_DVBT:
> +	case SYS_DVBT2:
> +		cnr2qual = dvb_t_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_t_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS:
> +		cnr2qual = dvb_s_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS2:
> +		cnr2qual = dvb_s2_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s2_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBC_ANNEX_A:
> +		cnr2qual = dvb_c_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_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) {
> +			*strength = (shift_status == EXACT_MATCH) ?
> +				    cnr2qual[i].cnr_good :
> +				    (shift_status == CLOSE_MATCH) ?
> +				    cnr2qual[i].cnr_ok :
> +				    cnr2qual[i].cnr_ok -
> +				    (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok);
> +			return 0;
> +		}
> +
> +	}
> +
> +	/* default to some random values if we can't match against the table */
> +	*strength = (shift_status == EXACT_MATCH) ? 34 : 10;

Perhaps you could, instead, use the frequency shift to calculate the
strength between 34 and 10. 

So, assuming that you take my suggestion of returning a value from 0 to 100,
and such value is stored at a "shift" var, you could do:

	*strength = 34 - 24 * shift / 100;

(ok, a linear interpolation is not what would happen on a real case, but
as the goal of a virtual driver is to validate if userspace is doing the 
right thing, this works just fine).

> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_init(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_power_up_delay_msec);
> +
> +	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
> +	tuner_dev->hw_state.if_frequency = 5000;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_sleep(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_sleep_delay_msec);
> +	tuner_dev->hw_state.power_status = STATUS_ASLEEP;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_suspend(struct dvb_frontend *fe)
> +{
> +	return dvb_dummy_tuner_sleep(fe);
> +}
> +
> +static int dvb_dummy_tuner_resume(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_resume_delay_msec);
> +	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
> +
> +	return 0;
> +}

For suspend/resume, I would not do any msleep(). Just set the status.

> +
> +static int dvb_dummy_tuner_set_params(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_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;
> +
> +	msleep_interruptible(config.mock_tune_delay_msec);
> +
> +	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 = STATUS_NO_LOCK;
> +		return -EINVAL;
> +	}

You can move the msleep() to be after checking the parameters.
This is what happens on other drivers.

> +
> +	tuner_dev->hw_state.tuned_frequency = c->frequency;
> +	tuner_dev->hw_state.bandwidth = c->bandwidth_hz;
> +	tuner_dev->hw_state.lock_status = STATUS_LOCKED;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_set_config(struct dvb_frontend *fe,
> +				      void *priv_cfg)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	WARN_ON(sizeof(tuner_dev->config) != sizeof(*priv_cfg));
> +	memcpy(&tuner_dev->config, priv_cfg, sizeof(*priv_cfg));

Huh? sizeof(void *priv_config) seems plain wrong to me. The above
shold be, instead:

	memcpy(&tuner_dev->config, priv_cfg, sizeof(tuner_dev->config));

but you should ensure that the caller driver will be filling it
properly there.

> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_frequency(struct dvb_frontend *fe,
> +					 u32 *frequency)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*frequency = tuner_dev->hw_state.tuned_frequency;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_bandwidth(struct dvb_frontend *fe,
> +					 u32 *bandwidth)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*bandwidth = tuner_dev->hw_state.bandwidth;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_if_frequency(struct dvb_frontend *fe,
> +					    u32 *frequency)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*frequency = tuner_dev->hw_state.if_frequency;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_status(struct dvb_frontend *fe, u32 *status)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*status = tuner_dev->hw_state.lock_status;
> +
> +	return 0;
> +}
> +
> +static const struct dvb_tuner_ops dvb_dummy_tuner_ops = {
> +	.init = dvb_dummy_tuner_init,
> +	.sleep = dvb_dummy_tuner_sleep,
> +	.suspend = dvb_dummy_tuner_suspend,
> +	.resume = dvb_dummy_tuner_resume,
> +	.set_params = dvb_dummy_tuner_set_params,
> +	.set_config = dvb_dummy_tuner_set_config,
> +	.get_bandwidth = dvb_dummy_tuner_get_bandwidth,
> +	.get_frequency = dvb_dummy_tuner_get_frequency,
> +	.get_if_frequency = dvb_dummy_tuner_get_if_frequency,
> +	.get_status = dvb_dummy_tuner_get_status,
> +	.get_rf_strength = dvb_dummy_tuner_get_signal_strength
> +};
> +
> +static const struct i2c_device_id dvb_dummy_tuner_i2c_id_table[] = {
> +	{"dvb_dummy_tuner", 0},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, dvb_dummy_tuner_i2c_id_table);
> +
> +static int dvb_dummy_tuner_i2c_probe(struct i2c_client *client,
> +				     const struct i2c_device_id *id)
> +{
> +	struct dvb_dummy_tuner_config *config = client->dev.platform_data;
> +	struct dvb_frontend *fe = config->fe;
> +	struct dvb_dummy_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,
> +	       &dvb_dummy_tuner_ops,
> +	       sizeof(struct dvb_tuner_ops));
> +
> +	fe->tuner_priv = client;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_i2c_remove(struct i2c_client *client)
> +{
> +	struct dvb_dummy_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 dvb_dummy_tuner_i2c_driver = {
> +	.driver = {
> +		.name = "dvb_dummy_tuner",
> +		.suppress_bind_attrs = true,
> +	},
> +	.probe		= dvb_dummy_tuner_i2c_probe,
> +	.remove		= dvb_dummy_tuner_i2c_remove,
> +	.id_table	= dvb_dummy_tuner_i2c_id_table,
> +};
> +module_i2c_driver(dvb_dummy_tuner_i2c_driver);
> +
> +MODULE_DESCRIPTION("DVB Dummy Tuner");
> +MODULE_AUTHOR("Daniel W. S. Almeida");
> +MODULE_LICENSE("GPL");



Thanks,
Mauro
_______________________________________________
Linux-kernel-mentees mailing list
Linux-kernel-mentees@lists.linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/linux-kernel-mentees

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC 2/3] media: dvb_dummy_fe.c: lose TS lock on bad snr
  2020-03-18  6:00   ` [Linux-kernel-mentees] " Daniel W. S. Almeida
@ 2020-03-18  8:43     ` Mauro Carvalho Chehab
  -1 siblings, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2020-03-18  8:43 UTC (permalink / raw)
  To: Daniel W. S. Almeida
  Cc: sean, kstewart, allison, tglx, linux-media, skhan, linux-kernel-mentees

Em Wed, 18 Mar 2020 03:00:17 -0300
"Daniel W. S. Almeida" <dwlsalmeida@gmail.com> escreveu:

> From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
> 
> Periodically check the signal quality and eventually lose the lock if
> the quality is sub-par. A fake tuner can return a bad quality signal to
> the demod if the frequency is too far off from a valid frequency.
> 
> Signed-off-by: Daniel W. S. Almeida <dwlsalmeida@gmail.com>
> ---
>  drivers/media/dvb-frontends/dvb_dummy_fe.c | 149 ++++++++++++++++++++-
>  1 file changed, 144 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/media/dvb-frontends/dvb_dummy_fe.c b/drivers/media/dvb-frontends/dvb_dummy_fe.c
> index 9ff1ebaa5e04..726c964a523d 100644
> --- a/drivers/media/dvb-frontends/dvb_dummy_fe.c
> +++ b/drivers/media/dvb-frontends/dvb_dummy_fe.c
> @@ -9,24 +9,155 @@
>  #include <linux/init.h>
>  #include <linux/string.h>
>  #include <linux/slab.h>
> +#include <linux/workqueue.h>
> +#include <linux/random.h>
>  
>  #include <media/dvb_frontend.h>
>  #include "dvb_dummy_fe.h"
>  
>  
> +struct dvb_dummy_fe_cnr_to_qual_s {
> +	/* attempt to use the same values as libdvbv5 */
> +	u32 modulation;
> +	u32 fec;
> +	u32 cnr_ok, cnr_good;
> +};
> +
> +struct dvb_dummy_fe_cnr_to_qual_s dvb_c_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QAM_256, FEC_NONE,  34., 38.},
> +	{ QAM_64,  FEC_NONE,  30., 34.},
> +};
> +
> +struct dvb_dummy_fe_cnr_to_qual_s dvb_s_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK, FEC_1_2,  7., 10.},
> +
> +	{ QPSK, FEC_2_3,  9., 12.},
> +	{ QPSK, FEC_3_4, 10., 13.},
> +	{ QPSK, FEC_5_6, 11., 14.},
> +
> +	{ QPSK, FEC_7_8, 12., 15.},
> +};
> +
> +struct dvb_dummy_fe_cnr_to_qual_s dvb_s2_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK,  FEC_1_2,   9.,  12.},
> +	{ QPSK,  FEC_2_3,  11.,  14.},
> +	{ QPSK,  FEC_3_4,  12.,  15.},
> +	{ QPSK,  FEC_5_6,  12.,  15.},
> +	{ QPSK,  FEC_8_9,  13.,  16.},
> +	{ QPSK,  FEC_9_10, 13.5, 16.5},
> +	{ PSK_8, FEC_2_3,  14.5, 17.5},
> +	{ PSK_8, FEC_3_4,  16.,  19.},
> +	{ PSK_8, FEC_5_6,  17.5, 20.5},
> +	{ PSK_8, FEC_8_9,  19.,  22.},
> +};
> +
> +static struct dvb_dummy_fe_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{   QPSK, FEC_1_2,  4.1,  5.9},
> +	{   QPSK, FEC_2_3,  6.1,  9.6},
> +	{   QPSK, FEC_3_4,  7.2, 12.4},
> +	{   QPSK, FEC_5_6,  8.5, 15.6},
> +	{   QPSK, FEC_7_8,  9.2, 17.5},
> +
> +	{ QAM_16, FEC_1_2,  9.8, 11.8},
> +	{ QAM_16, FEC_2_3, 12.1, 15.3},
> +	{ QAM_16, FEC_3_4, 13.4, 18.1},
> +	{ QAM_16, FEC_5_6, 14.8, 21.3},
> +	{ QAM_16, FEC_7_8, 15.7, 23.6},
> +
> +	{ QAM_64, FEC_1_2, 14.0, 16.0},
> +	{ QAM_64, FEC_2_3, 19.9, 25.4},
> +	{ QAM_64, FEC_3_4, 24.9, 27.9},
> +	{ QAM_64, FEC_5_6, 21.3, 23.3},
> +	{ QAM_64, FEC_7_8, 22.0, 24.0},
> +};

Same comment as before: multiply everything to 1000.

> +
> +struct dvb_dummy_fe_config {
> +	/* probability of losing the lock due to low snr */
> +	u8 drop_tslock_probability_on_low_snr;
> +};
> +
>  struct dvb_dummy_fe_state {
>  	struct dvb_frontend frontend;
> +	struct dvb_dummy_fe_config config;
> +	struct delayed_work poll_snr;
> +	enum fe_status status;
>  };
>  
> +void poll_snr_handler(struct work_struct *work)
> +{
> +	/* periodically check the signal quality and eventually
> +	 * lose the TS lock if it dips too low
> +	 */

We use multi-line comments at the Kernel as:

	/*
	 * foo
	 * bar
	 */


> +	struct dvb_dummy_fe_state *state =
> +		container_of(work, struct dvb_dummy_fe_state, poll_snr.work);
> +	struct dtv_frontend_properties *c = &state->frontend.dtv_property_cache;
> +	struct dvb_dummy_fe_cnr_to_qual_s *cnr2qual = NULL;
> +	struct dvb_dummy_fe_config *config = &state->config;
> +	u32 array_size = 0;
> +	u16 snr = 0;
> +	u32 i;

Please avoid breaking assignments on multiple lines. It makes harder
to read.

What I would do, instead, is to split it on a different way:

	struct dvb_dummy_fe_state *state;
	struct dtv_frontend_properties *c;
	struct dvb_dummy_fe_config *config;
	...

	state = container_of(work, struct dvb_dummy_fe_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);
> +
> +	switch (c->delivery_system) {
> +	case SYS_DVBT:
> +	case SYS_DVBT2:
> +		cnr2qual = dvb_t_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_t_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS:
> +		cnr2qual = dvb_s_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS2:
> +		cnr2qual = dvb_s2_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s2_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBC_ANNEX_A:
> +		cnr2qual = dvb_c_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_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) {
> +
> +			if (snr < cnr2qual[i].cnr_ok) {
> +				/* eventually lose the TS lock */
> +				if (prandom_u32_max(100) <
> +				    config->drop_tslock_probability_on_low_snr)
> +					state->status = 0;
> +			}

Hmm.. what about the reverse: if it lost TS lock, shouldn't it 
randomly recover?

> +		}
> +	}
> +
> +	schedule_delayed_work(&(state->poll_snr), msecs_to_jiffies(2000));
> +}
>  
>  static int dvb_dummy_fe_read_status(struct dvb_frontend *fe,
>  				    enum fe_status *status)
>  {
> -	*status = FE_HAS_SIGNAL
> -		| FE_HAS_CARRIER
> -		| FE_HAS_VITERBI
> -		| FE_HAS_SYNC
> -		| FE_HAS_LOCK;
> +
> +	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
> +
> +	*status = state->status;
>  
>  	return 0;
>  }
> @@ -80,11 +211,18 @@ static int dvb_dummy_fe_set_frontend(struct dvb_frontend *fe)
>  
>  static int dvb_dummy_fe_sleep(struct dvb_frontend *fe)
>  {
> +	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
> +
> +	cancel_delayed_work_sync(&(state->poll_snr));
>  	return 0;
>  }
>  
>  static int dvb_dummy_fe_init(struct dvb_frontend *fe)
>  {
> +	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
> +
> +	INIT_DELAYED_WORK(&(state->poll_snr), &poll_snr_handler);
> +	schedule_delayed_work(&(state->poll_snr), msecs_to_jiffies(2000));
>  	return 0;
>  }
>  
> @@ -104,6 +242,7 @@ static void dvb_dummy_fe_release(struct dvb_frontend *fe)
>  {
>  	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
>  
> +	cancel_delayed_work_sync(&(state->poll_snr));
>  	kfree(state);
>  }
>  

The rest of the code sounds good to me.


Thanks,
Mauro

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [Linux-kernel-mentees] [RFC 2/3] media: dvb_dummy_fe.c: lose TS lock on bad snr
@ 2020-03-18  8:43     ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2020-03-18  8:43 UTC (permalink / raw)
  To: Daniel W. S. Almeida
  Cc: kstewart, sean, tglx, linux-kernel-mentees, allison, linux-media

Em Wed, 18 Mar 2020 03:00:17 -0300
"Daniel W. S. Almeida" <dwlsalmeida@gmail.com> escreveu:

> From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
> 
> Periodically check the signal quality and eventually lose the lock if
> the quality is sub-par. A fake tuner can return a bad quality signal to
> the demod if the frequency is too far off from a valid frequency.
> 
> Signed-off-by: Daniel W. S. Almeida <dwlsalmeida@gmail.com>
> ---
>  drivers/media/dvb-frontends/dvb_dummy_fe.c | 149 ++++++++++++++++++++-
>  1 file changed, 144 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/media/dvb-frontends/dvb_dummy_fe.c b/drivers/media/dvb-frontends/dvb_dummy_fe.c
> index 9ff1ebaa5e04..726c964a523d 100644
> --- a/drivers/media/dvb-frontends/dvb_dummy_fe.c
> +++ b/drivers/media/dvb-frontends/dvb_dummy_fe.c
> @@ -9,24 +9,155 @@
>  #include <linux/init.h>
>  #include <linux/string.h>
>  #include <linux/slab.h>
> +#include <linux/workqueue.h>
> +#include <linux/random.h>
>  
>  #include <media/dvb_frontend.h>
>  #include "dvb_dummy_fe.h"
>  
>  
> +struct dvb_dummy_fe_cnr_to_qual_s {
> +	/* attempt to use the same values as libdvbv5 */
> +	u32 modulation;
> +	u32 fec;
> +	u32 cnr_ok, cnr_good;
> +};
> +
> +struct dvb_dummy_fe_cnr_to_qual_s dvb_c_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QAM_256, FEC_NONE,  34., 38.},
> +	{ QAM_64,  FEC_NONE,  30., 34.},
> +};
> +
> +struct dvb_dummy_fe_cnr_to_qual_s dvb_s_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK, FEC_1_2,  7., 10.},
> +
> +	{ QPSK, FEC_2_3,  9., 12.},
> +	{ QPSK, FEC_3_4, 10., 13.},
> +	{ QPSK, FEC_5_6, 11., 14.},
> +
> +	{ QPSK, FEC_7_8, 12., 15.},
> +};
> +
> +struct dvb_dummy_fe_cnr_to_qual_s dvb_s2_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK,  FEC_1_2,   9.,  12.},
> +	{ QPSK,  FEC_2_3,  11.,  14.},
> +	{ QPSK,  FEC_3_4,  12.,  15.},
> +	{ QPSK,  FEC_5_6,  12.,  15.},
> +	{ QPSK,  FEC_8_9,  13.,  16.},
> +	{ QPSK,  FEC_9_10, 13.5, 16.5},
> +	{ PSK_8, FEC_2_3,  14.5, 17.5},
> +	{ PSK_8, FEC_3_4,  16.,  19.},
> +	{ PSK_8, FEC_5_6,  17.5, 20.5},
> +	{ PSK_8, FEC_8_9,  19.,  22.},
> +};
> +
> +static struct dvb_dummy_fe_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{   QPSK, FEC_1_2,  4.1,  5.9},
> +	{   QPSK, FEC_2_3,  6.1,  9.6},
> +	{   QPSK, FEC_3_4,  7.2, 12.4},
> +	{   QPSK, FEC_5_6,  8.5, 15.6},
> +	{   QPSK, FEC_7_8,  9.2, 17.5},
> +
> +	{ QAM_16, FEC_1_2,  9.8, 11.8},
> +	{ QAM_16, FEC_2_3, 12.1, 15.3},
> +	{ QAM_16, FEC_3_4, 13.4, 18.1},
> +	{ QAM_16, FEC_5_6, 14.8, 21.3},
> +	{ QAM_16, FEC_7_8, 15.7, 23.6},
> +
> +	{ QAM_64, FEC_1_2, 14.0, 16.0},
> +	{ QAM_64, FEC_2_3, 19.9, 25.4},
> +	{ QAM_64, FEC_3_4, 24.9, 27.9},
> +	{ QAM_64, FEC_5_6, 21.3, 23.3},
> +	{ QAM_64, FEC_7_8, 22.0, 24.0},
> +};

Same comment as before: multiply everything to 1000.

> +
> +struct dvb_dummy_fe_config {
> +	/* probability of losing the lock due to low snr */
> +	u8 drop_tslock_probability_on_low_snr;
> +};
> +
>  struct dvb_dummy_fe_state {
>  	struct dvb_frontend frontend;
> +	struct dvb_dummy_fe_config config;
> +	struct delayed_work poll_snr;
> +	enum fe_status status;
>  };
>  
> +void poll_snr_handler(struct work_struct *work)
> +{
> +	/* periodically check the signal quality and eventually
> +	 * lose the TS lock if it dips too low
> +	 */

We use multi-line comments at the Kernel as:

	/*
	 * foo
	 * bar
	 */


> +	struct dvb_dummy_fe_state *state =
> +		container_of(work, struct dvb_dummy_fe_state, poll_snr.work);
> +	struct dtv_frontend_properties *c = &state->frontend.dtv_property_cache;
> +	struct dvb_dummy_fe_cnr_to_qual_s *cnr2qual = NULL;
> +	struct dvb_dummy_fe_config *config = &state->config;
> +	u32 array_size = 0;
> +	u16 snr = 0;
> +	u32 i;

Please avoid breaking assignments on multiple lines. It makes harder
to read.

What I would do, instead, is to split it on a different way:

	struct dvb_dummy_fe_state *state;
	struct dtv_frontend_properties *c;
	struct dvb_dummy_fe_config *config;
	...

	state = container_of(work, struct dvb_dummy_fe_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);
> +
> +	switch (c->delivery_system) {
> +	case SYS_DVBT:
> +	case SYS_DVBT2:
> +		cnr2qual = dvb_t_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_t_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS:
> +		cnr2qual = dvb_s_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS2:
> +		cnr2qual = dvb_s2_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s2_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBC_ANNEX_A:
> +		cnr2qual = dvb_c_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_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) {
> +
> +			if (snr < cnr2qual[i].cnr_ok) {
> +				/* eventually lose the TS lock */
> +				if (prandom_u32_max(100) <
> +				    config->drop_tslock_probability_on_low_snr)
> +					state->status = 0;
> +			}

Hmm.. what about the reverse: if it lost TS lock, shouldn't it 
randomly recover?

> +		}
> +	}
> +
> +	schedule_delayed_work(&(state->poll_snr), msecs_to_jiffies(2000));
> +}
>  
>  static int dvb_dummy_fe_read_status(struct dvb_frontend *fe,
>  				    enum fe_status *status)
>  {
> -	*status = FE_HAS_SIGNAL
> -		| FE_HAS_CARRIER
> -		| FE_HAS_VITERBI
> -		| FE_HAS_SYNC
> -		| FE_HAS_LOCK;
> +
> +	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
> +
> +	*status = state->status;
>  
>  	return 0;
>  }
> @@ -80,11 +211,18 @@ static int dvb_dummy_fe_set_frontend(struct dvb_frontend *fe)
>  
>  static int dvb_dummy_fe_sleep(struct dvb_frontend *fe)
>  {
> +	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
> +
> +	cancel_delayed_work_sync(&(state->poll_snr));
>  	return 0;
>  }
>  
>  static int dvb_dummy_fe_init(struct dvb_frontend *fe)
>  {
> +	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
> +
> +	INIT_DELAYED_WORK(&(state->poll_snr), &poll_snr_handler);
> +	schedule_delayed_work(&(state->poll_snr), msecs_to_jiffies(2000));
>  	return 0;
>  }
>  
> @@ -104,6 +242,7 @@ static void dvb_dummy_fe_release(struct dvb_frontend *fe)
>  {
>  	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
>  
> +	cancel_delayed_work_sync(&(state->poll_snr));
>  	kfree(state);
>  }
>  

The rest of the code sounds good to me.


Thanks,
Mauro
_______________________________________________
Linux-kernel-mentees mailing list
Linux-kernel-mentees@lists.linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/linux-kernel-mentees

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC 1/3] media: dvb_dummy_tuner: implement driver skeleton
  2020-03-18  6:00   ` [Linux-kernel-mentees] " Daniel W. S. Almeida
@ 2020-03-18  8:54     ` Kieran Bingham
  -1 siblings, 0 replies; 21+ messages in thread
From: Kieran Bingham @ 2020-03-18  8:54 UTC (permalink / raw)
  To: Daniel W. S. Almeida, mchehab, sean, kstewart, allison, tglx
  Cc: linux-media, skhan, linux-kernel-mentees

Hi Daniel,

On 18/03/2020 06:00, Daniel W. S. Almeida wrote:
> From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
> 
> 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.

Interesting project :-)

> Signed-off-by: Daniel W. S. Almeida <dwlsalmeida@gmail.com>
> ---
>  drivers/media/tuners/Kconfig           |   9 +
>  drivers/media/tuners/Makefile          |   1 +
>  drivers/media/tuners/dvb_dummy_tuner.c | 421 +++++++++++++++++++++++++
>  3 files changed, 431 insertions(+)
>  create mode 100644 drivers/media/tuners/dvb_dummy_tuner.c
> 
> diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
> index e104bb7766e1..8ad54339ceee 100644
> --- a/drivers/media/tuners/Kconfig
> +++ b/drivers/media/tuners/Kconfig
> @@ -296,4 +296,13 @@ config MEDIA_TUNER_QM1D1B0004
>  	default m if !MEDIA_SUBDRV_AUTOSELECT
>  	help
>  	  Sharp QM1D1B0004 ISDB-S tuner driver.
> +
> +config MEDIA_TUNER_DVB_DUMMY_TUNER
> +	tristate "Dummy DVB Media Tuner"
> +	depends on MEDIA_SUPPORT && I2C
> +	help
> +	  Dummy DVB media tuner driver
> +	  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.
>  endmenu
> diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
> index 7b4f8423501e..f98de1cf2e19 100644
> --- a/drivers/media/tuners/Makefile
> +++ b/drivers/media/tuners/Makefile
> @@ -44,5 +44,6 @@ obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
>  obj-$(CONFIG_MEDIA_TUNER_QM1D1B0004) += qm1d1b0004.o
>  obj-$(CONFIG_MEDIA_TUNER_M88RS6000T) += m88rs6000t.o
>  obj-$(CONFIG_MEDIA_TUNER_TDA18250) += tda18250.o
> +obj-$(CONFIG_MEDIA_TUNER_DVB_DUMMY_TUNER) += dvb_dummy_tuner.o


We have other virtual drivers named "vimc, vivid, vim2m" ... should this
become "vidvb" ?

>  
>  ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
> diff --git a/drivers/media/tuners/dvb_dummy_tuner.c b/drivers/media/tuners/dvb_dummy_tuner.c
> new file mode 100644
> index 000000000000..1408a8c84be2
> --- /dev/null
> +++ b/drivers/media/tuners/dvb_dummy_tuner.c
> @@ -0,0 +1,421 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2020 Daniel W. S. Almeida <dwlsalmeida@gmail.com>
> + *
> + * 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.
> + *
> + */
> +
> +#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>
> +
> +struct dvb_dummy_tuner_config {
> +	struct dvb_frontend *fe;
> +	u32 mock_power_up_delay_msec;
> +	u32 mock_tune_delay_msec;
> +	u32 mock_sleep_delay_msec;
> +	u32 mock_resume_delay_msec;
> +	u32 dummy_valid_dvb_t_frequencies[8];
> +	u32 dummy_valid_dvb_c_frequencies[8];
> +	u32 dummy_valid_dvb_s_frequencies[8];
> +	u8  max_frequency_shift_hz;
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s {
> +	/* attempt to use the same values as libdvbv5 */
> +	u32 modulation;
> +	u32 fec;
> +	u32 cnr_ok, cnr_good;
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_c_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QAM_256, FEC_NONE,  34., 38.},
> +	{ QAM_64,  FEC_NONE,  30., 34.},
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_s_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK, FEC_1_2,  7., 10.},
> +
> +	{ QPSK, FEC_2_3,  9., 12.},
> +	{ QPSK, FEC_3_4, 10., 13.},
> +	{ QPSK, FEC_5_6, 11., 14.},
> +
> +	{ QPSK, FEC_7_8, 12., 15.},
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_s2_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK,  FEC_1_2,   9.,  12.},
> +	{ QPSK,  FEC_2_3,  11.,  14.},
> +	{ QPSK,  FEC_3_4,  12.,  15.},
> +	{ QPSK,  FEC_5_6,  12.,  15.},
> +	{ QPSK,  FEC_8_9,  13.,  16.},
> +	{ QPSK,  FEC_9_10, 13.5, 16.5},
> +	{ PSK_8, FEC_2_3,  14.5, 17.5},
> +	{ PSK_8, FEC_3_4,  16.,  19.},
> +	{ PSK_8, FEC_5_6,  17.5, 20.5},
> +	{ PSK_8, FEC_8_9,  19.,  22.},
> +};
> +
> +static struct dvb_dummy_tuner_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{   QPSK, FEC_1_2,  4.1,  5.9},
> +	{   QPSK, FEC_2_3,  6.1,  9.6},
> +	{   QPSK, FEC_3_4,  7.2, 12.4},
> +	{   QPSK, FEC_5_6,  8.5, 15.6},
> +	{   QPSK, FEC_7_8,  9.2, 17.5},
> +
> +	{ QAM_16, FEC_1_2,  9.8, 11.8},
> +	{ QAM_16, FEC_2_3, 12.1, 15.3},
> +	{ QAM_16, FEC_3_4, 13.4, 18.1},
> +	{ QAM_16, FEC_5_6, 14.8, 21.3},
> +	{ QAM_16, FEC_7_8, 15.7, 23.6},
> +
> +	{ QAM_64, FEC_1_2, 14.0, 16.0},
> +	{ QAM_64, FEC_2_3, 19.9, 25.4},
> +	{ QAM_64, FEC_3_4, 24.9, 27.9},
> +	{ QAM_64, FEC_5_6, 21.3, 23.3},
> +	{ QAM_64, FEC_7_8, 22.0, 24.0},
> +};
> +
> +enum dvb_dummy_tuner_lock_status {
> +	STATUS_NO_LOCK = 0,
> +	STATUS_LOCKED = TUNER_STATUS_LOCKED,
> +};
> +
> +enum dvb_dummy_tuner_power_status {
> +	STATUS_UNKNOWN,
> +	STATUS_ASLEEP,
> +	STATUS_ACTIVE
> +};
> +
> +enum dvb_dummy_tuner_frequency_shift_status {
> +	EXACT_MATCH,
> +	CLOSE_MATCH,
> +	TOO_FAR_OFF
> +};
> +
> +struct dvb_dummy_tuner_hardware_state {
> +	enum dvb_dummy_tuner_power_status power_status;
> +	enum dvb_dummy_tuner_lock_status lock_status;
> +	u32 if_frequency;
> +	u32 tuned_frequency;
> +	u32 bandwidth;
> +};
> +
> +struct dvb_dummy_tuner_dev {
> +	struct dvb_frontend *fe;
> +	struct dvb_dummy_tuner_hardware_state hw_state;
> +	struct dvb_dummy_tuner_config config;
> +};
> +
> +static struct dvb_dummy_tuner_dev*
> +dvb_dummy_tuner_get_dev(struct dvb_frontend *fe)
> +{
> +	struct i2c_client *client = fe->tuner_priv;
> +
> +	return (struct dvb_dummy_tuner_dev *)i2c_get_clientdata(client);
> +}
> +
> +static bool dvb_dummy_tuner_check_frequency_shift(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +	u32 *valid_frequencies = NULL;
> +	u32 array_sz = 0;
> +	u32 i;
> +
> +	switch (c->delivery_system) {
> +	case SYS_DVBT:
> +	case SYS_DVBT2:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_t_frequencies;
> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_t_frequencies);
> +		break;
> +	case SYS_DVBS:
> +	case SYS_DVBS2:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_s_frequencies;
> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_s_frequencies);
> +		break;
> +	case SYS_DVBC_ANNEX_A:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_c_frequencies;
> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_c_frequencies);
> +		break;
> +
> +	default:
> +		pr_warn("%s: unsupported delivery system: %u\n",
> +			__func__,
> +			c->delivery_system);
> +		break;
> +	}
> +
> +	for (i = 0; i < array_sz; i++) {
> +		if (c->frequency == valid_frequencies[i])
> +			return EXACT_MATCH;
> +		else if (c->frequency >= valid_frequencies[i] -
> +			 config.max_frequency_shift_hz &&
> +			 c->frequency <= valid_frequencies[i] +
> +			 config.max_frequency_shift_hz)
> +			return CLOSE_MATCH;
> +	}
> +
> +	return TOO_FAR_OFF;
> +}
> +
> +static int
> +dvb_dummy_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength)
> +{
> +	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +	struct dvb_dummy_tuner_cnr_to_qual_s *cnr2qual = NULL;
> +	u32 array_size = 0;
> +	enum dvb_dummy_tuner_frequency_shift_status shift_status;
> +	u32 i;
> +
> +	shift_status = dvb_dummy_tuner_check_frequency_shift(fe);
> +
> +	switch (c->delivery_system) {
> +	case SYS_DVBT:
> +	case SYS_DVBT2:
> +		cnr2qual = dvb_t_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_t_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS:
> +		cnr2qual = dvb_s_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS2:
> +		cnr2qual = dvb_s2_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s2_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBC_ANNEX_A:
> +		cnr2qual = dvb_c_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_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) {
> +			*strength = (shift_status == EXACT_MATCH) ?
> +				    cnr2qual[i].cnr_good :
> +				    (shift_status == CLOSE_MATCH) ?
> +				    cnr2qual[i].cnr_ok :
> +				    cnr2qual[i].cnr_ok -
> +				    (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok);
> +			return 0;
> +		}
> +
> +	}
> +
> +	/* default to some random values if we can't match against the table */
> +	*strength = (shift_status == EXACT_MATCH) ? 34 : 10;
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_init(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_power_up_delay_msec);
> +
> +	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
> +	tuner_dev->hw_state.if_frequency = 5000;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_sleep(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_sleep_delay_msec);
> +	tuner_dev->hw_state.power_status = STATUS_ASLEEP;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_suspend(struct dvb_frontend *fe)
> +{
> +	return dvb_dummy_tuner_sleep(fe);
> +}
> +
> +static int dvb_dummy_tuner_resume(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_resume_delay_msec);
> +	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_set_params(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_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;
> +
> +	msleep_interruptible(config.mock_tune_delay_msec);
> +
> +	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 = STATUS_NO_LOCK;
> +		return -EINVAL;
> +	}
> +
> +	tuner_dev->hw_state.tuned_frequency = c->frequency;
> +	tuner_dev->hw_state.bandwidth = c->bandwidth_hz;
> +	tuner_dev->hw_state.lock_status = STATUS_LOCKED;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_set_config(struct dvb_frontend *fe,
> +				      void *priv_cfg)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	WARN_ON(sizeof(tuner_dev->config) != sizeof(*priv_cfg));
> +	memcpy(&tuner_dev->config, priv_cfg, sizeof(*priv_cfg));
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_frequency(struct dvb_frontend *fe,
> +					 u32 *frequency)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*frequency = tuner_dev->hw_state.tuned_frequency;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_bandwidth(struct dvb_frontend *fe,
> +					 u32 *bandwidth)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*bandwidth = tuner_dev->hw_state.bandwidth;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_if_frequency(struct dvb_frontend *fe,
> +					    u32 *frequency)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*frequency = tuner_dev->hw_state.if_frequency;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_status(struct dvb_frontend *fe, u32 *status)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*status = tuner_dev->hw_state.lock_status;
> +
> +	return 0;
> +}
> +
> +static const struct dvb_tuner_ops dvb_dummy_tuner_ops = {
> +	.init = dvb_dummy_tuner_init,
> +	.sleep = dvb_dummy_tuner_sleep,
> +	.suspend = dvb_dummy_tuner_suspend,
> +	.resume = dvb_dummy_tuner_resume,
> +	.set_params = dvb_dummy_tuner_set_params,
> +	.set_config = dvb_dummy_tuner_set_config,
> +	.get_bandwidth = dvb_dummy_tuner_get_bandwidth,
> +	.get_frequency = dvb_dummy_tuner_get_frequency,
> +	.get_if_frequency = dvb_dummy_tuner_get_if_frequency,
> +	.get_status = dvb_dummy_tuner_get_status,
> +	.get_rf_strength = dvb_dummy_tuner_get_signal_strength
> +};
> +
> +static const struct i2c_device_id dvb_dummy_tuner_i2c_id_table[] = {
> +	{"dvb_dummy_tuner", 0},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, dvb_dummy_tuner_i2c_id_table);
> +
> +static int dvb_dummy_tuner_i2c_probe(struct i2c_client *client,
> +				     const struct i2c_device_id *id)
> +{
> +	struct dvb_dummy_tuner_config *config = client->dev.platform_data;
> +	struct dvb_frontend *fe = config->fe;
> +	struct dvb_dummy_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,
> +	       &dvb_dummy_tuner_ops,
> +	       sizeof(struct dvb_tuner_ops));
> +
> +	fe->tuner_priv = client;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_i2c_remove(struct i2c_client *client)
> +{
> +	struct dvb_dummy_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 dvb_dummy_tuner_i2c_driver = {
> +	.driver = {
> +		.name = "dvb_dummy_tuner",
> +		.suppress_bind_attrs = true,
> +	},
> +	.probe		= dvb_dummy_tuner_i2c_probe,
> +	.remove		= dvb_dummy_tuner_i2c_remove,
> +	.id_table	= dvb_dummy_tuner_i2c_id_table,
> +};
> +module_i2c_driver(dvb_dummy_tuner_i2c_driver);

I suspect as a dummy tuner, this should be a platform driver or such
rather than an I2C driver, as I assume there is not actual bus or device
to bind to, and it would then require 'hogging' an available I2C address.



> +
> +MODULE_DESCRIPTION("DVB Dummy Tuner");
> +MODULE_AUTHOR("Daniel W. S. Almeida");
> +MODULE_LICENSE("GPL");
> 


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [Linux-kernel-mentees] [RFC 1/3] media: dvb_dummy_tuner: implement driver skeleton
@ 2020-03-18  8:54     ` Kieran Bingham
  0 siblings, 0 replies; 21+ messages in thread
From: Kieran Bingham @ 2020-03-18  8:54 UTC (permalink / raw)
  To: Daniel W. S. Almeida, mchehab, sean, kstewart, allison, tglx
  Cc: linux-kernel-mentees, linux-media

Hi Daniel,

On 18/03/2020 06:00, Daniel W. S. Almeida wrote:
> From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
> 
> 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.

Interesting project :-)

> Signed-off-by: Daniel W. S. Almeida <dwlsalmeida@gmail.com>
> ---
>  drivers/media/tuners/Kconfig           |   9 +
>  drivers/media/tuners/Makefile          |   1 +
>  drivers/media/tuners/dvb_dummy_tuner.c | 421 +++++++++++++++++++++++++
>  3 files changed, 431 insertions(+)
>  create mode 100644 drivers/media/tuners/dvb_dummy_tuner.c
> 
> diff --git a/drivers/media/tuners/Kconfig b/drivers/media/tuners/Kconfig
> index e104bb7766e1..8ad54339ceee 100644
> --- a/drivers/media/tuners/Kconfig
> +++ b/drivers/media/tuners/Kconfig
> @@ -296,4 +296,13 @@ config MEDIA_TUNER_QM1D1B0004
>  	default m if !MEDIA_SUBDRV_AUTOSELECT
>  	help
>  	  Sharp QM1D1B0004 ISDB-S tuner driver.
> +
> +config MEDIA_TUNER_DVB_DUMMY_TUNER
> +	tristate "Dummy DVB Media Tuner"
> +	depends on MEDIA_SUPPORT && I2C
> +	help
> +	  Dummy DVB media tuner driver
> +	  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.
>  endmenu
> diff --git a/drivers/media/tuners/Makefile b/drivers/media/tuners/Makefile
> index 7b4f8423501e..f98de1cf2e19 100644
> --- a/drivers/media/tuners/Makefile
> +++ b/drivers/media/tuners/Makefile
> @@ -44,5 +44,6 @@ obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o
>  obj-$(CONFIG_MEDIA_TUNER_QM1D1B0004) += qm1d1b0004.o
>  obj-$(CONFIG_MEDIA_TUNER_M88RS6000T) += m88rs6000t.o
>  obj-$(CONFIG_MEDIA_TUNER_TDA18250) += tda18250.o
> +obj-$(CONFIG_MEDIA_TUNER_DVB_DUMMY_TUNER) += dvb_dummy_tuner.o


We have other virtual drivers named "vimc, vivid, vim2m" ... should this
become "vidvb" ?

>  
>  ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
> diff --git a/drivers/media/tuners/dvb_dummy_tuner.c b/drivers/media/tuners/dvb_dummy_tuner.c
> new file mode 100644
> index 000000000000..1408a8c84be2
> --- /dev/null
> +++ b/drivers/media/tuners/dvb_dummy_tuner.c
> @@ -0,0 +1,421 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2020 Daniel W. S. Almeida <dwlsalmeida@gmail.com>
> + *
> + * 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.
> + *
> + */
> +
> +#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>
> +
> +struct dvb_dummy_tuner_config {
> +	struct dvb_frontend *fe;
> +	u32 mock_power_up_delay_msec;
> +	u32 mock_tune_delay_msec;
> +	u32 mock_sleep_delay_msec;
> +	u32 mock_resume_delay_msec;
> +	u32 dummy_valid_dvb_t_frequencies[8];
> +	u32 dummy_valid_dvb_c_frequencies[8];
> +	u32 dummy_valid_dvb_s_frequencies[8];
> +	u8  max_frequency_shift_hz;
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s {
> +	/* attempt to use the same values as libdvbv5 */
> +	u32 modulation;
> +	u32 fec;
> +	u32 cnr_ok, cnr_good;
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_c_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QAM_256, FEC_NONE,  34., 38.},
> +	{ QAM_64,  FEC_NONE,  30., 34.},
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_s_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK, FEC_1_2,  7., 10.},
> +
> +	{ QPSK, FEC_2_3,  9., 12.},
> +	{ QPSK, FEC_3_4, 10., 13.},
> +	{ QPSK, FEC_5_6, 11., 14.},
> +
> +	{ QPSK, FEC_7_8, 12., 15.},
> +};
> +
> +struct dvb_dummy_tuner_cnr_to_qual_s dvb_s2_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{ QPSK,  FEC_1_2,   9.,  12.},
> +	{ QPSK,  FEC_2_3,  11.,  14.},
> +	{ QPSK,  FEC_3_4,  12.,  15.},
> +	{ QPSK,  FEC_5_6,  12.,  15.},
> +	{ QPSK,  FEC_8_9,  13.,  16.},
> +	{ QPSK,  FEC_9_10, 13.5, 16.5},
> +	{ PSK_8, FEC_2_3,  14.5, 17.5},
> +	{ PSK_8, FEC_3_4,  16.,  19.},
> +	{ PSK_8, FEC_5_6,  17.5, 20.5},
> +	{ PSK_8, FEC_8_9,  19.,  22.},
> +};
> +
> +static struct dvb_dummy_tuner_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
> +	/* from libdvbv5 source code */
> +	{   QPSK, FEC_1_2,  4.1,  5.9},
> +	{   QPSK, FEC_2_3,  6.1,  9.6},
> +	{   QPSK, FEC_3_4,  7.2, 12.4},
> +	{   QPSK, FEC_5_6,  8.5, 15.6},
> +	{   QPSK, FEC_7_8,  9.2, 17.5},
> +
> +	{ QAM_16, FEC_1_2,  9.8, 11.8},
> +	{ QAM_16, FEC_2_3, 12.1, 15.3},
> +	{ QAM_16, FEC_3_4, 13.4, 18.1},
> +	{ QAM_16, FEC_5_6, 14.8, 21.3},
> +	{ QAM_16, FEC_7_8, 15.7, 23.6},
> +
> +	{ QAM_64, FEC_1_2, 14.0, 16.0},
> +	{ QAM_64, FEC_2_3, 19.9, 25.4},
> +	{ QAM_64, FEC_3_4, 24.9, 27.9},
> +	{ QAM_64, FEC_5_6, 21.3, 23.3},
> +	{ QAM_64, FEC_7_8, 22.0, 24.0},
> +};
> +
> +enum dvb_dummy_tuner_lock_status {
> +	STATUS_NO_LOCK = 0,
> +	STATUS_LOCKED = TUNER_STATUS_LOCKED,
> +};
> +
> +enum dvb_dummy_tuner_power_status {
> +	STATUS_UNKNOWN,
> +	STATUS_ASLEEP,
> +	STATUS_ACTIVE
> +};
> +
> +enum dvb_dummy_tuner_frequency_shift_status {
> +	EXACT_MATCH,
> +	CLOSE_MATCH,
> +	TOO_FAR_OFF
> +};
> +
> +struct dvb_dummy_tuner_hardware_state {
> +	enum dvb_dummy_tuner_power_status power_status;
> +	enum dvb_dummy_tuner_lock_status lock_status;
> +	u32 if_frequency;
> +	u32 tuned_frequency;
> +	u32 bandwidth;
> +};
> +
> +struct dvb_dummy_tuner_dev {
> +	struct dvb_frontend *fe;
> +	struct dvb_dummy_tuner_hardware_state hw_state;
> +	struct dvb_dummy_tuner_config config;
> +};
> +
> +static struct dvb_dummy_tuner_dev*
> +dvb_dummy_tuner_get_dev(struct dvb_frontend *fe)
> +{
> +	struct i2c_client *client = fe->tuner_priv;
> +
> +	return (struct dvb_dummy_tuner_dev *)i2c_get_clientdata(client);
> +}
> +
> +static bool dvb_dummy_tuner_check_frequency_shift(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +	u32 *valid_frequencies = NULL;
> +	u32 array_sz = 0;
> +	u32 i;
> +
> +	switch (c->delivery_system) {
> +	case SYS_DVBT:
> +	case SYS_DVBT2:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_t_frequencies;
> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_t_frequencies);
> +		break;
> +	case SYS_DVBS:
> +	case SYS_DVBS2:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_s_frequencies;
> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_s_frequencies);
> +		break;
> +	case SYS_DVBC_ANNEX_A:
> +		valid_frequencies =
> +			config.dummy_valid_dvb_c_frequencies;
> +		array_sz = ARRAY_SIZE(config.dummy_valid_dvb_c_frequencies);
> +		break;
> +
> +	default:
> +		pr_warn("%s: unsupported delivery system: %u\n",
> +			__func__,
> +			c->delivery_system);
> +		break;
> +	}
> +
> +	for (i = 0; i < array_sz; i++) {
> +		if (c->frequency == valid_frequencies[i])
> +			return EXACT_MATCH;
> +		else if (c->frequency >= valid_frequencies[i] -
> +			 config.max_frequency_shift_hz &&
> +			 c->frequency <= valid_frequencies[i] +
> +			 config.max_frequency_shift_hz)
> +			return CLOSE_MATCH;
> +	}
> +
> +	return TOO_FAR_OFF;
> +}
> +
> +static int
> +dvb_dummy_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength)
> +{
> +	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> +	struct dvb_dummy_tuner_cnr_to_qual_s *cnr2qual = NULL;
> +	u32 array_size = 0;
> +	enum dvb_dummy_tuner_frequency_shift_status shift_status;
> +	u32 i;
> +
> +	shift_status = dvb_dummy_tuner_check_frequency_shift(fe);
> +
> +	switch (c->delivery_system) {
> +	case SYS_DVBT:
> +	case SYS_DVBT2:
> +		cnr2qual = dvb_t_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_t_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS:
> +		cnr2qual = dvb_s_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBS2:
> +		cnr2qual = dvb_s2_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_s2_cnr_2_qual);
> +		break;
> +
> +	case SYS_DVBC_ANNEX_A:
> +		cnr2qual = dvb_c_cnr_2_qual;
> +		array_size = ARRAY_SIZE(dvb_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) {
> +			*strength = (shift_status == EXACT_MATCH) ?
> +				    cnr2qual[i].cnr_good :
> +				    (shift_status == CLOSE_MATCH) ?
> +				    cnr2qual[i].cnr_ok :
> +				    cnr2qual[i].cnr_ok -
> +				    (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok);
> +			return 0;
> +		}
> +
> +	}
> +
> +	/* default to some random values if we can't match against the table */
> +	*strength = (shift_status == EXACT_MATCH) ? 34 : 10;
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_init(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_power_up_delay_msec);
> +
> +	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
> +	tuner_dev->hw_state.if_frequency = 5000;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_sleep(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_sleep_delay_msec);
> +	tuner_dev->hw_state.power_status = STATUS_ASLEEP;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_suspend(struct dvb_frontend *fe)
> +{
> +	return dvb_dummy_tuner_sleep(fe);
> +}
> +
> +static int dvb_dummy_tuner_resume(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_tuner_config config = tuner_dev->config;
> +
> +	msleep_interruptible(config.mock_resume_delay_msec);
> +	tuner_dev->hw_state.power_status = STATUS_ACTIVE;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_set_params(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +	struct dvb_dummy_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;
> +
> +	msleep_interruptible(config.mock_tune_delay_msec);
> +
> +	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 = STATUS_NO_LOCK;
> +		return -EINVAL;
> +	}
> +
> +	tuner_dev->hw_state.tuned_frequency = c->frequency;
> +	tuner_dev->hw_state.bandwidth = c->bandwidth_hz;
> +	tuner_dev->hw_state.lock_status = STATUS_LOCKED;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_set_config(struct dvb_frontend *fe,
> +				      void *priv_cfg)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	WARN_ON(sizeof(tuner_dev->config) != sizeof(*priv_cfg));
> +	memcpy(&tuner_dev->config, priv_cfg, sizeof(*priv_cfg));
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_frequency(struct dvb_frontend *fe,
> +					 u32 *frequency)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*frequency = tuner_dev->hw_state.tuned_frequency;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_bandwidth(struct dvb_frontend *fe,
> +					 u32 *bandwidth)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*bandwidth = tuner_dev->hw_state.bandwidth;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_if_frequency(struct dvb_frontend *fe,
> +					    u32 *frequency)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*frequency = tuner_dev->hw_state.if_frequency;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_get_status(struct dvb_frontend *fe, u32 *status)
> +{
> +	struct dvb_dummy_tuner_dev *tuner_dev = dvb_dummy_tuner_get_dev(fe);
> +
> +	*status = tuner_dev->hw_state.lock_status;
> +
> +	return 0;
> +}
> +
> +static const struct dvb_tuner_ops dvb_dummy_tuner_ops = {
> +	.init = dvb_dummy_tuner_init,
> +	.sleep = dvb_dummy_tuner_sleep,
> +	.suspend = dvb_dummy_tuner_suspend,
> +	.resume = dvb_dummy_tuner_resume,
> +	.set_params = dvb_dummy_tuner_set_params,
> +	.set_config = dvb_dummy_tuner_set_config,
> +	.get_bandwidth = dvb_dummy_tuner_get_bandwidth,
> +	.get_frequency = dvb_dummy_tuner_get_frequency,
> +	.get_if_frequency = dvb_dummy_tuner_get_if_frequency,
> +	.get_status = dvb_dummy_tuner_get_status,
> +	.get_rf_strength = dvb_dummy_tuner_get_signal_strength
> +};
> +
> +static const struct i2c_device_id dvb_dummy_tuner_i2c_id_table[] = {
> +	{"dvb_dummy_tuner", 0},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, dvb_dummy_tuner_i2c_id_table);
> +
> +static int dvb_dummy_tuner_i2c_probe(struct i2c_client *client,
> +				     const struct i2c_device_id *id)
> +{
> +	struct dvb_dummy_tuner_config *config = client->dev.platform_data;
> +	struct dvb_frontend *fe = config->fe;
> +	struct dvb_dummy_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,
> +	       &dvb_dummy_tuner_ops,
> +	       sizeof(struct dvb_tuner_ops));
> +
> +	fe->tuner_priv = client;
> +
> +	return 0;
> +}
> +
> +static int dvb_dummy_tuner_i2c_remove(struct i2c_client *client)
> +{
> +	struct dvb_dummy_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 dvb_dummy_tuner_i2c_driver = {
> +	.driver = {
> +		.name = "dvb_dummy_tuner",
> +		.suppress_bind_attrs = true,
> +	},
> +	.probe		= dvb_dummy_tuner_i2c_probe,
> +	.remove		= dvb_dummy_tuner_i2c_remove,
> +	.id_table	= dvb_dummy_tuner_i2c_id_table,
> +};
> +module_i2c_driver(dvb_dummy_tuner_i2c_driver);

I suspect as a dummy tuner, this should be a platform driver or such
rather than an I2C driver, as I assume there is not actual bus or device
to bind to, and it would then require 'hogging' an available I2C address.



> +
> +MODULE_DESCRIPTION("DVB Dummy Tuner");
> +MODULE_AUTHOR("Daniel W. S. Almeida");
> +MODULE_LICENSE("GPL");
> 

_______________________________________________
Linux-kernel-mentees mailing list
Linux-kernel-mentees@lists.linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/linux-kernel-mentees

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC 1/3] media: dvb_dummy_tuner: implement driver skeleton
  2020-03-18  8:54     ` [Linux-kernel-mentees] " Kieran Bingham
@ 2020-03-18  9:13       ` Mauro Carvalho Chehab
  -1 siblings, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2020-03-18  9:13 UTC (permalink / raw)
  To: Kieran Bingham
  Cc: Daniel W. S. Almeida, sean, kstewart, allison, tglx, linux-media,
	skhan, linux-kernel-mentees

Em Wed, 18 Mar 2020 08:54:18 +0000
Kieran Bingham <kieran.bingham+renesas@ideasonboard.com> escreveu:

> Hi Daniel,
> 
> On 18/03/2020 06:00, Daniel W. S. Almeida wrote:
> > From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
> > 
> > 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.  

...

> > +static struct i2c_driver dvb_dummy_tuner_i2c_driver = {
> > +	.driver = {
> > +		.name = "dvb_dummy_tuner",
> > +		.suppress_bind_attrs = true,
> > +	},
> > +	.probe		= dvb_dummy_tuner_i2c_probe,
> > +	.remove		= dvb_dummy_tuner_i2c_remove,
> > +	.id_table	= dvb_dummy_tuner_i2c_id_table,
> > +};
> > +module_i2c_driver(dvb_dummy_tuner_i2c_driver);  
> 
> I suspect as a dummy tuner, this should be a platform driver or such
> rather than an I2C driver, as I assume there is not actual bus or device
> to bind to, and it would then require 'hogging' an available I2C address.

Interesting point. Yet, I guess that the best it to keep it as i2c. 

The DVB framework splits the driver on 3 parts:

	- a frontend driver;
	- a tuner driver;
	- a bridge driver.

on all real hardware, the frontend and bridge drivers are i2c (with
very few exceptions, where it is integrated on the same chipset and
don't use an i2c-like bus internally).

Keeping it as an I2C driver helps to keep it closer to a real hardware,
with would help driver developers to use it as a reference for their
projects.

Thanks,
Mauro

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [Linux-kernel-mentees] [RFC 1/3] media: dvb_dummy_tuner: implement driver skeleton
@ 2020-03-18  9:13       ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2020-03-18  9:13 UTC (permalink / raw)
  To: Kieran Bingham
  Cc: kstewart, sean, tglx, linux-kernel-mentees, Daniel W. S. Almeida,
	allison, linux-media

Em Wed, 18 Mar 2020 08:54:18 +0000
Kieran Bingham <kieran.bingham+renesas@ideasonboard.com> escreveu:

> Hi Daniel,
> 
> On 18/03/2020 06:00, Daniel W. S. Almeida wrote:
> > From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
> > 
> > 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.  

...

> > +static struct i2c_driver dvb_dummy_tuner_i2c_driver = {
> > +	.driver = {
> > +		.name = "dvb_dummy_tuner",
> > +		.suppress_bind_attrs = true,
> > +	},
> > +	.probe		= dvb_dummy_tuner_i2c_probe,
> > +	.remove		= dvb_dummy_tuner_i2c_remove,
> > +	.id_table	= dvb_dummy_tuner_i2c_id_table,
> > +};
> > +module_i2c_driver(dvb_dummy_tuner_i2c_driver);  
> 
> I suspect as a dummy tuner, this should be a platform driver or such
> rather than an I2C driver, as I assume there is not actual bus or device
> to bind to, and it would then require 'hogging' an available I2C address.

Interesting point. Yet, I guess that the best it to keep it as i2c. 

The DVB framework splits the driver on 3 parts:

	- a frontend driver;
	- a tuner driver;
	- a bridge driver.

on all real hardware, the frontend and bridge drivers are i2c (with
very few exceptions, where it is integrated on the same chipset and
don't use an i2c-like bus internally).

Keeping it as an I2C driver helps to keep it closer to a real hardware,
with would help driver developers to use it as a reference for their
projects.

Thanks,
Mauro
_______________________________________________
Linux-kernel-mentees mailing list
Linux-kernel-mentees@lists.linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/linux-kernel-mentees

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC 3/3] media: dvb_dummy_fe.c: write PSI information into DMX buffer
  2020-03-18  6:00   ` [Linux-kernel-mentees] " Daniel W. S. Almeida
@ 2020-03-18 10:56     ` Mauro Carvalho Chehab
  -1 siblings, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2020-03-18 10:56 UTC (permalink / raw)
  To: Daniel W. S. Almeida
  Cc: sean, kstewart, allison, tglx, linux-media, skhan, linux-kernel-mentees

Em Wed, 18 Mar 2020 03:00:18 -0300
"Daniel W. S. Almeida" <dwlsalmeida@gmail.com> escreveu:

> From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
> 
> Periodically feed the demux with PSI packets. This is needed so userspace
> apps can retrieve information about the Transport Stream and eventually
> tune into a (dummy) channel.
> 
> Currently this commit adds support for working with 3 PSI tables:
> PAT, PMT and SDT. A dummy service with a dummy program is hardcoded
> in the driver code.
> ---
>  drivers/media/dvb-frontends/dvb_dummy_fe.c | 1409 +++++++++++++++++++-

I would place the PSI generator code on a separate file. It would make
easier for people to re-use just the dummy driver code to not have the
generator code mixed into it.

>  1 file changed, 1398 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/media/dvb-frontends/dvb_dummy_fe.c b/drivers/media/dvb-frontends/dvb_dummy_fe.c
> index 726c964a523d..10b1bd22c15d 100644
> --- a/drivers/media/dvb-frontends/dvb_dummy_fe.c
> +++ b/drivers/media/dvb-frontends/dvb_dummy_fe.c
> @@ -11,11 +11,1408 @@
>  #include <linux/slab.h>
>  #include <linux/workqueue.h>
>  #include <linux/random.h>
> +#include <linux/crc32.h>
> +#include <linux/string.h>
>  
>  #include <media/dvb_frontend.h>
> -#include "dvb_dummy_fe.h"
>  
>  
> +#define CRC_SIZE_IN_BYTES 32
> +#define NUM_CHANNELS 1
> +#define TRANSPORT_STREAM_ID 0x744 /* a single stream */
> +#define TS_SYNC_BYTE 0x47
> +#define TS_PACKET_LEN 188
> +#define TS_PAYLOAD_LEN 184
> +#define DMX_BUF_LEN (20 * TS_PACKET_LEN)
> +
> +
> +enum dvb_dummy_descriptors {
> +	SERVICE_DESCRIPTOR = 0x48,
> +};
> +
> +enum dvb_dummy_stream_types {
> +	ISO_IEC_13818_3_Audio = 0x4,
> +};
> +
> +struct dvb_dummy_channel {
> +	u16 transport_stream_id;
> +	struct dvb_dummy_table_sdt_service *service;
> +	u16 program_num;
> +	/* a single program with one or more streams associated with it */
> +	struct dvb_dummy_table_pat_program *program;
> +	struct dvb_dummy_table_pmt_stream *streams;
> +};
> +
> +struct dvb_dummy_desc {
> +	u8 type;
> +	u8 length;
> +	struct dvb_dummy_desc *next;
> +	u8 *data;
> +} __packed;
> +
> +struct dvb_dummy_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 dvb_dummy_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 dvb_dummy_table_pat_program {
> +	u16 service_id;
> +	union {
> +		u16 bitfield;
> +		struct {
> +			u16 pid:13;
> +			u8  reserved:3;
> +		} __packed;
> +	} __packed;
> +	struct dvb_dummy_table_pat_program *next;
> +} __packed;
> +
> +struct dvb_dummy_table_pat {
> +	struct dvb_dummy_table_header header;
> +	u16 programs;
> +	struct dvb_dummy_table_pat_program *program;
> +} __packed;
> +
> +struct dvb_dummy_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 dvb_dummy_desc *descriptor;
> +	struct dvb_dummy_table_sdt_service *next;
> +} __packed;
> +
> +struct dvb_dummy_table_sdt {
> +	struct dvb_dummy_table_header header;
> +	u16 network_id;
> +	u8  reserved;
> +	struct dvb_dummy_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 dvb_dummy_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 dvb_dummy_desc *descriptor;
> +	struct dvb_dummy_table_pmt_stream *next;
> +} __packed;
> +
> +struct dvb_dummy_table_pmt {
> +	struct dvb_dummy_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 dvb_dummy_desc *descriptor;
> +	struct dvb_dummy_table_pmt_stream *stream;
> +} __packed;
> +
> +struct dvb_dummy_mpeg_ts_adaption {
> +	u8 length;
> +	struct {
> +		u8 extension:1;
> +		u8 private_data:1;
> +		u8 splicing_point:1;
> +		u8 OPCR:1;
> +		u8 PCR:1;
> +		u8 priority:1;
> +		u8 random_access:1;
> +		u8 discontinued:1;
> +	} __packed;
> +	u8 data[];
> +} __packed;
> +
> +
> +struct dvb_dummy_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 dvb_dummy_mpeg_ts_adaption adaption[];
> +} __packed;
> +
> +struct dvb_dummy_fe_config {
> +	/* probability of losing the lock due to low snr */
> +	u8 drop_tslock_probability_on_low_snr;
> +};
> +
> +struct dvb_dummy_fe_state {
> +	struct dvb_frontend frontend;
> +	struct dvb_dummy_fe_config config;
> +	struct delayed_work poll_snr;
> +	enum fe_status status;
> +	struct dvb_dummy_channel *channels;
> +};

I would place the enums, defines and struct definitions above
on a separate header file.

> +
> +static u32 dvb_dummy_fe_mpeg_ts_psi_write_stuffing(void *to, u32 len)
> +{
> +	memset(to, 0xFF, len);

Please use lower case.

> +	return len;
> +}
> +
> +static u32 dvb_dummy_fe_mpeg_ts_psi_write_into(void *to,
> +					    const void *from,
> +					    size_t len,
> +					    u32 offset,
> +					    u16 pid,
> +					    bool new_psi_section,
> +					    u8 *continuity_counter,
> +					    bool is_crc)

Looking on this code and the rest function calls, I think you could
define a struct with the stuff that it is needed for writing
a PSI.

> +{
> +	/*   Packetize PSI sections into TS packets:
> +	 * - push a TS header (4bytes) every 184 bytes
> +	 * - manage the continuity_counter
> +	 * - add stuffing after the CRC
> +	 */

Same as commented on patch 1/3: the first line of a multi-line comment 
should be just:

	/*

> +
> +	u32 nbytes_past_boundary = (offset % TS_PACKET_LEN);
> +	bool aligned = nbytes_past_boundary == 0;
> +	bool split = len > TS_PAYLOAD_LEN;
> +	u32 payload_write_len = (split) ? TS_PAYLOAD_LEN : len;
> +
> +	struct dvb_dummy_mpeg_ts ts_header = {0};
> +
> +	u32 nbytes = 0; /* number of bytes written by this function */
> +
> +	if (new_psi_section && !aligned) {
> +		/* must pad the buffer with the complement to get a
> +		 * multiple of 188
> +		 */
> +		nbytes +=
> +			dvb_dummy_fe_mpeg_ts_psi_write_stuffing(
> +						to + offset + nbytes,
> +						TS_PACKET_LEN -
> +						nbytes_past_boundary);
> +
> +		/* 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 = pid;
> +		ts_header.priority = 0;
> +		ts_header.scrambling = 0; /* not scrambled */
> +		ts_header.continuity_counter = *continuity_counter;
> +		ts_header.payload_start = 0; /* no adaption for now */
> +
> +
> +		/* copy the header minus the adaption pointer*/
> +		memcpy(to + offset + nbytes,
> +		&ts_header,
> +		sizeof(ts_header) -
> +		sizeof(struct dvb_dummy_mpeg_ts_adaption *));
> +
> +		nbytes +=
> +		sizeof(ts_header) -
> +		sizeof(struct dvb_dummy_mpeg_ts_adaption *);
> +	}
> +
> +	if (new_psi_section) {
> +		/* write the pointer_field in the first byte of the payload */
> +		memset(to + offset + nbytes, 0x0, 1);
> +		nbytes += 1;
> +		--payload_write_len; /* one byte was used by the pointer field*/
> +	}
> +
> +	/* write as much of the payload as we possibly can */
> +	memcpy(to + offset + nbytes, from, payload_write_len);
> +	nbytes += payload_write_len;
> +
> +	if (split) {
> +		/* next TS packet keeps the same PID, but increments the
> +		 * counter
> +		 */
> +		++(*continuity_counter);
> +		/* 'nbytes' written from a total of 'len' requested*/
> +		len -= nbytes;
> +		/* recursively write the rest of the data until we do not
> +		 * need to split it anymore
> +		 */
> +		nbytes +=
> +		dvb_dummy_fe_mpeg_ts_psi_write_into(to,
> +						    from + nbytes,
> +						    len,
> +						    offset + nbytes,
> +						    pid,
> +						    false,
> +						    continuity_counter,
> +						    is_crc);
> +	}
> +
> +	if (is_crc)
> +		/* as the CRC is last in the section, stuff the rest of the
> +		 * packet if there is any remaining space in there
> +		 */
> +
> +		nbytes +=
> +		dvb_dummy_fe_mpeg_ts_psi_write_stuffing(to + nbytes,
> +							TS_PAYLOAD_LEN -
> +							nbytes);
> +
> +	return nbytes;
> +}
> +
> +static u32 table_section_crc32_write_into(char *buf,
> +					  u32 offset,
> +					  u16 pid,
> +					  u8 *continuity_counter)
> +{
> +	/* the CRC is the last entry in the section */
> +	u32 nbytes = 0;
> +	u32 crc;
> +
> +	crc = crc32(0, buf, offset);
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +					    &crc,
> +					    CRC_SIZE_IN_BYTES,
> +					    offset,
> +					    pid,
> +					    false,
> +					    continuity_counter,
> +					    true);
> +
> +
> +	return nbytes;
> +}
> +
> +static struct dvb_dummy_desc*
> +dvb_dummy_fe_mpeg_descriptor_init(struct dvb_dummy_desc *head,
> +				  u8 type,
> +				  u8 length)
> +{
> +	struct dvb_dummy_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL);
> +
> +	desc->type = type;
> +	desc->length = length;
> +	desc->data = kzalloc(length, GFP_KERNEL);
> +
> +	if (head) {
> +		while (head->next)
> +			head = head->next;
> +
> +		head->next = desc;
> +	}
> +
> +	return desc;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_descriptor_destroy(struct dvb_dummy_desc *desc)
> +{
> +
> +	struct dvb_dummy_desc *curr = desc;
> +	struct dvb_dummy_desc *tmp = NULL;
> +
> +	while (curr) {
> +		tmp = curr;
> +		curr = curr->next;
> +		kfree(tmp->data);
> +		kfree(tmp);
> +	}
> +
> +}
> +
> +static u32
> +dvb_dummy_fe_mpeg_descriptor_compute_length(struct dvb_dummy_desc *desc)
> +{
> +	u32 length = 0;
> +
> +	if (!desc)
> +		return 0;
> +
> +	while (desc) {
> +		length += desc->length;
> +		desc = desc->next;
> +	}
> +
> +	return length;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_descriptor_assign(struct dvb_dummy_desc *desc,
> +				    struct dvb_dummy_desc *desc_ptr,
> +				    u16 *desc_length)
> +{
> +
> +	if (desc_ptr)
> +		/* clean the old data */
> +		dvb_dummy_fe_mpeg_descriptor_destroy(desc_ptr);
> +
> +	*desc_length = dvb_dummy_fe_mpeg_descriptor_compute_length(desc);
> +	desc_ptr = desc;
> +}
> +
> +static u32 dvb_dummy_fe_mpeg_desc_write_into(char *buf,
> +					     u32 offset,
> +					     struct dvb_dummy_desc *desc,
> +					     u16 pid,
> +					     u8 *continuity_counter)
> +{
> +	u32 nbytes = 0; /* the number of bytes written by this function */
> +
> +	/* u8 type + u8 length */
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +					    desc,
> +					    16,
> +					    offset,
> +					    pid,
> +					    false,
> +					    continuity_counter,
> +					    false);
> +
> +	/* move 'from' pointer to point to u8 data[] */
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +				desc + nbytes + sizeof(struct dvb_dummy_desc *),
> +				desc->length,
> +				offset + nbytes,
> +				pid,
> +				false,
> +				continuity_counter,
> +				false);
> +
> +	return nbytes;
> +}
> +
> +static u32
> +dvb_dummy_fe_mpeg_table_header_write_into(char *buf,
> +					  u32 offset,
> +					  struct dvb_dummy_table_header *h,
> +					  u16 pid,
> +					  u8 *continuity_counter)
> +{
> +	/* the number of bytes written by this function */
> +	u32 nbytes = 0;
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +					h,
> +					sizeof(struct dvb_dummy_table_header),
> +					offset,
> +					pid,
> +					true,
> +					continuity_counter,
> +					false);
> +
> +	return nbytes;
> +}
> +
> +static u16
> +dvb_dummy_fe_mpeg_pat_table_compute_section_length
> +(struct dvb_dummy_table_pat *pat)
> +{
> +	/* see ISO/IEC 13818-1 : 2000 p.43 */
> +	u16 length = 0;
> +	u32 i;
> +
> +	length += 40;
> +
> +	for (i = 0; i < pat->programs; ++i)
> +		length += 32;
> +
> +	length += CRC_SIZE_IN_BYTES;
> +
> +	WARN_ON(length > 1021);

Please add some defines for those "magic" numbers: 40, 31, 1021, as it
makes easier for any reviewer to understand what are their meanings.

Or, even better: use sizeof().

So, for example, if you have a "struct foo_table_header", the best
would be to do:

	len += sizeof(foo_table_header);

> +	return length;
> +}
> +
> +static u16
> +dvb_dummy_fe_mpeg_pmt_table_compute_section_length
> +(struct dvb_dummy_table_pmt *pmt)
> +{
> +	/* see ISO/IEC 13818-1 : 2000 p.46 */
> +	u16 length = 0;
> +	struct dvb_dummy_table_pmt_stream *s = pmt->stream;
> +
> +	length += 72;

Same here and on other constants below.

> +	length += /* do not fail if 'desc_length' has not been computed yet */
> +		dvb_dummy_fe_mpeg_descriptor_compute_length(pmt->descriptor);

Please place this on a single line. If needed, reduce the name of the
function. For example, a name like: dvb_dummy_fe_mpeg_desc_comp_len()
would be equivalent to the big name.

> +	length += pmt->desc_length;
> +
> +	while (s) {
> +		length += 40;
> +		length +=
> +			dvb_dummy_fe_mpeg_descriptor_compute_length
> +			(s->descriptor);
> +		s = s->next;
> +	}
> +
> +	length += CRC_SIZE_IN_BYTES;
> +
> +	WARN_ON(length > 1021);
> +	return length;
> +}
> +
> +static u16
> +dvb_dummy_fe_mpeg_sdt_table_compute_section_length
> +(struct dvb_dummy_table_sdt *sdt)
> +{
> +	/* see ETSI EN 300 468 V 1.10.1 p.24 */
> +	u16 length = 0;
> +	struct dvb_dummy_table_sdt_service *s = sdt->service;
> +
> +	length += 64;
> +	while (s) {
> +		length += 40;
> +		length +=
> +		/* do not fail if 'desc_length' has not been computed yet */
> +			dvb_dummy_fe_mpeg_descriptor_compute_length
> +			(s->descriptor);
> +	}
> +
> +	length += CRC_SIZE_IN_BYTES;
> +
> +	WARN_ON(length > 1021);
> +	return length;
> +}
> +
> +static struct dvb_dummy_table_pat_program*
> +dvb_dummy_fe_mpeg_pat_program_init(struct dvb_dummy_table_pat_program *head,
> +				   u16 service_id,
> +				   u16 pid)
> +{
> +
> +	struct dvb_dummy_table_pat_program *program;
> +
> +	program = kzalloc(sizeof(struct dvb_dummy_table_pat_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;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pat_program_destroy(struct dvb_dummy_table_pat_program *p)
> +{
> +	struct dvb_dummy_table_pat_program *curr = p;
> +	struct dvb_dummy_table_pat_program *tmp = NULL;
> +
> +	while (curr) {
> +		tmp = curr;
> +		curr = curr->next;
> +		kfree(tmp);
> +	}
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pat_program_assign(struct dvb_dummy_table_pat *pat,
> +				     struct dvb_dummy_table_pat_program *p)
> +{
> +	u16 program_count = 0;
> +	struct dvb_dummy_table_pat_program *program = p;
> +
> +	if (pat->program)
> +		dvb_dummy_fe_mpeg_pat_program_destroy(pat->program);
> +
> +	while (program) {
> +		++program_count;
> +		program = program->next;
> +	}
> +
> +	pat->programs = program_count;
> +
> +	/* Recompute section length */
> +	pat->header.section_length =
> +		dvb_dummy_fe_mpeg_pat_table_compute_section_length(pat);
> +
> +	pat->program = p;
> +}
> +
> +static struct dvb_dummy_table_pat_program*
> +dvb_dummy_fe_mpeg_pat_program_concat_into_new
> +(struct dvb_dummy_channel *channels)

Hmm... please declare the header, instead, with:

	struct dvb_dummy_channel channels[NUM_CHANNELS]

as this would let gcc and static analyzers to know what's the size of
the array that will be used at the code.

(please use the same concept on other similar places)

> +{
> +	u32 i;
> +	struct dvb_dummy_table_pat_program *curr = NULL;
> +	struct dvb_dummy_table_pat_program *head = NULL;
> +	struct dvb_dummy_table_pat_program *tail = NULL;
> +
> +	for (i = 0; i < NUM_CHANNELS; ++i) {
> +		curr = channels[i].program;
> +
> +		if (!curr)
> +			continue;
> +
> +		while (curr->next) {
> +			tail = dvb_dummy_fe_mpeg_pat_program_init
> +			       (tail, curr->service_id, curr->pid);
> +
> +			if (!head)
> +				head = tail;
> +
> +			curr = curr->next;
> +		}
> +	}
> +
> +	return head;
> +}
> +
> +
> +static void dvb_dummy_fe_mpeg_pat_table_init(struct dvb_dummy_table_pat *pat,
> +					     bool update_version_num,
> +					     u16 transport_stream_id)

Please align the arguments. In order to avoid 80 col warnings, you
could break function declarations like below:

	static void 
	dvb_dummy_fe_mpeg_pat_table_init(struct dvb_dummy_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;
> +
> +	if (update_version_num)
> +	/* ETSI 300 468: indicates changes in the TS described by this table*/
> +		++pat_version;

Instead, place the comment before the if:

	/* 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 =
> +		dvb_dummy_fe_mpeg_pat_table_compute_section_length(pat);

Same as before: please shorten the function name and place it on a single
line.

Btw, for all static functions like the above, instead naming them as:

		dvb_dummy_fe_mpeg_*

You could just use:

		dummy_fe_*

So, the like could be written, instead, as:

	pat->header.section_len = dummy_fe_pat_comp_section_len(pat);

> +
> +}
> +
> +static u32 dvb_dummy_fe_mpeg_pat_write_into(char *buf,
> +					    u32 offset,
> +					    struct dvb_dummy_table_pat *pat)
> +{
> +
> +	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 dvb_dummy_table_pat_program *p = pat->program;
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_table_header_write_into(buf,
> +						  offset,
> +						  &pat->header,
> +						  pat_pid,
> +						  &continuity_counter);
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +				pat + sizeof(struct dvb_dummy_table_header),
> +				sizeof(pat->programs),
> +				offset + nbytes,
> +				pat_pid,
> +				false,
> +				&continuity_counter,
> +				false);
> +
> +
> +	while (p) {
> +		/* skip the pointer */
> +		nbytes +=
> +			dvb_dummy_fe_mpeg_ts_psi_write_into
> +				(buf,
> +				 p,
> +				 sizeof(*p) -
> +				 sizeof(struct dvb_dummy_table_pat_program *),
> +				 offset + nbytes,
> +				 pat_pid,
> +				 false,
> +				 &continuity_counter,
> +				 false);
> +
> +		p = p->next;
> +	}
> +
> +	nbytes += table_section_crc32_write_into(buf,
> +						 offset + nbytes,
> +						 pat_pid,
> +						 &continuity_counter);
> +	return nbytes;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pat_table_destroy(struct dvb_dummy_table_pat *p)
> +{
> +	dvb_dummy_fe_mpeg_pat_program_destroy(p->program);
> +}
> +
> +static struct dvb_dummy_table_pmt_stream*
> +dvb_dummy_fe_mpeg_pmt_stream_init(struct dvb_dummy_table_pmt_stream *head,
> +				  enum dvb_dummy_stream_types stream_type,
> +				  u16 es_pid)
> +{
> +
> +	struct dvb_dummy_table_pmt_stream *stream =
> +		kzalloc(sizeof(struct dvb_dummy_table_pmt_stream),
> +			GFP_KERNEL);
> +
> +	stream->type = stream_type;
> +	stream->elementary_pid = es_pid;
> +	stream->reserved = 0x07;
> +
> +	stream->desc_length =
> +		dvb_dummy_fe_mpeg_descriptor_compute_length(stream->descriptor);

Same here: please shorten the function and place the assignment on a single
line.

> +
> +	stream->zero = 0x0;
> +	stream->reserved2 = 0x0F;

Lower case for all hexadecimal values.

> +
> +	if (head) {
> +		while (head->next)
> +			head = head->next;
> +
> +		head->next = stream;
> +	}
> +
> +	return stream;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_stream_destroy(struct dvb_dummy_table_pmt_stream *s)
> +{
> +	struct dvb_dummy_table_pmt_stream *curr_stream = s;
> +	struct dvb_dummy_table_pmt_stream *tmp_stream = NULL;
> +
> +	while (curr_stream) {
> +		tmp_stream = curr_stream;
> +		curr_stream = curr_stream->next;
> +		kfree(tmp_stream);
> +	}
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_stream_assign(struct dvb_dummy_table_pmt *pmt,
> +				    struct dvb_dummy_table_pmt_stream *s)
> +{
> +	u16 stream_count = 0;
> +	u16 table_descriptor_count = 0;
> +	struct dvb_dummy_table_pmt_stream *stream = s;
> +	struct dvb_dummy_desc *desc = s->descriptor;
> +
> +	if (pmt->stream)
> +		dvb_dummy_fe_mpeg_pmt_stream_destroy(pmt->stream);
> +
> +	while (stream) {
> +		++stream_count;
> +		stream = stream->next;
> +	}
> +
> +	while (desc) {
> +		++table_descriptor_count;
> +		desc = desc->next;
> +	}
> +
> +	/* Recompute section length */
> +	pmt->header.section_length =
> +		dvb_dummy_fe_mpeg_pmt_table_compute_section_length(pmt);

Same here: shorten function names.

> +
> +	pmt->stream = s;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_stream_match_with_sections
> +(struct dvb_dummy_channel *channels,

Channels is an array. Use channels[NUM_CHANNELS] for this argument.

> +struct dvb_dummy_table_pmt *sections, u32 nsections)
> +{
> +	struct dvb_dummy_table_pmt *curr_section = NULL;
> +	u32 i, j;
> +	bool match;
> +
> +	for (i = 0; i < NUM_CHANNELS; ++i) {
> +
> +		for (j = 0; j < nsections; ++j) {
> +			curr_section = &sections[j];
> +
> +			if (!curr_section)
> +				continue;
> +


> +			match = curr_section->header.id ==
> +					channels[i].program_num;
> +
> +			if (match) {

Just do:

			if (curr_section->header.id == channels[i].program_num)

> +				dvb_dummy_fe_mpeg_pmt_stream_assign
> +				(curr_section, channels[i].streams);
> +				break;
> +			}
> +		}
> +	}
> +}
> +
> +static u16
> +dvb_dummy_fe_mpeg_pmt_get_pid(struct dvb_dummy_table_pmt *section,
> +			      struct dvb_dummy_table_pat *pat)

Please align arguments.

Btw, you should run checkpatch on its strict mode. It would have
complained about this and similar patterns.

> +{
> +	struct dvb_dummy_table_pat_program *program = pat->program;
> +
> +	while (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
> +		 */
> +		if (program->service_id == section->header.id)
> +			return pat->program->pid;
> +
> +	return -1;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_table_init(struct dvb_dummy_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;
> +
> +	if (update_version_num)
> +	/* ETSI 300 468: indicates changes in the TS described by this table*/
> +		++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 =
> +		dvb_dummy_fe_mpeg_descriptor_compute_length(pmt->descriptor);
> +
> +	pmt->header.section_length =
> +		dvb_dummy_fe_mpeg_pmt_table_compute_section_length(pmt);
> +
> +}
> +
> +static u32 dvb_dummy_fe_mpeg_pmt_write_into(char *buf,
> +					    u32 offset,
> +					    struct dvb_dummy_table_pmt *pmt,
> +					    u16 pid)
> +{
> +	u32 nbytes = 0; /* the number of bytes written */
> +	u8 continuity_counter = 0;
> +	struct dvb_dummy_desc *table_descriptor = pmt->descriptor;
> +	struct dvb_dummy_table_pmt_stream *stream = pmt->stream;
> +	struct dvb_dummy_desc *stream_descriptor = (stream) ?
> +						    pmt->stream->descriptor :
> +						    NULL;
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_table_header_write_into(buf,
> +						  offset,
> +						  &pmt->header,
> +						  pid,
> +						  &continuity_counter);
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +				pmt + sizeof(struct dvb_dummy_table_header *),
> +				32,
> +				offset + nbytes,
> +				pid,
> +				false,
> +				&continuity_counter,
> +				false);
> +
> +
> +	while (table_descriptor) {
> +		nbytes +=
> +			dvb_dummy_fe_mpeg_desc_write_into(buf,
> +							  offset + nbytes,
> +							  table_descriptor,
> +							  pid,
> +							  &continuity_counter);
> +
> +		table_descriptor = table_descriptor->next;
> +	}
> +
> +	while (stream) {
> +		/* u8 type + (2* u16) bitfields */
> +		nbytes +=
> +		dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +						    stream,
> +						    40,
> +						    offset + nbytes,
> +						    pid,
> +						    false,
> +						    &continuity_counter,
> +						    false);
> +
> +		while (stream_descriptor) {
> +			nbytes +=
> +				dvb_dummy_fe_mpeg_desc_write_into(buf,
> +							offset + nbytes,
> +							stream_descriptor,
> +							pid,
> +							&continuity_counter);
> +
> +			stream_descriptor = stream_descriptor->next;
> +		}
> +
> +		stream = stream->next;
> +	}
> +
> +	nbytes += table_section_crc32_write_into(buf,
> +						 offset + nbytes,
> +						 pid,
> +						 &continuity_counter);
> +	return nbytes;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_table_destroy(struct dvb_dummy_table_pmt *pmt)
> +{
> +
> +	struct dvb_dummy_desc *curr_desc = pmt->descriptor;
> +	struct dvb_dummy_desc *tmp_desc = NULL;
> +
> +	while (curr_desc) {
> +		tmp_desc = curr_desc;
> +		curr_desc = curr_desc->next;
> +		dvb_dummy_fe_mpeg_descriptor_destroy(tmp_desc);
> +		kfree(tmp_desc);
> +	}
> +
> +	dvb_dummy_fe_mpeg_pmt_stream_destroy(pmt->stream);
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_sdt_table_init(struct dvb_dummy_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;
> +
> +	if (update_version_num)
> +	/* ETSI 300 468: indicates changes in the TS described by this table*/
> +		++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 =
> +		dvb_dummy_fe_mpeg_sdt_table_compute_section_length(sdt);
> +}
> +
> +static u32 dvb_dummy_fe_mpeg_sdt_write_into(char *buf,
> +					    u32 offset,
> +					    struct dvb_dummy_table_sdt *sdt)
> +{
> +	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 dvb_dummy_table_sdt_service *service = sdt->service;
> +	struct dvb_dummy_desc *service_desc = (sdt->service) ?
> +					       sdt->service->descriptor :
> +					       NULL;
> +
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_table_header_write_into(buf,
> +						  offset,
> +						  &sdt->header,
> +						  sdt_pid,
> +						  &continuity_counter);
> +	/* copy u16 network_id + u8 reserved)*/
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +				sdt + sizeof(struct dvb_dummy_table_header),
> +				16,
> +				offset + nbytes,
> +				sdt_pid,
> +				false,
> +				&continuity_counter,
> +				false);
> +
> +
> +	while (service) {
> +		/* u16 service_id + u8 + u16 bitfield */
> +		nbytes +=
> +		dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +						    service,
> +						    40,
> +						    offset + nbytes,
> +						    sdt_pid,
> +						    false,
> +						    &continuity_counter,
> +						    false);
> +
> +		while (service_desc) {
> +			nbytes +=
> +				dvb_dummy_fe_mpeg_desc_write_into(buf,
> +							  offset + nbytes,
> +							  service_desc,
> +							  sdt_pid,
> +							  &continuity_counter);
> +			service_desc = service_desc->next;
> +		}
> +
> +		service = service->next;
> +	}
> +
> +	nbytes += table_section_crc32_write_into(buf,
> +						 offset + nbytes,
> +						 sdt_pid,
> +						 &continuity_counter);
> +	return nbytes;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_sdt_table_destroy(struct dvb_dummy_table_sdt *sdt)
> +{
> +	struct dvb_dummy_table_sdt_service *curr_service = sdt->service;
> +	struct dvb_dummy_table_sdt_service *tmp_service = NULL;
> +	struct dvb_dummy_desc *curr_desc = (sdt->service) ?
> +					   sdt->service->descriptor : NULL;
> +	struct dvb_dummy_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;
> +			dvb_dummy_fe_mpeg_descriptor_destroy(tmp_desc);
> +			kfree(tmp_desc);
> +		}
> +
> +		/* then clear the current service */
> +		tmp_service = curr_service;
> +		curr_service = curr_service->next;
> +		kfree(tmp_service);
> +	}
> +}
> +
> +static struct dvb_dummy_table_sdt_service*
> +dvb_dummy_fe_mpeg_sdt_service_init(struct dvb_dummy_table_sdt_service *head,
> +				   u16 service_id)
> +{
> +	struct dvb_dummy_table_sdt_service *service;
> +
> +	service = kzalloc(sizeof(struct dvb_dummy_table_sdt_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;
> +
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_sdt_service_destroy
> +(struct dvb_dummy_table_sdt_service *service)
> +{
> +
> +	struct dvb_dummy_table_sdt_service *curr = service;
> +	struct dvb_dummy_table_sdt_service *tmp = NULL;
> +
> +	while (curr) {
> +		tmp = curr;
> +		curr = curr->next;
> +		kfree(tmp);
> +	}
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_sdt_service_assign
> +(struct dvb_dummy_table_sdt *sdt,
> +struct dvb_dummy_table_sdt_service *service)
> +{
> +	if (sdt->service)
> +		/* clean up old services */
> +		dvb_dummy_fe_mpeg_sdt_service_destroy(sdt->service);
> +
> +	sdt->service = service;
> +
> +	sdt->header.section_length =
> +		dvb_dummy_fe_mpeg_sdt_table_compute_section_length(sdt);
> +}
> +
> +static struct dvb_dummy_table_sdt_service*
> +dvb_dummy_fe_mpeg_sdt_service_concat_into_new
> +(struct dvb_dummy_channel *channels)
> +{
> +	u32 i = 0;
> +	struct dvb_dummy_table_sdt_service *curr = NULL;
> +	struct dvb_dummy_table_sdt_service *head = NULL;
> +	struct dvb_dummy_table_sdt_service *tail = NULL;
> +
> +	for (; i < NUM_CHANNELS; ++i) {
> +		curr = channels[i].service;
> +
> +		if (!curr)
> +			continue;
> +
> +		while (curr->next) {
> +			tail = dvb_dummy_fe_mpeg_sdt_service_init(tail,
> +				channels[i].service->service_id);
> +
> +			if (!head)
> +				head = tail;
> +
> +			curr = curr->next;
> +		}
> +	}
> +
> +	return head;
> +}
> +
> +static void dvb_dummy_fe_init_channels(struct dvb_dummy_channel *channels)
> +{
> +
> +	const u16 pcm_audio_channel_service_id = 0x880;
> +	const u16 pcm_audio_channel_program_num = 0x880;
> +	const u16 pcm_audio_channel_program_pid = 0x101; /* packet id for PMT*/
> +	const u16 pcm_audio_channel_audio_stream_id = 0x111;
> +
> +	struct dvb_dummy_channel pcm_audio_channel;
> +	struct dvb_dummy_table_sdt_service *pcm_audio_channel_service;
> +	struct dvb_dummy_desc_service *pcm_audio_channel_service_descriptor;
> +	u16 desc_length;
> +
> +	channels = kcalloc(NUM_CHANNELS,
> +			   sizeof(struct dvb_dummy_channel),
> +			   GFP_KERNEL);
> +
> +
> +	pcm_audio_channel_service =
> +		dvb_dummy_fe_mpeg_sdt_service_init
> +			(NULL, pcm_audio_channel_service_id);
> +
> +	pcm_audio_channel_service_descriptor =
> +		(struct dvb_dummy_desc_service *)
> +		dvb_dummy_fe_mpeg_descriptor_init(NULL,
> +					SERVICE_DESCRIPTOR,
> +					sizeof(struct dvb_dummy_desc_service));
> +
> +	pcm_audio_channel_service_descriptor->name = "Sine Wave PCM Audio";
> +	pcm_audio_channel_service_descriptor->service_type
> +		= DIGITAL_TELEVISION_SERVICE;
> +	pcm_audio_channel_service_descriptor->length = 8 /* u8 service_type */
> +		+ strlen(pcm_audio_channel_service_descriptor->name)
> +		+ strlen(pcm_audio_channel_service_descriptor->name_emph)
> +		+ strlen(pcm_audio_channel_service_descriptor->provider)
> +		+ strlen(pcm_audio_channel_service_descriptor->provider_emph);
> +
> +	dvb_dummy_fe_mpeg_descriptor_assign(
> +					(struct dvb_dummy_desc *)
> +					pcm_audio_channel_service_descriptor,
> +					(struct dvb_dummy_desc *)
> +					pcm_audio_channel_service->descriptor,
> +					&desc_length);
> +
> +	pcm_audio_channel_service->desc_length = desc_length;
> +
> +	pcm_audio_channel.transport_stream_id = TRANSPORT_STREAM_ID;
> +
> +	pcm_audio_channel.program =
> +		dvb_dummy_fe_mpeg_pat_program_init(NULL,
> +					pcm_audio_channel_service_id,
> +					pcm_audio_channel_program_pid);
> +
> +	pcm_audio_channel.program_num = pcm_audio_channel_program_num;
> +	pcm_audio_channel.streams =
> +		dvb_dummy_fe_mpeg_pmt_stream_init(NULL,
> +					ISO_IEC_13818_3_Audio,
> +					pcm_audio_channel_audio_stream_id);
> +
> +	memcpy(channels, &pcm_audio_channel, sizeof(struct dvb_dummy_channel));
> +}
> +
> +static void dvb_dummy_fe_channels_destroy(struct dvb_dummy_channel *channels)
> +{
> +	u32 i;
> +	struct dvb_dummy_channel *curr;
> +
> +	for (i = 0; i < NUM_CHANNELS; ++i) {
> +		curr = &channels[i];
> +		dvb_dummy_fe_mpeg_sdt_service_destroy(curr->service);
> +		dvb_dummy_fe_mpeg_pat_program_destroy(curr->program);
> +		dvb_dummy_fe_mpeg_pmt_stream_destroy(curr->streams);
> +		kfree(curr);
> +	}
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_create_section_for_each_pat_entry
> +(struct dvb_dummy_table_pat *pat, struct dvb_dummy_table_pmt *sections)
> +{
> +	/* PMTs contain information about programs. For each program,
> +	 * there is one PMT
> +	 */
> +
> +	struct dvb_dummy_table_pat_program *program = pat->program;
> +	u32 i = 0;
> +
> +	while (program) {
> +		dvb_dummy_fe_mpeg_pmt_table_init(&sections[i],
> +						 false,
> +						 sections[i].header.id,
> +						 0);
> +
> +		++i;
> +		program = program->next;
> +	}
> +}

There are lots of code above that look similar to the ones I already
commented.

I won't keep repeating myself everytime. When sending a second
version, please be sure that my notes will be taken into the
account for the entire patchset.

> +
> +static void dvb_dummy_fe_thread_mpeg_ts_tick(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
> +	const unsigned int SLEEP_MSECS = 10;
> +	u32 ticks = 0;
> +	u32 i;
> +	char *buf = kzalloc(DMX_BUF_LEN, GFP_KERNEL);
> +	u32 buffer_offset;
> +
> +	struct dvb_dummy_table_pat pat = {0};
> +	struct dvb_dummy_table_sdt sdt = {0};
> +
> +	struct dvb_dummy_table_pmt *pmt_sections;
> +
> +	struct dvb_dummy_table_pat_program *programs = NULL;
> +	struct dvb_dummy_table_sdt_service *services = NULL;
> +
> +	bool update_version_num = false;
> +	u16 pmt_pid;
> +
> +	dvb_dummy_fe_init_channels(state->channels);
> +
> +	programs =
> +		dvb_dummy_fe_mpeg_pat_program_concat_into_new(state->channels);
> +	services =
> +		dvb_dummy_fe_mpeg_sdt_service_concat_into_new(state->channels);
> +
> +	/* assemble all programs and assign to PAT */
> +	dvb_dummy_fe_mpeg_pat_program_assign(&pat, programs);
> +
> +	/* assemble all services and assign to SDT */
> +	dvb_dummy_fe_mpeg_sdt_service_assign(&sdt, services);
> +
> +	/* a section for each program_id */
> +	pmt_sections = kcalloc(pat.programs,
> +			       sizeof(struct dvb_dummy_table_pmt),
> +			       GFP_KERNEL);
> +
> +	dvb_dummy_fe_mpeg_pmt_create_section_for_each_pat_entry(&pat,
> +								pmt_sections);
> +
> +	dvb_dummy_fe_mpeg_pmt_stream_match_with_sections(state->channels,
> +							 pmt_sections,
> +							 pat.programs);
> +
> +	dvb_dummy_fe_mpeg_pat_table_init(&pat,
> +					 update_version_num,
> +					 TRANSPORT_STREAM_ID);
> +	dvb_dummy_fe_mpeg_sdt_table_init(&sdt,
> +				    update_version_num,
> +				    TRANSPORT_STREAM_ID);
> +	while (true) {
> +
> +		memset(buf, 0, DMX_BUF_LEN);
> +		buffer_offset = 0;
> +
> +		if (!ticks || (ticks % 50) == 0) {

Hmm... just:

		if (!(ticks % 50)) {

should be enough, as (0 % 50) is equal to zero. So, no need to do the
first check.

> +			/* push PSI packets into the buffer */
> +
> +			buffer_offset +=
> +				dvb_dummy_fe_mpeg_pat_write_into(buf,
> +								 buffer_offset,
> +								 &pat);
> +			for (i = 0; i < pat.programs; ++i) {
> +
> +				pmt_pid =
> +				dvb_dummy_fe_mpeg_pmt_get_pid(&pmt_sections[i],
> +							      &pat);
> +
> +				WARN_ON(pmt_pid < 0); /* not found */
> +
> +				buffer_offset +=
> +					/* write each section into buffer */
> +					dvb_dummy_fe_mpeg_pmt_write_into(buf,
> +							buffer_offset,
> +							&pmt_sections[i],
> +							pmt_pid);
> +			}
> +
> +			buffer_offset +=
> +				dvb_dummy_fe_mpeg_sdt_write_into(buf,
> +								 buffer_offset,
> +								 &sdt);
> +
> +
> +			WARN_ON(buffer_offset > DMX_BUF_LEN); /* overflow */
> +			msleep_interruptible(SLEEP_MSECS);
> +		}
> +	}
> +
> +	dvb_dummy_fe_mpeg_pat_table_destroy(&pat);
> +	dvb_dummy_fe_mpeg_sdt_table_destroy(&sdt);
> +
> +	for (i = 0; i < pat.programs; ++i) {
> +		/* destroy all PMT sections */
> +		dvb_dummy_fe_mpeg_pmt_table_destroy(&pmt_sections[i]);
> +		kfree(&pmt_sections[i]);
> +	}
> +
> +	dvb_dummy_fe_channels_destroy(state->channels);
> +	kfree(buf);
> +}
> +
>  struct dvb_dummy_fe_cnr_to_qual_s {
>  	/* attempt to use the same values as libdvbv5 */
>  	u32 modulation;
> @@ -75,17 +1472,7 @@ static struct dvb_dummy_fe_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
>  	{ QAM_64, FEC_7_8, 22.0, 24.0},
>  };
>  
> -struct dvb_dummy_fe_config {
> -	/* probability of losing the lock due to low snr */
> -	u8 drop_tslock_probability_on_low_snr;
> -};
>  
> -struct dvb_dummy_fe_state {
> -	struct dvb_frontend frontend;
> -	struct dvb_dummy_fe_config config;
> -	struct delayed_work poll_snr;
> -	enum fe_status status;
> -};
>  
>  void poll_snr_handler(struct work_struct *work)
>  {

Thanks,
Mauro

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [Linux-kernel-mentees] [RFC 3/3] media: dvb_dummy_fe.c: write PSI information into DMX buffer
@ 2020-03-18 10:56     ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 21+ messages in thread
From: Mauro Carvalho Chehab @ 2020-03-18 10:56 UTC (permalink / raw)
  To: Daniel W. S. Almeida
  Cc: kstewart, sean, tglx, linux-kernel-mentees, allison, linux-media

Em Wed, 18 Mar 2020 03:00:18 -0300
"Daniel W. S. Almeida" <dwlsalmeida@gmail.com> escreveu:

> From: "Daniel W. S. Almeida" <dwlsalmeida@gmail.com>
> 
> Periodically feed the demux with PSI packets. This is needed so userspace
> apps can retrieve information about the Transport Stream and eventually
> tune into a (dummy) channel.
> 
> Currently this commit adds support for working with 3 PSI tables:
> PAT, PMT and SDT. A dummy service with a dummy program is hardcoded
> in the driver code.
> ---
>  drivers/media/dvb-frontends/dvb_dummy_fe.c | 1409 +++++++++++++++++++-

I would place the PSI generator code on a separate file. It would make
easier for people to re-use just the dummy driver code to not have the
generator code mixed into it.

>  1 file changed, 1398 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/media/dvb-frontends/dvb_dummy_fe.c b/drivers/media/dvb-frontends/dvb_dummy_fe.c
> index 726c964a523d..10b1bd22c15d 100644
> --- a/drivers/media/dvb-frontends/dvb_dummy_fe.c
> +++ b/drivers/media/dvb-frontends/dvb_dummy_fe.c
> @@ -11,11 +11,1408 @@
>  #include <linux/slab.h>
>  #include <linux/workqueue.h>
>  #include <linux/random.h>
> +#include <linux/crc32.h>
> +#include <linux/string.h>
>  
>  #include <media/dvb_frontend.h>
> -#include "dvb_dummy_fe.h"
>  
>  
> +#define CRC_SIZE_IN_BYTES 32
> +#define NUM_CHANNELS 1
> +#define TRANSPORT_STREAM_ID 0x744 /* a single stream */
> +#define TS_SYNC_BYTE 0x47
> +#define TS_PACKET_LEN 188
> +#define TS_PAYLOAD_LEN 184
> +#define DMX_BUF_LEN (20 * TS_PACKET_LEN)
> +
> +
> +enum dvb_dummy_descriptors {
> +	SERVICE_DESCRIPTOR = 0x48,
> +};
> +
> +enum dvb_dummy_stream_types {
> +	ISO_IEC_13818_3_Audio = 0x4,
> +};
> +
> +struct dvb_dummy_channel {
> +	u16 transport_stream_id;
> +	struct dvb_dummy_table_sdt_service *service;
> +	u16 program_num;
> +	/* a single program with one or more streams associated with it */
> +	struct dvb_dummy_table_pat_program *program;
> +	struct dvb_dummy_table_pmt_stream *streams;
> +};
> +
> +struct dvb_dummy_desc {
> +	u8 type;
> +	u8 length;
> +	struct dvb_dummy_desc *next;
> +	u8 *data;
> +} __packed;
> +
> +struct dvb_dummy_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 dvb_dummy_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 dvb_dummy_table_pat_program {
> +	u16 service_id;
> +	union {
> +		u16 bitfield;
> +		struct {
> +			u16 pid:13;
> +			u8  reserved:3;
> +		} __packed;
> +	} __packed;
> +	struct dvb_dummy_table_pat_program *next;
> +} __packed;
> +
> +struct dvb_dummy_table_pat {
> +	struct dvb_dummy_table_header header;
> +	u16 programs;
> +	struct dvb_dummy_table_pat_program *program;
> +} __packed;
> +
> +struct dvb_dummy_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 dvb_dummy_desc *descriptor;
> +	struct dvb_dummy_table_sdt_service *next;
> +} __packed;
> +
> +struct dvb_dummy_table_sdt {
> +	struct dvb_dummy_table_header header;
> +	u16 network_id;
> +	u8  reserved;
> +	struct dvb_dummy_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 dvb_dummy_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 dvb_dummy_desc *descriptor;
> +	struct dvb_dummy_table_pmt_stream *next;
> +} __packed;
> +
> +struct dvb_dummy_table_pmt {
> +	struct dvb_dummy_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 dvb_dummy_desc *descriptor;
> +	struct dvb_dummy_table_pmt_stream *stream;
> +} __packed;
> +
> +struct dvb_dummy_mpeg_ts_adaption {
> +	u8 length;
> +	struct {
> +		u8 extension:1;
> +		u8 private_data:1;
> +		u8 splicing_point:1;
> +		u8 OPCR:1;
> +		u8 PCR:1;
> +		u8 priority:1;
> +		u8 random_access:1;
> +		u8 discontinued:1;
> +	} __packed;
> +	u8 data[];
> +} __packed;
> +
> +
> +struct dvb_dummy_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 dvb_dummy_mpeg_ts_adaption adaption[];
> +} __packed;
> +
> +struct dvb_dummy_fe_config {
> +	/* probability of losing the lock due to low snr */
> +	u8 drop_tslock_probability_on_low_snr;
> +};
> +
> +struct dvb_dummy_fe_state {
> +	struct dvb_frontend frontend;
> +	struct dvb_dummy_fe_config config;
> +	struct delayed_work poll_snr;
> +	enum fe_status status;
> +	struct dvb_dummy_channel *channels;
> +};

I would place the enums, defines and struct definitions above
on a separate header file.

> +
> +static u32 dvb_dummy_fe_mpeg_ts_psi_write_stuffing(void *to, u32 len)
> +{
> +	memset(to, 0xFF, len);

Please use lower case.

> +	return len;
> +}
> +
> +static u32 dvb_dummy_fe_mpeg_ts_psi_write_into(void *to,
> +					    const void *from,
> +					    size_t len,
> +					    u32 offset,
> +					    u16 pid,
> +					    bool new_psi_section,
> +					    u8 *continuity_counter,
> +					    bool is_crc)

Looking on this code and the rest function calls, I think you could
define a struct with the stuff that it is needed for writing
a PSI.

> +{
> +	/*   Packetize PSI sections into TS packets:
> +	 * - push a TS header (4bytes) every 184 bytes
> +	 * - manage the continuity_counter
> +	 * - add stuffing after the CRC
> +	 */

Same as commented on patch 1/3: the first line of a multi-line comment 
should be just:

	/*

> +
> +	u32 nbytes_past_boundary = (offset % TS_PACKET_LEN);
> +	bool aligned = nbytes_past_boundary == 0;
> +	bool split = len > TS_PAYLOAD_LEN;
> +	u32 payload_write_len = (split) ? TS_PAYLOAD_LEN : len;
> +
> +	struct dvb_dummy_mpeg_ts ts_header = {0};
> +
> +	u32 nbytes = 0; /* number of bytes written by this function */
> +
> +	if (new_psi_section && !aligned) {
> +		/* must pad the buffer with the complement to get a
> +		 * multiple of 188
> +		 */
> +		nbytes +=
> +			dvb_dummy_fe_mpeg_ts_psi_write_stuffing(
> +						to + offset + nbytes,
> +						TS_PACKET_LEN -
> +						nbytes_past_boundary);
> +
> +		/* 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 = pid;
> +		ts_header.priority = 0;
> +		ts_header.scrambling = 0; /* not scrambled */
> +		ts_header.continuity_counter = *continuity_counter;
> +		ts_header.payload_start = 0; /* no adaption for now */
> +
> +
> +		/* copy the header minus the adaption pointer*/
> +		memcpy(to + offset + nbytes,
> +		&ts_header,
> +		sizeof(ts_header) -
> +		sizeof(struct dvb_dummy_mpeg_ts_adaption *));
> +
> +		nbytes +=
> +		sizeof(ts_header) -
> +		sizeof(struct dvb_dummy_mpeg_ts_adaption *);
> +	}
> +
> +	if (new_psi_section) {
> +		/* write the pointer_field in the first byte of the payload */
> +		memset(to + offset + nbytes, 0x0, 1);
> +		nbytes += 1;
> +		--payload_write_len; /* one byte was used by the pointer field*/
> +	}
> +
> +	/* write as much of the payload as we possibly can */
> +	memcpy(to + offset + nbytes, from, payload_write_len);
> +	nbytes += payload_write_len;
> +
> +	if (split) {
> +		/* next TS packet keeps the same PID, but increments the
> +		 * counter
> +		 */
> +		++(*continuity_counter);
> +		/* 'nbytes' written from a total of 'len' requested*/
> +		len -= nbytes;
> +		/* recursively write the rest of the data until we do not
> +		 * need to split it anymore
> +		 */
> +		nbytes +=
> +		dvb_dummy_fe_mpeg_ts_psi_write_into(to,
> +						    from + nbytes,
> +						    len,
> +						    offset + nbytes,
> +						    pid,
> +						    false,
> +						    continuity_counter,
> +						    is_crc);
> +	}
> +
> +	if (is_crc)
> +		/* as the CRC is last in the section, stuff the rest of the
> +		 * packet if there is any remaining space in there
> +		 */
> +
> +		nbytes +=
> +		dvb_dummy_fe_mpeg_ts_psi_write_stuffing(to + nbytes,
> +							TS_PAYLOAD_LEN -
> +							nbytes);
> +
> +	return nbytes;
> +}
> +
> +static u32 table_section_crc32_write_into(char *buf,
> +					  u32 offset,
> +					  u16 pid,
> +					  u8 *continuity_counter)
> +{
> +	/* the CRC is the last entry in the section */
> +	u32 nbytes = 0;
> +	u32 crc;
> +
> +	crc = crc32(0, buf, offset);
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +					    &crc,
> +					    CRC_SIZE_IN_BYTES,
> +					    offset,
> +					    pid,
> +					    false,
> +					    continuity_counter,
> +					    true);
> +
> +
> +	return nbytes;
> +}
> +
> +static struct dvb_dummy_desc*
> +dvb_dummy_fe_mpeg_descriptor_init(struct dvb_dummy_desc *head,
> +				  u8 type,
> +				  u8 length)
> +{
> +	struct dvb_dummy_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL);
> +
> +	desc->type = type;
> +	desc->length = length;
> +	desc->data = kzalloc(length, GFP_KERNEL);
> +
> +	if (head) {
> +		while (head->next)
> +			head = head->next;
> +
> +		head->next = desc;
> +	}
> +
> +	return desc;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_descriptor_destroy(struct dvb_dummy_desc *desc)
> +{
> +
> +	struct dvb_dummy_desc *curr = desc;
> +	struct dvb_dummy_desc *tmp = NULL;
> +
> +	while (curr) {
> +		tmp = curr;
> +		curr = curr->next;
> +		kfree(tmp->data);
> +		kfree(tmp);
> +	}
> +
> +}
> +
> +static u32
> +dvb_dummy_fe_mpeg_descriptor_compute_length(struct dvb_dummy_desc *desc)
> +{
> +	u32 length = 0;
> +
> +	if (!desc)
> +		return 0;
> +
> +	while (desc) {
> +		length += desc->length;
> +		desc = desc->next;
> +	}
> +
> +	return length;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_descriptor_assign(struct dvb_dummy_desc *desc,
> +				    struct dvb_dummy_desc *desc_ptr,
> +				    u16 *desc_length)
> +{
> +
> +	if (desc_ptr)
> +		/* clean the old data */
> +		dvb_dummy_fe_mpeg_descriptor_destroy(desc_ptr);
> +
> +	*desc_length = dvb_dummy_fe_mpeg_descriptor_compute_length(desc);
> +	desc_ptr = desc;
> +}
> +
> +static u32 dvb_dummy_fe_mpeg_desc_write_into(char *buf,
> +					     u32 offset,
> +					     struct dvb_dummy_desc *desc,
> +					     u16 pid,
> +					     u8 *continuity_counter)
> +{
> +	u32 nbytes = 0; /* the number of bytes written by this function */
> +
> +	/* u8 type + u8 length */
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +					    desc,
> +					    16,
> +					    offset,
> +					    pid,
> +					    false,
> +					    continuity_counter,
> +					    false);
> +
> +	/* move 'from' pointer to point to u8 data[] */
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +				desc + nbytes + sizeof(struct dvb_dummy_desc *),
> +				desc->length,
> +				offset + nbytes,
> +				pid,
> +				false,
> +				continuity_counter,
> +				false);
> +
> +	return nbytes;
> +}
> +
> +static u32
> +dvb_dummy_fe_mpeg_table_header_write_into(char *buf,
> +					  u32 offset,
> +					  struct dvb_dummy_table_header *h,
> +					  u16 pid,
> +					  u8 *continuity_counter)
> +{
> +	/* the number of bytes written by this function */
> +	u32 nbytes = 0;
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +					h,
> +					sizeof(struct dvb_dummy_table_header),
> +					offset,
> +					pid,
> +					true,
> +					continuity_counter,
> +					false);
> +
> +	return nbytes;
> +}
> +
> +static u16
> +dvb_dummy_fe_mpeg_pat_table_compute_section_length
> +(struct dvb_dummy_table_pat *pat)
> +{
> +	/* see ISO/IEC 13818-1 : 2000 p.43 */
> +	u16 length = 0;
> +	u32 i;
> +
> +	length += 40;
> +
> +	for (i = 0; i < pat->programs; ++i)
> +		length += 32;
> +
> +	length += CRC_SIZE_IN_BYTES;
> +
> +	WARN_ON(length > 1021);

Please add some defines for those "magic" numbers: 40, 31, 1021, as it
makes easier for any reviewer to understand what are their meanings.

Or, even better: use sizeof().

So, for example, if you have a "struct foo_table_header", the best
would be to do:

	len += sizeof(foo_table_header);

> +	return length;
> +}
> +
> +static u16
> +dvb_dummy_fe_mpeg_pmt_table_compute_section_length
> +(struct dvb_dummy_table_pmt *pmt)
> +{
> +	/* see ISO/IEC 13818-1 : 2000 p.46 */
> +	u16 length = 0;
> +	struct dvb_dummy_table_pmt_stream *s = pmt->stream;
> +
> +	length += 72;

Same here and on other constants below.

> +	length += /* do not fail if 'desc_length' has not been computed yet */
> +		dvb_dummy_fe_mpeg_descriptor_compute_length(pmt->descriptor);

Please place this on a single line. If needed, reduce the name of the
function. For example, a name like: dvb_dummy_fe_mpeg_desc_comp_len()
would be equivalent to the big name.

> +	length += pmt->desc_length;
> +
> +	while (s) {
> +		length += 40;
> +		length +=
> +			dvb_dummy_fe_mpeg_descriptor_compute_length
> +			(s->descriptor);
> +		s = s->next;
> +	}
> +
> +	length += CRC_SIZE_IN_BYTES;
> +
> +	WARN_ON(length > 1021);
> +	return length;
> +}
> +
> +static u16
> +dvb_dummy_fe_mpeg_sdt_table_compute_section_length
> +(struct dvb_dummy_table_sdt *sdt)
> +{
> +	/* see ETSI EN 300 468 V 1.10.1 p.24 */
> +	u16 length = 0;
> +	struct dvb_dummy_table_sdt_service *s = sdt->service;
> +
> +	length += 64;
> +	while (s) {
> +		length += 40;
> +		length +=
> +		/* do not fail if 'desc_length' has not been computed yet */
> +			dvb_dummy_fe_mpeg_descriptor_compute_length
> +			(s->descriptor);
> +	}
> +
> +	length += CRC_SIZE_IN_BYTES;
> +
> +	WARN_ON(length > 1021);
> +	return length;
> +}
> +
> +static struct dvb_dummy_table_pat_program*
> +dvb_dummy_fe_mpeg_pat_program_init(struct dvb_dummy_table_pat_program *head,
> +				   u16 service_id,
> +				   u16 pid)
> +{
> +
> +	struct dvb_dummy_table_pat_program *program;
> +
> +	program = kzalloc(sizeof(struct dvb_dummy_table_pat_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;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pat_program_destroy(struct dvb_dummy_table_pat_program *p)
> +{
> +	struct dvb_dummy_table_pat_program *curr = p;
> +	struct dvb_dummy_table_pat_program *tmp = NULL;
> +
> +	while (curr) {
> +		tmp = curr;
> +		curr = curr->next;
> +		kfree(tmp);
> +	}
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pat_program_assign(struct dvb_dummy_table_pat *pat,
> +				     struct dvb_dummy_table_pat_program *p)
> +{
> +	u16 program_count = 0;
> +	struct dvb_dummy_table_pat_program *program = p;
> +
> +	if (pat->program)
> +		dvb_dummy_fe_mpeg_pat_program_destroy(pat->program);
> +
> +	while (program) {
> +		++program_count;
> +		program = program->next;
> +	}
> +
> +	pat->programs = program_count;
> +
> +	/* Recompute section length */
> +	pat->header.section_length =
> +		dvb_dummy_fe_mpeg_pat_table_compute_section_length(pat);
> +
> +	pat->program = p;
> +}
> +
> +static struct dvb_dummy_table_pat_program*
> +dvb_dummy_fe_mpeg_pat_program_concat_into_new
> +(struct dvb_dummy_channel *channels)

Hmm... please declare the header, instead, with:

	struct dvb_dummy_channel channels[NUM_CHANNELS]

as this would let gcc and static analyzers to know what's the size of
the array that will be used at the code.

(please use the same concept on other similar places)

> +{
> +	u32 i;
> +	struct dvb_dummy_table_pat_program *curr = NULL;
> +	struct dvb_dummy_table_pat_program *head = NULL;
> +	struct dvb_dummy_table_pat_program *tail = NULL;
> +
> +	for (i = 0; i < NUM_CHANNELS; ++i) {
> +		curr = channels[i].program;
> +
> +		if (!curr)
> +			continue;
> +
> +		while (curr->next) {
> +			tail = dvb_dummy_fe_mpeg_pat_program_init
> +			       (tail, curr->service_id, curr->pid);
> +
> +			if (!head)
> +				head = tail;
> +
> +			curr = curr->next;
> +		}
> +	}
> +
> +	return head;
> +}
> +
> +
> +static void dvb_dummy_fe_mpeg_pat_table_init(struct dvb_dummy_table_pat *pat,
> +					     bool update_version_num,
> +					     u16 transport_stream_id)

Please align the arguments. In order to avoid 80 col warnings, you
could break function declarations like below:

	static void 
	dvb_dummy_fe_mpeg_pat_table_init(struct dvb_dummy_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;
> +
> +	if (update_version_num)
> +	/* ETSI 300 468: indicates changes in the TS described by this table*/
> +		++pat_version;

Instead, place the comment before the if:

	/* 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 =
> +		dvb_dummy_fe_mpeg_pat_table_compute_section_length(pat);

Same as before: please shorten the function name and place it on a single
line.

Btw, for all static functions like the above, instead naming them as:

		dvb_dummy_fe_mpeg_*

You could just use:

		dummy_fe_*

So, the like could be written, instead, as:

	pat->header.section_len = dummy_fe_pat_comp_section_len(pat);

> +
> +}
> +
> +static u32 dvb_dummy_fe_mpeg_pat_write_into(char *buf,
> +					    u32 offset,
> +					    struct dvb_dummy_table_pat *pat)
> +{
> +
> +	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 dvb_dummy_table_pat_program *p = pat->program;
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_table_header_write_into(buf,
> +						  offset,
> +						  &pat->header,
> +						  pat_pid,
> +						  &continuity_counter);
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +				pat + sizeof(struct dvb_dummy_table_header),
> +				sizeof(pat->programs),
> +				offset + nbytes,
> +				pat_pid,
> +				false,
> +				&continuity_counter,
> +				false);
> +
> +
> +	while (p) {
> +		/* skip the pointer */
> +		nbytes +=
> +			dvb_dummy_fe_mpeg_ts_psi_write_into
> +				(buf,
> +				 p,
> +				 sizeof(*p) -
> +				 sizeof(struct dvb_dummy_table_pat_program *),
> +				 offset + nbytes,
> +				 pat_pid,
> +				 false,
> +				 &continuity_counter,
> +				 false);
> +
> +		p = p->next;
> +	}
> +
> +	nbytes += table_section_crc32_write_into(buf,
> +						 offset + nbytes,
> +						 pat_pid,
> +						 &continuity_counter);
> +	return nbytes;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pat_table_destroy(struct dvb_dummy_table_pat *p)
> +{
> +	dvb_dummy_fe_mpeg_pat_program_destroy(p->program);
> +}
> +
> +static struct dvb_dummy_table_pmt_stream*
> +dvb_dummy_fe_mpeg_pmt_stream_init(struct dvb_dummy_table_pmt_stream *head,
> +				  enum dvb_dummy_stream_types stream_type,
> +				  u16 es_pid)
> +{
> +
> +	struct dvb_dummy_table_pmt_stream *stream =
> +		kzalloc(sizeof(struct dvb_dummy_table_pmt_stream),
> +			GFP_KERNEL);
> +
> +	stream->type = stream_type;
> +	stream->elementary_pid = es_pid;
> +	stream->reserved = 0x07;
> +
> +	stream->desc_length =
> +		dvb_dummy_fe_mpeg_descriptor_compute_length(stream->descriptor);

Same here: please shorten the function and place the assignment on a single
line.

> +
> +	stream->zero = 0x0;
> +	stream->reserved2 = 0x0F;

Lower case for all hexadecimal values.

> +
> +	if (head) {
> +		while (head->next)
> +			head = head->next;
> +
> +		head->next = stream;
> +	}
> +
> +	return stream;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_stream_destroy(struct dvb_dummy_table_pmt_stream *s)
> +{
> +	struct dvb_dummy_table_pmt_stream *curr_stream = s;
> +	struct dvb_dummy_table_pmt_stream *tmp_stream = NULL;
> +
> +	while (curr_stream) {
> +		tmp_stream = curr_stream;
> +		curr_stream = curr_stream->next;
> +		kfree(tmp_stream);
> +	}
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_stream_assign(struct dvb_dummy_table_pmt *pmt,
> +				    struct dvb_dummy_table_pmt_stream *s)
> +{
> +	u16 stream_count = 0;
> +	u16 table_descriptor_count = 0;
> +	struct dvb_dummy_table_pmt_stream *stream = s;
> +	struct dvb_dummy_desc *desc = s->descriptor;
> +
> +	if (pmt->stream)
> +		dvb_dummy_fe_mpeg_pmt_stream_destroy(pmt->stream);
> +
> +	while (stream) {
> +		++stream_count;
> +		stream = stream->next;
> +	}
> +
> +	while (desc) {
> +		++table_descriptor_count;
> +		desc = desc->next;
> +	}
> +
> +	/* Recompute section length */
> +	pmt->header.section_length =
> +		dvb_dummy_fe_mpeg_pmt_table_compute_section_length(pmt);

Same here: shorten function names.

> +
> +	pmt->stream = s;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_stream_match_with_sections
> +(struct dvb_dummy_channel *channels,

Channels is an array. Use channels[NUM_CHANNELS] for this argument.

> +struct dvb_dummy_table_pmt *sections, u32 nsections)
> +{
> +	struct dvb_dummy_table_pmt *curr_section = NULL;
> +	u32 i, j;
> +	bool match;
> +
> +	for (i = 0; i < NUM_CHANNELS; ++i) {
> +
> +		for (j = 0; j < nsections; ++j) {
> +			curr_section = &sections[j];
> +
> +			if (!curr_section)
> +				continue;
> +


> +			match = curr_section->header.id ==
> +					channels[i].program_num;
> +
> +			if (match) {

Just do:

			if (curr_section->header.id == channels[i].program_num)

> +				dvb_dummy_fe_mpeg_pmt_stream_assign
> +				(curr_section, channels[i].streams);
> +				break;
> +			}
> +		}
> +	}
> +}
> +
> +static u16
> +dvb_dummy_fe_mpeg_pmt_get_pid(struct dvb_dummy_table_pmt *section,
> +			      struct dvb_dummy_table_pat *pat)

Please align arguments.

Btw, you should run checkpatch on its strict mode. It would have
complained about this and similar patterns.

> +{
> +	struct dvb_dummy_table_pat_program *program = pat->program;
> +
> +	while (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
> +		 */
> +		if (program->service_id == section->header.id)
> +			return pat->program->pid;
> +
> +	return -1;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_table_init(struct dvb_dummy_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;
> +
> +	if (update_version_num)
> +	/* ETSI 300 468: indicates changes in the TS described by this table*/
> +		++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 =
> +		dvb_dummy_fe_mpeg_descriptor_compute_length(pmt->descriptor);
> +
> +	pmt->header.section_length =
> +		dvb_dummy_fe_mpeg_pmt_table_compute_section_length(pmt);
> +
> +}
> +
> +static u32 dvb_dummy_fe_mpeg_pmt_write_into(char *buf,
> +					    u32 offset,
> +					    struct dvb_dummy_table_pmt *pmt,
> +					    u16 pid)
> +{
> +	u32 nbytes = 0; /* the number of bytes written */
> +	u8 continuity_counter = 0;
> +	struct dvb_dummy_desc *table_descriptor = pmt->descriptor;
> +	struct dvb_dummy_table_pmt_stream *stream = pmt->stream;
> +	struct dvb_dummy_desc *stream_descriptor = (stream) ?
> +						    pmt->stream->descriptor :
> +						    NULL;
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_table_header_write_into(buf,
> +						  offset,
> +						  &pmt->header,
> +						  pid,
> +						  &continuity_counter);
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +				pmt + sizeof(struct dvb_dummy_table_header *),
> +				32,
> +				offset + nbytes,
> +				pid,
> +				false,
> +				&continuity_counter,
> +				false);
> +
> +
> +	while (table_descriptor) {
> +		nbytes +=
> +			dvb_dummy_fe_mpeg_desc_write_into(buf,
> +							  offset + nbytes,
> +							  table_descriptor,
> +							  pid,
> +							  &continuity_counter);
> +
> +		table_descriptor = table_descriptor->next;
> +	}
> +
> +	while (stream) {
> +		/* u8 type + (2* u16) bitfields */
> +		nbytes +=
> +		dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +						    stream,
> +						    40,
> +						    offset + nbytes,
> +						    pid,
> +						    false,
> +						    &continuity_counter,
> +						    false);
> +
> +		while (stream_descriptor) {
> +			nbytes +=
> +				dvb_dummy_fe_mpeg_desc_write_into(buf,
> +							offset + nbytes,
> +							stream_descriptor,
> +							pid,
> +							&continuity_counter);
> +
> +			stream_descriptor = stream_descriptor->next;
> +		}
> +
> +		stream = stream->next;
> +	}
> +
> +	nbytes += table_section_crc32_write_into(buf,
> +						 offset + nbytes,
> +						 pid,
> +						 &continuity_counter);
> +	return nbytes;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_table_destroy(struct dvb_dummy_table_pmt *pmt)
> +{
> +
> +	struct dvb_dummy_desc *curr_desc = pmt->descriptor;
> +	struct dvb_dummy_desc *tmp_desc = NULL;
> +
> +	while (curr_desc) {
> +		tmp_desc = curr_desc;
> +		curr_desc = curr_desc->next;
> +		dvb_dummy_fe_mpeg_descriptor_destroy(tmp_desc);
> +		kfree(tmp_desc);
> +	}
> +
> +	dvb_dummy_fe_mpeg_pmt_stream_destroy(pmt->stream);
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_sdt_table_init(struct dvb_dummy_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;
> +
> +	if (update_version_num)
> +	/* ETSI 300 468: indicates changes in the TS described by this table*/
> +		++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 =
> +		dvb_dummy_fe_mpeg_sdt_table_compute_section_length(sdt);
> +}
> +
> +static u32 dvb_dummy_fe_mpeg_sdt_write_into(char *buf,
> +					    u32 offset,
> +					    struct dvb_dummy_table_sdt *sdt)
> +{
> +	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 dvb_dummy_table_sdt_service *service = sdt->service;
> +	struct dvb_dummy_desc *service_desc = (sdt->service) ?
> +					       sdt->service->descriptor :
> +					       NULL;
> +
> +
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_table_header_write_into(buf,
> +						  offset,
> +						  &sdt->header,
> +						  sdt_pid,
> +						  &continuity_counter);
> +	/* copy u16 network_id + u8 reserved)*/
> +	nbytes +=
> +	dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +				sdt + sizeof(struct dvb_dummy_table_header),
> +				16,
> +				offset + nbytes,
> +				sdt_pid,
> +				false,
> +				&continuity_counter,
> +				false);
> +
> +
> +	while (service) {
> +		/* u16 service_id + u8 + u16 bitfield */
> +		nbytes +=
> +		dvb_dummy_fe_mpeg_ts_psi_write_into(buf,
> +						    service,
> +						    40,
> +						    offset + nbytes,
> +						    sdt_pid,
> +						    false,
> +						    &continuity_counter,
> +						    false);
> +
> +		while (service_desc) {
> +			nbytes +=
> +				dvb_dummy_fe_mpeg_desc_write_into(buf,
> +							  offset + nbytes,
> +							  service_desc,
> +							  sdt_pid,
> +							  &continuity_counter);
> +			service_desc = service_desc->next;
> +		}
> +
> +		service = service->next;
> +	}
> +
> +	nbytes += table_section_crc32_write_into(buf,
> +						 offset + nbytes,
> +						 sdt_pid,
> +						 &continuity_counter);
> +	return nbytes;
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_sdt_table_destroy(struct dvb_dummy_table_sdt *sdt)
> +{
> +	struct dvb_dummy_table_sdt_service *curr_service = sdt->service;
> +	struct dvb_dummy_table_sdt_service *tmp_service = NULL;
> +	struct dvb_dummy_desc *curr_desc = (sdt->service) ?
> +					   sdt->service->descriptor : NULL;
> +	struct dvb_dummy_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;
> +			dvb_dummy_fe_mpeg_descriptor_destroy(tmp_desc);
> +			kfree(tmp_desc);
> +		}
> +
> +		/* then clear the current service */
> +		tmp_service = curr_service;
> +		curr_service = curr_service->next;
> +		kfree(tmp_service);
> +	}
> +}
> +
> +static struct dvb_dummy_table_sdt_service*
> +dvb_dummy_fe_mpeg_sdt_service_init(struct dvb_dummy_table_sdt_service *head,
> +				   u16 service_id)
> +{
> +	struct dvb_dummy_table_sdt_service *service;
> +
> +	service = kzalloc(sizeof(struct dvb_dummy_table_sdt_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;
> +
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_sdt_service_destroy
> +(struct dvb_dummy_table_sdt_service *service)
> +{
> +
> +	struct dvb_dummy_table_sdt_service *curr = service;
> +	struct dvb_dummy_table_sdt_service *tmp = NULL;
> +
> +	while (curr) {
> +		tmp = curr;
> +		curr = curr->next;
> +		kfree(tmp);
> +	}
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_sdt_service_assign
> +(struct dvb_dummy_table_sdt *sdt,
> +struct dvb_dummy_table_sdt_service *service)
> +{
> +	if (sdt->service)
> +		/* clean up old services */
> +		dvb_dummy_fe_mpeg_sdt_service_destroy(sdt->service);
> +
> +	sdt->service = service;
> +
> +	sdt->header.section_length =
> +		dvb_dummy_fe_mpeg_sdt_table_compute_section_length(sdt);
> +}
> +
> +static struct dvb_dummy_table_sdt_service*
> +dvb_dummy_fe_mpeg_sdt_service_concat_into_new
> +(struct dvb_dummy_channel *channels)
> +{
> +	u32 i = 0;
> +	struct dvb_dummy_table_sdt_service *curr = NULL;
> +	struct dvb_dummy_table_sdt_service *head = NULL;
> +	struct dvb_dummy_table_sdt_service *tail = NULL;
> +
> +	for (; i < NUM_CHANNELS; ++i) {
> +		curr = channels[i].service;
> +
> +		if (!curr)
> +			continue;
> +
> +		while (curr->next) {
> +			tail = dvb_dummy_fe_mpeg_sdt_service_init(tail,
> +				channels[i].service->service_id);
> +
> +			if (!head)
> +				head = tail;
> +
> +			curr = curr->next;
> +		}
> +	}
> +
> +	return head;
> +}
> +
> +static void dvb_dummy_fe_init_channels(struct dvb_dummy_channel *channels)
> +{
> +
> +	const u16 pcm_audio_channel_service_id = 0x880;
> +	const u16 pcm_audio_channel_program_num = 0x880;
> +	const u16 pcm_audio_channel_program_pid = 0x101; /* packet id for PMT*/
> +	const u16 pcm_audio_channel_audio_stream_id = 0x111;
> +
> +	struct dvb_dummy_channel pcm_audio_channel;
> +	struct dvb_dummy_table_sdt_service *pcm_audio_channel_service;
> +	struct dvb_dummy_desc_service *pcm_audio_channel_service_descriptor;
> +	u16 desc_length;
> +
> +	channels = kcalloc(NUM_CHANNELS,
> +			   sizeof(struct dvb_dummy_channel),
> +			   GFP_KERNEL);
> +
> +
> +	pcm_audio_channel_service =
> +		dvb_dummy_fe_mpeg_sdt_service_init
> +			(NULL, pcm_audio_channel_service_id);
> +
> +	pcm_audio_channel_service_descriptor =
> +		(struct dvb_dummy_desc_service *)
> +		dvb_dummy_fe_mpeg_descriptor_init(NULL,
> +					SERVICE_DESCRIPTOR,
> +					sizeof(struct dvb_dummy_desc_service));
> +
> +	pcm_audio_channel_service_descriptor->name = "Sine Wave PCM Audio";
> +	pcm_audio_channel_service_descriptor->service_type
> +		= DIGITAL_TELEVISION_SERVICE;
> +	pcm_audio_channel_service_descriptor->length = 8 /* u8 service_type */
> +		+ strlen(pcm_audio_channel_service_descriptor->name)
> +		+ strlen(pcm_audio_channel_service_descriptor->name_emph)
> +		+ strlen(pcm_audio_channel_service_descriptor->provider)
> +		+ strlen(pcm_audio_channel_service_descriptor->provider_emph);
> +
> +	dvb_dummy_fe_mpeg_descriptor_assign(
> +					(struct dvb_dummy_desc *)
> +					pcm_audio_channel_service_descriptor,
> +					(struct dvb_dummy_desc *)
> +					pcm_audio_channel_service->descriptor,
> +					&desc_length);
> +
> +	pcm_audio_channel_service->desc_length = desc_length;
> +
> +	pcm_audio_channel.transport_stream_id = TRANSPORT_STREAM_ID;
> +
> +	pcm_audio_channel.program =
> +		dvb_dummy_fe_mpeg_pat_program_init(NULL,
> +					pcm_audio_channel_service_id,
> +					pcm_audio_channel_program_pid);
> +
> +	pcm_audio_channel.program_num = pcm_audio_channel_program_num;
> +	pcm_audio_channel.streams =
> +		dvb_dummy_fe_mpeg_pmt_stream_init(NULL,
> +					ISO_IEC_13818_3_Audio,
> +					pcm_audio_channel_audio_stream_id);
> +
> +	memcpy(channels, &pcm_audio_channel, sizeof(struct dvb_dummy_channel));
> +}
> +
> +static void dvb_dummy_fe_channels_destroy(struct dvb_dummy_channel *channels)
> +{
> +	u32 i;
> +	struct dvb_dummy_channel *curr;
> +
> +	for (i = 0; i < NUM_CHANNELS; ++i) {
> +		curr = &channels[i];
> +		dvb_dummy_fe_mpeg_sdt_service_destroy(curr->service);
> +		dvb_dummy_fe_mpeg_pat_program_destroy(curr->program);
> +		dvb_dummy_fe_mpeg_pmt_stream_destroy(curr->streams);
> +		kfree(curr);
> +	}
> +}
> +
> +static void
> +dvb_dummy_fe_mpeg_pmt_create_section_for_each_pat_entry
> +(struct dvb_dummy_table_pat *pat, struct dvb_dummy_table_pmt *sections)
> +{
> +	/* PMTs contain information about programs. For each program,
> +	 * there is one PMT
> +	 */
> +
> +	struct dvb_dummy_table_pat_program *program = pat->program;
> +	u32 i = 0;
> +
> +	while (program) {
> +		dvb_dummy_fe_mpeg_pmt_table_init(&sections[i],
> +						 false,
> +						 sections[i].header.id,
> +						 0);
> +
> +		++i;
> +		program = program->next;
> +	}
> +}

There are lots of code above that look similar to the ones I already
commented.

I won't keep repeating myself everytime. When sending a second
version, please be sure that my notes will be taken into the
account for the entire patchset.

> +
> +static void dvb_dummy_fe_thread_mpeg_ts_tick(struct dvb_frontend *fe)
> +{
> +	struct dvb_dummy_fe_state *state = fe->demodulator_priv;
> +	const unsigned int SLEEP_MSECS = 10;
> +	u32 ticks = 0;
> +	u32 i;
> +	char *buf = kzalloc(DMX_BUF_LEN, GFP_KERNEL);
> +	u32 buffer_offset;
> +
> +	struct dvb_dummy_table_pat pat = {0};
> +	struct dvb_dummy_table_sdt sdt = {0};
> +
> +	struct dvb_dummy_table_pmt *pmt_sections;
> +
> +	struct dvb_dummy_table_pat_program *programs = NULL;
> +	struct dvb_dummy_table_sdt_service *services = NULL;
> +
> +	bool update_version_num = false;
> +	u16 pmt_pid;
> +
> +	dvb_dummy_fe_init_channels(state->channels);
> +
> +	programs =
> +		dvb_dummy_fe_mpeg_pat_program_concat_into_new(state->channels);
> +	services =
> +		dvb_dummy_fe_mpeg_sdt_service_concat_into_new(state->channels);
> +
> +	/* assemble all programs and assign to PAT */
> +	dvb_dummy_fe_mpeg_pat_program_assign(&pat, programs);
> +
> +	/* assemble all services and assign to SDT */
> +	dvb_dummy_fe_mpeg_sdt_service_assign(&sdt, services);
> +
> +	/* a section for each program_id */
> +	pmt_sections = kcalloc(pat.programs,
> +			       sizeof(struct dvb_dummy_table_pmt),
> +			       GFP_KERNEL);
> +
> +	dvb_dummy_fe_mpeg_pmt_create_section_for_each_pat_entry(&pat,
> +								pmt_sections);
> +
> +	dvb_dummy_fe_mpeg_pmt_stream_match_with_sections(state->channels,
> +							 pmt_sections,
> +							 pat.programs);
> +
> +	dvb_dummy_fe_mpeg_pat_table_init(&pat,
> +					 update_version_num,
> +					 TRANSPORT_STREAM_ID);
> +	dvb_dummy_fe_mpeg_sdt_table_init(&sdt,
> +				    update_version_num,
> +				    TRANSPORT_STREAM_ID);
> +	while (true) {
> +
> +		memset(buf, 0, DMX_BUF_LEN);
> +		buffer_offset = 0;
> +
> +		if (!ticks || (ticks % 50) == 0) {

Hmm... just:

		if (!(ticks % 50)) {

should be enough, as (0 % 50) is equal to zero. So, no need to do the
first check.

> +			/* push PSI packets into the buffer */
> +
> +			buffer_offset +=
> +				dvb_dummy_fe_mpeg_pat_write_into(buf,
> +								 buffer_offset,
> +								 &pat);
> +			for (i = 0; i < pat.programs; ++i) {
> +
> +				pmt_pid =
> +				dvb_dummy_fe_mpeg_pmt_get_pid(&pmt_sections[i],
> +							      &pat);
> +
> +				WARN_ON(pmt_pid < 0); /* not found */
> +
> +				buffer_offset +=
> +					/* write each section into buffer */
> +					dvb_dummy_fe_mpeg_pmt_write_into(buf,
> +							buffer_offset,
> +							&pmt_sections[i],
> +							pmt_pid);
> +			}
> +
> +			buffer_offset +=
> +				dvb_dummy_fe_mpeg_sdt_write_into(buf,
> +								 buffer_offset,
> +								 &sdt);
> +
> +
> +			WARN_ON(buffer_offset > DMX_BUF_LEN); /* overflow */
> +			msleep_interruptible(SLEEP_MSECS);
> +		}
> +	}
> +
> +	dvb_dummy_fe_mpeg_pat_table_destroy(&pat);
> +	dvb_dummy_fe_mpeg_sdt_table_destroy(&sdt);
> +
> +	for (i = 0; i < pat.programs; ++i) {
> +		/* destroy all PMT sections */
> +		dvb_dummy_fe_mpeg_pmt_table_destroy(&pmt_sections[i]);
> +		kfree(&pmt_sections[i]);
> +	}
> +
> +	dvb_dummy_fe_channels_destroy(state->channels);
> +	kfree(buf);
> +}
> +
>  struct dvb_dummy_fe_cnr_to_qual_s {
>  	/* attempt to use the same values as libdvbv5 */
>  	u32 modulation;
> @@ -75,17 +1472,7 @@ static struct dvb_dummy_fe_cnr_to_qual_s dvb_t_cnr_2_qual[] = {
>  	{ QAM_64, FEC_7_8, 22.0, 24.0},
>  };
>  
> -struct dvb_dummy_fe_config {
> -	/* probability of losing the lock due to low snr */
> -	u8 drop_tslock_probability_on_low_snr;
> -};
>  
> -struct dvb_dummy_fe_state {
> -	struct dvb_frontend frontend;
> -	struct dvb_dummy_fe_config config;
> -	struct delayed_work poll_snr;
> -	enum fe_status status;
> -};
>  
>  void poll_snr_handler(struct work_struct *work)
>  {

Thanks,
Mauro
_______________________________________________
Linux-kernel-mentees mailing list
Linux-kernel-mentees@lists.linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/linux-kernel-mentees

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC 2/3] media: dvb_dummy_fe.c: lose TS lock on bad snr
  2020-03-18  6:00   ` [Linux-kernel-mentees] " Daniel W. S. Almeida
  (?)
  (?)
@ 2020-03-18 14:42   ` kbuild test robot
  -1 siblings, 0 replies; 21+ messages in thread
From: kbuild test robot @ 2020-03-18 14:42 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 1769 bytes --]

Hi "Daniel,

[FYI, it's a private test report for your RFC patch.]
[auto build test ERROR on linuxtv-media/master]
[also build test ERROR on v5.6-rc6 next-20200317]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Daniel-W-S-Almeida/Implement-a-virtual-DVB-driver/20200318-161944
base:   git://linuxtv.org/media_tree.git master
config: parisc-allyesconfig (attached as .config)
compiler: hppa-linux-gcc (GCC) 9.2.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        GCC_VERSION=9.2.0 make.cross ARCH=parisc 

If you fix the issue, kindly add following tag
Reported-by: kbuild test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

>> hppa-linux-ld: drivers/media/dvb-frontends/dvb_dummy_fe.o:(.data+0x190): multiple definition of `dvb_s_cnr_2_qual'; drivers/media/tuners/dvb_dummy_tuner.o:(.data+0x210): first defined here
>> hppa-linux-ld: drivers/media/dvb-frontends/dvb_dummy_fe.o:(.data+0xf0): multiple definition of `dvb_s2_cnr_2_qual'; drivers/media/tuners/dvb_dummy_tuner.o:(.data+0x170): first defined here
>> hppa-linux-ld: drivers/media/dvb-frontends/dvb_dummy_fe.o:(.data+0x1e0): multiple definition of `dvb_c_cnr_2_qual'; drivers/media/tuners/dvb_dummy_tuner.o:(.data+0x260): first defined here

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org

[-- Attachment #2: config.gz --]
[-- Type: application/gzip, Size: 60431 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [RFC 2/3] media: dvb_dummy_fe.c: lose TS lock on bad snr
  2020-03-18  6:00   ` [Linux-kernel-mentees] " Daniel W. S. Almeida
                     ` (2 preceding siblings ...)
  (?)
@ 2020-03-18 21:15   ` kbuild test robot
  -1 siblings, 0 replies; 21+ messages in thread
From: kbuild test robot @ 2020-03-18 21:15 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 1238 bytes --]

Hi "Daniel,

[FYI, it's a private test report for your RFC patch.]
[auto build test WARNING on linuxtv-media/master]
[also build test WARNING on v5.6-rc6 next-20200317]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/Daniel-W-S-Almeida/Implement-a-virtual-DVB-driver/20200318-161944
base:   git://linuxtv.org/media_tree.git master
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.1-180-g0558317d-dirty
        make ARCH=x86_64 allmodconfig
        make C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__'

If you fix the issue, kindly add following tag
Reported-by: kbuild test robot <lkp@intel.com>


sparse warnings: (new ones prefixed by >>)

>> drivers/media/dvb-frontends/dvb_dummy_fe.c:90:6: sparse: sparse: symbol 'poll_snr_handler' was not declared. Should it be static?

Please review and possibly fold the followup patch.

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [RFC PATCH] media: dvb_dummy_fe.c: poll_snr_handler() can be static
  2020-03-18  6:00   ` [Linux-kernel-mentees] " Daniel W. S. Almeida
                     ` (3 preceding siblings ...)
  (?)
@ 2020-03-18 21:15   ` kbuild test robot
  -1 siblings, 0 replies; 21+ messages in thread
From: kbuild test robot @ 2020-03-18 21:15 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 742 bytes --]


Fixes: 56586dd7135b ("media: dvb_dummy_fe.c: lose TS lock on bad snr")
Signed-off-by: kbuild test robot <lkp@intel.com>
---
 dvb_dummy_fe.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/media/dvb-frontends/dvb_dummy_fe.c b/drivers/media/dvb-frontends/dvb_dummy_fe.c
index 726c964a523d7..56edb1dbf8d94 100644
--- a/drivers/media/dvb-frontends/dvb_dummy_fe.c
+++ b/drivers/media/dvb-frontends/dvb_dummy_fe.c
@@ -87,7 +87,7 @@ struct dvb_dummy_fe_state {
 	enum fe_status status;
 };
 
-void poll_snr_handler(struct work_struct *work)
+static void poll_snr_handler(struct work_struct *work)
 {
 	/* periodically check the signal quality and eventually
 	 * lose the TS lock if it dips too low

^ permalink raw reply related	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2020-03-18 21:15 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-03-18  6:00 [RFC 0/3] Implement a virtual DVB driver Daniel W. S. Almeida
2020-03-18  6:00 ` [Linux-kernel-mentees] " Daniel W. S. Almeida
2020-03-18  6:00 ` [RFC 1/3] media: dvb_dummy_tuner: implement driver skeleton Daniel W. S. Almeida
2020-03-18  6:00   ` [Linux-kernel-mentees] " Daniel W. S. Almeida
2020-03-18  8:17   ` Mauro Carvalho Chehab
2020-03-18  8:17     ` [Linux-kernel-mentees] " Mauro Carvalho Chehab
2020-03-18  8:54   ` Kieran Bingham
2020-03-18  8:54     ` [Linux-kernel-mentees] " Kieran Bingham
2020-03-18  9:13     ` Mauro Carvalho Chehab
2020-03-18  9:13       ` [Linux-kernel-mentees] " Mauro Carvalho Chehab
2020-03-18  6:00 ` [RFC 2/3] media: dvb_dummy_fe.c: lose TS lock on bad snr Daniel W. S. Almeida
2020-03-18  6:00   ` [Linux-kernel-mentees] " Daniel W. S. Almeida
2020-03-18  8:43   ` Mauro Carvalho Chehab
2020-03-18  8:43     ` [Linux-kernel-mentees] " Mauro Carvalho Chehab
2020-03-18 14:42   ` kbuild test robot
2020-03-18 21:15   ` kbuild test robot
2020-03-18 21:15   ` [RFC PATCH] media: dvb_dummy_fe.c: poll_snr_handler() can be static kbuild test robot
2020-03-18  6:00 ` [RFC 3/3] media: dvb_dummy_fe.c: write PSI information into DMX buffer Daniel W. S. Almeida
2020-03-18  6:00   ` [Linux-kernel-mentees] " Daniel W. S. Almeida
2020-03-18 10:56   ` Mauro Carvalho Chehab
2020-03-18 10:56     ` [Linux-kernel-mentees] " Mauro Carvalho Chehab

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.