2020-05-02 03:24:15

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 00/11] media: vidtv: implement a virtual DVB driver

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


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

This series currently adds:
-fake tuner, demodulator and bridge drivers
-a PSI generator
-a PES/TS packetizer
-a SMPTE 302m encoder, capable of encoding AES3 audio into MPEG TS
-a barebones TS mux abstraction

I appreciate any feedback!

Changes in v4:
Added a PES packetizer
Implemented a minimum version of the SMPTE 302m encoder for AES3 audio
Fixed endianness in the PSI generator, converting fields to big endian where applicable
Added a minimal TS mux abstraction

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

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

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

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

Fix memcpy call for the config struct

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

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

Hexadecimal literals are now lower case

Prefer short function names / reduce function signatures

Add #defines for constants when computing section lengths

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


Daniel W. S. Almeida (11):
media: vidtv: add Kconfig entry
media: vidtv: implement a tuner driver
media: vidtv: implement a demodulator driver
media: vidtv: move config structs into a separate header
media: vidtv: add a bridge driver
media: vidtv: add wrappers for memcpy and memset
media: vidtv: add MPEG TS common code
media: vidtv: implement a PSI generator
media: vidtv: implement a PES packetizer
media: vidtv: Implement a SMPTE 302M encoder
media: vidtv: Add a MPEG Transport Stream Multiplexer

drivers/media/test-drivers/Kconfig | 10 +
drivers/media/test-drivers/Makefile | 1 +
drivers/media/test-drivers/vidtv/Kconfig | 11 +
drivers/media/test-drivers/vidtv/Makefile | 7 +
.../media/test-drivers/vidtv/vidtv_bridge.c | 442 +++++++
.../media/test-drivers/vidtv/vidtv_bridge.h | 39 +
.../media/test-drivers/vidtv/vidtv_channel.c | 326 +++++
.../media/test-drivers/vidtv/vidtv_channel.h | 66 +
.../media/test-drivers/vidtv/vidtv_common.c | 51 +
.../media/test-drivers/vidtv/vidtv_common.h | 35 +
.../media/test-drivers/vidtv/vidtv_config.h | 35 +
.../media/test-drivers/vidtv/vidtv_demod.c | 494 +++++++
.../media/test-drivers/vidtv/vidtv_demod.h | 34 +
.../media/test-drivers/vidtv/vidtv_encoder.h | 103 ++
drivers/media/test-drivers/vidtv/vidtv_mux.c | 423 ++++++
drivers/media/test-drivers/vidtv/vidtv_mux.h | 105 ++
drivers/media/test-drivers/vidtv/vidtv_pes.c | 429 ++++++
drivers/media/test-drivers/vidtv/vidtv_pes.h | 185 +++
drivers/media/test-drivers/vidtv/vidtv_psi.c | 1155 +++++++++++++++++
drivers/media/test-drivers/vidtv/vidtv_psi.h | 362 ++++++
.../media/test-drivers/vidtv/vidtv_s302m.c | 608 +++++++++
.../media/test-drivers/vidtv/vidtv_s302m.h | 99 ++
drivers/media/test-drivers/vidtv/vidtv_ts.c | 130 ++
drivers/media/test-drivers/vidtv/vidtv_ts.h | 103 ++
.../media/test-drivers/vidtv/vidtv_tuner.c | 403 ++++++
25 files changed, 5656 insertions(+)
create mode 100644 drivers/media/test-drivers/vidtv/Kconfig
create mode 100644 drivers/media/test-drivers/vidtv/Makefile
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_bridge.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_bridge.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_channel.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_channel.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_common.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_common.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_config.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_demod.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_demod.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_encoder.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_mux.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_mux.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_pes.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_pes.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_psi.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_psi.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_s302m.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_s302m.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_tuner.c

--
2.26.2


2020-05-02 03:24:18

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 01/11] media: vidtv: add Kconfig entry

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

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

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

diff --git a/drivers/media/test-drivers/Kconfig b/drivers/media/test-drivers/Kconfig
index 188381c855939..7d273a8a7acc2 100644
--- a/drivers/media/test-drivers/Kconfig
+++ b/drivers/media/test-drivers/Kconfig
@@ -4,6 +4,10 @@ menuconfig V4L_TEST_DRIVERS
bool "V4L test drivers"
depends on VIDEO_DEV

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

source "drivers/media/test-drivers/vimc/Kconfig"
@@ -24,3 +28,9 @@ config VIDEO_VIM2M
source "drivers/media/test-drivers/vicodec/Kconfig"

endif #V4L_TEST_DRIVERS
+
+if DVB_TEST_DRIVERS
+
+source "drivers/media/test-drivers/vidtv/Kconfig"
+
+endif #DVB_TEST_DRIVERS
diff --git a/drivers/media/test-drivers/Makefile b/drivers/media/test-drivers/Makefile
index 74410d3a9f2d2..9f0e4ebb2efe7 100644
--- a/drivers/media/test-drivers/Makefile
+++ b/drivers/media/test-drivers/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_VIDEO_VIMC) += vimc/
obj-$(CONFIG_VIDEO_VIVID) += vivid/
obj-$(CONFIG_VIDEO_VIM2M) += vim2m.o
obj-$(CONFIG_VIDEO_VICODEC) += vicodec/
+obj-$(CONFIG_DVB_VIDTV) += vidtv/
diff --git a/drivers/media/test-drivers/vidtv/Kconfig b/drivers/media/test-drivers/vidtv/Kconfig
new file mode 100644
index 0000000000000..22c4fd39461f1
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DVB_VIDTV
+ tristate "Virtual DVB Driver (vidtv)"
+ depends on DVB_CORE && MEDIA_SUPPORT && I2C
+ help
+ The virtual DVB test driver serves as a reference DVB driver and helps
+ validate the existing APIs in the media subsystem. It can also aid developers
+ working on userspace applications.
+
+
+ When in doubt, say N.
diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
new file mode 100644
index 0000000000000..d1558d84eeaed
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+
--
2.26.2

2020-05-02 03:24:29

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 02/11] media: vidtv: implement a tuner driver

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

The virtual DVB test driver serves as a reference DVB driver and helps
validate the existing APIs in the media subsystem. It can also aid developers
working on userspace applications.

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

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

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

+obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o
diff --git a/drivers/media/test-drivers/vidtv/vidtv_tuner.c b/drivers/media/test-drivers/vidtv/vidtv_tuner.c
new file mode 100644
index 0000000000000..a790508f935b3
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_tuner.c
@@ -0,0 +1,411 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * The Virtual DVB test driver serves as a reference DVB driver and helps
+ * validate the existing APIs in the media subsystem. It can also aid
+ * developers working on userspace applications.
+ *
+ * The vidtv tuner should support common TV standards such as
+ * DVB-T/T2/S/S2, ISDB-T and ATSC when completed.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <media/dvb_frontend.h>
+
+MODULE_DESCRIPTION("Virtual DTV Tuner");
+MODULE_AUTHOR("Daniel W. S. Almeida");
+MODULE_LICENSE("GPL");
+
+struct vidtv_tuner_config {
+ struct dvb_frontend *fe;
+ u32 mock_power_up_delay_msec;
+ u32 mock_tune_delay_msec;
+ u32 vidtv_valid_dvb_t_freqs[8];
+ u32 vidtv_valid_dvb_c_freqs[8];
+ u32 vidtv_valid_dvb_s_freqs[8];
+ u8 max_frequency_shift_hz;
+};
+
+struct vidtv_tuner_cnr_to_qual_s {
+ /* attempt to use the same values as libdvbv5 */
+ u32 modulation;
+ u32 fec;
+ u32 cnr_ok, cnr_good;
+};
+
+struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_c_cnr_2_qual[] = {
+ /* from libdvbv5 source code, in milli db */
+ { QAM_256, FEC_NONE, 34000, 38000},
+ { QAM_64, FEC_NONE, 30000, 34000},
+};
+
+struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s_cnr_2_qual[] = {
+ /* from libdvbv5 source code, in milli db */
+ { QPSK, FEC_1_2, 7000, 10000},
+
+ { QPSK, FEC_2_3, 9000, 12000},
+ { QPSK, FEC_3_4, 10000, 13000},
+ { QPSK, FEC_5_6, 11000, 14000},
+
+ { QPSK, FEC_7_8, 12000, 15000},
+};
+
+struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s2_cnr_2_qual[] = {
+ /* from libdvbv5 source code, in milli db */
+ { QPSK, FEC_1_2, 9000, 12000},
+ { QPSK, FEC_2_3, 11000, 14000},
+ { QPSK, FEC_3_4, 12000, 15000},
+ { QPSK, FEC_5_6, 12000, 15000},
+ { QPSK, FEC_8_9, 13000, 16000},
+ { QPSK, FEC_9_10, 13500, 16500},
+ { PSK_8, FEC_2_3, 14500, 17500},
+ { PSK_8, FEC_3_4, 16000, 19000},
+ { PSK_8, FEC_5_6, 17500, 20500},
+ { PSK_8, FEC_8_9, 19000, 22000},
+};
+
+static struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_t_cnr_2_qual[] = {
+ /* from libdvbv5 source code, in milli db*/
+ { QPSK, FEC_1_2, 4100, 5900},
+ { QPSK, FEC_2_3, 6100, 9600},
+ { QPSK, FEC_3_4, 7200, 12400},
+ { QPSK, FEC_5_6, 8500, 15600},
+ { QPSK, FEC_7_8, 9200, 17500},
+
+ { QAM_16, FEC_1_2, 9800, 11800},
+ { QAM_16, FEC_2_3, 12100, 15300},
+ { QAM_16, FEC_3_4, 13400, 18100},
+ { QAM_16, FEC_5_6, 14800, 21300},
+ { QAM_16, FEC_7_8, 15700, 23600},
+
+ { QAM_64, FEC_1_2, 14000, 16000},
+ { QAM_64, FEC_2_3, 19900, 25400},
+ { QAM_64, FEC_3_4, 24900, 27900},
+ { QAM_64, FEC_5_6, 21300, 23300},
+ { QAM_64, FEC_7_8, 22000, 24000},
+};
+
+struct vidtv_tuner_hardware_state {
+ bool asleep;
+ u32 lock_status;
+ u32 if_frequency;
+ u32 tuned_frequency;
+ u32 bandwidth;
+};
+
+struct vidtv_tuner_dev {
+ struct dvb_frontend *fe;
+ struct vidtv_tuner_hardware_state hw_state;
+ struct vidtv_tuner_config config;
+};
+
+static struct vidtv_tuner_dev*
+vidtv_tuner_get_dev(struct dvb_frontend *fe)
+{
+ struct i2c_client *client = fe->tuner_priv;
+
+ return i2c_get_clientdata(client);
+}
+
+static s32 vidtv_tuner_check_frequency_shift(struct dvb_frontend *fe)
+{
+ struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ struct vidtv_tuner_config config = tuner_dev->config;
+ u32 *valid_freqs = NULL;
+ u32 array_sz = 0;
+ u32 i;
+ u32 shift;
+
+ switch (c->delivery_system) {
+ case SYS_DVBT:
+ case SYS_DVBT2:
+ valid_freqs = config.vidtv_valid_dvb_t_freqs;
+ array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_t_freqs);
+ break;
+ case SYS_DVBS:
+ case SYS_DVBS2:
+ valid_freqs = config.vidtv_valid_dvb_s_freqs;
+ array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_s_freqs);
+ break;
+ case SYS_DVBC_ANNEX_A:
+ valid_freqs = config.vidtv_valid_dvb_c_freqs;
+ array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_c_freqs);
+ break;
+
+ default:
+ pr_warn("%s: unsupported delivery system: %u\n",
+ __func__,
+ c->delivery_system);
+ break;
+ }
+
+ for (i = 0; i < array_sz; i++) {
+ shift = abs(c->frequency - valid_freqs[i]);
+
+ if (!shift)
+ return 0;
+
+ /*
+ * This will provide a value from 0 to 100 that would
+ * indicate how far is the tuned frequency from the
+ * right one.
+ */
+ if (shift < config.max_frequency_shift_hz)
+ return shift * 100 / config.max_frequency_shift_hz;
+ }
+
+ return -1;
+}
+
+static int
+vidtv_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength)
+{
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ struct vidtv_tuner_cnr_to_qual_s *cnr2qual = NULL;
+ u32 array_size = 0;
+ s32 shift;
+ u32 i;
+
+ shift = vidtv_tuner_check_frequency_shift(fe);
+
+ switch (c->delivery_system) {
+ case SYS_DVBT:
+ case SYS_DVBT2:
+ cnr2qual = vidtv_tuner_t_cnr_2_qual;
+ array_size = ARRAY_SIZE(vidtv_tuner_t_cnr_2_qual);
+ break;
+
+ case SYS_DVBS:
+ cnr2qual = vidtv_tuner_s_cnr_2_qual;
+ array_size = ARRAY_SIZE(vidtv_tuner_s_cnr_2_qual);
+ break;
+
+ case SYS_DVBS2:
+ cnr2qual = vidtv_tuner_s2_cnr_2_qual;
+ array_size = ARRAY_SIZE(vidtv_tuner_s2_cnr_2_qual);
+ break;
+
+ case SYS_DVBC_ANNEX_A:
+ cnr2qual = vidtv_tuner_c_cnr_2_qual;
+ array_size = ARRAY_SIZE(vidtv_tuner_c_cnr_2_qual);
+ break;
+
+ default:
+ pr_warn("%s: unsupported delivery system: %u\n",
+ __func__,
+ c->delivery_system);
+ return -EINVAL;
+ }
+
+ for (i = 0; i <= array_size; i++) {
+ if (cnr2qual[i].modulation != c->modulation ||
+ cnr2qual[i].fec != c->fec_inner)
+ continue;
+
+ if (!shift) {
+ *strength = cnr2qual[i].cnr_good;
+ return 0;
+ }
+ if (shift < 0) { /* Channel not tuned */
+ *strength = 0;
+ return 0;
+ }
+ /*
+ * Channel tuned at wrong frequency. Simulate that the
+ * Carrier S/N ratio is not too good.
+ */
+
+ *strength = cnr2qual[i].cnr_ok -
+ (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok);
+ return 0;
+ }
+
+ /*
+ * do a linear interpolation between 34dB and 10dB if we can't
+ * match against the table
+ */
+ *strength = 34 - 24 * shift / 100;
+ return 0;
+}
+
+static int vidtv_tuner_init(struct dvb_frontend *fe)
+{
+ struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
+ struct vidtv_tuner_config config = tuner_dev->config;
+
+ msleep_interruptible(config.mock_power_up_delay_msec);
+
+ tuner_dev->hw_state.asleep = false;
+ tuner_dev->hw_state.if_frequency = 5000;
+
+ return 0;
+}
+
+static int vidtv_tuner_sleep(struct dvb_frontend *fe)
+{
+ struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
+
+ tuner_dev->hw_state.asleep = true;
+ return 0;
+}
+
+static int vidtv_tuner_suspend(struct dvb_frontend *fe)
+{
+ struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
+
+ tuner_dev->hw_state.asleep = true;
+ return 0;
+}
+
+static int vidtv_tuner_resume(struct dvb_frontend *fe)
+{
+ struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
+
+ tuner_dev->hw_state.asleep = false;
+ return 0;
+}
+
+static int vidtv_tuner_set_params(struct dvb_frontend *fe)
+{
+ struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
+ struct vidtv_tuner_config config = tuner_dev->config;
+ struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+ u32 min_freq = fe->ops.tuner_ops.info.frequency_min_hz;
+ u32 max_freq = fe->ops.tuner_ops.info.frequency_max_hz;
+ u32 min_bw = fe->ops.tuner_ops.info.bandwidth_min;
+ u32 max_bw = fe->ops.tuner_ops.info.bandwidth_max;
+
+ if (c->frequency < min_freq || c->frequency > max_freq ||
+ c->bandwidth_hz < min_bw || c->bandwidth_hz > max_bw) {
+ tuner_dev->hw_state.lock_status = 0;
+ return -EINVAL;
+ }
+
+ tuner_dev->hw_state.tuned_frequency = c->frequency;
+ tuner_dev->hw_state.bandwidth = c->bandwidth_hz;
+ tuner_dev->hw_state.lock_status = TUNER_STATUS_LOCKED;
+
+ msleep_interruptible(config.mock_tune_delay_msec);
+ return 0;
+}
+
+static int vidtv_tuner_set_config(struct dvb_frontend *fe,
+ void *priv_cfg)
+{
+ struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
+
+ memcpy(&tuner_dev->config, priv_cfg, sizeof(tuner_dev->config));
+
+ return 0;
+}
+
+static int vidtv_tuner_get_frequency(struct dvb_frontend *fe,
+ u32 *frequency)
+{
+ struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
+
+ *frequency = tuner_dev->hw_state.tuned_frequency;
+
+ return 0;
+}
+
+static int vidtv_tuner_get_bandwidth(struct dvb_frontend *fe,
+ u32 *bandwidth)
+{
+ struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
+
+ *bandwidth = tuner_dev->hw_state.bandwidth;
+
+ return 0;
+}
+
+static int vidtv_tuner_get_if_frequency(struct dvb_frontend *fe,
+ u32 *frequency)
+{
+ struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
+
+ *frequency = tuner_dev->hw_state.if_frequency;
+
+ return 0;
+}
+
+static int vidtv_tuner_get_status(struct dvb_frontend *fe, u32 *status)
+{
+ struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
+
+ *status = tuner_dev->hw_state.lock_status;
+
+ return 0;
+}
+
+static const struct dvb_tuner_ops vidtv_tuner_ops = {
+ .init = vidtv_tuner_init,
+ .sleep = vidtv_tuner_sleep,
+ .suspend = vidtv_tuner_suspend,
+ .resume = vidtv_tuner_resume,
+ .set_params = vidtv_tuner_set_params,
+ .set_config = vidtv_tuner_set_config,
+ .get_bandwidth = vidtv_tuner_get_bandwidth,
+ .get_frequency = vidtv_tuner_get_frequency,
+ .get_if_frequency = vidtv_tuner_get_if_frequency,
+ .get_status = vidtv_tuner_get_status,
+ .get_rf_strength = vidtv_tuner_get_signal_strength
+};
+
+static const struct i2c_device_id vidtv_tuner_i2c_id_table[] = {
+ {"vidtv_tuner", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, vidtv_tuner_i2c_id_table);
+
+static int vidtv_tuner_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct vidtv_tuner_config *config = client->dev.platform_data;
+ struct dvb_frontend *fe = config->fe;
+ struct vidtv_tuner_dev *tuner_dev = NULL;
+
+ tuner_dev = kzalloc(sizeof(*tuner_dev), GFP_KERNEL);
+ if (!tuner_dev)
+ return -ENOMEM;
+
+ tuner_dev->fe = config->fe;
+ i2c_set_clientdata(client, tuner_dev);
+
+ memcpy(&fe->ops.tuner_ops,
+ &vidtv_tuner_ops,
+ sizeof(struct dvb_tuner_ops));
+
+ fe->tuner_priv = client;
+
+ return 0;
+}
+
+static int vidtv_tuner_i2c_remove(struct i2c_client *client)
+{
+ struct vidtv_tuner_dev *tuner_dev = i2c_get_clientdata(client);
+ struct dvb_frontend *fe = tuner_dev->fe;
+
+ memset(&fe->ops.tuner_ops, 0, sizeof(struct dvb_tuner_ops));
+ fe->tuner_priv = NULL;
+ kfree(tuner_dev);
+
+ return 0;
+}
+
+static struct i2c_driver vidtv_tuner_i2c_driver = {
+ .driver = {
+ .name = "vidtv_tuner",
+ .suppress_bind_attrs = true,
+ },
+ .probe = vidtv_tuner_i2c_probe,
+ .remove = vidtv_tuner_i2c_remove,
+ .id_table = vidtv_tuner_i2c_id_table,
+};
+module_i2c_driver(vidtv_tuner_i2c_driver);
--
2.26.2

2020-05-02 03:24:36

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 03/11] media: vidtv: implement a demodulator driver

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

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

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

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

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

-obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o
+obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o
diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.c b/drivers/media/test-drivers/vidtv/vidtv_demod.c
new file mode 100644
index 0000000000000..e5f157e4bbe48
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_demod.c
@@ -0,0 +1,493 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * The Virtual DVB test driver serves as a reference DVB driver and helps
+ * validate the existing APIs in the media subsystem. It can also aid
+ * developers working on userspace applications.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ * Based on the example driver written by Emard <[email protected]>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/random.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <media/dvb_frontend.h>
+#include "vidtv_demod.h"
+
+MODULE_DESCRIPTION("Virtual DVB Demodulator Driver");
+MODULE_AUTHOR("Daniel W. S. Almeida");
+MODULE_LICENSE("GPL");
+
+struct vidtv_demod_cnr_to_qual_s vidtv_demod_c_cnr_2_qual[] = {
+ /* from libdvbv5 source code, in milli db */
+ { QAM_256, FEC_NONE, 34000, 38000},
+ { QAM_64, FEC_NONE, 30000, 34000},
+};
+
+struct vidtv_demod_cnr_to_qual_s vidtv_demod_s_cnr_2_qual[] = {
+ /* from libdvbv5 source code, in milli db */
+ { QPSK, FEC_1_2, 7000, 10000},
+ { QPSK, FEC_2_3, 9000, 12000},
+ { QPSK, FEC_3_4, 10000, 13000},
+ { QPSK, FEC_5_6, 11000, 14000},
+ { QPSK, FEC_7_8, 12000, 15000},
+};
+
+struct vidtv_demod_cnr_to_qual_s vidtv_demod_s2_cnr_2_qual[] = {
+ /* from libdvbv5 source code, in milli db */
+ { QPSK, FEC_1_2, 9000, 12000},
+ { QPSK, FEC_2_3, 11000, 14000},
+ { QPSK, FEC_3_4, 12000, 15000},
+ { QPSK, FEC_5_6, 12000, 15000},
+ { QPSK, FEC_8_9, 13000, 16000},
+ { QPSK, FEC_9_10, 13500, 16500},
+ { PSK_8, FEC_2_3, 14500, 17500},
+ { PSK_8, FEC_3_4, 16000, 19000},
+ { PSK_8, FEC_5_6, 17500, 20500},
+ { PSK_8, FEC_8_9, 19000, 22000},
+};
+
+static struct vidtv_demod_cnr_to_qual_s vidtv_demod_t_cnr_2_qual[] = {
+ /* from libdvbv5 source code, in milli db*/
+ { QPSK, FEC_1_2, 4100, 5900},
+ { QPSK, FEC_2_3, 6100, 9600},
+ { QPSK, FEC_3_4, 7200, 12400},
+ { QPSK, FEC_5_6, 8500, 15600},
+ { QPSK, FEC_7_8, 9200, 17500},
+
+ { QAM_16, FEC_1_2, 9800, 11800},
+ { QAM_16, FEC_2_3, 12100, 15300},
+ { QAM_16, FEC_3_4, 13400, 18100},
+ { QAM_16, FEC_5_6, 14800, 21300},
+ { QAM_16, FEC_7_8, 15700, 23600},
+
+ { QAM_64, FEC_1_2, 14000, 16000},
+ { QAM_64, FEC_2_3, 19900, 25400},
+ { QAM_64, FEC_3_4, 24900, 27900},
+ { QAM_64, FEC_5_6, 21300, 23300},
+ { QAM_64, FEC_7_8, 22000, 24000},
+};
+
+static struct vidtv_demod_cnr_to_qual_s
+*vidtv_match_cnr_s(struct dvb_frontend *fe)
+{
+ struct dtv_frontend_properties *c;
+ struct vidtv_demod_cnr_to_qual_s *cnr2qual = NULL;
+ u32 array_size = 0;
+ u32 i;
+
+ c = &fe->dtv_property_cache;
+
+ switch (c->delivery_system) {
+ case SYS_DVBT:
+ case SYS_DVBT2:
+ cnr2qual = vidtv_demod_t_cnr_2_qual;
+ array_size = ARRAY_SIZE(vidtv_demod_t_cnr_2_qual);
+ break;
+
+ case SYS_DVBS:
+ cnr2qual = vidtv_demod_s_cnr_2_qual;
+ array_size = ARRAY_SIZE(vidtv_demod_s_cnr_2_qual);
+ break;
+
+ case SYS_DVBS2:
+ cnr2qual = vidtv_demod_s2_cnr_2_qual;
+ array_size = ARRAY_SIZE(vidtv_demod_s2_cnr_2_qual);
+ break;
+
+ case SYS_DVBC_ANNEX_A:
+ cnr2qual = vidtv_demod_c_cnr_2_qual;
+ array_size = ARRAY_SIZE(vidtv_demod_c_cnr_2_qual);
+ break;
+
+ default:
+ pr_warn("%s: unsupported delivery system: %u\n",
+ __func__,
+ c->delivery_system);
+ break;
+ }
+
+ for (i = 0; i <= array_size; i++)
+ if (cnr2qual[i].modulation == c->modulation &&
+ cnr2qual[i].fec == c->fec_inner)
+ return &cnr2qual[i];
+
+ return NULL; /* not found */
+}
+
+static void vidtv_demod_poll_snr_handler(struct work_struct *work)
+{
+ /*
+ * periodically check the signal quality and eventually
+ * lose the TS lock if it dips too low
+ */
+ struct vidtv_demod_state *state;
+ struct dtv_frontend_properties *c;
+ struct vidtv_demod_cnr_to_qual_s *cnr2qual = NULL;
+ struct vidtv_demod_config *config;
+ u16 snr = 0;
+
+ state = container_of(work, struct vidtv_demod_state, poll_snr.work);
+ c = &state->frontend.dtv_property_cache;
+ config = &state->config;
+
+ if (!state->frontend.ops.tuner_ops.get_rf_strength)
+ return;
+
+ state->frontend.ops.tuner_ops.get_rf_strength(&state->frontend, &snr);
+
+ cnr2qual = vidtv_match_cnr_s(&state->frontend);
+ if (!cnr2qual)
+ return;
+
+ if (snr < cnr2qual->cnr_ok) {
+ /* eventually lose the TS lock */
+ if (prandom_u32_max(100) < config->drop_tslock_prob_on_low_snr)
+ state->status = 0;
+ } else {
+ /* recover if the signal improves */
+ if (prandom_u32_max(100) <
+ config->recover_tslock_prob_on_good_snr)
+ state->status = FE_HAS_SIGNAL |
+ FE_HAS_CARRIER |
+ FE_HAS_VITERBI |
+ FE_HAS_SYNC |
+ FE_HAS_LOCK;
+ }
+
+ schedule_delayed_work(&state->poll_snr, msecs_to_jiffies(2000));
+}
+
+static int vidtv_demod_read_status(struct dvb_frontend *fe,
+ enum fe_status *status)
+{
+ struct vidtv_demod_state *state = fe->demodulator_priv;
+
+ *status = state->status;
+
+ return 0;
+}
+
+static int vidtv_demod_read_ber(struct dvb_frontend *fe, u32 *ber)
+{
+ *ber = 0;
+ return 0;
+}
+
+static int vidtv_demod_read_signal_strength(struct dvb_frontend *fe,
+ u16 *strength)
+{
+ *strength = 0;
+ return 0;
+}
+
+static int vidtv_demod_read_snr(struct dvb_frontend *fe, u16 *snr)
+{
+ *snr = 0;
+ return 0;
+}
+
+static int vidtv_demod_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
+{
+ *ucblocks = 0;
+ return 0;
+}
+
+/*
+ * Should only be implemented if it actually reads something from the hardware.
+ * Also, it should check for the locks, in order to avoid report wrong data
+ * to userspace.
+ */
+static int vidtv_demod_get_frontend(struct dvb_frontend *fe,
+ struct dtv_frontend_properties *p)
+{
+ return 0;
+}
+
+static int vidtv_demod_set_frontend(struct dvb_frontend *fe)
+{
+ struct vidtv_demod_state *state = fe->demodulator_priv;
+ struct vidtv_demod_cnr_to_qual_s *cnr2qual = NULL;
+ u32 tuner_status = 0;
+
+ if (fe->ops.tuner_ops.set_params) {
+ fe->ops.tuner_ops.set_params(fe);
+
+ /* store the CNR returned by the tuner */
+ fe->ops.tuner_ops.get_rf_strength(fe, &state->tuner_cnr);
+
+ fe->ops.tuner_ops.get_status(fe, &tuner_status);
+ state->status = (state->tuner_cnr > 0) ? FE_HAS_SIGNAL |
+ FE_HAS_CARRIER |
+ FE_HAS_VITERBI |
+ FE_HAS_SYNC |
+ FE_HAS_LOCK :
+ 0;
+ cnr2qual = vidtv_match_cnr_s(fe);
+
+ /* signal isn't good: might lose the lock eventually */
+ if (tuner_status == TUNER_STATUS_LOCKED &&
+ state->tuner_cnr < cnr2qual->cnr_good) {
+ schedule_delayed_work(&state->poll_snr,
+ msecs_to_jiffies(2000));
+
+ state->poll_snr_thread_running = true;
+ }
+
+ if (fe->ops.i2c_gate_ctrl)
+ fe->ops.i2c_gate_ctrl(fe, 0);
+ }
+
+ return 0;
+}
+
+static int vidtv_demod_sleep(struct dvb_frontend *fe)
+{
+ struct vidtv_demod_state *state = fe->demodulator_priv;
+
+ if (state->poll_snr_thread_running) {
+ cancel_delayed_work_sync(&state->poll_snr);
+ state->poll_snr_thread_running = false;
+ state->poll_snr_thread_restart = true;
+ }
+ return 0;
+}
+
+static int vidtv_demod_init(struct dvb_frontend *fe)
+{
+ struct vidtv_demod_state *state = fe->demodulator_priv;
+ u32 tuner_status = 0;
+
+ if (state->cold_start)
+ INIT_DELAYED_WORK(&state->poll_snr,
+ &vidtv_demod_poll_snr_handler);
+
+ /*
+ * At resume, start the snr poll thread only if it was suspended with
+ * the thread running. Extra care should be taken here, as some tuner
+ * status change might happen at resume time (for example, due to a
+ * ioctl syscall to set_frontend, or due to a release syscall).
+ */
+ fe->ops.tuner_ops.get_status(fe, &tuner_status);
+
+ if (tuner_status == TUNER_STATUS_LOCKED &&
+ state->poll_snr_thread_restart) {
+ schedule_delayed_work(&state->poll_snr,
+ msecs_to_jiffies(2000));
+
+ state->poll_snr_thread_restart = false;
+ }
+
+ state->cold_start = false;
+ return 0;
+}
+
+static int vidtv_demod_set_tone(struct dvb_frontend *fe,
+ enum fe_sec_tone_mode tone)
+{
+ return 0;
+}
+
+static int vidtv_demod_set_voltage(struct dvb_frontend *fe,
+ enum fe_sec_voltage voltage)
+{
+ return 0;
+}
+
+static void vidtv_demod_release(struct dvb_frontend *fe)
+{
+ struct vidtv_demod_state *state = fe->demodulator_priv;
+
+ if (state->poll_snr_thread_running)
+ cancel_delayed_work_sync(&state->poll_snr);
+
+ kfree(state);
+}
+
+static const struct dvb_frontend_ops vidtv_demod_ofdm_ops = {
+ .delsys = { SYS_DVBT },
+ .info = {
+ .name = "Dummy DVB-T",
+ .frequency_min_hz = 0,
+ .frequency_max_hz = 863250 * kHz,
+ .frequency_stepsize_hz = 62500,
+ .caps = FE_CAN_FEC_1_2 |
+ FE_CAN_FEC_2_3 |
+ FE_CAN_FEC_3_4 |
+ FE_CAN_FEC_4_5 |
+ FE_CAN_FEC_5_6 |
+ FE_CAN_FEC_6_7 |
+ FE_CAN_FEC_7_8 |
+ FE_CAN_FEC_8_9 |
+ FE_CAN_FEC_AUTO |
+ FE_CAN_QAM_16 |
+ FE_CAN_QAM_64 |
+ FE_CAN_QAM_AUTO |
+ FE_CAN_TRANSMISSION_MODE_AUTO |
+ FE_CAN_GUARD_INTERVAL_AUTO |
+ FE_CAN_HIERARCHY_AUTO,
+ },
+
+ .release = vidtv_demod_release,
+
+ .init = vidtv_demod_init,
+ .sleep = vidtv_demod_sleep,
+
+ .set_frontend = vidtv_demod_set_frontend,
+ .get_frontend = vidtv_demod_get_frontend,
+
+ .read_status = vidtv_demod_read_status,
+ .read_ber = vidtv_demod_read_ber,
+ .read_signal_strength = vidtv_demod_read_signal_strength,
+ .read_snr = vidtv_demod_read_snr,
+ .read_ucblocks = vidtv_demod_read_ucblocks,
+};
+
+static const struct dvb_frontend_ops vidtv_demod_qam_ops = {
+ .delsys = { SYS_DVBC_ANNEX_A },
+ .info = {
+ .name = "Dummy DVB-C",
+ .frequency_min_hz = 51 * MHz,
+ .frequency_max_hz = 858 * MHz,
+ .frequency_stepsize_hz = 62500,
+ /* symbol_rate_min: SACLK/64 == (XIN/2)/64 */
+ .symbol_rate_min = (57840000 / 2) / 64,
+ .symbol_rate_max = (57840000 / 2) / 4, /* SACLK/4 */
+ .caps = FE_CAN_QAM_16 |
+ FE_CAN_QAM_32 |
+ FE_CAN_QAM_64 |
+ FE_CAN_QAM_128 |
+ FE_CAN_QAM_256 |
+ FE_CAN_FEC_AUTO |
+ FE_CAN_INVERSION_AUTO
+ },
+
+ .release = vidtv_demod_release,
+
+ .init = vidtv_demod_init,
+ .sleep = vidtv_demod_sleep,
+
+ .set_frontend = vidtv_demod_set_frontend,
+ .get_frontend = vidtv_demod_get_frontend,
+
+ .read_status = vidtv_demod_read_status,
+ .read_ber = vidtv_demod_read_ber,
+ .read_signal_strength = vidtv_demod_read_signal_strength,
+ .read_snr = vidtv_demod_read_snr,
+ .read_ucblocks = vidtv_demod_read_ucblocks,
+};
+
+static const struct dvb_frontend_ops vidtv_demod_qpsk_ops = {
+ .delsys = { SYS_DVBS },
+ .info = {
+ .name = "Dummy DVB-S",
+ .frequency_min_hz = 950 * MHz,
+ .frequency_max_hz = 2150 * MHz,
+ .frequency_stepsize_hz = 250 * kHz,
+ .frequency_tolerance_hz = 29500 * kHz,
+ .symbol_rate_min = 1000000,
+ .symbol_rate_max = 45000000,
+ .caps = FE_CAN_INVERSION_AUTO |
+ FE_CAN_FEC_1_2 |
+ FE_CAN_FEC_2_3 |
+ FE_CAN_FEC_3_4 |
+ FE_CAN_FEC_5_6 |
+ FE_CAN_FEC_7_8 |
+ FE_CAN_FEC_AUTO |
+ FE_CAN_QPSK
+ },
+
+ .release = vidtv_demod_release,
+
+ .init = vidtv_demod_init,
+ .sleep = vidtv_demod_sleep,
+
+ .set_frontend = vidtv_demod_set_frontend,
+ .get_frontend = vidtv_demod_get_frontend,
+
+ .read_status = vidtv_demod_read_status,
+ .read_ber = vidtv_demod_read_ber,
+ .read_signal_strength = vidtv_demod_read_signal_strength,
+ .read_snr = vidtv_demod_read_snr,
+ .read_ucblocks = vidtv_demod_read_ucblocks,
+
+ .set_voltage = vidtv_demod_set_voltage,
+ .set_tone = vidtv_demod_set_tone,
+};
+
+static const struct i2c_device_id vidtv_demod_i2c_id_table[] = {
+ {"vidtv_demod", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, vidtv_demod_i2c_id_table);
+
+static int vidtv_demod_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct vidtv_demod_config *config = client->dev.platform_data;
+ struct vidtv_demod_state *state;
+ const struct dvb_frontend_ops *ops;
+
+ /* allocate memory for the internal state */
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ switch (config->chosen_delsys) {
+ case SYS_DVBT:
+ ops = &vidtv_demod_ofdm_ops;
+ break;
+ case SYS_DVBC_ANNEX_A:
+ ops = &vidtv_demod_qam_ops;
+ break;
+ case SYS_DVBS:
+ ops = &vidtv_demod_qpsk_ops;
+ break;
+ default:
+ pr_err("%s: Unsupported delivery system. Falling back to DVB-T",
+ __func__);
+ ops = &vidtv_demod_ofdm_ops;
+ break;
+ }
+
+ /* create dvb_frontend */
+ memcpy(&state->frontend.ops,
+ ops,
+ sizeof(struct dvb_frontend_ops));
+
+ state->frontend.demodulator_priv = state;
+ /* return the pointer to the bridge driver */
+ config->frontend = &state->frontend;
+ i2c_set_clientdata(client, state);
+
+ return 0;
+}
+
+static int vidtv_demod_i2c_remove(struct i2c_client *client)
+{
+ struct vidtv_demod_state *state = i2c_get_clientdata(client);
+
+ memset(&state->frontend.ops, 0, sizeof(struct dvb_frontend_ops));
+ state->frontend.demodulator_priv = NULL;
+ kfree(state);
+
+ return 0;
+}
+
+static struct i2c_driver vidtv_demod_i2c_driver = {
+ .driver = {
+ .name = "vidtv_demod",
+ .suppress_bind_attrs = true,
+ },
+ .probe = vidtv_demod_i2c_probe,
+ .remove = vidtv_demod_i2c_remove,
+ .id_table = vidtv_demod_i2c_id_table,
+};
+
+module_i2c_driver(vidtv_demod_i2c_driver);
diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.h b/drivers/media/test-drivers/vidtv/vidtv_demod.h
new file mode 100644
index 0000000000000..49c2a43f71661
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_demod.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * The Virtual DTV test driver serves as a reference DVB driver and helps
+ * validate the existing APIs in the media subsystem. It can also aid
+ * developers working on userspace applications.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ * Based on the example driver written by Emard <[email protected]>
+ */
+
+#ifndef VIDTV_DEMOD_H
+#define VIDTV_DEMOD_H
+
+#include <linux/dvb/frontend.h>
+#include <media/dvb_frontend.h>
+
+struct vidtv_demod_cnr_to_qual_s {
+ /* attempt to use the same values as libdvbv5 */
+ u32 modulation;
+ u32 fec;
+ u32 cnr_ok, cnr_good;
+};
+
+struct vidtv_demod_config {
+ struct dvb_frontend *frontend;
+ /* probability of losing the lock due to low snr */
+ u8 drop_tslock_prob_on_low_snr;
+ /* probability of recovering when the signal improves */
+ u8 recover_tslock_prob_on_good_snr;
+ u8 chosen_delsys;
+};
+
+struct vidtv_demod_state {
+ struct dvb_frontend frontend;
+ struct vidtv_demod_config config;
+ struct delayed_work poll_snr;
+ enum fe_status status;
+ u16 tuner_cnr;
+ bool cold_start;
+ bool poll_snr_thread_running;
+ bool poll_snr_thread_restart;
+};
+#endif // VIDTV_DEMOD_H
--
2.26.2

2020-05-02 03:24:43

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 04/11] media: vidtv: move config structs into a separate header

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

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

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
.../media/test-drivers/vidtv/vidtv_config.h | 35 +++++++++++++++++++
.../media/test-drivers/vidtv/vidtv_demod.c | 1 +
.../media/test-drivers/vidtv/vidtv_demod.h | 9 -----
.../media/test-drivers/vidtv/vidtv_tuner.c | 12 ++-----
4 files changed, 38 insertions(+), 19 deletions(-)
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_config.h

diff --git a/drivers/media/test-drivers/vidtv/vidtv_config.h b/drivers/media/test-drivers/vidtv/vidtv_config.h
new file mode 100644
index 0000000000000..7b95bf2444556
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_config.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * The Virtual DTV test driver serves as a reference DVB driver and helps
+ * validate the existing APIs in the media subsystem. It can also aid
+ * developers working on userspace applications.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_CONFIG_H
+#define VIDTV_CONFIG_H
+
+#include <linux/types.h>
+#include <media/dvb_frontend.h>
+
+struct vidtv_tuner_config {
+ struct dvb_frontend *fe;
+ u32 mock_power_up_delay_msec;
+ u32 mock_tune_delay_msec;
+ u32 vidtv_valid_dvb_t_freqs[8];
+ u32 vidtv_valid_dvb_c_freqs[8];
+ u32 vidtv_valid_dvb_s_freqs[8];
+ u8 max_frequency_shift_hz;
+};
+
+struct vidtv_demod_config {
+ struct dvb_frontend *frontend;
+ /* probability of losing the lock due to low snr */
+ u8 drop_tslock_prob_on_low_snr;
+ /* probability of recovering when the signal improves */
+ u8 recover_tslock_prob_on_good_snr;
+ u8 chosen_delsys;
+};
+
+#endif //VIDTV_CONFIG_H
diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.c b/drivers/media/test-drivers/vidtv/vidtv_demod.c
index e5f157e4bbe48..15436e565a7b0 100644
--- a/drivers/media/test-drivers/vidtv/vidtv_demod.c
+++ b/drivers/media/test-drivers/vidtv/vidtv_demod.c
@@ -19,6 +19,7 @@
#include <linux/i2c.h>
#include <media/dvb_frontend.h>
#include "vidtv_demod.h"
+#include "vidtv_config.h"

MODULE_DESCRIPTION("Virtual DVB Demodulator Driver");
MODULE_AUTHOR("Daniel W. S. Almeida");
diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.h b/drivers/media/test-drivers/vidtv/vidtv_demod.h
index 49c2a43f71661..269855efb77f3 100644
--- a/drivers/media/test-drivers/vidtv/vidtv_demod.h
+++ b/drivers/media/test-drivers/vidtv/vidtv_demod.h
@@ -21,15 +21,6 @@ struct vidtv_demod_cnr_to_qual_s {
u32 cnr_ok, cnr_good;
};

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

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

-struct vidtv_tuner_config {
- struct dvb_frontend *fe;
- u32 mock_power_up_delay_msec;
- u32 mock_tune_delay_msec;
- u32 vidtv_valid_dvb_t_freqs[8];
- u32 vidtv_valid_dvb_c_freqs[8];
- u32 vidtv_valid_dvb_s_freqs[8];
- u8 max_frequency_shift_hz;
-};
-
struct vidtv_tuner_cnr_to_qual_s {
/* attempt to use the same values as libdvbv5 */
u32 modulation;
--
2.26.2

2020-05-02 03:24:46

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 05/11] media: vidtv: add a bridge driver

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

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

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

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

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

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test-drivers/vidtv/Makefile | 2 +-
.../media/test-drivers/vidtv/vidtv_bridge.c | 379 ++++++++++++++++++
.../media/test-drivers/vidtv/vidtv_bridge.h | 37 ++
3 files changed, 417 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_bridge.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_bridge.h

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

-obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o
+obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c
new file mode 100644
index 0000000000000..05ca4027c869f
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * The Virtual DTV test driver serves as a reference DVB driver and helps
+ * validate the existing APIs in the media subsystem. It can also aid
+ * developers working on userspace applications.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#include <linux/types.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/time.h>
+#include "vidtv_bridge.h"
+
+#define TS_BUF_MAX_SZ (128 * 188)
+#define TUNER_DEFAULT_ADDR 0x68
+#define DEMOD_DEFAULT_ADDR 0x60
+
+MODULE_AUTHOR("Daniel W. S. Almeida");
+MODULE_LICENSE("GPL");
+
+static unsigned int drop_tslock_prob_on_low_snr;
+module_param(drop_tslock_prob_on_low_snr, uint, 0644);
+MODULE_PARM_DESC(drop_tslock_prob_on_low_snr,
+ "Probability of losing the TS lock if the signal quality is bad");
+
+static unsigned int recover_tslock_prob_on_good_snr;
+module_param(recover_tslock_prob_on_good_snr, uint, 0644);
+MODULE_PARM_DESC(recover_tslock_prob_on_good_snr,
+ "Probability recovering the TS lock when the signal improves");
+
+static unsigned int mock_power_up_delay_msec;
+module_param(mock_power_up_delay_msec, uint, 0644);
+MODULE_PARM_DESC(mock_power_up_delay_msec, "Simulate a power up delay");
+
+static unsigned int mock_tune_delay_msec;
+module_param(mock_tune_delay_msec, uint, 0644);
+MODULE_PARM_DESC(mock_tune_delay_msec, "Simulate a tune delay");
+
+static unsigned int vidtv_valid_dvb_t_freqs[8];
+module_param_array(vidtv_valid_dvb_t_freqs, uint, NULL, 0644);
+MODULE_PARM_DESC(vidtv_valid_dvb_t_freqs,
+ "Valid DVB-T frequencies to simulate");
+
+static unsigned int vidtv_valid_dvb_c_freqs[8];
+module_param_array(vidtv_valid_dvb_c_freqs, uint, NULL, 0644);
+MODULE_PARM_DESC(vidtv_valid_dvb_c_freqs,
+ "Valid DVB-C frequencies to simulate");
+
+static unsigned int vidtv_valid_dvb_s_freqs[8];
+module_param_array(vidtv_valid_dvb_s_freqs, uint, NULL, 0644);
+MODULE_PARM_DESC(vidtv_valid_dvb_s_freqs,
+ "Valid DVB-C frequencies to simulate");
+
+static unsigned int max_frequency_shift_hz;
+module_param(max_frequency_shift_hz, uint, 0644);
+MODULE_PARM_DESC(max_frequency_shift_hz,
+ "Maximum shift in HZ allowed when tuning in a channel");
+
+static unsigned int chosen_delsys = SYS_DVBT;
+module_param(chosen_delsys, uint, 0644);
+MODULE_PARM_DESC(chosen_delsys,
+ "The delivery system to simulate. Currently supported: DVB-T, DVB-C, DVB-S");
+
+static unsigned int ts_buf_sz = 20 * 188;
+module_param(ts_buf_sz, uint, 0644);
+MODULE_PARM_DESC(ts_buf_sz, "Optional size for the TS buffer");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nums);
+
+static int vidtv_start_streaming(struct vidtv_dvb *dvb)
+{
+ WARN_ON(dvb->streaming);
+ dvb->streaming = true;
+
+ return 0;
+}
+
+static int vidtv_stop_streaming(struct vidtv_dvb *dvb)
+{
+ /* mpeg thread will quit */
+ dvb->streaming = false;
+
+ return 0;
+}
+
+static int vidtv_start_feed(struct dvb_demux_feed *feed)
+{
+ struct dvb_demux *demux = feed->demux;
+ struct vidtv_dvb *dvb = demux->priv;
+ int rc, ret;
+
+ if (!demux->dmx.frontend)
+ return -EINVAL;
+
+ mutex_lock(&dvb->feed_lock);
+
+ dvb->nfeeds++;
+ rc = dvb->nfeeds;
+
+ if (dvb->nfeeds == 1) {
+ ret = vidtv_start_streaming(dvb);
+ if (ret < 0)
+ rc = ret;
+ }
+
+ mutex_unlock(&dvb->feed_lock);
+ return rc;
+}
+
+static int vidtv_stop_feed(struct dvb_demux_feed *feed)
+{
+ struct dvb_demux *demux = feed->demux;
+ struct vidtv_dvb *dvb = demux->priv;
+ int err = 0;
+
+ mutex_lock(&dvb->feed_lock);
+ dvb->nfeeds--;
+
+ if (!dvb->nfeeds)
+ err = vidtv_stop_streaming(dvb);
+
+ mutex_unlock(&dvb->feed_lock);
+ return err;
+}
+
+static int vidtv_master_xfer(struct i2c_adapter *i2c_adap,
+ struct i2c_msg msgs[],
+ int num)
+{
+ return 0;
+}
+
+static u32 vidtv_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C;
+}
+
+struct i2c_algorithm vidtv_i2c_algorithm = {
+ .master_xfer = vidtv_master_xfer,
+ .functionality = vidtv_i2c_func,
+};
+
+static int vidtv_bridge_i2c_register_adap(struct vidtv_dvb *dvb)
+{
+ struct i2c_adapter *i2c_adapter = dvb->i2c_adapter;
+
+ strscpy(i2c_adapter->name, "vidtv_i2c", sizeof(i2c_adapter->name));
+ i2c_adapter->owner = THIS_MODULE;
+ i2c_adapter->algo = &vidtv_i2c_algorithm;
+ i2c_adapter->algo_data = NULL;
+ i2c_adapter->timeout = 500;
+ i2c_adapter->retries = 3;
+ i2c_adapter->dev.parent = NULL;
+
+ i2c_set_adapdata(i2c_adapter, dvb);
+ return i2c_add_adapter(dvb->i2c_adapter);
+}
+
+static int vidtv_bridge_register_adap(struct vidtv_dvb *dvb)
+{
+ int ret = 0;
+
+ ret = dvb_register_adapter(&dvb->adapter,
+ KBUILD_MODNAME,
+ THIS_MODULE,
+ &dvb->i2c_adapter->dev,
+ adapter_nums);
+
+ return ret;
+}
+
+static int vidtv_bridge_dmx_init(struct vidtv_dvb *dvb)
+{
+ dvb->demux.dmx.capabilities = DMX_TS_FILTERING |
+ DMX_SECTION_FILTERING;
+
+ dvb->demux.priv = dvb;
+ dvb->demux.filternum = 256;
+ dvb->demux.feednum = 256;
+ dvb->demux.start_feed = vidtv_start_feed;
+ dvb->demux.stop_feed = vidtv_stop_feed;
+
+ return dvb_dmx_init(&dvb->demux);
+}
+
+static int vidtv_bridge_dmxdev_init(struct vidtv_dvb *dvb)
+{
+ dvb->dmx_dev.filternum = 256;
+ dvb->dmx_dev.demux = &dvb->demux.dmx;
+ dvb->dmx_dev.capabilities = 0;
+
+ return dvb_dmxdev_init(&dvb->dmx_dev, &dvb->adapter);
+}
+
+static void vidtv_bridge_probe_demod(struct vidtv_dvb *dvb, u32 n)
+{
+ struct vidtv_demod_config cfg = {0};
+
+ cfg.drop_tslock_prob_on_low_snr = drop_tslock_prob_on_low_snr;
+ cfg.recover_tslock_prob_on_good_snr = recover_tslock_prob_on_good_snr;
+ cfg.chosen_delsys = chosen_delsys;
+
+ dvb->i2c_client_demod[n] = dvb_module_probe("vidtv_demod",
+ NULL,
+ dvb->i2c_adapter,
+ DEMOD_DEFAULT_ADDR,
+ &cfg);
+
+ /* retrieve a pointer to struct dvb_frontend */
+ dvb->fe[n] = cfg.frontend;
+}
+
+static void vidtv_bridge_probe_tuner(struct vidtv_dvb *dvb, u32 n)
+{
+ struct vidtv_tuner_config cfg = {0};
+
+ cfg.fe = dvb->fe[n];
+ cfg.mock_power_up_delay_msec = mock_power_up_delay_msec;
+ cfg.mock_tune_delay_msec = mock_tune_delay_msec;
+
+ memcpy(cfg.vidtv_valid_dvb_t_freqs,
+ vidtv_valid_dvb_t_freqs,
+ sizeof(vidtv_valid_dvb_t_freqs));
+
+ memcpy(cfg.vidtv_valid_dvb_c_freqs,
+ vidtv_valid_dvb_c_freqs,
+ sizeof(vidtv_valid_dvb_c_freqs));
+
+ memcpy(cfg.vidtv_valid_dvb_s_freqs,
+ vidtv_valid_dvb_s_freqs,
+ sizeof(vidtv_valid_dvb_s_freqs));
+
+ cfg.max_frequency_shift_hz = max_frequency_shift_hz;
+
+ dvb->i2c_client_tuner[n] = dvb_module_probe("vidtv_tuner",
+ NULL,
+ dvb->i2c_adapter,
+ TUNER_DEFAULT_ADDR,
+ &cfg);
+}
+
+static int vidtv_bridge_dvb_init(struct vidtv_dvb *dvb)
+{
+ int ret;
+ int i, j;
+
+ ret = vidtv_bridge_i2c_register_adap(dvb);
+ if (ret < 0)
+ goto fail_i2c;
+
+ ret = vidtv_bridge_register_adap(dvb);
+ if (ret < 0)
+ goto fail_adapter;
+
+ vidtv_bridge_probe_demod(dvb, 0);
+ vidtv_bridge_probe_tuner(dvb, 0);
+
+ for (i = 0; i < NUM_FE; ++i) {
+ ret = dvb_register_frontend(&dvb->adapter, dvb->fe[i]);
+ if (ret < 0)
+ goto fail_fe;
+ }
+
+ ret = vidtv_bridge_dmx_init(dvb);
+ if (ret < 0)
+ goto fail_dmx;
+
+ ret = vidtv_bridge_dmxdev_init(dvb);
+ if (ret < 0)
+ goto fail_dmx_dev;
+
+ for (j = 0; j < NUM_FE; ++j) {
+ ret = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx,
+ &dvb->dmx_fe[j]);
+ if (ret < 0)
+ goto fail_dmx_conn;
+
+ /*
+ * The source of the demux is a frontend connected
+ * to the demux.
+ */
+ dvb->dmx_fe[j].source = DMX_FRONTEND_0;
+ }
+
+ return ret;
+
+fail_dmx_conn:
+ for (j = j - 1; j >= 0; --j)
+ dvb->demux.dmx.remove_frontend(&dvb->demux.dmx,
+ &dvb->dmx_fe[j]);
+fail_dmx_dev:
+ dvb_dmxdev_release(&dvb->dmx_dev);
+fail_dmx:
+ dvb_dmx_release(&dvb->demux);
+fail_fe:
+ for (i = i - 1; i >= 0; --i)
+ dvb_unregister_frontend(dvb->fe[i]);
+
+fail_adapter:
+ dvb_unregister_adapter(&dvb->adapter);
+
+fail_i2c:
+ i2c_del_adapter(dvb->i2c_adapter);
+
+ return ret;
+}
+
+static int vidtv_bridge_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret;
+ struct vidtv_dvb *dvb;
+
+ dvb = kzalloc(sizeof(*dvb), GFP_KERNEL);
+ if (!dvb)
+ return -ENOMEM;
+
+ ret = vidtv_bridge_dvb_init(dvb);
+ if (ret < 0)
+ goto err_dvb;
+
+ mutex_init(&dvb->feed_lock);
+
+ i2c_set_clientdata(client, dvb);
+
+ return ret;
+
+err_dvb:
+ kfree(dvb);
+ return ret;
+}
+
+static int vidtv_bridge_i2c_remove(struct i2c_client *client)
+{
+ struct vidtv_dvb *dvb;
+ u32 i;
+
+ dvb = i2c_get_clientdata(client);
+
+ mutex_destroy(&dvb->feed_lock);
+
+ for (i = 0; i < NUM_FE; ++i)
+ dvb->demux.dmx.remove_frontend(&dvb->demux.dmx,
+ &dvb->dmx_fe[i]);
+
+ dvb_dmxdev_release(&dvb->dmx_dev);
+ dvb_dmx_release(&dvb->demux);
+
+ for (i = 0; i < NUM_FE; ++i) {
+ dvb_unregister_frontend(dvb->fe[i]);
+ dvb_frontend_detach(dvb->fe[i]);
+ }
+
+ dvb_unregister_adapter(&dvb->adapter);
+
+ return 0;
+}
+
+static const struct i2c_device_id vidtv_bridge_id_table[] = {
+ {"vidtv_bridge", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, vidtv_bridge_id_table);
+
+static struct i2c_driver vidtv_bridge_driver = {
+ .driver = {
+ .name = "vidtv_bridge",
+ .suppress_bind_attrs = true,
+ },
+ .probe = vidtv_bridge_i2c_probe,
+ .remove = vidtv_bridge_i2c_remove,
+ .id_table = vidtv_bridge_id_table,
+};
+
+module_i2c_driver(vidtv_bridge_driver);
diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.h b/drivers/media/test-drivers/vidtv/vidtv_bridge.h
new file mode 100644
index 0000000000000..ef5c7cd2d64e3
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * The Virtual DTV test driver serves as a reference DVB driver and helps
+ * validate the existing APIs in the media subsystem. It can also aid
+ * developers working on userspace applications.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_BRIDGE_H
+#define VIDTV_BRIDGE_H
+
+#define NUM_FE 1
+
+#include <linux/types.h>
+#include <media/dvb_frontend.h>
+#include <media/dvb_demux.h>
+#include <media/dmxdev.h>
+#include <linux/i2c.h>
+
+struct vidtv_dvb {
+ struct dvb_frontend *fe[NUM_FE];
+ struct dvb_adapter adapter;
+ struct dvb_demux demux;
+ struct dmxdev dmx_dev;
+ struct dmx_frontend dmx_fe[NUM_FE];
+ struct i2c_adapter *i2c_adapter;
+ struct i2c_client *i2c_client_demod[NUM_FE];
+ struct i2c_client *i2c_client_tuner[NUM_FE];
+
+ u32 nfeeds;
+ struct mutex feed_lock; /* start/stop feed */
+
+ bool streaming;
+};
+
+#endif // VIDTV_BRIDGE_H
--
2.26.2

2020-05-02 03:24:58

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 06/11] media: vidtv: add wrappers for memcpy and memset

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

A lot of code in this driver is for serializing structures. This is
error prone.

Therefore, prevent buffer overflows by wrapping memcpy and memset,
comparing the requested length against the buffer size.

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test-drivers/vidtv/Makefile | 3 ++
.../media/test-drivers/vidtv/vidtv_common.c | 44 +++++++++++++++++++
.../media/test-drivers/vidtv/vidtv_common.h | 28 ++++++++++++
3 files changed, 75 insertions(+)
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_common.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_common.h

diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
index a9f1993dd5443..9ea9485d42189 100644
--- a/drivers/media/test-drivers/vidtv/Makefile
+++ b/drivers/media/test-drivers/vidtv/Makefile
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: GPL-2.0

+vidtv_demod-objs := vidtv_common.o
+vidtv_bridge-objs := vidtv_common.o
+
obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.c b/drivers/media/test-drivers/vidtv/vidtv_common.c
new file mode 100644
index 0000000000000..28f10630499a9
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_common.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * The Virtual DVB test driver serves as a reference DVB driver and helps
+ * validate the existing APIs in the media subsystem. It can also aid
+ * developers working on userspace applications.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/printk.h>
+
+u32 vidtv_memcpy(void *to,
+ const void *from,
+ size_t len,
+ u32 offset,
+ u32 buf_sz)
+{
+ if (buf_sz && offset + len > buf_sz) {
+ pr_err("%s: overflow detected, skipping. Try increasing the buffer size",
+ __func__);
+ return 0;
+ }
+
+ memcpy(to, from, len);
+ return len;
+}
+
+u32 vidtv_memset(void *to,
+ int c,
+ size_t len,
+ u32 offset,
+ u32 buf_sz)
+{
+ if (buf_sz && offset + len > buf_sz) {
+ pr_err("%s: overflow detected, skipping. Try increasing the buffer size",
+ __func__);
+ return 0;
+ }
+
+ memset(to, c, len);
+ return len;
+}
diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.h b/drivers/media/test-drivers/vidtv/vidtv_common.h
new file mode 100644
index 0000000000000..64072c010dc66
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_common.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * The Virtual DVB test driver serves as a reference DVB driver and helps
+ * validate the existing APIs in the media subsystem. It can also aid
+ * developers working on userspace applications.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_COMMON_H
+#define VIDTV_COMMON_H
+
+#include <linux/types.h>
+#include <media/dvb_frontend.h>
+
+u32 vidtv_memcpy(void *to,
+ const void *from,
+ size_t len,
+ u32 offset,
+ u32 buf_sz);
+
+u32 vidtv_memset(void *to,
+ int c,
+ size_t len,
+ u32 offset,
+ u32 buf_sz);
+
+#endif // VIDTV_COMMON_H
--
2.26.2

2020-05-02 03:25:17

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 07/11] media: vidtv: add MPEG TS common code

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

Add code to work with MPEG TS packets, such as TS headers, adaptation
fields, PCR packets and NULL packets.

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test-drivers/vidtv/Makefile | 2 +-
drivers/media/test-drivers/vidtv/vidtv_ts.c | 130 ++++++++++++++++++++
drivers/media/test-drivers/vidtv/vidtv_ts.h | 103 ++++++++++++++++
3 files changed, 234 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.h

diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
index 9ea9485d42189..92001bc348615 100644
--- a/drivers/media/test-drivers/vidtv/Makefile
+++ b/drivers/media/test-drivers/vidtv/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0

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

obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
diff --git a/drivers/media/test-drivers/vidtv/vidtv_ts.c b/drivers/media/test-drivers/vidtv/vidtv_ts.c
new file mode 100644
index 0000000000000..f545c45c0fe7c
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_ts.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * The Virtual DVB test driver serves as a reference DVB driver and helps
+ * validate the existing APIs in the media subsystem. It can also aid
+ * developers working on userspace applications.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+#include "vidtv_ts.h"
+#include "vidtv_common.h"
+
+static u32 vidtv_ts_write_pcr_bits(u8 *buf, u64 pcr)
+{
+ /* Exact same from ffmpeg. PCR is a counter driven by a 27Mhz clock */
+ u64 pcr_low = pcr % 300, pcr_high = pcr / 300;
+
+ *buf++ = pcr_high >> 25;
+ *buf++ = pcr_high >> 17;
+ *buf++ = pcr_high >> 9;
+ *buf++ = pcr_high >> 1;
+ *buf++ = pcr_high << 7 | pcr_low >> 8 | 0x7e;
+ *buf++ = pcr_low;
+
+ return 6;
+}
+
+void vidtv_ts_inc_cc(u8 *continuity_counter)
+{
+ ++*continuity_counter;
+ if (*continuity_counter > TS_CC_MAX_VAL)
+ *continuity_counter = 0;
+}
+
+u32 vidtv_ts_null_write_into(struct null_packet_write_args args)
+{
+ u32 nbytes = 0;
+ struct vidtv_mpeg_ts ts_header = {0};
+
+ ts_header.sync_byte = TS_SYNC_BYTE;
+ ts_header.pid = TS_NULL_PACKET_PID;
+ ts_header.payload = 1;
+ ts_header.continuity_counter = *args.continuity_counter;
+
+ cpu_to_be16s(&ts_header.bitfield);
+
+ /* copy TS header */
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ &ts_header,
+ sizeof(ts_header),
+ args.dest_offset + nbytes,
+ args.buf_sz);
+
+ be16_to_cpus(&ts_header.bitfield);
+
+ vidtv_ts_inc_cc(args.continuity_counter);
+
+ /* fill the rest with empty data */
+ nbytes += vidtv_memset(args.dest_buf + args.dest_offset + nbytes,
+ 0xff,
+ TS_PACKET_LEN - nbytes,
+ args.dest_offset + nbytes,
+ args.buf_sz);
+
+ /* we should have written exactly _one_ 188byte packet */
+ WARN_ON(nbytes != TS_PACKET_LEN);
+
+ return nbytes;
+}
+
+u32 vidtv_ts_pcr_write_into(struct pcr_write_args args)
+{
+ u32 nbytes = 0;
+ struct vidtv_mpeg_ts ts_header = {0};
+ struct vidtv_mpeg_ts_adaption ts_adap = {0};
+
+ ts_header.sync_byte = TS_SYNC_BYTE;
+ ts_header.tei = 0;
+ ts_header.payload_start = 0;
+ ts_header.pid = args.pid;
+ ts_header.priority = 0;
+ ts_header.scrambling = 0;
+ /* cc is not incremented, see 13818-1 clause 2.4.3.3 */
+ ts_header.continuity_counter = *args.continuity_counter;
+ ts_header.payload = 0;
+ ts_header.adaptation_field = 1;
+
+ /* 13818-1 clause 2.4.3.5 */
+ ts_adap.length = 183;
+ ts_adap.PCR = 1;
+
+ cpu_to_be16s(&ts_header.bitfield);
+
+ /* copy TS header */
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ &ts_header,
+ sizeof(ts_header),
+ args.dest_offset + nbytes,
+ args.buf_sz);
+
+ be16_to_cpus(&ts_header.bitfield);
+
+ /* write the adap after the TS header */
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ &ts_adap,
+ sizeof(ts_adap),
+ args.dest_offset + nbytes,
+ args.buf_sz);
+
+ /* write the PCR optional */
+ cpu_to_be64s(&args.pcr);
+ nbytes += vidtv_ts_write_pcr_bits(args.dest_buf +
+ args.dest_offset +
+ nbytes,
+ args.pcr);
+ be64_to_cpus(&args.pcr);
+
+ nbytes += vidtv_memset(args.dest_buf + args.dest_offset + nbytes,
+ 0xff,
+ TS_PACKET_LEN - nbytes,
+ args.dest_offset + nbytes,
+ args.buf_sz);
+
+ /* we should have written exactly _one_ 188byte packet */
+ WARN_ON(nbytes != TS_PACKET_LEN);
+
+ return nbytes;
+}
diff --git a/drivers/media/test-drivers/vidtv/vidtv_ts.h b/drivers/media/test-drivers/vidtv/vidtv_ts.h
new file mode 100644
index 0000000000000..2c07bddc46119
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_ts.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * The Virtual DVB test driver serves as a reference DVB driver and helps
+ * validate the existing APIs in the media subsystem. It can also aid
+ * developers working on userspace applications.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_TS_H
+#define VIDTV_TS_H
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+
+#define TS_SYNC_BYTE 0x47
+#define TS_PACKET_LEN 188
+#define TS_PAYLOAD_LEN 184
+#define TS_NULL_PACKET_PID 0x1fff
+#define TS_CC_MAX_VAL 0x0f /* 4 bits */
+#define TS_LAST_VALID_PID 8191
+
+struct vidtv_mpeg_ts_adaption {
+ u8 length;
+ struct {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ 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;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 discontinued:1;
+ u8 random_access:1;
+ u8 priority:1;
+ u8 PCR:1;
+ u8 OPCR:1;
+ u8 splicing_point:1;
+ u8 private_data:1;
+ u8 extension:1;
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+ } __packed;
+ u8 data[];
+} __packed;
+
+struct vidtv_mpeg_ts {
+ u8 sync_byte;
+ union {
+ u16 bitfield;
+ struct {
+ u16 pid:13;
+ u16 priority:1;
+ u16 payload_start:1;
+ u16 tei:1;
+ } __packed;
+ } __packed;
+ struct {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 continuity_counter:4;
+ u8 payload:1;
+ u8 adaptation_field:1;
+ u8 scrambling:2;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 scrambling:2;
+ u8 adaptation_field:1;
+ u8 payload:1;
+ u8 continuity_counter:4;
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+ } __packed;
+ struct vidtv_mpeg_ts_adaption adaption[];
+} __packed;
+
+struct pcr_write_args {
+ void *dest_buf; /* The buffer to write into */
+ u32 dest_offset; /* The byte offset into the buffer */
+ u16 pid; /* the TS PID for the PCR packets */
+ u32 buf_sz; /* protect against overflow when this field is not zero */
+ u8 *continuity_counter;
+ u64 pcr; /* A sample from the system clock */
+};
+
+struct null_packet_write_args {
+ void *dest_buf;/* The buffer to write into */
+ u32 dest_offset;/* The byte offset into the buffer */
+ u32 buf_sz; /* protect against overflow when this field is not zero */
+ u8 *continuity_counter;
+};
+
+/* Increment the continuity counter */
+void vidtv_ts_inc_cc(u8 *continuity_counter);
+
+u32 vidtv_ts_null_write_into(struct null_packet_write_args args);
+
+u32 vidtv_ts_pcr_write_into(struct pcr_write_args args);
+
+#endif //VIDTV_TS_H
--
2.26.2

2020-05-02 03:25:58

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 08/11] media: vidtv: implement a PSI generator

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

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

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

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

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test-drivers/vidtv/Makefile | 2 +-
drivers/media/test-drivers/vidtv/vidtv_psi.c | 1137 ++++++++++++++++++
drivers/media/test-drivers/vidtv/vidtv_psi.h | 357 ++++++
3 files changed, 1495 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_psi.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_psi.h

diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
index 92001bc348615..e4f744aa53136 100644
--- a/drivers/media/test-drivers/vidtv/Makefile
+++ b/drivers/media/test-drivers/vidtv/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0

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

obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.c b/drivers/media/test-drivers/vidtv/vidtv_psi.c
new file mode 100644
index 0000000000000..191d37a248923
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_psi.c
@@ -0,0 +1,1137 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file contains the logic to work with MPEG Program-Specific Information.
+ * These are defined both in ISO/IEC 13818-1 (systems) and ETSI EN 300 468.
+ * PSI is carried in the form of table structures, and although each table might
+ * technically be broken into one or more sections, we do not do this here,
+ * hence 'table' and 'section' are interchangeable for us.
+ *
+ * This code currently supports three tables: PAT, PMT and SDT. These are the
+ * bare minimum to get userspace to recognize our MPEG transport stream. It can
+ * be extended to support more PSI tables in the future.
+ *
+ * A note on endianness: MPEG layout is big-endian, therefore:
+ * - All fields spanning more than a byte must undergo 'cpu_to_beXX()' before
+ * serialization. These convertions are done in the *_write_into() functions.
+ *
+ * - All byte sized bitfields must have their ordering reversed if
+ * __LITTLE_ENDIAN_BITFIELD is defined.
+ *
+ * Written by: Daniel W. S. Almeida <[email protected]>
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/crc32.h>
+#include <linux/string.h>
+#include <linux/printk.h>
+
+#include "vidtv_psi.h"
+#include "vidtv_common.h"
+#include "vidtv_ts.h"
+
+#define CRC_SIZE_IN_BYTES 4
+
+static u32 vidtv_psi_ts_psi_write_stuffing(void *to,
+ u32 len,
+ u32 offset,
+ u32 buf_sz)
+{
+ return vidtv_memset(to, 0xff, len, offset, buf_sz);
+}
+
+static u32
+vidtv_psi_ts_psi_write_into(struct psi_write_args args)
+{
+ /*
+ * Packetize PSI sections into TS packets:
+ * push a TS header (4bytes) every 184 bytes
+ * manage the continuity_counter
+ * add stuffing after the CRC
+ */
+
+ u32 nbytes_past_boundary = (args.dest_offset % TS_PACKET_LEN);
+ bool aligned = nbytes_past_boundary == 0;
+
+ /*
+ * whether we need to fragment the data into multiple ts packets
+ * if we are aligned we need to spare one byte for the pointer_field
+ */
+ bool split = (aligned) ?
+ args.len > TS_PAYLOAD_LEN - 1 :
+ nbytes_past_boundary + args.len > TS_PACKET_LEN;
+
+ /* how much we can write in this packet */
+ u32 payload_write_len = (split) ?
+ (aligned) ? TS_PAYLOAD_LEN :
+ TS_PACKET_LEN - nbytes_past_boundary :
+ args.len;
+
+ struct psi_write_args new_args = {0};
+ struct vidtv_mpeg_ts ts_header = {0};
+
+ u32 nbytes = 0; /* number of bytes written by this function */
+ u32 temp = 0;
+
+ /* Just a sanity check, should not really happen because we stuff
+ * the packet when we finish a section, i.e. when we write the crc at
+ * the end. But if this happens then we have messed up the logic
+ * somewhere.
+ */
+ WARN_ON(args.new_psi_section && !aligned);
+
+ if (aligned) {
+ /* if at a packet boundary, write a new TS header */
+ ts_header.sync_byte = TS_SYNC_BYTE;
+ ts_header.tei = 0;
+ ts_header.payload_start = 1;
+ ts_header.pid = args.pid;
+ ts_header.priority = 0;
+ ts_header.scrambling = 0;
+ ts_header.continuity_counter = *args.continuity_counter;
+ ts_header.payload = 1;
+ /* no adaptation field */
+ ts_header.adaptation_field = 0;
+
+ cpu_to_be16s(&ts_header.bitfield);
+
+ /* copy the header */
+ nbytes += vidtv_memcpy(args.dest_buf +
+ args.dest_offset +
+ nbytes,
+ &ts_header,
+ sizeof(ts_header),
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ be16_to_cpus(&ts_header.bitfield);
+
+ /*
+ * increment the countinuity counter since we have started
+ * a new packet
+ */
+ vidtv_ts_inc_cc(args.continuity_counter);
+ }
+
+ if (args.new_psi_section) {
+ /* write the pointer_field in the first byte of the payload */
+ temp = vidtv_memset(args.dest_buf + args.dest_offset + nbytes,
+ 0x0,
+ 1,
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+ /* one byte was used by the pointer field*/
+ nbytes += temp;
+ if (payload_write_len == TS_PAYLOAD_LEN)
+ payload_write_len -= temp;
+ }
+
+ /* write as much of the payload as we possibly can */
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ args.from,
+ payload_write_len,
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ if (split) {
+ /* 'payload_write_len' written from a total of 'len' requested*/
+ args.len -= payload_write_len;
+ /*
+ * recursively write the rest of the data until we do not
+ * need to split it anymore
+ */
+ memcpy(&new_args, &args, sizeof(struct psi_write_args));
+ new_args.from = args.from + payload_write_len;
+ new_args.dest_offset = args.dest_offset + nbytes;
+ new_args.new_psi_section = false;
+
+ nbytes += vidtv_psi_ts_psi_write_into(new_args);
+ }
+
+ /*
+ * as the CRC is last in the section, stuff the rest of the
+ * packet if there is any remaining space in there
+ */
+ if (args.is_crc)
+ nbytes += vidtv_psi_ts_psi_write_stuffing(args.dest_buf +
+ args.dest_offset +
+ nbytes,
+ TS_PACKET_LEN -
+ payload_write_len,
+ args.dest_offset +
+ nbytes,
+ args.dest_buf_sz);
+
+ return nbytes;
+}
+
+static u32 table_section_crc32_write_into(struct crc32_write_args args)
+{
+ /* the CRC is the last entry in the section */
+ u32 nbytes = 0;
+ u32 crc;
+ struct psi_write_args psi_args = {0};
+
+ crc = crc32_be(0, args.dest_buf, args.dest_offset);
+
+ psi_args.dest_buf = args.dest_buf;
+ psi_args.from = &crc;
+ psi_args.len = CRC_SIZE_IN_BYTES;
+ psi_args.dest_offset = args.dest_offset;
+ psi_args.pid = args.pid;
+ psi_args.new_psi_section = false;
+ psi_args.continuity_counter = args.continuity_counter;
+ psi_args.is_crc = true;
+ psi_args.dest_buf_sz = args.dest_buf_sz;
+
+ cpu_to_be32s(&crc);
+ nbytes += vidtv_psi_ts_psi_write_into(psi_args);
+ be32_to_cpus(&crc);
+
+ return nbytes;
+}
+
+struct vidtv_psi_desc *vidtv_psi_desc_init(struct vidtv_psi_desc *head,
+ u8 type,
+ u8 length)
+{
+ struct vidtv_psi_desc *desc;
+
+ /* alloc enough memory for the flexible array too */
+ desc = kzalloc(sizeof(*desc) + length, GFP_KERNEL);
+
+ desc->type = type;
+ desc->length = length;
+
+ if (head) {
+ while (head->next)
+ head = head->next;
+
+ head->next = desc;
+ }
+
+ return desc;
+}
+
+void vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc)
+{
+ struct vidtv_psi_desc *curr = desc;
+ struct vidtv_psi_desc *tmp = NULL;
+
+ while (curr) {
+ tmp = curr;
+ curr = curr->next;
+ kfree(tmp);
+ }
+}
+
+static u32
+vidtv_psi_desc_comp_len(struct vidtv_psi_desc *desc)
+{
+ u32 length = 0;
+
+ if (!desc)
+ return 0;
+
+ while (desc) {
+ length += desc->length;
+ desc = desc->next;
+ }
+
+ return length;
+}
+
+void vidtv_psi_desc_assign(struct vidtv_psi_desc **to,
+ struct vidtv_psi_desc *desc)
+{
+ /*
+ * Caller must recompute the section length afterwards at some point
+ * This function transfers ownedship of desc.
+ * Start by cleaning the old data
+ */
+ if (*to)
+ vidtv_psi_desc_destroy(*to);
+
+ *to = desc; /* reassign pointer */
+}
+
+static void vidtv_psi_desc_to_be(struct vidtv_psi_desc *desc)
+{
+ /*
+ * Convert descriptor endianness to big-endian on a field-by-field basis
+ * where applicable
+ */
+
+ switch (desc->type) {
+ /* nothing do do */
+ case SERVICE_DESCRIPTOR:
+ break;
+ case REGISTRATION_DESCRIPTOR:
+ cpu_to_be32s(&((struct vidtv_psi_desc_registration *)
+ desc)->format_identifier);
+ pr_alert("%s: descriptor type %d found\n",
+ __func__,
+ desc->type);
+ pr_alert("%s: change 'additional_info' endianness before calling\n",
+ __func__);
+ break;
+ default:
+ pr_err("%s: descriptor type %d not implemented, skipping\n",
+ __func__,
+ desc->type);
+ break;
+ }
+}
+
+static void vidtv_psi_desc_to_cpu(struct vidtv_psi_desc *desc)
+{
+ /*
+ * Convert descriptor endianness to native on a field-by-field basis
+ * where applicable
+ */
+
+ switch (desc->type) {
+ /* nothing do do */
+ case SERVICE_DESCRIPTOR:
+ break;
+ case REGISTRATION_DESCRIPTOR:
+ be32_to_cpus(&((struct vidtv_psi_desc_registration *)
+ desc)->format_identifier);
+ pr_alert("%s: descriptor type %d found\n",
+ __func__,
+ desc->type);
+ pr_alert("%s: change 'additional_info' endianness before calling\n",
+ __func__);
+ break;
+ default:
+ pr_err("%s: descriptor type %d not implemented, skipping\n",
+ __func__,
+ desc->type);
+ break;
+ }
+}
+
+static u32 vidtv_psi_desc_write_into(struct desc_write_args args)
+{
+ /* the number of bytes written by this function */
+ u32 nbytes = 0;
+ struct psi_write_args psi_args = {0};
+
+ psi_args.dest_buf = args.dest_buf;
+ psi_args.from = args.desc;
+
+ psi_args.len = sizeof_field(struct vidtv_psi_desc, type) +
+ sizeof_field(struct vidtv_psi_desc, length);
+
+ psi_args.dest_offset = args.dest_offset;
+ psi_args.pid = args.pid;
+ psi_args.new_psi_section = false;
+ psi_args.continuity_counter = args.continuity_counter;
+ psi_args.is_crc = false;
+ psi_args.dest_buf_sz = args.dest_buf_sz;
+
+ vidtv_psi_desc_to_be(args.desc);
+
+ nbytes += vidtv_psi_ts_psi_write_into(psi_args);
+
+ /* move 'from' pointer to point to u8 data[] */
+ psi_args.from = args.desc +
+ sizeof_field(struct vidtv_psi_desc, type) +
+ sizeof_field(struct vidtv_psi_desc, length) +
+ sizeof(struct vidtv_psi_desc *);
+
+ psi_args.len = args.desc->length;
+ psi_args.dest_offset = args.dest_offset + nbytes;
+
+ nbytes += vidtv_psi_ts_psi_write_into(psi_args);
+
+ vidtv_psi_desc_to_cpu(args.desc);
+
+ return nbytes;
+}
+
+static u32
+vidtv_psi_table_header_write_into(struct header_write_args args)
+{
+ /* the number of bytes written by this function */
+ u32 nbytes = 0;
+ struct psi_write_args psi_args = {0};
+
+ psi_args.dest_buf = args.dest_buf;
+ psi_args.from = args.h;
+ psi_args.len = sizeof(struct vidtv_psi_table_header);
+ psi_args.dest_offset = args.dest_offset;
+ psi_args.pid = args.pid;
+ psi_args.new_psi_section = true;
+ psi_args.continuity_counter = args.continuity_counter;
+ psi_args.is_crc = false;
+ psi_args.dest_buf_sz = args.dest_buf_sz;
+
+ cpu_to_be16s(&args.h->bitfield);
+ cpu_to_be16s(&args.h->id);
+
+ nbytes += vidtv_psi_ts_psi_write_into(psi_args);
+
+ be16_to_cpus(&args.h->bitfield);
+ be16_to_cpus(&args.h->id);
+
+ return nbytes;
+}
+
+void
+vidtv_psi_pat_table_comp_sec_len(struct vidtv_psi_table_pat *pat)
+{
+ /* see ISO/IEC 13818-1 : 2000 p.43 */
+ u16 length = 0;
+ u32 i;
+
+ /* from immediately after 'section_length' until 'last_section_number'*/
+ length += PAT_LEN_UNTIL_LAST_SECTION_NUMBER;
+
+ /* do not count the pointer */
+ for (i = 0; i < pat->programs; ++i)
+ length += sizeof(struct vidtv_psi_table_pat_program) -
+ sizeof(struct vidtv_psi_table_pat_program *);
+
+ length += CRC_SIZE_IN_BYTES;
+
+ WARN_ON(length > MAX_SECTION_LEN);
+ pat->header.section_length = length;
+}
+
+void vidtv_psi_pmt_table_comp_sec_len(struct vidtv_psi_table_pmt *pmt)
+{
+ /* see ISO/IEC 13818-1 : 2000 p.46 */
+ u16 length = 0;
+ struct vidtv_psi_table_pmt_stream *s = pmt->stream;
+
+ /* from immediately after 'section_length' until 'program_info_length'*/
+ length += PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH;
+
+ pmt->desc_length = vidtv_psi_desc_comp_len(pmt->descriptor);
+ length += pmt->desc_length;
+
+ while (s) {
+ /* skip both pointers at the end */
+ length += sizeof(struct vidtv_psi_table_pmt_stream) -
+ sizeof(struct vidtv_psi_desc *) -
+ sizeof(struct vidtv_psi_table_pmt_stream *);
+
+ s->desc_length = vidtv_psi_desc_comp_len(s->descriptor);
+ length += s->desc_length;
+
+ s = s->next;
+ }
+
+ length += CRC_SIZE_IN_BYTES;
+
+ WARN_ON(length > MAX_SECTION_LEN);
+ pmt->header.section_length = length;
+}
+
+void vidtv_psi_sdt_table_comp_sec_len(struct vidtv_psi_table_sdt *sdt)
+{
+ /* see ETSI EN 300 468 V 1.10.1 p.24 */
+ u16 length = 0;
+ struct vidtv_psi_table_sdt_service *s = sdt->service;
+
+ /*
+ * from immediately after 'section_length' until
+ * 'reserved_for_future_use'
+ */
+ length += SDT_LEN_UNTIL_RESERVED_FOR_FUTURE_USE;
+
+ while (s) {
+ /* skip both pointers at the end */
+ length += sizeof(struct vidtv_psi_table_sdt_service) -
+ sizeof(struct vidtv_psi_desc *) -
+ sizeof(struct vidtv_psi_table_sdt_service *);
+
+ s->desc_length = vidtv_psi_desc_comp_len(s->descriptor);
+ length += s->desc_length;
+
+ s = s->next;
+ }
+
+ length += CRC_SIZE_IN_BYTES;
+
+ WARN_ON(length > MAX_SECTION_LEN);
+ sdt->header.section_length = length;
+}
+
+struct vidtv_psi_table_pat_program*
+vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head,
+ u16 service_id,
+ u16 pid)
+{
+ /*
+ * if 'head' is attached to a table, caller should recompute
+ * the section length afterwards at some point
+ */
+ struct vidtv_psi_table_pat_program *program;
+
+ program = kzalloc(sizeof(*program), GFP_KERNEL);
+
+ program->service_id = service_id;
+ /* pid for the PMT section in the TS */
+ program->pid = pid;
+ program->next = NULL;
+ program->reserved = 0x7;
+
+ if (head) {
+ while (head->next)
+ head = head->next;
+
+ head->next = program;
+ }
+
+ return program;
+}
+
+void
+vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p)
+{
+ struct vidtv_psi_table_pat_program *curr = p;
+ struct vidtv_psi_table_pat_program *tmp = NULL;
+
+ while (curr) {
+ tmp = curr;
+ curr = curr->next;
+ kfree(tmp);
+ }
+}
+
+void
+vidtv_psi_pat_program_assign(struct vidtv_psi_table_pat *pat,
+ struct vidtv_psi_table_pat_program *p)
+{
+ /* This function transfers ownership of p to the table */
+
+ u16 program_count = 0;
+ struct vidtv_psi_table_pat_program *program = p;
+ struct vidtv_psi_table_pat_program *temp = pat->program;
+
+ while (program) {
+ ++program_count;
+ program = program->next;
+ }
+
+ pat->programs = program_count;
+ pat->program = p;
+
+ /* Recompute section length */
+ vidtv_psi_pat_table_comp_sec_len(pat);
+
+ /* reassign if the new size is too big */
+ if (pat->header.section_length > MAX_SECTION_LEN)
+ vidtv_psi_pat_program_assign(pat, temp);
+ else
+ vidtv_psi_pat_program_destroy(temp);
+}
+
+void vidtv_psi_pat_table_init(struct vidtv_psi_table_pat *pat,
+ bool update_version_num,
+ u16 transport_stream_id)
+{
+ static u8 pat_version;
+
+ pat->header.table_id = 0x0;
+ pat->header.syntax = 0x1;
+ pat->header.zero = 0x0;
+ pat->header.one = 0x03;
+
+ /* transport stream ID, at will */
+ pat->header.id = transport_stream_id;
+ pat->header.current_next = 0x1;
+
+ /* ETSI 300 468: indicates changes in the TS described by this table*/
+ if (update_version_num)
+ ++pat_version;
+
+ pat->header.version = pat_version;
+
+ pat->header.one2 = 0x03;
+ pat->header.section_id = 0x0;
+ pat->header.last_section = 0x0;
+
+ pat->programs = 0;
+
+ vidtv_psi_pat_table_comp_sec_len(pat);
+}
+
+u32 vidtv_psi_pat_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_pat *pat,
+ u32 buf_sz,
+ u8 *continuity_counter)
+{
+ /* the number of bytes written by this function */
+ u32 nbytes = 0;
+ const u16 pat_pid = VIDTV_PAT_PID;
+
+ struct vidtv_psi_table_pat_program *p = pat->program;
+ struct header_write_args h_args = {0};
+ struct psi_write_args args = {0};
+ struct crc32_write_args c_args = {0};
+
+ vidtv_psi_pat_table_comp_sec_len(pat);
+
+ h_args.dest_buf = buf;
+ h_args.dest_offset = offset;
+ h_args.h = &pat->header;
+ h_args.pid = pat_pid;
+ h_args.continuity_counter = continuity_counter;
+ h_args.dest_buf_sz = buf_sz;
+
+ nbytes += vidtv_psi_table_header_write_into(h_args);
+
+ /* note that the field 'u16 programs' is not really part of the PAT */
+
+ args.dest_buf = buf;
+ args.pid = pat_pid;
+ args.new_psi_section = false;
+ args.continuity_counter = continuity_counter;
+ args.is_crc = false;
+ args.dest_buf_sz = buf_sz;
+
+ while (p) {
+ /* copy the PAT programs */
+ cpu_to_be16s(&p->service_id);
+ cpu_to_be16s(&p->bitfield);
+
+ args.from = p;
+ /* skip the pointer */
+ args.len = sizeof(*p) -
+ sizeof(struct vidtv_psi_table_pat_program *);
+ args.dest_offset = offset + nbytes;
+
+ nbytes += vidtv_psi_ts_psi_write_into(args);
+
+ be16_to_cpus(&p->service_id);
+ be16_to_cpus(&p->bitfield);
+
+ p = p->next;
+ }
+
+ c_args.dest_buf = buf;
+ c_args.dest_offset = offset + nbytes;
+ c_args.pid = pat_pid;
+ c_args.continuity_counter = continuity_counter;
+ c_args.dest_buf_sz = buf_sz;
+
+ nbytes += table_section_crc32_write_into(c_args);
+
+ return nbytes;
+}
+
+void
+vidtv_psi_pat_table_destroy(struct vidtv_psi_table_pat *p)
+{
+ vidtv_psi_pat_program_destroy(p->program);
+}
+
+struct vidtv_psi_table_pmt_stream*
+vidtv_psi_pmt_stream_init(struct vidtv_psi_table_pmt_stream *head,
+ enum vidtv_psi_stream_types stream_type,
+ u16 es_pid)
+{
+ struct vidtv_psi_table_pmt_stream *stream;
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+
+ stream->type = stream_type;
+ stream->elementary_pid = es_pid;
+ stream->reserved = 0x07;
+
+ stream->desc_length = vidtv_psi_desc_comp_len(stream->descriptor);
+
+ stream->zero = 0x0;
+ stream->reserved2 = 0x0f;
+
+ if (head) {
+ while (head->next)
+ head = head->next;
+
+ head->next = stream;
+ }
+
+ return stream;
+}
+
+void vidtv_psi_pmt_stream_destroy(struct vidtv_psi_table_pmt_stream *s)
+{
+ struct vidtv_psi_table_pmt_stream *curr_stream = s;
+ struct vidtv_psi_table_pmt_stream *tmp_stream = NULL;
+
+ while (curr_stream) {
+ tmp_stream = curr_stream;
+ curr_stream = curr_stream->next;
+ kfree(tmp_stream);
+ }
+}
+
+void vidtv_psi_pmt_stream_assign(struct vidtv_psi_table_pmt *pmt,
+ struct vidtv_psi_table_pmt_stream *s)
+{
+ /* This function transfers ownership of s to the table */
+ struct vidtv_psi_table_pmt_stream *stream = s;
+ struct vidtv_psi_desc *desc = s->descriptor;
+ struct vidtv_psi_table_pmt_stream *temp = pmt->stream;
+
+ while (stream)
+ stream = stream->next;
+
+ while (desc)
+ desc = desc->next;
+
+ pmt->stream = s;
+ /* Recompute section length */
+ vidtv_psi_pmt_table_comp_sec_len(pmt);
+
+ /* reassign if the new size is too big */
+ if (pmt->header.section_length > MAX_SECTION_LEN)
+ vidtv_psi_pmt_stream_assign(pmt, temp);
+ else
+ vidtv_psi_pmt_stream_destroy(temp);
+}
+
+u16 vidtv_psi_pmt_get_pid(struct vidtv_psi_table_pmt *section,
+ struct vidtv_psi_table_pat *pat)
+{
+ struct vidtv_psi_table_pat_program *program = pat->program;
+
+ /*
+ * service_id is the same as program_number in the
+ * corresponding program_map_section
+ * see ETSI EN 300 468 v1.15.1 p. 24
+ */
+ while (program) {
+ if (program->service_id == section->header.id)
+ return pat->program->pid;
+
+ program = program->next;
+ }
+
+ return TS_LAST_VALID_PID + 1; /* not found */
+}
+
+void vidtv_psi_pmt_table_init(struct vidtv_psi_table_pmt *pmt,
+ bool update_version_num,
+ u16 program_number,
+ u16 pcr_pid)
+{
+ static u8 pmt_version;
+
+ pmt->header.table_id = 0x2;
+ pmt->header.syntax = 0x1;
+ pmt->header.zero = 0x0;
+ pmt->header.one = 0x3;
+
+ pmt->header.id = program_number;
+ pmt->header.current_next = 0x1;
+
+ /* ETSI 300 468: indicates changes in the TS described by this table*/
+ if (update_version_num)
+ ++pmt_version;
+
+ pmt->header.version = pmt_version;
+
+ pmt->header.one2 = 0x3;
+ pmt->header.section_id = 0;
+ pmt->header.last_section = 0;
+
+ pmt->pcr_pid = (pcr_pid) ? pcr_pid : 0x1fff;
+ pmt->reserved2 = 0x03;
+
+ pmt->reserved3 = 0x0f;
+ pmt->zero3 = 0x0;
+
+ pmt->desc_length = vidtv_psi_desc_comp_len(pmt->descriptor);
+
+ vidtv_psi_pmt_table_comp_sec_len(pmt);
+}
+
+u32 vidtv_psi_pmt_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_pmt *pmt,
+ u16 pid,
+ u32 buf_sz,
+ u8 *continuity_counter)
+{
+ /* the number of bytes written by this function */
+ u32 nbytes = 0;
+ struct vidtv_psi_desc *table_descriptor = pmt->descriptor;
+ struct vidtv_psi_table_pmt_stream *stream = pmt->stream;
+ struct vidtv_psi_desc *stream_descriptor = (stream) ?
+ pmt->stream->descriptor :
+ NULL;
+
+ struct header_write_args h_args = {0};
+ struct psi_write_args args = {0};
+ struct desc_write_args d_args = {0};
+ struct crc32_write_args c_args = {0};
+
+ vidtv_psi_pmt_table_comp_sec_len(pmt);
+
+ h_args.dest_buf = buf;
+ h_args.dest_offset = offset;
+ h_args.h = &pmt->header;
+ h_args.pid = pid;
+ h_args.continuity_counter = continuity_counter;
+ h_args.dest_buf_sz = buf_sz;
+
+ nbytes += vidtv_psi_table_header_write_into(h_args);
+
+ /* write the two bitfields */
+ cpu_to_be16s(&pmt->bitfield);
+ cpu_to_be16s(&pmt->bitfield2);
+
+ args.dest_buf = buf;
+ args.from = pmt + sizeof(struct vidtv_psi_table_header);
+ args.len = sizeof_field(struct vidtv_psi_table_pmt, bitfield) +
+ sizeof_field(struct vidtv_psi_table_pmt, bitfield2);
+ args.dest_offset = offset + nbytes;
+ args.pid = pid;
+ args.new_psi_section = false;
+ args.continuity_counter = continuity_counter;
+ args.is_crc = false;
+ args.dest_buf_sz = buf_sz;
+
+ nbytes += vidtv_psi_ts_psi_write_into(args);
+
+ be16_to_cpus(&pmt->bitfield);
+ be16_to_cpus(&pmt->bitfield2);
+
+ while (table_descriptor) {
+ /* write the descriptors, if any */
+ d_args.dest_buf = buf;
+ d_args.dest_offset = offset + nbytes;
+ d_args.desc = table_descriptor;
+ d_args.pid = pid;
+ d_args.continuity_counter = continuity_counter;
+ d_args.dest_buf_sz = buf_sz;
+
+ vidtv_psi_desc_to_be(d_args.desc);
+ nbytes += vidtv_psi_desc_write_into(d_args);
+ vidtv_psi_desc_to_cpu(d_args.desc);
+
+ table_descriptor = table_descriptor->next;
+ }
+
+ while (stream) {
+ /* write the streams, if any */
+ args.from = stream;
+ args.len = sizeof_field(struct vidtv_psi_table_pmt_stream,
+ type) +
+ sizeof_field(struct vidtv_psi_table_pmt_stream,
+ bitfield) +
+ sizeof_field(struct vidtv_psi_table_pmt_stream,
+ bitfield2);
+ args.dest_offset = offset + nbytes;
+
+ cpu_to_be16s(&stream->bitfield);
+ cpu_to_be16s(&stream->bitfield2);
+
+ nbytes += vidtv_psi_ts_psi_write_into(args);
+
+ be16_to_cpus(&stream->bitfield);
+ be16_to_cpus(&stream->bitfield2);
+
+ while (stream_descriptor) {
+ /* write the stream descriptors, if any */
+ d_args.desc = stream_descriptor;
+ d_args.dest_offset = offset + nbytes;
+
+ vidtv_psi_desc_to_be(d_args.desc);
+
+ nbytes += vidtv_psi_desc_write_into(d_args);
+
+ vidtv_psi_desc_to_cpu(d_args.desc);
+
+ stream_descriptor = stream_descriptor->next;
+ }
+
+ stream = stream->next;
+ }
+
+ c_args.dest_buf = buf;
+ c_args.dest_offset = offset + nbytes;
+ c_args.pid = pid;
+ c_args.continuity_counter = continuity_counter;
+ c_args.dest_buf_sz = buf_sz;
+
+ nbytes += table_section_crc32_write_into(c_args);
+
+ return nbytes;
+}
+
+void vidtv_psi_pmt_table_destroy(struct vidtv_psi_table_pmt *pmt)
+{
+ struct vidtv_psi_desc *curr_desc = pmt->descriptor;
+ struct vidtv_psi_desc *tmp_desc = NULL;
+
+ while (curr_desc) {
+ tmp_desc = curr_desc;
+ curr_desc = curr_desc->next;
+ vidtv_psi_desc_destroy(tmp_desc);
+ kfree(tmp_desc);
+ }
+
+ vidtv_psi_pmt_stream_destroy(pmt->stream);
+}
+
+void vidtv_psi_sdt_table_init(struct vidtv_psi_table_sdt *sdt,
+ bool update_version_num,
+ u16 transport_stream_id)
+{
+ static u8 sdt_version;
+
+ sdt->header.table_id = 0x42;
+
+ sdt->header.one = 0x3;
+ sdt->header.zero = 0x1;
+ /*
+ * The PAT, PMT, and CAT all set this to 0.
+ * Other tables set this to 1.
+ */
+ sdt->header.syntax = 0x1;
+
+ /*
+ * This is a 16-bit field which serves as a label for identification
+ * of the TS, about which the SDT informs, from any other multiplex
+ * within the delivery system.
+ */
+ sdt->header.id = transport_stream_id;
+ sdt->header.current_next = 0x1;
+
+ /* ETSI 300 468: indicates changes in the TS described by this table*/
+ if (update_version_num)
+ ++sdt_version;
+
+ sdt->header.version = sdt_version;
+
+ sdt->header.one2 = 0x3;
+ sdt->header.section_id = 0;
+ sdt->header.last_section = 0;
+
+ sdt->network_id = transport_stream_id;
+ sdt->reserved = 0xff;
+
+ vidtv_psi_sdt_table_comp_sec_len(sdt);
+}
+
+u32 vidtv_psi_sdt_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_sdt *sdt,
+ u32 buf_sz,
+ u8 *continuity_counter)
+{
+ u32 nbytes = 0; /* the number of bytes written */
+ u16 sdt_pid = VIDTV_SDT_PID; /* see ETSI EN 300 468 v1.15.1 p. 11 */
+
+ struct vidtv_psi_table_sdt_service *service = sdt->service;
+ struct vidtv_psi_desc *service_desc = (sdt->service) ?
+ sdt->service->descriptor :
+ NULL;
+
+ struct header_write_args h_args = {0};
+ struct psi_write_args args = {0};
+ struct desc_write_args d_args = {0};
+ struct crc32_write_args c_args = {0};
+
+ vidtv_psi_sdt_table_comp_sec_len(sdt);
+
+ h_args.dest_buf = buf;
+ h_args.dest_offset = offset;
+ h_args.h = &sdt->header;
+ h_args.pid = sdt_pid;
+ h_args.continuity_counter = continuity_counter;
+ h_args.dest_buf_sz = buf_sz;
+
+ nbytes += vidtv_psi_table_header_write_into(h_args);
+
+ args.dest_buf = buf;
+ args.from = sdt + sizeof(struct vidtv_psi_table_header);
+
+ args.len = sizeof_field(struct vidtv_psi_table_sdt, network_id) +
+ sizeof_field(struct vidtv_psi_table_sdt, reserved);
+
+ args.dest_offset = offset + nbytes;
+ args.pid = sdt_pid;
+ args.new_psi_section = false;
+ args.continuity_counter = continuity_counter;
+ args.is_crc = false;
+ args.dest_buf_sz = buf_sz;
+
+ /* copy u16 network_id + u8 reserved)*/
+ cpu_to_be16s(&sdt->network_id);
+
+ nbytes += vidtv_psi_ts_psi_write_into(args);
+
+ be16_to_cpus(&sdt->network_id);
+
+ while (service) {
+ /* copy the services, if any */
+ args.from = service;
+ /* skip both pointers at the end */
+ args.len = sizeof(struct vidtv_psi_table_sdt_service) -
+ sizeof(struct vidtv_psi_desc *) -
+ sizeof(struct vidtv_psi_table_sdt_service *);
+ args.dest_offset = offset + nbytes;
+
+ cpu_to_be16s(&service->service_id);
+ cpu_to_be16s(&service->bitfield);
+
+ nbytes += vidtv_psi_ts_psi_write_into(args);
+
+ be16_to_cpus(&service->service_id);
+ be16_to_cpus(&service->bitfield);
+
+ while (service_desc) {
+ /* copy the service descriptors, if any */
+ d_args.dest_buf = buf;
+ d_args.dest_offset = offset + nbytes;
+ d_args.desc = service_desc;
+ d_args.pid = sdt_pid;
+ d_args.continuity_counter = continuity_counter;
+ d_args.dest_buf_sz = buf_sz;
+
+ vidtv_psi_desc_to_be(d_args.desc);
+
+ nbytes += vidtv_psi_desc_write_into(d_args);
+
+ vidtv_psi_desc_to_cpu(d_args.desc);
+
+ service_desc = service_desc->next;
+ }
+
+ service = service->next;
+ }
+
+ c_args.dest_buf = buf;
+ c_args.dest_offset = offset + nbytes;
+ c_args.pid = sdt_pid;
+ c_args.continuity_counter = continuity_counter;
+ c_args.dest_buf_sz = buf_sz;
+
+ nbytes += table_section_crc32_write_into(c_args);
+
+ return nbytes;
+}
+
+void vidtv_psi_sdt_table_destroy(struct vidtv_psi_table_sdt *sdt)
+{
+ struct vidtv_psi_table_sdt_service *curr_service = sdt->service;
+ struct vidtv_psi_table_sdt_service *tmp_service = NULL;
+ struct vidtv_psi_desc *curr_desc = (sdt->service) ?
+ sdt->service->descriptor : NULL;
+ struct vidtv_psi_desc *tmp_desc = NULL;
+
+ while (curr_service) {
+ curr_desc = curr_service->descriptor;
+
+ while (curr_desc) {
+ /* clear all descriptors for the service */
+ tmp_desc = curr_desc;
+ curr_desc = curr_desc->next;
+ vidtv_psi_desc_destroy(tmp_desc);
+ kfree(tmp_desc);
+ }
+
+ /* then clear the current service */
+ tmp_service = curr_service;
+ curr_service = curr_service->next;
+ kfree(tmp_service);
+ }
+}
+
+struct vidtv_psi_table_sdt_service
+*vidtv_psi_sdt_service_init(struct vidtv_psi_table_sdt_service *head,
+ u16 service_id)
+{
+ /*
+ * if 'head' is attached to a table, caller should recompute
+ * the section length afterwards at some point
+ */
+ struct vidtv_psi_table_sdt_service *service;
+
+ service = kzalloc(sizeof(*service), GFP_KERNEL);
+
+ /*
+ * ETSI 300 468: this is a 16bit field which serves as a label to
+ * identify this service from any other service within the TS.
+ * The service id is the same as the program number in the
+ * corresponding program_map_section
+ */
+ service->service_id = service_id;
+ service->EIT_schedule = 0x0; /* TODO */
+ service->EIT_present_following = 0x0; /* TODO */
+ service->reserved = 0x3f; /* all bits on */
+ service->free_CA_mode = 0x0; /* not scrambled */
+ service->running_status = RUNNING;
+
+ if (head) {
+ while (head->next)
+ head = head->next;
+
+ head->next = service;
+ }
+
+ return service;
+}
+
+void
+vidtv_psi_sdt_service_destroy(struct vidtv_psi_table_sdt_service *service)
+{
+ struct vidtv_psi_table_sdt_service *curr = service;
+ struct vidtv_psi_table_sdt_service *tmp = NULL;
+
+ while (curr) {
+ tmp = curr;
+ curr = curr->next;
+ kfree(tmp);
+ }
+}
+
+void
+vidtv_psi_sdt_service_assign(struct vidtv_psi_table_sdt *sdt,
+ struct vidtv_psi_table_sdt_service *service)
+{
+ struct vidtv_psi_table_sdt_service *temp = sdt->service;
+
+ sdt->service = service;
+
+ /* recompute section length */
+ vidtv_psi_sdt_table_comp_sec_len(sdt);
+
+ /* reassign if the new size is too big */
+ if (sdt->header.section_length > MAX_SECTION_LEN)
+ vidtv_psi_sdt_service_assign(sdt, temp);
+ else
+ vidtv_psi_sdt_service_destroy(temp);
+}
+
+void
+vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat,
+ struct vidtv_psi_table_pmt sec[])
+
+{
+ /*
+ * PMTs contain information about programs. For each program,
+ * there is one PMT section. This function will create a section
+ * for each program found in the PAT
+ */
+ struct vidtv_psi_table_pat_program *program = pat->program;
+ u32 i = 0;
+
+ while (program) {
+ vidtv_psi_pmt_table_init(&sec[i],
+ false,
+ sec[i].header.id,
+ 0);
+
+ ++i;
+ program = program->next;
+ }
+}
diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.h b/drivers/media/test-drivers/vidtv/vidtv_psi.h
new file mode 100644
index 0000000000000..c5c8c143f0e4a
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_psi.h
@@ -0,0 +1,357 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This file contains the logic to work with MPEG Program-Specific Information.
+ * These are defined both in ISO/IEC 13818-1 (systems) and ETSI EN 300 468.
+ * PSI is carried in the form of table structures, and although each table might
+ * technically be broken into one or more sections, we do not do this here,
+ * hence 'table' and 'section' are interchangeable for us.
+ *
+ * This code currently supports three tables: PAT, PMT and SDT. These are the
+ * bare minimum to get userspace to recognize our MPEG transport stream. It can
+ * be extended to support more PSI tables in the future.
+ *
+ * A note on endianness: MPEG layout is big-endian, therefore:
+ * - All fields spanning more than a byte must undergo 'cpu_to_beXX()' before
+ * serialization. These convertions are done in the *_write_into() functions.
+ *
+ * - All byte sized bitfields must have their ordering reversed if
+ * __LITTLE_ENDIAN_BITFIELD is defined.
+ *
+ * Written by: Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_PSI_H
+#define VIDTV_PSI_H
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+
+/*
+ * all section lengths start immediately after the 'section_length' field
+ * see ISO/IEC 13818-1 : 2000 and ETSI EN 300 468 V 1.10.1 for
+ * reference
+ */
+#define PAT_LEN_UNTIL_LAST_SECTION_NUMBER 5
+#define PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH 9
+#define SDT_LEN_UNTIL_RESERVED_FOR_FUTURE_USE 8
+#define MAX_SECTION_LEN 1021
+#define VIDTV_PAT_PID 0
+#define VIDTV_SDT_PID 0x0011
+
+enum vidtv_psi_descriptors {
+ REGISTRATION_DESCRIPTOR = 0x05,
+ SERVICE_DESCRIPTOR = 0x48,
+};
+
+enum vidtv_psi_stream_types {
+ /* see ISO/IEC 13818-1 2000 p. 48 */
+ STREAM_PRIVATE_DATA = 0x06,
+};
+
+struct vidtv_psi_desc {
+ u8 type;
+ u8 length;
+ struct vidtv_psi_desc *next;
+ u8 data[];
+} __packed;
+
+struct vidtv_psi_desc_service {
+ u8 type;
+ u8 length;
+ struct vidtv_psi_desc *next;
+
+ u8 service_type;
+ char *name;
+ char *name_emph;
+ char *provider;
+ char *provider_emph;
+} __packed;
+
+struct vidtv_psi_desc_registration {
+ u8 type;
+ u8 length;
+ struct vidtv_psi_desc *next;
+
+ /*
+ * The format_identifier is a 32-bit value obtained from a Registration
+ * Authority as designated by ISO/IEC JTC 1/SC 29.
+ */
+ u32 format_identifier;
+ /*
+ * The meaning of additional_identification_info bytes, if any, are
+ * defined by the assignee of that format_identifier, and once defined
+ * they shall not change.
+ */
+ u8 additional_identification_info[];
+} __packed;
+
+struct vidtv_psi_table_header {
+ u8 table_id;
+ union {
+ u16 bitfield;
+ struct {
+ u8 syntax:1;
+ u8 zero:1;
+ u8 one:2;
+ u16 section_length:12;
+ } __packed;
+ } __packed;
+
+ u16 id; /* TS ID */
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 current_next:1;
+ u8 version:5;
+ u8 one2:2;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 one2:2;
+ u8 version:5;
+ u8 current_next:1;
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+ u8 section_id; /* section_number */
+ u8 last_section; /* last_section_number */
+} __packed;
+
+struct vidtv_psi_table_pat_program {
+ u16 service_id;
+ union {
+ u16 bitfield;
+ struct {
+ u8 reserved:3;
+ u16 pid:13;
+ } __packed;
+ } __packed;
+ struct vidtv_psi_table_pat_program *next;
+} __packed;
+
+struct vidtv_psi_table_pat {
+ struct vidtv_psi_table_header header;
+ u16 programs;
+ struct vidtv_psi_table_pat_program *program;
+} __packed;
+
+struct vidtv_psi_table_sdt_service {
+ u16 service_id;
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 EIT_present_following:1;
+ u8 EIT_schedule:1;
+ u8 reserved:6;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 reserved:6;
+ u8 EIT_schedule:1;
+ u8 EIT_present_following:1;
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+ union {
+ u16 bitfield;
+ struct {
+ u16 running_status:3;
+ u16 free_CA_mode:1;
+ u16 desc_length:12;
+ } __packed;
+ } __packed;
+ struct vidtv_psi_desc *descriptor;
+ struct vidtv_psi_table_sdt_service *next;
+} __packed;
+
+struct vidtv_psi_table_sdt {
+ struct vidtv_psi_table_header header;
+ u16 network_id; /* original_network_id */
+ u8 reserved;
+ struct vidtv_psi_table_sdt_service *service;
+} __packed;
+
+enum service_running_status {
+ RUNNING,
+};
+
+enum service_type {
+ /* see ETSI EN 300 468 v1.15.1 p. 77 */
+ DIGITAL_TELEVISION_SERVICE = 0x1,
+};
+
+struct vidtv_psi_table_pmt_stream {
+ u8 type;
+ union {
+ u16 bitfield;
+ struct {
+ u16 reserved:3;
+ u16 elementary_pid:13;
+ } __packed;
+ } __packed;
+ union {
+ u16 bitfield2;
+ struct {
+ u16 reserved2:4;
+ u16 zero:2;
+ u16 desc_length:10;
+ } __packed;
+ } __packed;
+ struct vidtv_psi_desc *descriptor;
+ struct vidtv_psi_table_pmt_stream *next;
+} __packed;
+
+struct vidtv_psi_table_pmt {
+ struct vidtv_psi_table_header header;
+ union {
+ u16 bitfield;
+ struct {
+ u16 reserved2:3;
+ u16 pcr_pid:13;
+ } __packed;
+ } __packed;
+
+ union {
+ u16 bitfield2;
+ struct {
+ u16 reserved3:4;
+ u16 zero3:2;
+ u16 desc_length:10; /* program_info_length */
+ } __packed;
+ } __packed;
+ struct vidtv_psi_desc *descriptor;
+ struct vidtv_psi_table_pmt_stream *stream;
+} __packed;
+
+struct psi_write_args {
+ void *dest_buf; /* the buffer to write into */
+ void *from;
+ size_t len; /* how much to write */
+ u32 dest_offset; /* where to start writing in the buffer */
+ u16 pid; /* TS packet ID */
+ bool new_psi_section; /* set when starting a table section */
+ u8 *continuity_counter; /* TS: incremented on every new packet */
+ bool is_crc; /* set when writing the CRC at the end */
+ u32 dest_buf_sz; /* protect against overflow if not zero */
+};
+
+struct desc_write_args {
+ void *dest_buf;
+ u32 dest_offset;
+ struct vidtv_psi_desc *desc;
+ u16 pid;
+ u8 *continuity_counter;
+ u32 dest_buf_sz;
+};
+
+struct crc32_write_args {
+ void *dest_buf;
+ u32 dest_offset;
+ u16 pid;
+ u8 *continuity_counter;
+ u32 dest_buf_sz;
+};
+
+struct header_write_args {
+ void *dest_buf;
+ u32 dest_offset;
+ struct vidtv_psi_table_header *h;
+ u16 pid;
+ u8 *continuity_counter;
+ u32 dest_buf_sz;
+};
+
+struct vidtv_psi_desc *vidtv_psi_desc_init(struct vidtv_psi_desc *head,
+ u8 type,
+ u8 length);
+
+void vidtv_psi_pat_table_init(struct vidtv_psi_table_pat *pat,
+ bool update_version_num,
+ u16 transport_stream_id);
+
+struct vidtv_psi_table_pat_program*
+vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head,
+ u16 service_id,
+ u16 pid);
+
+struct vidtv_psi_table_pmt_stream*
+vidtv_psi_pmt_stream_init(struct vidtv_psi_table_pmt_stream *head,
+ enum vidtv_psi_stream_types stream_type,
+ u16 es_pid);
+
+void vidtv_psi_pmt_table_init(struct vidtv_psi_table_pmt *pmt,
+ bool update_version_num,
+ u16 program_number,
+ u16 pcr_pid);
+
+void
+vidtv_psi_sdt_table_init(struct vidtv_psi_table_sdt *sdt,
+ bool update_version_num,
+ u16 transport_stream_id);
+
+struct vidtv_psi_table_sdt_service*
+vidtv_psi_sdt_service_init(struct vidtv_psi_table_sdt_service *head,
+ u16 service_id);
+
+void
+vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc);
+
+void
+vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p);
+
+void
+vidtv_psi_pat_table_destroy(struct vidtv_psi_table_pat *p);
+
+void
+vidtv_psi_pmt_stream_destroy(struct vidtv_psi_table_pmt_stream *s);
+
+void
+vidtv_psi_pmt_table_destroy(struct vidtv_psi_table_pmt *pmt);
+
+void
+vidtv_psi_sdt_table_destroy(struct vidtv_psi_table_sdt *sdt);
+
+void
+vidtv_psi_sdt_service_destroy(struct vidtv_psi_table_sdt_service *service);
+
+void
+vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc);
+
+void
+vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p);
+
+void
+vidtv_psi_sdt_service_assign(struct vidtv_psi_table_sdt *sdt,
+ struct vidtv_psi_table_sdt_service *service);
+
+void vidtv_psi_desc_assign(struct vidtv_psi_desc **to,
+ struct vidtv_psi_desc *desc);
+
+void vidtv_psi_pat_program_assign(struct vidtv_psi_table_pat *pat,
+ struct vidtv_psi_table_pat_program *p);
+
+void vidtv_psi_pmt_stream_assign(struct vidtv_psi_table_pmt *pmt,
+ struct vidtv_psi_table_pmt_stream *s);
+void
+vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat,
+ struct vidtv_psi_table_pmt *sec);
+
+u16 vidtv_psi_pmt_get_pid(struct vidtv_psi_table_pmt *section,
+ struct vidtv_psi_table_pat *pat);
+
+void vidtv_psi_pat_table_comp_sec_len(struct vidtv_psi_table_pat *pat);
+void vidtv_psi_pmt_table_comp_sec_len(struct vidtv_psi_table_pmt *pmt);
+void vidtv_psi_sdt_table_comp_sec_len(struct vidtv_psi_table_sdt *sdt);
+
+u32 vidtv_psi_pat_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_pat *pat,
+ u32 buf_sz,
+ u8 *continuity_counter);
+
+u32 vidtv_psi_sdt_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_sdt *sdt,
+ u32 buf_sz,
+ u8 *continuity_counter);
+
+u32 vidtv_psi_pmt_write_into(char *buf,
+ u32 offset,
+ struct vidtv_psi_table_pmt *pmt,
+ u16 pid,
+ u32 buf_sz,
+ u8 *continuity_counter);
+
+#endif // VIDTV_PSI_H
--
2.26.2

2020-05-02 03:26:17

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 10/11] media: vidtv: Implement a SMPTE 302M encoder

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

Implement a S302M encoder to make it possible to insert PCM audio data
in the generated MPEG Transport Stream.

This shall enable passing an audio signal into userspace so it can be
decoded and played by media software.

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test-drivers/vidtv/Makefile | 3 +-
.../media/test-drivers/vidtv/vidtv_common.h | 2 +
.../media/test-drivers/vidtv/vidtv_encoder.h | 103 +++
.../media/test-drivers/vidtv/vidtv_s302m.c | 608 ++++++++++++++++++
.../media/test-drivers/vidtv/vidtv_s302m.h | 99 +++
5 files changed, 814 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_encoder.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_s302m.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_s302m.h

diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
index e3a6540f50e87..c916eb19d73bb 100644
--- a/drivers/media/test-drivers/vidtv/Makefile
+++ b/drivers/media/test-drivers/vidtv/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0

vidtv_demod-objs := vidtv_common.o
-vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o vidtv_pes.o
+vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o vidtv_pes.o \
+ vidtv_s302m.o

obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.h b/drivers/media/test-drivers/vidtv/vidtv_common.h
index 3b68f95c5f6c8..170646497eb58 100644
--- a/drivers/media/test-drivers/vidtv/vidtv_common.h
+++ b/drivers/media/test-drivers/vidtv/vidtv_common.h
@@ -13,6 +13,8 @@
#include <linux/types.h>
#include <media/dvb_frontend.h>

+#define CLOCK_UNIT_90KHZ 90000
+
u32 vidtv_memcpy(void *to,
const void *from,
size_t len,
diff --git a/drivers/media/test-drivers/vidtv/vidtv_encoder.h b/drivers/media/test-drivers/vidtv/vidtv_encoder.h
new file mode 100644
index 0000000000000..f483200fd781c
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_encoder.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Vidtv serves as a reference DVB driver and helps validate the existing APIs
+ * in the media subsystem. It can also aid developers working on userspace
+ * applications.
+ *
+ * This file contains a generic encoder type that can provide data for a stream
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_ENCODER_H
+#define VIDTV_ENCODER_H
+
+#include <linux/types.h>
+
+enum vidtv_encoder_id {
+ /* add IDs here when implementing new encoders */
+ S302M,
+};
+
+struct vidtv_encoder {
+ /* so we can cast to a concrete implementation when needed */
+ enum vidtv_encoder_id id;
+ /* usually same as the stream name */
+ char *name;
+
+ /* the encoder's internal buffer for the access units */
+ u8 *encoder_buf;
+ /* the encoder buffer size, in bytes */
+ u32 encoder_buf_sz;
+
+ /* our byte position in the encoder buffer */
+ u32 encoder_buf_offset;
+
+ /* how many samples we have encoded in total */
+ u32 sample_count;
+
+ u32 previous_sample_count;
+
+ /* the number of access units ready */
+ u32 nunits;
+ /* the number of samples per access unit */
+ u32 *samples_per_unit;
+ /* pts array mapping pts[i] -> AU[i] */
+ u64 *pts;
+ /* dts array mapping dts[i] -> AU[i] */
+ u64 *dts;
+ /* array mapping how many bytes were written per AU */
+ u32 *nbytes;
+ /* array keeping track of AU offsets in buffer */
+ u32 *offsets;
+
+ /*
+ * the source of raw data to be encoded, encoder might set a default
+ * source if NULL
+ */
+ void *src_buf;
+ /* the source buffer size, in bytes */
+ u32 src_buf_sz;
+
+ /* our byte position in the src buffer */
+ u32 src_buf_offset;
+
+ bool video; /* either video or audio */
+ void *ctx; /* encoder-specific state */
+
+ /* Examples: Audio streams (0xc0-0xdf), Video streams (0xe0-0xef) */
+ u16 stream_id;
+
+ /* the TS PID to use for the elementary stream in this encoder */
+ u16 es_pid;
+
+ /* prepare enough AUs for the given amount of time */
+ void *(*encode)(struct vidtv_encoder *e, u64 elapsed_time_usecs);
+
+ /* clear the encoder output */
+ u8 (*clear)(struct vidtv_encoder *e);
+
+ /* attempt to synchronize with the encoder below */
+ struct vidtv_encoder *sync;
+
+ u32 sampling_rate_hz; /* or fps, if video */
+
+ /* controls dynamic memory allocation for the arrays */
+ u8 access_unit_capacity;
+
+ /*
+ * called when the encoder runs out of data
+ * the encoder might optionally wrap around the src data if this is not
+ * implemented
+ * this is so the source can read data in a piecemeal fashion instead
+ * of having to provide it all at once
+ */
+ void (*last_sample_cb)(u32 sample_no);
+
+ /* destroy this encoder, freeing any allocated resources */
+ void (*destroy)(struct vidtv_encoder *e);
+
+ struct vidtv_encoder *next;
+};
+
+#endif /* VIDTV_ENCODER_H */
diff --git a/drivers/media/test-drivers/vidtv/vidtv_s302m.c b/drivers/media/test-drivers/vidtv/vidtv_s302m.c
new file mode 100644
index 0000000000000..b08bfff7b8f27
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_s302m.c
@@ -0,0 +1,608 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Vidtv serves as a reference DVB driver and helps validate the existing APIs
+ * in the media subsystem. It can also aid developers working on userspace
+ * applications.
+ *
+ * This file contains the code for an AES3 (also known as AES/EBU) encoder.
+ * It is based on EBU Tech 3250 and SMPTE 302M technical documents.
+ *
+ * This encoder currently supports 16bit AES3 subframes using 16bit signed
+ * integers.
+ *
+ * Note: AU stands for Access Unit, and AAU stands for Audio Access Unit
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/crc32.h>
+#include <linux/vmalloc.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+
+#include "vidtv_s302m.h"
+#include "vidtv_encoder.h"
+#include "vidtv_common.h"
+
+#define S302M_SAMPLING_RATE_HZ 48000
+
+/* see EBU Tech 3250 2004 clause 4 */
+#define CHANNEL_STATUS_BIT_LEN 92
+
+#define S302M_2CHANNELS 0
+#define S302M_BITS_PER_SAMPLE_16 0
+
+/* see preamble definition in EBU Tech 3250 2004 clause 2.4 */
+#define PREAMBLE_X_F0 0xe2 /* sub-frame 1 */
+#define PREAMBLE_Y_F0 0xe4 /* sub-frame 2 */
+#define PREAMBLE_Z_F0 0xe8 /* sub-frame 1 + block start */
+/* F0 and F1 refer to whether the F bit was set in the previous sub-frame */
+#define PREAMBLE_X_F1 0x1d /* sub-frame 1 */
+#define PREAMBLE_Y_F1 0X1b /* sub-frame 2 */
+#define PREAMBLE_Z_F1 0x17 /* sub-frame 1 + block start */
+
+#define S302M_BLOCK_SZ 192
+#define S302M_SIN_LUT_SZ 1024
+
+static int s302m_sin_lut[S302M_SIN_LUT_SZ] = {
+ 0x8000, 0x80c9, 0x8192, 0x825b, 0x8324, 0x83ed, 0x84b6, 0x857e,
+ 0x8647, 0x8710, 0x87d9, 0x88a1, 0x896a, 0x8a32, 0x8afb, 0x8bc3,
+ 0x8c8b, 0x8d53, 0x8e1b, 0x8ee3, 0x8fab, 0x9072, 0x9139, 0x9201,
+ 0x92c7, 0x938e, 0x9455, 0x951b, 0x95e1, 0x96a7, 0x976d, 0x9833,
+ 0x98f8, 0x99bd, 0x9a82, 0x9b47, 0x9c0b, 0x9ccf, 0x9d93, 0x9e56,
+ 0x9f19, 0x9fdc, 0xa09f, 0xa161, 0xa223, 0xa2e5, 0xa3a6, 0xa467,
+ 0xa527, 0xa5e8, 0xa6a7, 0xa767, 0xa826, 0xa8e5, 0xa9a3, 0xaa61,
+ 0xab1f, 0xabdc, 0xac98, 0xad55, 0xae10, 0xaecc, 0xaf87, 0xb041,
+ 0xb0fb, 0xb1b5, 0xb26e, 0xb326, 0xb3de, 0xb496, 0xb54d, 0xb603,
+ 0xb6b9, 0xb76f, 0xb824, 0xb8d8, 0xb98c, 0xba3f, 0xbaf2, 0xbba4,
+ 0xbc56, 0xbd07, 0xbdb7, 0xbe67, 0xbf17, 0xbfc5, 0xc073, 0xc121,
+ 0xc1cd, 0xc279, 0xc325, 0xc3d0, 0xc47a, 0xc524, 0xc5cc, 0xc675,
+ 0xc71c, 0xc7c3, 0xc869, 0xc90f, 0xc9b3, 0xca57, 0xcafb, 0xcb9d,
+ 0xcc3f, 0xcce0, 0xcd81, 0xce20, 0xcebf, 0xcf5d, 0xcffb, 0xd097,
+ 0xd133, 0xd1ce, 0xd268, 0xd302, 0xd39a, 0xd432, 0xd4c9, 0xd55f,
+ 0xd5f5, 0xd689, 0xd71d, 0xd7b0, 0xd842, 0xd8d3, 0xd964, 0xd9f3,
+ 0xda82, 0xdb0f, 0xdb9c, 0xdc28, 0xdcb3, 0xdd3d, 0xddc7, 0xde4f,
+ 0xded7, 0xdf5d, 0xdfe3, 0xe068, 0xe0eb, 0xe16e, 0xe1f0, 0xe271,
+ 0xe2f1, 0xe370, 0xe3ee, 0xe46b, 0xe4e8, 0xe563, 0xe5dd, 0xe656,
+ 0xe6cf, 0xe746, 0xe7bc, 0xe831, 0xe8a6, 0xe919, 0xe98b, 0xe9fc,
+ 0xea6d, 0xeadc, 0xeb4a, 0xebb7, 0xec23, 0xec8e, 0xecf8, 0xed61,
+ 0xedc9, 0xee30, 0xee96, 0xeefa, 0xef5e, 0xefc1, 0xf022, 0xf083,
+ 0xf0e2, 0xf140, 0xf19d, 0xf1f9, 0xf254, 0xf2ae, 0xf307, 0xf35e,
+ 0xf3b5, 0xf40a, 0xf45f, 0xf4b2, 0xf504, 0xf555, 0xf5a5, 0xf5f3,
+ 0xf641, 0xf68d, 0xf6d8, 0xf722, 0xf76b, 0xf7b3, 0xf7fa, 0xf83f,
+ 0xf884, 0xf8c7, 0xf909, 0xf94a, 0xf989, 0xf9c8, 0xfa05, 0xfa41,
+ 0xfa7c, 0xfab6, 0xfaee, 0xfb26, 0xfb5c, 0xfb91, 0xfbc5, 0xfbf8,
+ 0xfc29, 0xfc59, 0xfc88, 0xfcb6, 0xfce3, 0xfd0e, 0xfd39, 0xfd62,
+ 0xfd89, 0xfdb0, 0xfdd5, 0xfdfa, 0xfe1d, 0xfe3e, 0xfe5f, 0xfe7e,
+ 0xfe9c, 0xfeb9, 0xfed5, 0xfeef, 0xff09, 0xff21, 0xff37, 0xff4d,
+ 0xff61, 0xff74, 0xff86, 0xff97, 0xffa6, 0xffb4, 0xffc1, 0xffcd,
+ 0xffd8, 0xffe1, 0xffe9, 0xfff0, 0xfff5, 0xfff9, 0xfffd, 0xfffe,
+ 0xffff, 0xfffe, 0xfffd, 0xfff9, 0xfff5, 0xfff0, 0xffe9, 0xffe1,
+ 0xffd8, 0xffcd, 0xffc1, 0xffb4, 0xffa6, 0xff97, 0xff86, 0xff74,
+ 0xff61, 0xff4d, 0xff37, 0xff21, 0xff09, 0xfeef, 0xfed5, 0xfeb9,
+ 0xfe9c, 0xfe7e, 0xfe5f, 0xfe3e, 0xfe1d, 0xfdfa, 0xfdd5, 0xfdb0,
+ 0xfd89, 0xfd62, 0xfd39, 0xfd0e, 0xfce3, 0xfcb6, 0xfc88, 0xfc59,
+ 0xfc29, 0xfbf8, 0xfbc5, 0xfb91, 0xfb5c, 0xfb26, 0xfaee, 0xfab6,
+ 0xfa7c, 0xfa41, 0xfa05, 0xf9c8, 0xf989, 0xf94a, 0xf909, 0xf8c7,
+ 0xf884, 0xf83f, 0xf7fa, 0xf7b3, 0xf76b, 0xf722, 0xf6d8, 0xf68d,
+ 0xf641, 0xf5f3, 0xf5a5, 0xf555, 0xf504, 0xf4b2, 0xf45f, 0xf40a,
+ 0xf3b5, 0xf35e, 0xf307, 0xf2ae, 0xf254, 0xf1f9, 0xf19d, 0xf140,
+ 0xf0e2, 0xf083, 0xf022, 0xefc1, 0xef5e, 0xeefa, 0xee96, 0xee30,
+ 0xedc9, 0xed61, 0xecf8, 0xec8e, 0xec23, 0xebb7, 0xeb4a, 0xeadc,
+ 0xea6d, 0xe9fc, 0xe98b, 0xe919, 0xe8a6, 0xe831, 0xe7bc, 0xe746,
+ 0xe6cf, 0xe656, 0xe5dd, 0xe563, 0xe4e8, 0xe46b, 0xe3ee, 0xe370,
+ 0xe2f1, 0xe271, 0xe1f0, 0xe16e, 0xe0eb, 0xe068, 0xdfe3, 0xdf5d,
+ 0xded7, 0xde4f, 0xddc7, 0xdd3d, 0xdcb3, 0xdc28, 0xdb9c, 0xdb0f,
+ 0xda82, 0xd9f3, 0xd964, 0xd8d3, 0xd842, 0xd7b0, 0xd71d, 0xd689,
+ 0xd5f5, 0xd55f, 0xd4c9, 0xd432, 0xd39a, 0xd302, 0xd268, 0xd1ce,
+ 0xd133, 0xd097, 0xcffb, 0xcf5d, 0xcebf, 0xce20, 0xcd81, 0xcce0,
+ 0xcc3f, 0xcb9d, 0xcafb, 0xca57, 0xc9b3, 0xc90f, 0xc869, 0xc7c3,
+ 0xc71c, 0xc675, 0xc5cc, 0xc524, 0xc47a, 0xc3d0, 0xc325, 0xc279,
+ 0xc1cd, 0xc121, 0xc073, 0xbfc5, 0xbf17, 0xbe67, 0xbdb7, 0xbd07,
+ 0xbc56, 0xbba4, 0xbaf2, 0xba3f, 0xb98c, 0xb8d8, 0xb824, 0xb76f,
+ 0xb6b9, 0xb603, 0xb54d, 0xb496, 0xb3de, 0xb326, 0xb26e, 0xb1b5,
+ 0xb0fb, 0xb041, 0xaf87, 0xaecc, 0xae10, 0xad55, 0xac98, 0xabdc,
+ 0xab1f, 0xaa61, 0xa9a3, 0xa8e5, 0xa826, 0xa767, 0xa6a7, 0xa5e8,
+ 0xa527, 0xa467, 0xa3a6, 0xa2e5, 0xa223, 0xa161, 0xa09f, 0x9fdc,
+ 0x9f19, 0x9e56, 0x9d93, 0x9ccf, 0x9c0b, 0x9b47, 0x9a82, 0x99bd,
+ 0x98f8, 0x9833, 0x976d, 0x96a7, 0x95e1, 0x951b, 0x9455, 0x938e,
+ 0x92c7, 0x9201, 0x9139, 0x9072, 0x8fab, 0x8ee3, 0x8e1b, 0x8d53,
+ 0x8c8b, 0x8bc3, 0x8afb, 0x8a32, 0x896a, 0x88a1, 0x87d9, 0x8710,
+ 0x8647, 0x857e, 0x84b6, 0x83ed, 0x8324, 0x825b, 0x8192, 0x80c9,
+ 0x8000, 0x7f36, 0x7e6d, 0x7da4, 0x7cdb, 0x7c12, 0x7b49, 0x7a81,
+ 0x79b8, 0x78ef, 0x7826, 0x775e, 0x7695, 0x75cd, 0x7504, 0x743c,
+ 0x7374, 0x72ac, 0x71e4, 0x711c, 0x7054, 0x6f8d, 0x6ec6, 0x6dfe,
+ 0x6d38, 0x6c71, 0x6baa, 0x6ae4, 0x6a1e, 0x6958, 0x6892, 0x67cc,
+ 0x6707, 0x6642, 0x657d, 0x64b8, 0x63f4, 0x6330, 0x626c, 0x61a9,
+ 0x60e6, 0x6023, 0x5f60, 0x5e9e, 0x5ddc, 0x5d1a, 0x5c59, 0x5b98,
+ 0x5ad8, 0x5a17, 0x5958, 0x5898, 0x57d9, 0x571a, 0x565c, 0x559e,
+ 0x54e0, 0x5423, 0x5367, 0x52aa, 0x51ef, 0x5133, 0x5078, 0x4fbe,
+ 0x4f04, 0x4e4a, 0x4d91, 0x4cd9, 0x4c21, 0x4b69, 0x4ab2, 0x49fc,
+ 0x4946, 0x4890, 0x47db, 0x4727, 0x4673, 0x45c0, 0x450d, 0x445b,
+ 0x43a9, 0x42f8, 0x4248, 0x4198, 0x40e8, 0x403a, 0x3f8c, 0x3ede,
+ 0x3e32, 0x3d86, 0x3cda, 0x3c2f, 0x3b85, 0x3adb, 0x3a33, 0x398a,
+ 0x38e3, 0x383c, 0x3796, 0x36f0, 0x364c, 0x35a8, 0x3504, 0x3462,
+ 0x33c0, 0x331f, 0x327e, 0x31df, 0x3140, 0x30a2, 0x3004, 0x2f68,
+ 0x2ecc, 0x2e31, 0x2d97, 0x2cfd, 0x2c65, 0x2bcd, 0x2b36, 0x2aa0,
+ 0x2a0a, 0x2976, 0x28e2, 0x284f, 0x27bd, 0x272c, 0x269b, 0x260c,
+ 0x257d, 0x24f0, 0x2463, 0x23d7, 0x234c, 0x22c2, 0x2238, 0x21b0,
+ 0x2128, 0x20a2, 0x201c, 0x1f97, 0x1f14, 0x1e91, 0x1e0f, 0x1d8e,
+ 0x1d0e, 0x1c8f, 0x1c11, 0x1b94, 0x1b17, 0x1a9c, 0x1a22, 0x19a9,
+ 0x1930, 0x18b9, 0x1843, 0x17ce, 0x1759, 0x16e6, 0x1674, 0x1603,
+ 0x1592, 0x1523, 0x14b5, 0x1448, 0x13dc, 0x1371, 0x1307, 0x129e,
+ 0x1236, 0x11cf, 0x1169, 0x1105, 0x10a1, 0x103e, 0xfdd, 0xf7c,
+ 0xf1d, 0xebf, 0xe62, 0xe06, 0xdab, 0xd51, 0xcf8, 0xca1,
+ 0xc4a, 0xbf5, 0xba0, 0xb4d, 0xafb, 0xaaa, 0xa5a, 0xa0c,
+ 0x9be, 0x972, 0x927, 0x8dd, 0x894, 0x84c, 0x805, 0x7c0,
+ 0x77b, 0x738, 0x6f6, 0x6b5, 0x676, 0x637, 0x5fa, 0x5be,
+ 0x583, 0x549, 0x511, 0x4d9, 0x4a3, 0x46e, 0x43a, 0x407,
+ 0x3d6, 0x3a6, 0x377, 0x349, 0x31c, 0x2f1, 0x2c6, 0x29d,
+ 0x276, 0x24f, 0x22a, 0x205, 0x1e2, 0x1c1, 0x1a0, 0x181,
+ 0x163, 0x146, 0x12a, 0x110, 0xf6, 0xde, 0xc8, 0xb2,
+ 0x9e, 0x8b, 0x79, 0x68, 0x59, 0x4b, 0x3e, 0x32,
+ 0x27, 0x1e, 0x16, 0xf, 0xa, 0x6, 0x2, 0x1,
+ 0x0, 0x1, 0x2, 0x6, 0xa, 0xf, 0x16, 0x1e,
+ 0x27, 0x32, 0x3e, 0x4b, 0x59, 0x68, 0x79, 0x8b,
+ 0x9e, 0xb2, 0xc8, 0xde, 0xf6, 0x110, 0x12a, 0x146,
+ 0x163, 0x181, 0x1a0, 0x1c1, 0x1e2, 0x205, 0x22a, 0x24f,
+ 0x276, 0x29d, 0x2c6, 0x2f1, 0x31c, 0x349, 0x377, 0x3a6,
+ 0x3d6, 0x407, 0x43a, 0x46e, 0x4a3, 0x4d9, 0x511, 0x549,
+ 0x583, 0x5be, 0x5fa, 0x637, 0x676, 0x6b5, 0x6f6, 0x738,
+ 0x77b, 0x7c0, 0x805, 0x84c, 0x894, 0x8dd, 0x927, 0x972,
+ 0x9be, 0xa0c, 0xa5a, 0xaaa, 0xafb, 0xb4d, 0xba0, 0xbf5,
+ 0xc4a, 0xca1, 0xcf8, 0xd51, 0xdab, 0xe06, 0xe62, 0xebf,
+ 0xf1d, 0xf7c, 0xfdd, 0x103e, 0x10a1, 0x1105, 0x1169, 0x11cf,
+ 0x1236, 0x129e, 0x1307, 0x1371, 0x13dc, 0x1448, 0x14b5, 0x1523,
+ 0x1592, 0x1603, 0x1674, 0x16e6, 0x1759, 0x17ce, 0x1843, 0x18b9,
+ 0x1930, 0x19a9, 0x1a22, 0x1a9c, 0x1b17, 0x1b94, 0x1c11, 0x1c8f,
+ 0x1d0e, 0x1d8e, 0x1e0f, 0x1e91, 0x1f14, 0x1f97, 0x201c, 0x20a2,
+ 0x2128, 0x21b0, 0x2238, 0x22c2, 0x234c, 0x23d7, 0x2463, 0x24f0,
+ 0x257d, 0x260c, 0x269b, 0x272c, 0x27bd, 0x284f, 0x28e2, 0x2976,
+ 0x2a0a, 0x2aa0, 0x2b36, 0x2bcd, 0x2c65, 0x2cfd, 0x2d97, 0x2e31,
+ 0x2ecc, 0x2f68, 0x3004, 0x30a2, 0x3140, 0x31df, 0x327e, 0x331f,
+ 0x33c0, 0x3462, 0x3504, 0x35a8, 0x364c, 0x36f0, 0x3796, 0x383c,
+ 0x38e3, 0x398a, 0x3a33, 0x3adb, 0x3b85, 0x3c2f, 0x3cda, 0x3d86,
+ 0x3e32, 0x3ede, 0x3f8c, 0x403a, 0x40e8, 0x4198, 0x4248, 0x42f8,
+ 0x43a9, 0x445b, 0x450d, 0x45c0, 0x4673, 0x4727, 0x47db, 0x4890,
+ 0x4946, 0x49fc, 0x4ab2, 0x4b69, 0x4c21, 0x4cd9, 0x4d91, 0x4e4a,
+ 0x4f04, 0x4fbe, 0x5078, 0x5133, 0x51ef, 0x52aa, 0x5367, 0x5423,
+ 0x54e0, 0x559e, 0x565c, 0x571a, 0x57d9, 0x5898, 0x5958, 0x5a17,
+ 0x5ad8, 0x5b98, 0x5c59, 0x5d1a, 0x5ddc, 0x5e9e, 0x5f60, 0x6023,
+ 0x60e6, 0x61a9, 0x626c, 0x6330, 0x63f4, 0x64b8, 0x657d, 0x6642,
+ 0x6707, 0x67cc, 0x6892, 0x6958, 0x6a1e, 0x6ae4, 0x6baa, 0x6c71,
+ 0x6d38, 0x6dfe, 0x6ec6, 0x6f8d, 0x7054, 0x711c, 0x71e4, 0x72ac,
+ 0x7374, 0x743c, 0x7504, 0x75cd, 0x7695, 0x775e, 0x7826, 0x78ef,
+ 0x79b8, 0x7a81, 0x7b49, 0x7c12, 0x7cdb, 0x7da4, 0x7e6d, 0x7f36
+};
+
+static void vidtv_s302m_compute_nunits(struct vidtv_encoder *e)
+{
+ /*
+ * match the amount of video units if we are trying to sync with a video
+ * encoder
+ */
+ if (e->sync && e->sync->video) {
+ e->nunits = e->sync->nunits;
+ return;
+ }
+
+ /* otherwise return enough samples for the timedelta in one AU */
+ e->nunits = 1;
+}
+
+static void
+vidtv_s302m_compute_sample_count_v(struct vidtv_encoder *e)
+{
+ /* compute sample count for VAU[i] in 'sync' */
+ u32 vau_duration_usecs;
+ u32 sample_duration_usecs;
+ u32 i;
+ u32 sample_count;
+ u32 s;
+
+ vau_duration_usecs = USEC_PER_SEC / e->sync->sampling_rate_hz;
+ sample_duration_usecs = USEC_PER_SEC / e->sampling_rate_hz;
+
+ for (i = 0; i < e->sync->nunits; ++i) {
+ sample_count = e->samples_per_unit[i];
+ s = DIV_ROUND_UP(vau_duration_usecs, sample_duration_usecs);
+ e->samples_per_unit[i] = s;
+ }
+}
+
+static void
+vidtv_s302m_compute_sample_count(struct vidtv_encoder *e,
+ u64 elapsed_time_usecs)
+{
+ /* compute sample count for 'elapsed_time_usecs' */
+ u32 sample_duration_usecs = USEC_PER_SEC / e->sampling_rate_hz;
+
+ e->samples_per_unit[0] = elapsed_time_usecs / sample_duration_usecs;
+}
+
+static void vidtv_s302m_compute_pts(struct vidtv_encoder *e)
+{
+ u32 count = e->previous_sample_count;
+ u32 i;
+
+ for (i = 0; i < e->nunits; ++i) {
+ count += e->samples_per_unit[i];
+
+ e->pts[i] = count *
+ CLOCK_UNIT_90KHZ / e->sampling_rate_hz;
+ }
+}
+
+static void vidtv_s302m_compute_pts_v(struct vidtv_encoder *e)
+{
+ u32 i;
+
+ /* use the same pts for video */
+ for (i = 0; i < e->sync->nunits; ++i)
+ e->pts[i] = e->sync->pts[i];
+}
+
+static bool vidtv_s302m_get_c_bit(struct vidtv_encoder *e)
+{
+ /*
+ * see EBU Tech 3250 2004 clause 5.2.1: minimal implementation of
+ * channel status
+ */
+ struct vidtv_s302m_ctx *ctx = e->ctx;
+ bool start_was_z;
+
+ start_was_z = ctx->last_start_preamble == PREAMBLE_Z_F0 ||
+ ctx->last_start_preamble == PREAMBLE_Z_F1;
+
+ if (!start_was_z)
+ return false;
+
+ ++ctx->current_c_bit;
+
+ /* set the bit only if it is the first C bit after a Z preamble */
+ return !(ctx->current_c_bit % CHANNEL_STATUS_BIT_LEN);
+}
+
+static s16 vidtv_s302m_get_sample(struct vidtv_encoder *e)
+{
+ s16 ret;
+
+ /* bug somewhere */
+ WARN_ON(e->src_buf_offset > e->src_buf_sz);
+
+ if (e->src_buf_offset >= e->src_buf_sz) {
+ /* let the source know we are out of data */
+ if (e->last_sample_cb)
+ e->last_sample_cb(e->sample_count);
+
+ e->src_buf_offset = 0;
+ }
+
+ ret = *(s16 *)(e->src_buf + e->src_buf_offset);
+
+ e->sample_count++;
+ e->src_buf_offset += sizeof(s16);
+
+ return ret;
+}
+
+static void vidtv_s302m_toggle_subframe(struct vidtv_encoder *e)
+{
+ struct vidtv_s302m_ctx *ctx = e->ctx;
+
+ ctx->is_subframe_a = !ctx->is_subframe_a;
+}
+
+static bool vidtv_s302m_is_block_start(const struct vidtv_encoder *e)
+{
+ return e->sample_count % S302M_BLOCK_SZ;
+}
+
+static bool vidtv_s302m_get_f_bit(const struct vidtv_encoder *e)
+{
+ const struct vidtv_s302m_ctx *ctx = e->ctx;
+
+ return vidtv_s302m_is_block_start(e) && ctx->is_subframe_a;
+}
+
+static u8 vidtv_s302m_get_preamble(struct vidtv_encoder *e)
+{
+ /*
+ * some codecs might disregard the preambles (e.g. ffmpeg s302m), but
+ * we implement them according to the specs anyway, because some other
+ * codecs might rely on them.
+ */
+
+ struct vidtv_s302m_ctx *ctx = e->ctx;
+
+ bool start_was_x = ctx->last_start_preamble == PREAMBLE_X_F0 ||
+ ctx->last_start_preamble == PREAMBLE_X_F1;
+
+ bool start_was_z = ctx->last_start_preamble == PREAMBLE_Z_F0 ||
+ ctx->last_start_preamble == PREAMBLE_Z_F1;
+
+ bool is_block_start = vidtv_s302m_is_block_start(e);
+
+ /* bug somewhere: a block always starts with an A subframe */
+ WARN_ON(!ctx->is_subframe_a && is_block_start);
+
+ if (!ctx->is_subframe_a)
+ return (ctx->last_f) ? PREAMBLE_Y_F1 : PREAMBLE_Y_F0;
+
+ if (start_was_x && is_block_start) {
+ if (ctx->last_f) {
+ ctx->last_start_preamble = PREAMBLE_Z_F0;
+ return PREAMBLE_Z_F0;
+ }
+
+ ctx->last_start_preamble = PREAMBLE_Z_F1;
+ return PREAMBLE_Z_F1;
+ }
+
+ if (start_was_z && is_block_start) {
+ if (ctx->last_f) {
+ ctx->last_start_preamble = PREAMBLE_X_F0;
+ return PREAMBLE_X_F0;
+ }
+
+ ctx->last_start_preamble = PREAMBLE_X_F1;
+ return PREAMBLE_X_F1;
+ }
+
+ return PREAMBLE_X_F0;
+}
+
+static u32 vidtv_s302m_write_subframe(struct vidtv_encoder *e,
+ struct vidtv_s302m_subframe_16 *f)
+{
+ u32 nbytes = 0;
+
+ nbytes += vidtv_memcpy(e->encoder_buf + e->encoder_buf_offset,
+ f,
+ sizeof(*f),
+ e->encoder_buf_offset,
+ VIDTV_S302M_BUF_SZ);
+
+ e->encoder_buf_offset += nbytes;
+
+ return nbytes;
+}
+
+static void vidtv_s302m_write_h(struct vidtv_encoder *e, u32 p_sz)
+{
+ struct vidtv_smpte_s302m_es h = {0};
+ u32 nbytes = 0;
+
+ h.bits_per_sample = S302M_BITS_PER_SAMPLE_16;
+ h.channel_identification = 0;
+ h.num_channels = S302M_2CHANNELS;
+ h.audio_packet_size = p_sz;
+
+ cpu_to_be32s(&h.bitfield);
+
+ nbytes += vidtv_memcpy(e->encoder_buf + e->encoder_buf_offset,
+ &h,
+ sizeof(h),
+ e->encoder_buf_offset,
+ e->encoder_buf_sz);
+
+ be32_to_cpus(&h.bitfield);
+
+ e->encoder_buf_offset += nbytes;
+}
+
+static void vidtv_s302m_write_frames(struct vidtv_encoder *e)
+{
+ u32 nbytes = 0;
+ u32 nbytes_per_unit = 0;
+ u32 preamble_bit_num = 0;
+ const u8 preamble_bit_count = 4;
+ u32 au_sz = 0;
+ struct vidtv_s302m_ctx *ctx = e->ctx;
+ struct vidtv_s302m_subframe_16 a = {0};
+ struct vidtv_s302m_subframe_16 b = {0};
+ u8 preamble_a = 0;
+ u8 preamble_b = 0;
+ u8 aux = 0;
+ s16 sample = 0;
+ bool v = true;
+ bool u = false;
+ bool c;
+ bool f;
+
+ u32 i;
+ u32 j;
+
+ ctx->is_subframe_a = true;
+
+ for (i = 0; i < e->nunits; ++i) {
+ /* stereo: each sample will generate two subframes */
+ au_sz = e->samples_per_unit[i] *
+ sizeof(struct vidtv_s302m_subframe_16) *
+ 2;
+
+ vidtv_s302m_write_h(e, au_sz);
+
+ for (j = 0; j < e->samples_per_unit[i]; ++j) {
+ /* keep this in this order */
+ preamble_a = vidtv_s302m_get_preamble(e);
+ sample = cpu_to_le16(vidtv_s302m_get_sample(e));
+ c = vidtv_s302m_get_c_bit(e);
+ f = vidtv_s302m_get_f_bit(e);
+
+ a.preamble = vidtv_extract_bits(preamble_a,
+ preamble_bit_num % 8,
+ preamble_bit_count);
+ a.aux = aux;
+ a.data_word = sample;
+ a.v = v;
+ a.u = u;
+ a.c = c;
+ a.f = f;
+
+ vidtv_s302m_toggle_subframe(e);
+
+ preamble_b = vidtv_s302m_get_preamble(e);
+ c = vidtv_s302m_get_c_bit(e);
+ f = vidtv_s302m_get_f_bit(e);
+
+ b.preamble = vidtv_extract_bits(preamble_b,
+ preamble_bit_num % 8,
+ preamble_bit_count);
+ b.aux = aux;
+ b.data_word = sample;
+ b.v = v;
+ b.u = u;
+ b.c = c;
+ b.f = f;
+
+ preamble_bit_num += preamble_bit_count;
+
+ nbytes_per_unit += vidtv_s302m_write_subframe(e, &a);
+ nbytes_per_unit += vidtv_s302m_write_subframe(e, &b);
+
+ nbytes += nbytes_per_unit;
+
+ vidtv_s302m_toggle_subframe(e);
+ }
+
+ e->nbytes[i] = nbytes;
+
+ /* did we write more bytes than we initially computed? */
+ WARN_ON(au_sz != nbytes_per_unit);
+
+ e->offsets[i] = nbytes_per_unit;
+ nbytes_per_unit = 0;
+ }
+}
+
+static void *vidtv_s302m_encode(struct vidtv_encoder *e, u64 elapsed_time)
+{
+ /*
+ * According to SMPTE 302M, an audio access unit is specified as those
+ * AES3 words that are associated with a corresponding video frame.
+ * Therefore we should write one AAU for every VAU in the corresponding
+ * video encoder ('sync'), using the same values for PTS as used by the
+ * video encoder.
+ *
+ * I assume that it is also possible to send audio without any
+ * associated video, as in a radio-like service. If this is the case,
+ * we are sending only _one_ AAU with enough audio data for
+ * 'elapsed_time' instead, computing the value for PTS manually.
+ */
+
+ vidtv_s302m_compute_nunits(e);
+
+ if (e->sync && e->sync->video) {
+ vidtv_s302m_compute_sample_count_v(e);
+ vidtv_s302m_compute_pts_v(e);
+ } else {
+ vidtv_s302m_compute_sample_count(e, elapsed_time);
+ vidtv_s302m_compute_pts(e);
+ }
+
+ vidtv_s302m_write_frames(e);
+
+ return e->encoder_buf;
+}
+
+static u8 vidtv_s302m_clear(struct vidtv_encoder *e)
+{
+ u8 ret = e->nunits;
+
+ e->nunits = 0;
+ memset(e->samples_per_unit, 0, e->access_unit_capacity);
+ memset(e->nbytes, 0, e->access_unit_capacity);
+ memset(e->offsets, 0, e->access_unit_capacity);
+ memset(e->pts, 0, e->access_unit_capacity);
+ memset(e->dts, 0, e->access_unit_capacity);
+
+ return ret;
+}
+
+struct vidtv_encoder
+*vidtv_s302m_encoder_init(struct vidtv_s302m_encoder_init_args args)
+{
+ struct vidtv_encoder *e = kzalloc(sizeof(e), GFP_KERNEL);
+ u32 priv_sz = sizeof(struct vidtv_s302m_ctx);
+
+ if (args.sync)
+ args.access_unit_capacity = args.sync->access_unit_capacity;
+
+ e->id = S302M;
+
+ e->name = kzalloc(strlen(args.name), GFP_KERNEL);
+ strncpy(e->name, args.name, strlen(e->name));
+
+ e->encoder_buf = vzalloc(VIDTV_S302M_BUF_SZ);
+ e->encoder_buf_sz = VIDTV_S302M_BUF_SZ;
+ e->encoder_buf_offset = 0;
+
+ e->sample_count = 0;
+ e->previous_sample_count = 0;
+ e->nunits = 0;
+
+ e->samples_per_unit = kcalloc(args.access_unit_capacity,
+ sizeof(u32),
+ GFP_KERNEL);
+
+ e->pts = kcalloc(args.access_unit_capacity, sizeof(u64), GFP_KERNEL);
+ e->dts = kcalloc(args.access_unit_capacity, sizeof(u64), GFP_KERNEL);
+
+ e->nbytes = kcalloc(args.access_unit_capacity,
+ sizeof(u32),
+ GFP_KERNEL);
+
+ e->offsets = kcalloc(args.access_unit_capacity,
+ sizeof(u32),
+ GFP_KERNEL);
+
+ e->src_buf = (args.src_buf) ? args.src_buf : &s302m_sin_lut;
+ e->src_buf_sz = (args.src_buf) ? args.src_buf_sz : S302M_SIN_LUT_SZ;
+ e->src_buf_offset = 0;
+
+ e->video = false;
+ e->ctx = kzalloc(priv_sz, GFP_KERNEL);
+
+ e->encode = vidtv_s302m_encode;
+ e->clear = vidtv_s302m_clear;
+
+ e->es_pid = args.es_pid;
+
+ e->sync = args.sync;
+ e->sampling_rate_hz = S302M_SAMPLING_RATE_HZ;
+ e->access_unit_capacity = args.access_unit_capacity;
+
+ /* we will wrap around 'src' if this is NULL */
+ e->last_sample_cb = args.last_sample_cb;
+
+ e->destroy = vidtv_s302m_encoder_destroy;
+
+ if (args.head) {
+ while (args.head->next)
+ args.head = args.head->next;
+
+ args.head->next = e;
+ }
+
+ e->next = NULL;
+
+ return e;
+}
+
+void vidtv_s302m_encoder_destroy(struct vidtv_encoder *e)
+{
+ WARN_ON(e->id != S302M);
+ kfree(e->name);
+ kfree(e->encoder_buf);
+ kfree(e->samples_per_unit);
+ kfree(e->pts);
+ kfree(e->dts);
+ kfree(e->nbytes);
+ kfree(e->offsets);
+ kfree(e->ctx);
+ kfree(e);
+}
diff --git a/drivers/media/test-drivers/vidtv/vidtv_s302m.h b/drivers/media/test-drivers/vidtv/vidtv_s302m.h
new file mode 100644
index 0000000000000..bfb8b0c80eddf
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_s302m.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Vidtv serves as a reference DVB driver and helps validate the existing APIs
+ * in the media subsystem. It can also aid developers working on userspace
+ * applications.
+ *
+ * This file contains the code for an AES3 (also known as AES/EBU) encoder.
+ * It is based on EBU Tech 3250 and SMPTE 302M technical documents.
+ *
+ * This encoder currently supports 16bit AES3 subframes using 16bit signed
+ * integers.
+ *
+ * Note: AU stands for Access Unit, and AAU stands for Audio Access Unit
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_S302M_H
+#define VIDTV_S302M_H
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+#include "vidtv_encoder.h"
+
+/* see SMPTE 302M 2007 clause 7.3 */
+#define VIDTV_S302M_BUF_SZ 65024
+
+/* see ETSI TS 102 154 v.1.2.1 clause 7.3.5 */
+#define VIDTV_S302M_FORMAT_IDENTIFIER 0x42535344
+
+struct vidtv_s302m_ctx {
+ struct vidtv_encoder *enc;
+
+ /* whether the F bit was set for the last sub-frame */
+ bool last_f;
+ /* either X or Z */
+ u8 last_start_preamble;
+ /* are we writing an A subframe now? */
+ bool is_subframe_a;
+ /* we send a bit per subframe */
+ u8 current_c_bit;
+};
+
+struct vidtv_smpte_s302m_es {
+ union {
+ u32 bitfield;
+ struct {
+ u16 audio_packet_size:16;
+ u16 num_channels:2;
+ u16 channel_identification:8;
+ u16 bits_per_sample:2; /* 0x0 for 16bits */
+ u16 zero:4;
+ } __packed;
+ } __packed;
+} __packed;
+
+struct vidtv_s302m_subframe_16 {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 f:1;
+ u8 c:1;
+ u8 u:1;
+ u8 v:1;
+ u16 data_word:16; /* little endian, use cpu_to_le before writing */
+ u8 zero:4;
+ u8 aux:4;
+ u8 preamble:4;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 preamble:4;
+ u8 aux:4;
+ u8 zero:4;
+ u16 data_word:16; /* little endian */
+ u8 v:1;
+ u8 u:1;
+ u8 c:1;
+ u8 f:1;
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+}__packed;
+
+struct vidtv_s302m_encoder_init_args {
+ char *name;
+ void *src_buf;
+ u32 src_buf_sz;
+ u16 es_pid;
+ struct vidtv_encoder *sync;
+ u8 access_unit_capacity;
+ void (*last_sample_cb)(u32 sample_no);
+
+ /* optionally chain to this encoder */
+ struct vidtv_encoder *head;
+};
+
+struct vidtv_encoder
+*vidtv_s302m_encoder_init(struct vidtv_s302m_encoder_init_args args);
+
+void vidtv_s302m_encoder_destroy(struct vidtv_encoder *encoder);
+
+#endif /* VIDTV_S302M_H */
--
2.26.2

2020-05-02 03:26:47

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 09/11] media: vidtv: implement a PES packetizer

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

Implement the PES logic to convert encoder data into MPEG TS packets.
These TS packets can then be fed into a TS multiplexer and eventually
into userspace.

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test-drivers/vidtv/Makefile | 2 +-
.../media/test-drivers/vidtv/vidtv_common.c | 7 +
.../media/test-drivers/vidtv/vidtv_common.h | 2 +
drivers/media/test-drivers/vidtv/vidtv_pes.c | 429 ++++++++++++++++++
drivers/media/test-drivers/vidtv/vidtv_pes.h | 185 ++++++++
5 files changed, 624 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_pes.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_pes.h

diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
index e4f744aa53136..e3a6540f50e87 100644
--- a/drivers/media/test-drivers/vidtv/Makefile
+++ b/drivers/media/test-drivers/vidtv/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0

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

obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.c b/drivers/media/test-drivers/vidtv/vidtv_common.c
index 28f10630499a9..b1412b497e1e3 100644
--- a/drivers/media/test-drivers/vidtv/vidtv_common.c
+++ b/drivers/media/test-drivers/vidtv/vidtv_common.c
@@ -42,3 +42,10 @@ u32 vidtv_memset(void *to,
memset(to, c, len);
return len;
}
+
+u64 vidtv_extract_bits(u64 value, u8 start_bit, u8 nbits)
+{
+ u64 mask = ((1 << nbits) - 1) << start_bit;
+
+ return value & mask;
+}
diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.h b/drivers/media/test-drivers/vidtv/vidtv_common.h
index 64072c010dc66..3b68f95c5f6c8 100644
--- a/drivers/media/test-drivers/vidtv/vidtv_common.h
+++ b/drivers/media/test-drivers/vidtv/vidtv_common.h
@@ -25,4 +25,6 @@ u32 vidtv_memset(void *to,
u32 offset,
u32 buf_sz);

+u64 vidtv_extract_bits(u64 value, u8 start_bit, u8 nbits);
+
#endif // VIDTV_COMMON_H
diff --git a/drivers/media/test-drivers/vidtv/vidtv_pes.c b/drivers/media/test-drivers/vidtv/vidtv_pes.c
new file mode 100644
index 0000000000000..bc631bac07778
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_pes.c
@@ -0,0 +1,429 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Vidtv serves as a reference DVB driver and helps validate the existing APIs
+ * in the media subsystem. It can also aid developers working on userspace
+ * applications.
+ *
+ * This file contains the logic to translate the ES data for one access unit
+ * from an encoder into MPEG TS packets. It does so by first encapsulating it
+ * with a PES header and then splitting it into TS packets.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#include <linux/types.h>
+#include <linux/printk.h>
+#include "vidtv_pes.h"
+#include "vidtv_common.h"
+#include "vidtv_ts.h"
+
+#define PRIVATE_STREAM_1_ID 0xbd /* private_stream_1. See SMPTE 302M-2007 p.6 */
+#define PES_HEADER_MAX_STUFFING_BYTES 32
+#define PES_TS_HEADER_MAX_STUFFING_BYTES 182
+
+static u32 vidtv_pes_op_get_regular_len(bool send_pts, bool send_dts)
+{
+ u32 len = 0;
+
+ /* the flags must always be sent */
+ len += sizeof(struct vidtv_pes_optional);
+
+ /* From all optionals, we might send these for now */
+ if (send_pts && send_dts)
+ len += sizeof(struct vidtv_pes_optional_pts_dts);
+ else if (send_pts)
+ len += sizeof(struct vidtv_pes_optional_pts);
+
+ return len;
+}
+
+static u32 vidtv_pes_h_get_regular_len(bool send_pts, bool send_dts)
+{
+ /* PES header length notwithstanding stuffing bytes */
+ u32 len = 0;
+
+ len += sizeof(struct vidtv_mpeg_pes);
+ len += vidtv_pes_op_get_regular_len(send_pts, send_dts);
+
+ return len;
+}
+
+static u32 vidtv_pes_write_header_stuffing(struct vidtv_mpeg_pes *pes_h,
+ struct pes_header_write_args args)
+{
+ /*
+ * This is a fixed 8-bit value equal to '1111 1111' that can be inserted
+ * by the encoder, for example to meet the requirements of the channel.
+ * It is discarded by the decoder. No more than 32 stuffing bytes shall
+ * be present in one PES packet header.
+ */
+
+ WARN_ON(args.n_pes_h_s_bytes > PES_HEADER_MAX_STUFFING_BYTES);
+
+ if (args.n_pes_h_s_bytes > PES_HEADER_MAX_STUFFING_BYTES)
+ args.n_pes_h_s_bytes = PES_HEADER_MAX_STUFFING_BYTES;
+
+ /* gives the length of the remainder of the PES header in bytes */
+ pes_h->length += args.n_pes_h_s_bytes;
+
+ return vidtv_memset(args.dest_buf + args.dest_offset,
+ 0xff,
+ args.n_pes_h_s_bytes,
+ args.dest_offset,
+ args.dest_buf_sz);
+}
+
+static u32 vidtv_pes_write_pts_dts(struct pes_header_write_args args)
+{
+ u32 nbytes = 0; /* the number of bytes written by this function */
+
+ struct vidtv_pes_optional_pts pts = {0};
+ struct vidtv_pes_optional_pts_dts pts_dts = {0};
+ void *op = NULL;
+ size_t op_sz = 0;
+
+ if (!args.send_pts && args.send_dts)
+ return 0;
+
+ /* see ISO/IEC 13818-1 : 2000 p. 32 */
+
+ if (args.send_pts && args.send_dts) {
+ cpu_to_be64s(&args.pts);
+ cpu_to_be64s(&args.dts);
+ pts_dts.three = 0x3;
+
+ pts_dts.pts1 = vidtv_extract_bits(args.pts, 30, 3);
+ pts_dts.marker1 = 0x1;
+ pts_dts.pts2 = vidtv_extract_bits(args.pts, 15, 15);
+ pts_dts.marker2 = 0x1;
+ pts_dts.pts3 = vidtv_extract_bits(args.pts, 0, 15);
+ pts_dts.marker3 = 0x1;
+
+ pts_dts.one = 0x1;
+
+ pts_dts.dts1 = vidtv_extract_bits(args.dts, 30, 3);
+ pts_dts.marker1 = 0x1;
+ pts_dts.dts2 = vidtv_extract_bits(args.dts, 15, 15);
+ pts_dts.marker2 = 0x1;
+ pts_dts.dts3 = vidtv_extract_bits(args.dts, 0, 15);
+ pts_dts.marker3 = 0x1;
+
+ be64_to_cpus(&args.pts);
+ be64_to_cpus(&args.dts);
+
+ op = &pts_dts;
+ op_sz = sizeof(pts_dts);
+
+ } else if (args.send_pts) {
+ cpu_to_be64s(&args.pts);
+ pts.two = 0x2;
+ pts.pts1 = vidtv_extract_bits(args.pts, 30, 3);
+ pts.marker1 = 0x1;
+ pts.pts2 = vidtv_extract_bits(args.pts, 15, 15);
+ pts.marker2 = 0x1;
+ pts.pts3 = vidtv_extract_bits(args.pts, 0, 15);
+ pts.marker3 = 0x1;
+ be64_to_cpus(&args.pts);
+
+ op = &pts;
+ op_sz = sizeof(pts);
+ }
+
+ /* copy PTS/DTS optional */
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ op,
+ op_sz,
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ return nbytes;
+}
+
+static u32 vidtv_pes_write_h(struct pes_header_write_args args)
+{
+ u32 nbytes = 0; /* the number of bytes written by this function */
+
+ struct vidtv_mpeg_pes pes_header = {0};
+ struct vidtv_pes_optional pes_optional = {0};
+ struct pes_header_write_args pts_dts_args = args;
+
+ pes_header.packet_start_code_prefix = PES_START_CODE_PREFIX;
+
+ pes_header.stream_id = (args.is_s302m_pkt) ?
+ PRIVATE_STREAM_1_ID :
+ args.stream_id;
+
+ pes_header.length = vidtv_pes_op_get_regular_len(args.send_pts,
+ args.send_dts);
+
+ pes_optional.two = 0x2;
+
+ pes_optional.PTS_DTS = (args.send_pts && args.send_dts) ?
+ 0x3 :
+ (args.send_pts) ?
+ 0x2 :
+ 0x0;
+
+ /* copy header */
+ cpu_to_be32s(&pes_header.bitfield);
+ cpu_to_be16s(&pes_header.length);
+
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ &pes_header,
+ sizeof(pes_header),
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ be32_to_cpus(&pes_header.bitfield);
+ be16_to_cpus(&pes_header.length);
+
+ /* copy optional header */
+ cpu_to_be16s(&pes_optional.bitfield);
+
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ &pes_optional,
+ sizeof(pes_optional),
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ be16_to_cpus(&pes_optional.bitfield);
+
+ pts_dts_args.dest_offset = args.dest_offset + nbytes;
+ nbytes += vidtv_pes_write_pts_dts(pts_dts_args);
+
+ /* write any PES header stuffing */
+ nbytes += vidtv_pes_write_header_stuffing(&pes_header, args);
+
+ return nbytes;
+}
+
+static u32 vidtv_pes_write_stuffing(struct vidtv_mpeg_ts *ts_h,
+ void *dest_buf,
+ u32 dest_offset,
+ u32 n_stuffing_bytes,
+ u32 buf_sz)
+{
+ /*
+ * For Transport Stream packets carrying PES packets, stuffing is
+ * needed when there is insufficient PES packet data to completely
+ * fill the Transport Stream packet payload bytes. Stuffing is
+ * accomplished by defining an adaptation field longer than the sum of
+ * the lengths of the data elements in it, so that the payload bytes
+ * remaining after the adaptation field exactly accommodates the
+ * available PES packet data. The extra space in the adaptation field
+ * is filled with stuffing bytes.
+ *
+ */
+
+ /* the number of bytes written by this function */
+ u32 nbytes = 0;
+ struct vidtv_mpeg_ts_adaption ts_adap = {0};
+
+ if (!n_stuffing_bytes)
+ return nbytes;
+
+ ts_h->adaptation_field = 1;
+
+ WARN_ON(n_stuffing_bytes > PES_TS_HEADER_MAX_STUFFING_BYTES);
+
+ if (n_stuffing_bytes > PES_TS_HEADER_MAX_STUFFING_BYTES)
+ n_stuffing_bytes = PES_TS_HEADER_MAX_STUFFING_BYTES;
+
+ /* the AF will only be its 'length' field with a value of zero */
+ if (n_stuffing_bytes == 1) {
+ nbytes += vidtv_memset(dest_buf + dest_offset + nbytes,
+ 0,
+ n_stuffing_bytes,
+ dest_offset + nbytes,
+ buf_sz);
+
+ return nbytes;
+ }
+
+ n_stuffing_bytes -= sizeof(ts_adap);
+
+ /* length _immediately_ following 'adaptation_field_length' */
+ ts_adap.length = sizeof(ts_adap) -
+ sizeof(ts_adap.length) +
+ n_stuffing_bytes;
+
+ /* write the adap after the TS header */
+ nbytes += vidtv_memcpy(dest_buf + dest_offset + nbytes,
+ &ts_adap,
+ sizeof(ts_adap),
+ dest_offset + nbytes,
+ buf_sz);
+
+ /* write the stuffing bytes */
+ nbytes += vidtv_memset(dest_buf + dest_offset + nbytes,
+ 0xff,
+ n_stuffing_bytes,
+ dest_offset + nbytes,
+ buf_sz);
+
+ return nbytes;
+}
+
+static u32 vidtv_pes_write_ts_h(struct pes_ts_header_write_args args)
+{
+ /* number of bytes written by this function */
+ u32 nbytes = 0;
+ struct vidtv_mpeg_ts ts_header = {0};
+
+ ts_header.sync_byte = TS_SYNC_BYTE;
+ ts_header.tei = 0;
+ ts_header.pid = args.pid;
+ ts_header.priority = 0;
+ ts_header.scrambling = 0; /* not scrambled */
+ ts_header.adaptation_field = 0;
+ ts_header.payload = 1;
+
+ ts_header.payload_start = (!args.wrote_pes_header) ? 1 : 0;
+ ts_header.continuity_counter = *args.continuity_counter;
+
+ vidtv_ts_inc_cc(args.continuity_counter);
+
+ cpu_to_be16s(&ts_header.bitfield);
+
+ /* write the TS header */
+ nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
+ &ts_header,
+ sizeof(ts_header),
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ be16_to_cpus(&ts_header.bitfield);
+ /* write stuffing, if any */
+ nbytes += vidtv_pes_write_stuffing(&ts_header,
+ args.dest_buf,
+ args.dest_offset + nbytes,
+ args.n_stuffing_bytes,
+ args.dest_buf_sz);
+
+ return nbytes;
+}
+
+u32 vidtv_pes_write_into(struct pes_write_args args)
+{
+ u32 nbytes_past_boundary = (args.dest_offset % TS_PACKET_LEN);
+ bool aligned = (nbytes_past_boundary == 0);
+
+ struct pes_ts_header_write_args ts_header_args = {0};
+ struct pes_header_write_args pes_header_args = {0};
+
+ /* number of bytes written by this function */
+ u32 nbytes = 0;
+ u32 remaining_len = args.access_unit_len;
+
+ bool wrote_pes_header = false;
+ bool stuff = false;
+
+ u32 available_space = 0;
+ u32 payload_write_len = 0;
+ u32 num_stuffing_bytes = 0;
+
+ /* Just a sanity check, should not really happen because we stuff the
+ * TS packet when we finish writing the PES data, but if this happens
+ * then we have messed up the logic somewhere.
+ *
+ * Also note that, unlike packets for PSI data, we need to carry PES
+ * packets aligned with the payload of transport packets, that is the
+ * first byte of each PES header must be the first byte in the payload
+ * of a transport packet. As a consequence, the last byte of a PES
+ * packet must be the last byte of the payload of a transport packet.
+ */
+ WARN_ON(!aligned);
+
+ if (args.send_dts && !args.send_pts) {
+ pr_warn("%s: forbidden value '01' for PTS_DTS flags", __func__);
+ args.send_pts = true;
+ args.pts = args.dts;
+ }
+
+ /* see SMPTE 302M clause 6.4 */
+ if (args.is_s302m_pkt) {
+ args.send_dts = false;
+ args.send_pts = true;
+ }
+
+ while (remaining_len) {
+ /*
+ * The amount of space initially available in the TS packet.
+ * if this is the beginning of the PES packet, we need to
+ * take into account the space needed for the TS header _and_
+ * for the PES header
+ */
+ available_space = (!wrote_pes_header) ?
+ TS_PAYLOAD_LEN -
+ vidtv_pes_h_get_regular_len(args.send_pts,
+ args.send_dts) :
+ TS_PAYLOAD_LEN;
+
+ /* if the encoder has inserted stuffing bytes in the PES
+ * header, account for them.
+ */
+ available_space -= args.n_pes_h_s_bytes;
+
+ /* whether we need to stuff the TS packet to align the buffer */
+ stuff = remaining_len < available_space;
+
+ /*
+ * how much of the _actual_ payload we should write in this
+ * packet.
+ */
+ payload_write_len = (stuff) ?
+ remaining_len :
+ available_space;
+
+ num_stuffing_bytes = available_space - payload_write_len;
+
+ /* write ts header */
+ ts_header_args.dest_buf = args.dest_buf;
+ ts_header_args.dest_offset = args.dest_offset + nbytes;
+ ts_header_args.dest_buf_sz = args.dest_buf_sz;
+ ts_header_args.pid = args.pid;
+ ts_header_args.continuity_counter = args.continuity_counter;
+ ts_header_args.wrote_pes_header = wrote_pes_header;
+ ts_header_args.n_stuffing_bytes = num_stuffing_bytes;
+
+ nbytes += vidtv_pes_write_ts_h(ts_header_args);
+
+ if (!wrote_pes_header) {
+ /* write the PES header only once */
+ pes_header_args.dest_buf = args.dest_buf;
+
+ pes_header_args.dest_offset = args.dest_offset +
+ nbytes;
+
+ pes_header_args.dest_buf_sz = args.dest_buf_sz;
+ pes_header_args.is_s302m_pkt = args.is_s302m_pkt;
+ pes_header_args.send_pts = args.send_pts;
+ pes_header_args.pts = args.pts;
+ pes_header_args.send_dts = args.send_dts;
+ pes_header_args.dts = args.dts;
+ pes_header_args.stream_id = args.stream_id;
+ pes_header_args.n_pes_h_s_bytes = args.n_pes_h_s_bytes;
+
+ nbytes += vidtv_pes_write_h(pes_header_args);
+ wrote_pes_header = true;
+ }
+
+ /* write as much of the payload as we possibly can */
+ nbytes += vidtv_memcpy(args.dest_buf +
+ args.dest_offset +
+ nbytes,
+ args.from,
+ payload_write_len,
+ args.dest_offset + nbytes,
+ args.dest_buf_sz);
+
+ args.from += payload_write_len;
+ args.dest_offset += nbytes;
+
+ /* sanity check for underflow */
+ WARN_ON(remaining_len - payload_write_len > remaining_len);
+ remaining_len -= payload_write_len;
+ }
+
+ return nbytes;
+}
diff --git a/drivers/media/test-drivers/vidtv/vidtv_pes.h b/drivers/media/test-drivers/vidtv/vidtv_pes.h
new file mode 100644
index 0000000000000..00ede32adf476
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_pes.h
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Vidtv serves as a reference DVB driver and helps validate the existing APIs
+ * in the media subsystem. It can also aid developers working on userspace
+ * applications.
+ *
+ * This file contains the logic to translate the ES data for one access unit
+ * from an encoder into MPEG TS packets. It does so by first encapsulating it
+ * with a PES header and then splitting it into TS packets.
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_PES_H
+#define VIDTV_PES_H
+
+#include <asm/byteorder.h>
+#include <linux/types.h>
+#include "vidtv_common.h"
+
+#define PES_MAX_LEN 65536 /* Set 'length' to 0 if greater */
+#define PES_START_CODE_PREFIX 0x001 /* 00 00 01 */
+
+struct vidtv_pes_optional_pts {
+ struct {
+ #if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 marker3:1; /* always 0x1 */
+ u16 pts3:15;
+ u8 marker2:1; /* always 0x1 */
+ u16 pts2:15;
+ u8 marker1:1; /* always 0x1 */
+ u8 pts1:3;
+ u8 two:4; /* always 0010b */
+ #elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 two:4; /* always 0010b */
+ u8 pts1:3;
+ u8 marker1:1; /* always 0x1 */
+ u16 pts2:15;
+ u8 marker2:1; /* always 0x1 */
+ u16 pts3:15;
+ u8 marker3:1; /* always 0x1 */
+ #else
+ #error "Please fix <asm/byteorder.h>"
+ #endif
+ } __packed;
+} __packed;
+
+struct vidtv_pes_optional_pts_dts {
+ struct {
+ #if defined(__LITTLE_ENDIAN_BITFIELD)
+ u8 marker6:1; /* always 0x1 */
+ u16 dts3:15;
+ u8 marker5:1; /* always 0x1 */
+ u16 dts2:15;
+ u8 marker4:1; /* always 0x1 */
+ u8 dts1:3;
+ u8 one:4; /* always 0001b */
+ u8 marker3:1; /* always 0x1 */
+ u16 pts3:15;
+ u8 marker2:1; /* always 0x1 */
+ u16 pts2:15;
+ u8 marker1:1; /* always 0x1 */
+ u8 pts1:3;
+ u8 three:4; /* always 0011b */
+ #elif defined(__BIG_ENDIAN_BITFIELD)
+ u8 three:4; /* always 0011b */
+ u8 pts1:3;
+ u8 marker1:1; /* always 0x1 */
+ u16 pts2:15;
+ u8 marker2:1; /* always 0x1 */
+ u16 pts3:15;
+ u8 marker3:1; /* always 0x1 */
+ u8 one:4; /* always 0001b */
+ u8 dts1:3;
+ u8 marker4:1; /* always 0x1 */
+ u16 dts2:15;
+ u8 marker5:1; /* always 0x1 */
+ u16 dts3:15;
+ u8 marker6:1; /* always 0x1 */
+ #else
+ #error "Please fix <asm/byteorder.h>"
+ #endif
+ } __packed;
+} __packed;
+
+struct vidtv_pes_optional {
+ union {
+ u16 bitfield;
+ struct {
+ u16 two:2; /* always 0x2*/
+ u16 PES_scrambling_control:2;
+ u16 PES_priority:1;
+ u16 data_alignment_indicator:1; /* ununsed for us */
+ u16 copyright:1;
+ u16 original_or_copy:1;
+ u16 PTS_DTS:2;
+ /* These flags show which components are actually
+ * present in the "optinal fields" in the optinal PES
+ * header and which are not. Vidtv currently does
+ * not need any of these.
+ */
+ u16 ESCR:1;
+ u16 ES_rate:1;
+ u16 DSM_trick_mode:1;
+ u16 additional_copy_info:1;
+ u16 PES_CRC:1;
+ u16 PES_extension:1;
+ } __packed;
+ } __packed;
+ u8 length;
+} __packed;
+
+struct vidtv_mpeg_pes {
+ union {
+ u32 bitfield;
+ struct {
+ /* These two together make the 32-bit start-code */
+ u32 packet_start_code_prefix:24;
+ u32 stream_id:8;
+ } __packed;
+ } __packed;
+ /* after this field until the end of the PES data payload */
+ u16 length;
+ struct vidtv_pes_optional optional[];
+} __packed;
+
+struct pes_header_write_args {
+ void *dest_buf;
+ u32 dest_offset;
+ u32 dest_buf_sz;
+ bool is_s302m_pkt;
+
+ bool send_pts;
+ u64 pts;
+
+ bool send_dts;
+ u64 dts;
+
+ u16 stream_id;
+ /* might be used by an encoder if needed, gets discarded by decoder */
+ u32 n_pes_h_s_bytes;
+};
+
+struct pes_ts_header_write_args {
+ void *dest_buf;
+ u32 dest_offset;
+ u32 dest_buf_sz;
+ u16 pid;
+ u8 *continuity_counter;
+ bool wrote_pes_header;
+ u32 n_stuffing_bytes;
+};
+
+struct pes_write_args {
+ void *dest_buf; /* pointer to a program mux buffer */
+ void *from; /* pointer to the encoder buffer */
+
+ /* the size of one access unit (with any headers it might need) */
+ u32 access_unit_len;
+
+ u32 dest_offset; /* where to start writing in the program mux buffer */
+ u32 dest_buf_sz; /* how big is the program mux buffer */
+ u16 pid; /* TS packet ID */
+
+ /* use SMPTE 302M to packetize the data */
+ bool is_s302m_pkt;
+
+ u8 *continuity_counter; /* incremented for every TS packet */
+
+ /* Examples: Audio streams (0xc0-0xdf), Video streams (0xe0-0xef) */
+ u16 stream_id;
+
+ bool send_pts;
+ u64 pts;
+
+ bool send_dts;
+ u64 dts;
+
+ /* might be used by an encoder if needed, gets discarded by decoder */
+ u32 n_pes_h_s_bytes;
+};
+
+u32 vidtv_pes_write_into(struct pes_write_args args);
+
+#endif // VIDTV_PES_H
--
2.26.2

2020-05-02 03:26:53

by Daniel Almeida

[permalink] [raw]
Subject: [RFC, WIP, v4 11/11] media: vidtv: Add a MPEG Transport Stream Multiplexer

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

Add a MPEG Transport Stream multiplexer responsible for polling encoders,
interleaving packets, padding the resulting stream with NULL packets if
necessary and then delivering the resulting TS packets to the bridge
driver so it can feed the demux.

This patch includes a "channel" abstraction, which attempts to map a
MPEG service into a struct that vidtv can work with.

When vidtv boots, it will create some hardcoded channels:

-Their services will be concatenated to populate the SDT.
-Their programs will be concatenated to populate the PAT
-For each program in the PAT, a PMT section will be created
-The PMT section for a channel will be assigned its streams.
-Every stream will have its corresponding encoder polled to produce TS packets
-These packets may be interleaved by the mux and then delivered to the bridg

Signed-off-by: Daniel W. S. Almeida <[email protected]>
---
drivers/media/test-drivers/vidtv/Makefile | 2 +-
.../media/test-drivers/vidtv/vidtv_bridge.c | 67 ++-
.../media/test-drivers/vidtv/vidtv_bridge.h | 2 +
.../media/test-drivers/vidtv/vidtv_channel.c | 326 ++++++++++++++
.../media/test-drivers/vidtv/vidtv_channel.h | 66 +++
.../media/test-drivers/vidtv/vidtv_common.h | 3 +
drivers/media/test-drivers/vidtv/vidtv_mux.c | 423 ++++++++++++++++++
drivers/media/test-drivers/vidtv/vidtv_mux.h | 105 +++++
drivers/media/test-drivers/vidtv/vidtv_psi.c | 18 +
drivers/media/test-drivers/vidtv/vidtv_psi.h | 5 +
10 files changed, 1014 insertions(+), 3 deletions(-)
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_channel.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_channel.h
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_mux.c
create mode 100644 drivers/media/test-drivers/vidtv/vidtv_mux.h

diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
index c916eb19d73bb..a1d29001fffe3 100644
--- a/drivers/media/test-drivers/vidtv/Makefile
+++ b/drivers/media/test-drivers/vidtv/Makefile
@@ -2,6 +2,6 @@

vidtv_demod-objs := vidtv_common.o
vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o vidtv_pes.o \
- vidtv_s302m.o
+ vidtv_s302m.o vidtv_channel.o vidtv_mux.o

obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c
index 05ca4027c869f..c9876372fdebd 100644
--- a/drivers/media/test-drivers/vidtv/vidtv_bridge.c
+++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c
@@ -13,8 +13,10 @@
#include <linux/workqueue.h>
#include <linux/time.h>
#include "vidtv_bridge.h"
+#include "vidtv_ts.h"
+#include "vidtv_mux.h"

-#define TS_BUF_MAX_SZ (128 * 188)
+#define TS_BUF_MAX_SZ (128 * TS_PACKET_LEN)
#define TUNER_DEFAULT_ADDR 0x68
#define DEMOD_DEFAULT_ADDR 0x60

@@ -64,16 +66,63 @@ module_param(chosen_delsys, uint, 0644);
MODULE_PARM_DESC(chosen_delsys,
"The delivery system to simulate. Currently supported: DVB-T, DVB-C, DVB-S");

-static unsigned int ts_buf_sz = 20 * 188;
+static unsigned int ts_buf_sz = 20 * TS_PACKET_LEN;
module_param(ts_buf_sz, uint, 0644);
MODULE_PARM_DESC(ts_buf_sz, "Optional size for the TS buffer");

DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nums);

+/*
+ * Influences the signal acquisition time. See ISO/IEC 13818-1 : 2000. p. 113.
+ */
+static unsigned int si_period_msec = 40;
+module_param(si_period_msec, uint, 0644);
+MODULE_PARM_DESC(si_period_msec, "How often to send SI packets. Default: 40ms");
+
+static unsigned int pcr_period_msec = 40;
+module_param(pcr_period_msec, uint, 0644);
+MODULE_PARM_DESC(pcr_period_msec, "How often to send PCR packets. Default: 40ms");
+
+static unsigned int mux_rate_kbytes_sec = 4096;
+module_param(mux_rate_kbytes_sec, uint, 0644);
+MODULE_PARM_DESC(mux_rate_kbytes_sec, "Optional mux rate: will pad stream if below");
+
+static unsigned int pcr_pid = 0x200
+module_param(pcr_pid, uint, 0644);
+MODULE_PARM_DESC(pcr_pid, "Optional PCR PID for all channels: defaults to 0x200");
+
+static bool vidtv_bridge_check_demod_lock(struct vidtv_dvb *dvb, u32 n)
+{
+ enum fe_status status;
+
+ dvb->fe[n]->ops.read_status(dvb->fe[n], &status);
+
+ return status == FE_HAS_SIGNAL |
+ FE_HAS_CARRIER |
+ FE_HAS_VITERBI |
+ FE_HAS_SYNC |
+ FE_HAS_LOCK;
+}
+
+static void
+vidtv_bridge_on_new_pkts_avail(void *priv, u8 *buf, u32 npkts)
+{
+ /*
+ * called on a separate thread by the mux when new packets become
+ * available
+ */
+ struct vidtv_dvb *dvb = (struct vidtv_dvb *)priv;
+
+ /* drop packets if we lose the lock */
+ if (vidtv_bridge_check_demod_lock(dvb, 0))
+ dvb_dmx_swfilter_packets(&dvb->demux, buf, npkts);
+}
+
static int vidtv_start_streaming(struct vidtv_dvb *dvb)
{
WARN_ON(dvb->streaming);
dvb->streaming = true;
+ vidtv_mux_start_thread(dvb->mux);

return 0;
}
@@ -82,6 +131,7 @@ static int vidtv_stop_streaming(struct vidtv_dvb *dvb)
{
/* mpeg thread will quit */
dvb->streaming = false;
+ vidtv_mux_stop_thread(dvb->mux);

return 0;
}
@@ -313,6 +363,7 @@ static int vidtv_bridge_i2c_probe(struct i2c_client *client,
{
int ret;
struct vidtv_dvb *dvb;
+ struct vidtv_mux_init_args mux_args = {0};

dvb = kzalloc(sizeof(*dvb), GFP_KERNEL);
if (!dvb)
@@ -324,6 +375,16 @@ static int vidtv_bridge_i2c_probe(struct i2c_client *client,

mutex_init(&dvb->feed_lock);

+ mux_args.mux_rate_kbytes_sec = mux_rate_kbytes_sec;
+ mux_args.on_new_packets_available_cb = vidtv_bridge_on_new_pkts_avail;
+ mux_args.ts_buf_sz = ts_buf_sz;
+ mux_args.pcr_period_usecs = pcr_period_msecs * 1000;
+ mux_args.si_period_usecs = si_period_msecs * 1000;
+ mux_args.pcr_pid = pcr_pid;
+ mux_args.priv = dvb;
+
+ dvb->mux = vidtv_mux_init(mux_args);
+
i2c_set_clientdata(client, dvb);

return ret;
@@ -340,6 +401,8 @@ static int vidtv_bridge_i2c_remove(struct i2c_client *client)

dvb = i2c_get_clientdata(client);

+ vidtv_mux_destroy(dvb->mux);
+
mutex_destroy(&dvb->feed_lock);

for (i = 0; i < NUM_FE; ++i)
diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.h b/drivers/media/test-drivers/vidtv/vidtv_bridge.h
index ef5c7cd2d64e3..f5e6931058c9c 100644
--- a/drivers/media/test-drivers/vidtv/vidtv_bridge.h
+++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.h
@@ -17,6 +17,7 @@
#include <media/dvb_demux.h>
#include <media/dmxdev.h>
#include <linux/i2c.h>
+#include "vidtv_mux.h"

struct vidtv_dvb {
struct dvb_frontend *fe[NUM_FE];
@@ -32,6 +33,7 @@ struct vidtv_dvb {
struct mutex feed_lock; /* start/stop feed */

bool streaming;
+ struct vidtv_mux *mux;
};

#endif // VIDTV_BRIDGE_H
diff --git a/drivers/media/test-drivers/vidtv/vidtv_channel.c b/drivers/media/test-drivers/vidtv/vidtv_channel.c
new file mode 100644
index 0000000000000..3e89a40eb7ca6
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_channel.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Vidtv serves as a reference DVB driver and helps validate the existing APIs
+ * in the media subsystem. It can also aid developers working on userspace
+ * applications.
+ *
+ * This file contains the code for a 'channel' abstraction.
+ *
+ * When vidtv boots, it will create some hardcoded channels.
+ * Their services will be concatenated to populate the SDT.
+ * Their programs will be concatenated to populate the PAT
+ * For each program in the PAT, a PMT section will be created
+ * The PMT section for a channel will be assigned its streams.
+ * Every stream will have its corresponding encoder polled to produce TS packets
+ * These packets may be interleaved by the mux and then delivered to the bridge
+ *
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include "vidtv_channel.h"
+#include "vidtv_psi.h"
+#include "vidtv_encoder.h"
+#include "vidtv_mux.h"
+#include "vidtv_common.h"
+#include "vidtv_s302m.h"
+
+static void vidtv_channel_encoder_destroy(struct vidtv_encoder *e)
+{
+ struct vidtv_encoder *curr = e;
+
+ while (curr) {
+ /* forward the call to the derived type */
+ curr->destroy(curr);
+ curr = curr->next;
+ }
+}
+
+static struct vidtv_channel
+*vidtv_channel_s302m_init(struct vidtv_channel *head)
+{
+ /* init an audio only channel with a s302m encoder */
+ const u16 s302m_service_id = 0x880;
+ const u16 s302m_program_num = 0x880;
+ const u16 s302m_program_pid = 0x101; /* packet id for PMT*/
+ const u16 s302m_es_pid = 0x111; /* packet id for the ES */
+ const u16 s302m_pes_audio_stream_id = 0xbd; /* PES: private_stream_1 */
+
+ struct vidtv_channel *s302m = kzalloc(sizeof(*s302m), GFP_KERNEL);
+ struct vidtv_psi_table_sdt_service *s302m_service;
+ struct vidtv_psi_desc_service *s302m_s_desc;
+ struct vidtv_s302m_encoder_init_args encoder_args = {0};
+
+ s302m_service = vidtv_psi_sdt_service_init(NULL, s302m_service_id);
+
+ s302m_s_desc = (struct vidtv_psi_desc_service *)
+ vidtv_psi_desc_init(NULL,
+ SERVICE_DESCRIPTOR,
+ sizeof(*s302m_s_desc));
+
+ s302m_s_desc->name = "Sine Wave PCM Audio";
+ s302m_s_desc->service_type = DIGITAL_TELEVISION_SERVICE;
+
+ s302m_s_desc->length = sizeof(s302m_s_desc->service_type)
+ + strlen(s302m_s_desc->name)
+ + strlen(s302m_s_desc->name_emph)
+ + strlen(s302m_s_desc->provider)
+ + strlen(s302m_s_desc->provider_emph);
+
+ vidtv_psi_desc_assign(&s302m_service->descriptor,
+ (struct vidtv_psi_desc *)
+ s302m_s_desc);
+
+ s302m->transport_stream_id = TRANSPORT_STREAM_ID;
+
+ s302m->program = vidtv_psi_pat_program_init(NULL,
+ s302m_service_id,
+ s302m_program_pid);
+
+ s302m->program_num = s302m_program_num;
+
+ s302m->streams = vidtv_psi_pmt_stream_init(NULL,
+ STREAM_PRIVATE_DATA,
+ s302m_pes_audio_stream_id);
+
+ encoder_args.access_unit_capacity = 16;
+ encoder_args.es_pid = s302m_es_pid;
+
+ s302m->encoders = vidtv_s302m_encoder_init(encoder_args);
+
+ if (head) {
+ while (head->next)
+ head = head->next;
+
+ head->next = s302m;
+ }
+
+ return s302m;
+}
+
+static struct vidtv_psi_table_sdt_service
+*vidtv_channel_sdt_serv_cat_into_new(struct vidtv_channel *channels)
+{
+ struct vidtv_channel *cur_chnl = channels;
+ struct vidtv_psi_table_sdt_service *curr = NULL;
+ struct vidtv_psi_table_sdt_service *head = NULL;
+ struct vidtv_psi_table_sdt_service *tail = NULL;
+ u16 service_id;
+
+ while (cur_chnl) {
+ curr = cur_chnl->service;
+ service_id = curr->service_id;
+
+ if (!curr)
+ continue;
+
+ while (curr->next) {
+ tail = vidtv_psi_sdt_service_init(tail, service_id);
+
+ if (!head)
+ head = tail;
+
+ curr = curr->next;
+ }
+
+ cur_chnl = cur_chnl->next;
+ }
+
+ return head;
+}
+
+static struct vidtv_psi_table_pat_program*
+vidtv_channel_pat_prog_cat_into_new(struct vidtv_channel *channels)
+{
+ struct vidtv_channel *cur_chnl = channels;
+ struct vidtv_psi_table_pat_program *curr = NULL;
+ struct vidtv_psi_table_pat_program *head = NULL;
+ struct vidtv_psi_table_pat_program *tail = NULL;
+
+ while (cur_chnl) {
+ curr = cur_chnl->program;
+
+ if (!curr)
+ continue;
+
+ while (curr->next) {
+ tail = vidtv_psi_pat_program_init(tail,
+ curr->service_id,
+ curr->pid);
+
+ if (!head)
+ head = tail;
+
+ curr = curr->next;
+ }
+
+ cur_chnl = cur_chnl->next;
+ }
+
+ return head;
+}
+
+static void
+vidtv_channel_pmt_match_sections(struct vidtv_channel *channels,
+ struct vidtv_psi_table_pmt sections[],
+ u32 nsections)
+{
+ struct vidtv_psi_table_pmt *curr_section = NULL;
+ struct vidtv_channel *cur_chnl = channels;
+ u32 j;
+
+ while (cur_chnl) {
+ for (j = 0; j < nsections; ++j) {
+ curr_section = &sections[j];
+
+ if (!curr_section)
+ continue;
+
+ /* we got a match */
+ if (curr_section->header.id == cur_chnl->program_num) {
+ vidtv_psi_pmt_stream_assign(curr_section,
+ cur_chnl->streams);
+ break;
+ }
+ }
+
+ cur_chnl = cur_chnl->next;
+ }
+}
+
+void vidtv_channel_si_init(struct vidtv_mux *m)
+{
+ struct vidtv_psi_table_pat *pat = m->si.pat;
+ struct vidtv_psi_table_sdt *sdt = m->si.sdt;
+
+ struct vidtv_psi_table_pmt *pmt_sections = m->si.pmt_secs;
+
+ struct vidtv_psi_table_pat_program *programs = NULL;
+ struct vidtv_psi_table_sdt_service *services = NULL;
+
+ bool update_version_num = false;
+
+ vidtv_psi_pat_table_init(pat,
+ update_version_num,
+ TRANSPORT_STREAM_ID);
+
+ vidtv_psi_sdt_table_init(sdt,
+ update_version_num,
+ TRANSPORT_STREAM_ID);
+
+ programs = vidtv_channel_pat_prog_cat_into_new(m->channels);
+ services = vidtv_channel_sdt_serv_cat_into_new(m->channels);
+
+ /* assemble all programs and assign to PAT */
+ vidtv_psi_pat_program_assign(pat, programs);
+
+ /* assemble all services and assign to SDT */
+ vidtv_psi_sdt_service_assign(sdt, services);
+
+ /* a section for each program_id */
+ pmt_sections = kcalloc(pat->programs,
+ sizeof(struct vidtv_psi_table_pmt),
+ GFP_KERNEL);
+
+ vidtv_psi_pmt_create_sec_for_each_pat_entry(pat,
+ pmt_sections);
+
+ vidtv_channel_pmt_match_sections(m->channels,
+ pmt_sections,
+ pat->programs);
+}
+
+void vidtv_channel_si_destroy(struct vidtv_mux *m)
+{
+ u32 i;
+
+ vidtv_psi_pat_table_destroy(m->si.pat);
+
+ for (i = 0; i < m->si.num_pmt_sections; ++i)
+ vidtv_psi_pmt_table_destroy(&m->si.pmt_secs[i]);
+
+ kfree(m->si.pmt_secs);
+ vidtv_psi_sdt_table_destroy(m->si.sdt);
+}
+
+void vidtv_channels_init(struct vidtv_mux *m)
+{
+ /* we only have a single channel for now */
+ m->channels = vidtv_channel_s302m_init(NULL);
+}
+
+void vidtv_channels_destroy(struct vidtv_mux *m)
+{
+ struct vidtv_channel *curr = m->channels;
+
+ while (curr) {
+ vidtv_psi_sdt_service_destroy(curr->service);
+ vidtv_psi_pat_program_destroy(curr->program);
+ vidtv_psi_pmt_stream_destroy(curr->streams);
+ vidtv_channel_encoder_destroy(curr->encoders);
+ curr = curr->next;
+ }
+}
+
+static void
+vidtv_channels_add_registration_s302m(struct vidtv_psi_table_pmt *sec)
+{
+ struct vidtv_psi_desc_registration *s302m_r_desc;
+
+ /* there might be some descriptors there already */
+ struct vidtv_psi_desc *parent = sec->descriptor;
+
+ s302m_r_desc = (struct vidtv_psi_desc_registration *)
+ vidtv_psi_desc_init(parent,
+ REGISTRATION_DESCRIPTOR,
+ sizeof(*s302m_r_desc));
+
+ s302m_r_desc->format_identifier = VIDTV_S302M_FORMAT_IDENTIFIER;
+
+ if (!parent)
+ vidtv_psi_desc_assign(&sec->descriptor,
+ (struct vidtv_psi_desc *)s302m_r_desc);
+
+ /* we are adding to the table, so recompute the length */
+ vidtv_psi_pmt_table_comp_sec_len(sec);
+}
+
+void vidtv_channels_add_registration_descs(struct vidtv_mux *m)
+{
+ /*
+ * Some formats might need a registration descriptor to be recognized.
+ * S302M needs it, and ffmpeg actually checks for it, so add such
+ * descriptor at the PMT section that contains the stream
+ */
+ struct vidtv_channel *cur_chnl = m->channels;
+ struct vidtv_encoder *e = NULL;
+ struct vidtv_psi_table_pmt *sec = NULL;
+
+ while (cur_chnl) {
+ e = cur_chnl->encoders;
+ sec = vidtv_psi_find_pmt_sec(m->si.pmt_secs,
+ m->si.pat->programs,
+ cur_chnl->program_num);
+
+ /* bug somewhere */
+ WARN_ON(!sec);
+ if (!sec)
+ continue;
+
+ while (e) {
+ switch (e->id) {
+ case S302M:
+ vidtv_channels_add_registration_s302m(sec);
+ break;
+ default:
+ break;
+ }
+
+ e = e->next;
+ }
+
+ cur_chnl = cur_chnl->next;
+ }
+}
diff --git a/drivers/media/test-drivers/vidtv/vidtv_channel.h b/drivers/media/test-drivers/vidtv/vidtv_channel.h
new file mode 100644
index 0000000000000..02141c4c732f0
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_channel.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Vidtv serves as a reference DVB driver and helps validate the existing APIs
+ * in the media subsystem. It can also aid developers working on userspace
+ * applications.
+ *
+ * This file contains the code for a 'channel' abstraction.
+ *
+ * When vidtv boots, it will create some hardcoded channels.
+ * Their services will be concatenated to populate the SDT.
+ * Their programs will be concatenated to populate the PAT
+ * For each program in the PAT, a PMT section will be created
+ * The PMT section for a channel will be assigned its streams.
+ * Every stream will have its corresponding encoder polled to produce TS packets
+ * These packets may be interleaved by the mux and then delivered to the bridge
+ *
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_CHANNEL_H
+#define VIDTV_CHANNEL_H
+
+#include <linux/types.h>
+#include "vidtv_psi.h"
+#include "vidtv_encoder.h"
+#include "vidtv_mux.h"
+
+struct vidtv_channel {
+ /* a number to identify the TS, chosen at will */
+ u16 transport_stream_id;
+
+ /* will be concatenated into the SDT */
+ struct vidtv_psi_table_sdt_service *service;
+
+ /* the link between SDT, PAT and PMT */
+ u16 program_num;
+
+ /*
+ * a single program with one or more streams associated with it.
+ * Will be concatenated into the PAT
+ */
+ struct vidtv_psi_table_pat_program *program;
+
+ /*
+ * one or more streams associated with the program
+ * Will populate the PMT section for this program
+ */
+ struct vidtv_psi_table_pmt_stream *streams;
+
+ /* a list of encoders, one for each stream */
+ struct vidtv_encoder *encoders;
+
+ struct vidtv_channel *next;
+};
+
+/* init SI data from the channels */
+void vidtv_channel_si_init(struct vidtv_mux *m);
+void vidtv_channel_si_destroy(struct vidtv_mux *m);
+
+void vidtv_channels_init(struct vidtv_mux *m);
+void vidtv_channels_destroy(struct vidtv_mux *m);
+
+void vidtv_channels_add_registration_descs(struct vidtv_mux *m);
+
+#endif //VIDTV_CHANNEL_H
diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.h b/drivers/media/test-drivers/vidtv/vidtv_common.h
index 170646497eb58..64c85503d4113 100644
--- a/drivers/media/test-drivers/vidtv/vidtv_common.h
+++ b/drivers/media/test-drivers/vidtv/vidtv_common.h
@@ -14,6 +14,9 @@
#include <media/dvb_frontend.h>

#define CLOCK_UNIT_90KHZ 90000
+#define CLOCK_UNIT_27MHZ 27000000
+#define SLEEP_USECS 10000
+#define TRANSPORT_STREAM_ID 0x744

u32 vidtv_memcpy(void *to,
const void *from,
diff --git a/drivers/media/test-drivers/vidtv/vidtv_mux.c b/drivers/media/test-drivers/vidtv/vidtv_mux.c
new file mode 100644
index 0000000000000..6d553be27622f
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_mux.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Vidtv serves as a reference DVB driver and helps validate the existing APIs
+ * in the media subsystem. It can also aid developers working on userspace
+ * applications.
+ *
+ * This file contains the multiplexer logic for TS packets from different
+ * elementary streams
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include "vidtv_mux.h"
+#include "vidtv_ts.h"
+#include "vidtv_pes.h"
+#include "vidtv_encoder.h"
+#include "vidtv_channel.h"
+#include "vidtv_common.h"
+#include "vidtv_psi.h"
+
+static struct vidtv_mux_pid_ctx
+*vidtv_mux_get_pid_ctx(struct vidtv_mux *m, u16 pid)
+{
+ struct vidtv_mux_pid_ctx *ctx;
+
+ hash_for_each_possible(m->pid_ctx, ctx, h, pid)
+ if (ctx->pid == pid)
+ return ctx;
+
+ return NULL;
+}
+
+static struct vidtv_mux_pid_ctx
+*vidtv_mux_create_pid_ctx_once(struct vidtv_mux *m, u16 pid)
+{
+ struct vidtv_mux_pid_ctx *ctx;
+
+ ctx = vidtv_mux_get_pid_ctx(m, pid);
+
+ if (ctx)
+ goto end;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ ctx->pid = pid;
+ ctx->cc = 0;
+ hash_add(m->pid_ctx, &ctx->h, pid);
+
+end:
+ return ctx;
+}
+
+static void vidtv_mux_pid_ctx_init(struct vidtv_mux *m)
+{
+ struct vidtv_psi_table_pat_program *p = m->si.pat->program;
+
+ hash_init(m->pid_ctx);
+ /* push the pcr pid ctx */
+ vidtv_mux_create_pid_ctx_once(m, m->pcr_pid);
+ /* push the null packet pid ctx */
+ vidtv_mux_create_pid_ctx_once(m, TS_NULL_PACKET_PID);
+ /* push the PAT pid ctx */
+ vidtv_mux_create_pid_ctx_once(m, VIDTV_PAT_PID);
+ /* push the SDT pid ctx */
+ vidtv_mux_create_pid_ctx_once(m, VIDTV_SDT_PID);
+
+ /* add a ctx for all PMT sections */
+ while (p) {
+ vidtv_mux_create_pid_ctx_once(m, p->pid);
+ p = p->next;
+ }
+}
+
+static void vidtv_mux_pid_ctx_destroy(struct vidtv_mux *m)
+{
+ int bkt;
+ struct vidtv_mux_pid_ctx *ctx;
+
+ hash_for_each(m->pid_ctx, bkt, ctx, h) {
+ kfree(ctx);
+ }
+}
+
+static void vidtv_mux_update_clk(struct vidtv_mux *m)
+{
+ /* call this at every thread iteration */
+ u64 elapsed_time;
+
+ /* this will not hold a value yet if we have just started */
+ m->timing.past_jiffies = m->timing.current_jiffies ?
+ m->timing.current_jiffies :
+ get_jiffies_64();
+
+ m->timing.current_jiffies = get_jiffies_64();
+
+ elapsed_time = jiffies_to_usecs(m->timing.current_jiffies -
+ m->timing.past_jiffies);
+
+ /* update the 27Mhz clock proportionally to the elapsed time */
+ m->timing.clk += (CLOCK_UNIT_27MHZ / USEC_PER_SEC) * elapsed_time;
+}
+
+static u32 vidtv_mux_push_si(struct vidtv_mux *m)
+{
+ u32 initial_offset = m->ts_buf_offset;
+ struct vidtv_mux_pid_ctx *pat_ctx, *pmt_ctx, *sdt_ctx;
+ u32 nbytes; /* the number of bytes written by this function */
+ u16 pmt_pid;
+ u32 i;
+
+ pat_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_PAT_PID);
+ sdt_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_SDT_PID);
+
+ m->ts_buf_offset += vidtv_psi_pat_write_into(m->ts_buf,
+ m->ts_buf_offset,
+ m->si.pat,
+ m->ts_buf_sz,
+ &pat_ctx->cc);
+
+ for (i = 0; i < m->si.num_pmt_sections; ++i) {
+ pmt_pid = vidtv_psi_pmt_get_pid(&m->si.pmt_secs[i],
+ m->si.pat);
+
+ /* not found */
+ WARN_ON(pmt_pid > TS_LAST_VALID_PID);
+ if (pmt_pid > TS_LAST_VALID_PID)
+ continue;
+
+ pmt_ctx = vidtv_mux_get_pid_ctx(m, pmt_pid);
+
+ /* write each section into buffer */
+ m->ts_buf_offset += vidtv_psi_pmt_write_into(m->ts_buf,
+ m->ts_buf_offset,
+ &m->si.pmt_secs[i],
+ pmt_pid,
+ m->ts_buf_sz,
+ &pmt_ctx->cc);
+ }
+
+ m->ts_buf_offset += vidtv_psi_sdt_write_into(m->ts_buf,
+ m->ts_buf_offset,
+ m->si.sdt,
+ m->ts_buf_sz,
+ &sdt_ctx->cc);
+
+ nbytes = m->ts_buf_offset - initial_offset;
+ return nbytes;
+}
+
+static u32 vidtv_mux_push_pcr(struct vidtv_mux *m)
+{
+ struct pcr_write_args args = {0};
+ struct vidtv_mux_pid_ctx *ctx;
+
+ ctx = vidtv_mux_get_pid_ctx(m, m->pcr_pid);
+ args.dest_buf = m->ts_buf;
+ args.pid = m->pcr_pid;
+ args.buf_sz = m->ts_buf_sz;
+ args.continuity_counter = &ctx->cc;
+
+ /* the 27Mhz clock will feed both parts of the PCR bitfield */
+ args.pcr = m->timing.clk;
+
+ return vidtv_ts_pcr_write_into(args);
+}
+
+static bool vidtv_mux_should_push_pcr(struct vidtv_mux *m)
+{
+ u64 next_pcr_at;
+
+ next_pcr_at = m->timing.start_jiffies +
+ usecs_to_jiffies(m->num_streamed_pcr *
+ m->timing.pcr_period_usecs);
+
+ return time_after64(m->timing.current_jiffies, next_pcr_at);
+}
+
+static bool vidtv_mux_should_push_si(struct vidtv_mux *m)
+{
+ u64 next_si_at;
+
+ next_si_at = m->timing.start_jiffies +
+ usecs_to_jiffies(m->num_streamed_si *
+ m->timing.si_period_usecs);
+
+ return time_after64(m->timing.current_jiffies, next_si_at);
+}
+
+static u32 vidtv_mux_packetize_access_units(struct vidtv_mux *m,
+ struct vidtv_encoder *e)
+{
+ /* the number of bytes written by this function */
+ u32 nbytes = 0;
+ struct pes_write_args args = {0};
+ u32 initial_offset = m->ts_buf_offset;
+
+ u32 i;
+ u8 *buf;
+ struct vidtv_mux_pid_ctx *pid_ctx;
+
+ pid_ctx = vidtv_mux_create_pid_ctx_once(m, e->es_pid);
+
+ args.dest_buf = m->ts_buf;
+ args.dest_buf_sz = m->ts_buf_sz;
+ args.pid = e->es_pid;
+ args.is_s302m_pkt = (e->id == S302M);
+ args.continuity_counter = &pid_ctx->cc;
+ args.send_pts = true;
+
+ for (i = 0; i < e->nunits; ++i) {
+ buf = e->encoder_buf + e->offsets[i];
+ args.from = buf;
+ args.access_unit_len = e->nbytes[i];
+ args.dest_offset = m->ts_buf_offset;
+ args.pts = e->pts[i];
+
+ m->ts_buf_offset += vidtv_pes_write_into(args);
+ }
+
+ /* clear the encoder state once we have written the current ES data */
+ e->clear(e);
+
+ nbytes = m->ts_buf_offset - initial_offset;
+ return nbytes;
+}
+
+static u32 vidtv_mux_poll_encoders(struct vidtv_mux *m)
+{
+ u32 nbytes = 0;
+ struct vidtv_channel *cur_chnl = m->channels;
+ struct vidtv_encoder *e = NULL;
+
+ u64 elapsed_time_usecs = jiffies_to_usecs(m->timing.current_jiffies -
+ m->timing.past_jiffies);
+ while (cur_chnl) {
+ e = cur_chnl->encoders;
+
+ while (e) {
+ /* encode for 'elapsed_time_usecs' */
+ e->encode(e, elapsed_time_usecs);
+ /* get the TS packets into the mux buffer */
+ nbytes += vidtv_mux_packetize_access_units(m, e);
+ /* grab next encoder */
+ e = e->next;
+ }
+
+ /* grab the next channel */
+ cur_chnl = cur_chnl->next;
+ }
+
+ return nbytes;
+}
+
+static u32 vidtv_mux_pad_with_nulls(struct vidtv_mux *m, u32 npkts)
+{
+ struct null_packet_write_args args = {0};
+ u32 initial_offset = m->ts_buf_offset;
+ u32 nbytes; /* the number of bytes written by this function */
+ u32 i;
+ struct vidtv_mux_pid_ctx *ctx;
+
+ ctx = vidtv_mux_get_pid_ctx(m, TS_NULL_PACKET_PID);
+
+ args.dest_buf = m->ts_buf;
+ args.buf_sz = m->ts_buf_sz;
+ args.continuity_counter = &ctx->cc;
+ args.dest_offset = m->ts_buf_offset;
+
+ for (i = 0; i < npkts; ++i) {
+ m->ts_buf_offset += vidtv_ts_null_write_into(args);
+ args.dest_offset = m->ts_buf_offset;
+ }
+
+ nbytes = m->ts_buf_offset - initial_offset;
+
+ /* sanity check */
+ WARN_ON(nbytes != npkts * TS_PACKET_LEN);
+
+ return nbytes;
+}
+
+static u32 vidtv_mux_check_mux_rate(struct vidtv_mux *m)
+{
+ /*
+ * attempt to maintain a constant mux rate, padding with null packets
+ * if needed
+ */
+
+ u32 nbytes = 0; /* the number of bytes written by this function */
+
+ u64 nbytes_expected; /* the number of bytes we should have written */
+ u64 nbytes_streamed; /* the number of bytes we actually wrote */
+ u32 num_null_pkts; /* number of null packets to bridge the gap */
+
+ u64 elapsed_time_usecs = jiffies_to_usecs(m->timing.current_jiffies -
+ m->timing.past_jiffies);
+
+ nbytes_expected = (m->mux_rate_kbytes_sec / 1000) / USEC_PER_SEC;
+ nbytes_expected *= elapsed_time_usecs;
+
+ nbytes_streamed = m->num_streamed_pkts * TS_PACKET_LEN;
+
+ if (nbytes_streamed < nbytes_expected) {
+ /* can't write half a packet: roundup to a 188 multiple */
+ nbytes_expected = roundup(nbytes_expected, TS_PACKET_LEN);
+ num_null_pkts = nbytes_expected / TS_PACKET_LEN;
+ nbytes += vidtv_mux_pad_with_nulls(m, num_null_pkts);
+ }
+
+ return nbytes;
+}
+
+static void vidtv_mux_clear(struct vidtv_mux *m)
+{
+ /* clear the packets currently in the mux */
+ memset(m->ts_buf, 0, m->ts_buf_sz);
+ /* point to the beginning of the buffer again */
+ m->ts_buf_offset = 0;
+}
+
+static void vidtv_mux_tick(struct work_struct *work)
+{
+ struct vidtv_mux *m = container_of(work,
+ struct vidtv_mux,
+ mpeg_thread);
+ u32 nbytes;
+ u32 npkts;
+
+ while (m->streaming) {
+ nbytes = 0;
+
+ vidtv_mux_update_clk(m);
+
+ if (vidtv_mux_should_push_pcr(m))
+ nbytes += vidtv_mux_push_pcr(m);
+
+ if (vidtv_mux_should_push_si(m))
+ nbytes += vidtv_mux_push_si(m);
+
+ nbytes += vidtv_mux_poll_encoders(m);
+
+ nbytes += vidtv_mux_check_mux_rate(m);
+
+ npkts = nbytes / TS_PACKET_LEN;
+ /* if the buffer is not aligned there is a bug somewhere */
+ WARN_ON(nbytes % TS_PACKET_LEN);
+
+ if (m->on_new_packets_available_cb)
+ m->on_new_packets_available_cb(m->priv,
+ m->ts_buf,
+ npkts);
+
+ m->num_streamed_pkts += npkts;
+ vidtv_mux_clear(m);
+
+ usleep_range(SLEEP_USECS, 2 * SLEEP_USECS);
+ }
+}
+
+void vidtv_mux_start_thread(struct vidtv_mux *m)
+{
+ WARN_ON(m->streaming);
+
+ if (m->streaming)
+ return;
+
+ m->streaming = true;
+ m->timing.start_jiffies = get_jiffies_64();
+ schedule_work(&m->mpeg_thread);
+}
+
+void vidtv_mux_stop_thread(struct vidtv_mux *m)
+{
+ /* thread will quit */
+ m->streaming = false;
+}
+
+struct vidtv_mux *vidtv_mux_init(struct vidtv_mux_init_args args)
+{
+ struct vidtv_mux *m = kzalloc(sizeof(*m), GFP_KERNEL);
+
+ m->timing.pcr_period_usecs = args.pcr_period_usecs;
+ m->timing.si_period_usecs = args.si_period_usecs;
+
+ m->mux_rate_kbytes_sec = args.mux_rate_kbytes_sec;
+
+ m->on_new_packets_available_cb = args.on_new_packets_available_cb;
+
+ m->ts_buf = vmalloc(args.ts_buf_sz);
+ m->ts_buf_sz = args.ts_buf_sz;
+
+ m->si.pat = kzalloc(sizeof(*m->si.pat), GFP_KERNEL);
+ m->si.sdt = kzalloc(sizeof(*m->si.sdt), GFP_KERNEL);
+
+ vidtv_channels_init(m);
+
+ /* will alloc data for pmt_sections after initializing pat */
+ vidtv_channel_si_init(m);
+
+ INIT_WORK(&m->mpeg_thread, vidtv_mux_tick);
+
+ m->pcr_pid = args.pcr_pid;
+ m->priv = args.priv;
+
+ vidtv_mux_pid_ctx_init(m);
+ vidtv_channels_add_registration_descs(m);
+
+ return m;
+}
+
+void vidtv_mux_destroy(struct vidtv_mux *m)
+{
+ vidtv_mux_pid_ctx_destroy(m);
+ vidtv_channel_si_destroy(m);
+ vidtv_channels_destroy(m);
+ kfree(m->si.sdt);
+ kfree(m->si.pat);
+ vfree(m->ts_buf);
+ kfree(m);
+}
diff --git a/drivers/media/test-drivers/vidtv/vidtv_mux.h b/drivers/media/test-drivers/vidtv/vidtv_mux.h
new file mode 100644
index 0000000000000..1dffa2010d518
--- /dev/null
+++ b/drivers/media/test-drivers/vidtv/vidtv_mux.h
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Vidtv serves as a reference DVB driver and helps validate the existing APIs
+ * in the media subsystem. It can also aid developers working on userspace
+ * applications.
+ *
+ * This file contains the multiplexer logic for TS packets from different
+ * elementary streams
+ *
+ * Written by Daniel W. S. Almeida <[email protected]>
+ */
+
+#ifndef VIDTV_MUX_H
+#define VIDTV_MUX_H
+
+#include <linux/types.h>
+#include <linux/hashtable.h>
+#include <linux/workqueue.h>
+#include "vidtv_psi.h"
+struct vidtv_mux_timing {
+ u64 start_jiffies;
+ u64 current_jiffies;
+ u64 past_jiffies;
+
+ /* a 27Mhz clock from which we will drive the PCR */
+ u64 clk;
+
+ u64 pcr_period_usecs;
+ u64 si_period_usecs;
+};
+
+struct vidtv_mux_si {
+ /* the SI tables */
+ struct vidtv_psi_table_pat *pat;
+ struct vidtv_psi_table_pmt *pmt_secs; /* the PMT sections */
+ /* as many sections as programs in the PAT */
+ u16 num_pmt_sections;
+ struct vidtv_psi_table_sdt *sdt;
+};
+
+struct vidtv_mux_pid_ctx {
+ u16 pid;
+ u8 cc; /* continuity counter */
+ struct hlist_node h;
+};
+
+struct vidtv_mux {
+ struct vidtv_mux_timing timing;
+
+ /* the bit rate for the TS, in kbytes */
+ u32 mux_rate_kbytes_sec;
+
+ /* a hash table to keep track of per-PID metadata */
+ DECLARE_HASHTABLE(pid_ctx, 3);
+
+ /* a callback to inform of new TS packets ready */
+ void (*on_new_packets_available_cb)(void *priv, u8 *buf, u32 npackets);
+
+ /* the TS buffer */
+ u8 *ts_buf;
+
+ /* the TS buffer size */
+ u32 ts_buf_sz;
+
+ /* where we are in the TS buffer now */
+ u32 ts_buf_offset;
+
+ /* a list of channels */
+ struct vidtv_channel *channels;
+
+ struct vidtv_mux_si si;
+ u64 num_streamed_pcr;
+ u64 num_streamed_si;
+
+ /* total number of packets streamed */
+ u64 num_streamed_pkts;
+
+ struct work_struct mpeg_thread;
+
+ /* whether to keep running the main loop */
+ bool streaming;
+
+ /* the pcr PID for _all_ channels */
+ u16 pcr_pid;
+
+ void *priv;
+};
+
+struct vidtv_mux_init_args {
+ u32 mux_rate_kbytes_sec;
+ void (*on_new_packets_available_cb)(void *priv, u8 *buf, u32 npackets);
+ u32 ts_buf_sz;
+ u64 pcr_period_usecs;
+ u64 si_period_usecs;
+ u16 pcr_pid;
+ void *priv;
+};
+
+struct vidtv_mux *vidtv_mux_init(struct vidtv_mux_init_args args);
+void vidtv_mux_destroy(struct vidtv_mux *m);
+
+void vidtv_mux_start_thread(struct vidtv_mux *m);
+void vidtv_mux_stop_thread(struct vidtv_mux *m);
+
+#endif //VIDTV_MUX_H
diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.c b/drivers/media/test-drivers/vidtv/vidtv_psi.c
index 191d37a248923..ac7a52f03843a 100644
--- a/drivers/media/test-drivers/vidtv/vidtv_psi.c
+++ b/drivers/media/test-drivers/vidtv/vidtv_psi.c
@@ -1135,3 +1135,21 @@ vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat,
program = program->next;
}
}
+
+struct vidtv_psi_table_pmt
+*vidtv_psi_find_pmt_sec(struct vidtv_psi_table_pmt pmt_sections[],
+ u16 nsections,
+ u16 program_num)
+{
+ /* find the PMT section associated with 'program_num' */
+ struct vidtv_psi_table_pmt *sec = NULL;
+ u32 i;
+
+ for (i = 0; i < nsections; ++i) {
+ sec = &pmt_sections[i];
+ if (sec->header.id == program_num)
+ return sec;
+ }
+
+ return NULL;
+}
diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.h b/drivers/media/test-drivers/vidtv/vidtv_psi.h
index c5c8c143f0e4a..7aa502b89a2e2 100644
--- a/drivers/media/test-drivers/vidtv/vidtv_psi.h
+++ b/drivers/media/test-drivers/vidtv/vidtv_psi.h
@@ -354,4 +354,9 @@ u32 vidtv_psi_pmt_write_into(char *buf,
u32 buf_sz,
u8 *continuity_counter);

+struct vidtv_psi_table_pmt
+*vidtv_psi_find_pmt_sec(struct vidtv_psi_table_pmt *pmt_sections,
+ u16 nsections,
+ u16 program_num);
+
#endif // VIDTV_PSI_H
--
2.26.2

2020-05-02 05:01:15

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 01/11] media: vidtv: add Kconfig entry

Em Sat, 2 May 2020 00:22:06 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> From: "Daniel W. S. Almeida" <[email protected]>
>
> Add the necessary Kconfig entries and a dummy Makefile to compile the new
> virtual DVB test driver (vidtv).

Patch looks good. Please notice that this should be the last patch at the
series, as otherwise it would break git bisect.

>
> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> ---
> drivers/media/test-drivers/Kconfig | 10 ++++++++++
> drivers/media/test-drivers/Makefile | 1 +
> drivers/media/test-drivers/vidtv/Kconfig | 11 +++++++++++
> drivers/media/test-drivers/vidtv/Makefile | 2 ++
> 4 files changed, 24 insertions(+)
> create mode 100644 drivers/media/test-drivers/vidtv/Kconfig
> create mode 100644 drivers/media/test-drivers/vidtv/Makefile
>
> diff --git a/drivers/media/test-drivers/Kconfig b/drivers/media/test-drivers/Kconfig
> index 188381c855939..7d273a8a7acc2 100644
> --- a/drivers/media/test-drivers/Kconfig
> +++ b/drivers/media/test-drivers/Kconfig
> @@ -4,6 +4,10 @@ menuconfig V4L_TEST_DRIVERS
> bool "V4L test drivers"
> depends on VIDEO_DEV
>
> +menuconfig DVB_TEST_DRIVERS
> + bool "DVB test drivers"
> + depends on DVB_CORE && MEDIA_SUPPORT && I2C
> +
> if V4L_TEST_DRIVERS
>
> source "drivers/media/test-drivers/vimc/Kconfig"
> @@ -24,3 +28,9 @@ config VIDEO_VIM2M
> source "drivers/media/test-drivers/vicodec/Kconfig"
>
> endif #V4L_TEST_DRIVERS
> +
> +if DVB_TEST_DRIVERS
> +
> +source "drivers/media/test-drivers/vidtv/Kconfig"
> +
> +endif #DVB_TEST_DRIVERS
> diff --git a/drivers/media/test-drivers/Makefile b/drivers/media/test-drivers/Makefile
> index 74410d3a9f2d2..9f0e4ebb2efe7 100644
> --- a/drivers/media/test-drivers/Makefile
> +++ b/drivers/media/test-drivers/Makefile
> @@ -7,3 +7,4 @@ obj-$(CONFIG_VIDEO_VIMC) += vimc/
> obj-$(CONFIG_VIDEO_VIVID) += vivid/
> obj-$(CONFIG_VIDEO_VIM2M) += vim2m.o
> obj-$(CONFIG_VIDEO_VICODEC) += vicodec/
> +obj-$(CONFIG_DVB_VIDTV) += vidtv/
> diff --git a/drivers/media/test-drivers/vidtv/Kconfig b/drivers/media/test-drivers/vidtv/Kconfig
> new file mode 100644
> index 0000000000000..22c4fd39461f1
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/Kconfig
> @@ -0,0 +1,11 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config DVB_VIDTV
> + tristate "Virtual DVB Driver (vidtv)"
> + depends on DVB_CORE && MEDIA_SUPPORT && I2C
> + help
> + The virtual DVB test driver serves as a reference DVB driver and helps
> + validate the existing APIs in the media subsystem. It can also aid developers
> + working on userspace applications.
> +
> +
> + When in doubt, say N.
> diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
> new file mode 100644
> index 0000000000000..d1558d84eeaed
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/Makefile
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0
> +



Thanks,
Mauro

2020-05-02 05:34:56

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 02/11] media: vidtv: implement a tuner driver

Em Sat, 2 May 2020 00:22:07 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> From: "Daniel W. S. Almeida" <[email protected]>
>
> The virtual DVB test driver serves as a reference DVB driver and helps
> validate the existing APIs in the media subsystem. It can also aid developers
> working on userspace applications.
>
> This dummy tuner should support common TV standards such as DVB-T/T2/S/S2,
> ISDB-T and ATSC when completed.
>
> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> ---
> drivers/media/test-drivers/vidtv/Makefile | 1 +
> .../media/test-drivers/vidtv/vidtv_tuner.c | 411 ++++++++++++++++++
> 2 files changed, 412 insertions(+)
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_tuner.c
>
> diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
> index d1558d84eeaed..e625810a82603 100644
> --- a/drivers/media/test-drivers/vidtv/Makefile
> +++ b/drivers/media/test-drivers/vidtv/Makefile
> @@ -1,2 +1,3 @@
> # SPDX-License-Identifier: GPL-2.0
>
> +obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_tuner.c b/drivers/media/test-drivers/vidtv/vidtv_tuner.c
> new file mode 100644
> index 0000000000000..a790508f935b3
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_tuner.c
> @@ -0,0 +1,411 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * The Virtual DVB test driver serves as a reference DVB driver and helps
> + * validate the existing APIs in the media subsystem. It can also aid
> + * developers working on userspace applications.
> + *
> + * The vidtv tuner should support common TV standards such as
> + * DVB-T/T2/S/S2, ISDB-T and ATSC when completed.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#include <linux/errno.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +#include <media/dvb_frontend.h>
> +
> +MODULE_DESCRIPTION("Virtual DTV Tuner");
> +MODULE_AUTHOR("Daniel W. S. Almeida");
> +MODULE_LICENSE("GPL");
> +
> +struct vidtv_tuner_config {
> + struct dvb_frontend *fe;
> + u32 mock_power_up_delay_msec;
> + u32 mock_tune_delay_msec;
> + u32 vidtv_valid_dvb_t_freqs[8];
> + u32 vidtv_valid_dvb_c_freqs[8];
> + u32 vidtv_valid_dvb_s_freqs[8];
> + u8 max_frequency_shift_hz;
> +};
> +
> +struct vidtv_tuner_cnr_to_qual_s {
> + /* attempt to use the same values as libdvbv5 */
> + u32 modulation;
> + u32 fec;
> + u32 cnr_ok, cnr_good;
> +};
> +
> +struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_c_cnr_2_qual[] = {
> + /* from libdvbv5 source code, in milli db */
> + { QAM_256, FEC_NONE, 34000, 38000},
> + { QAM_64, FEC_NONE, 30000, 34000},
> +};
> +
> +struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s_cnr_2_qual[] = {
> + /* from libdvbv5 source code, in milli db */
> + { QPSK, FEC_1_2, 7000, 10000},
> +
> + { QPSK, FEC_2_3, 9000, 12000},
> + { QPSK, FEC_3_4, 10000, 13000},
> + { QPSK, FEC_5_6, 11000, 14000},
> +
> + { QPSK, FEC_7_8, 12000, 15000},
> +};
> +
> +struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s2_cnr_2_qual[] = {
> + /* from libdvbv5 source code, in milli db */
> + { QPSK, FEC_1_2, 9000, 12000},
> + { QPSK, FEC_2_3, 11000, 14000},
> + { QPSK, FEC_3_4, 12000, 15000},
> + { QPSK, FEC_5_6, 12000, 15000},
> + { QPSK, FEC_8_9, 13000, 16000},
> + { QPSK, FEC_9_10, 13500, 16500},
> + { PSK_8, FEC_2_3, 14500, 17500},
> + { PSK_8, FEC_3_4, 16000, 19000},
> + { PSK_8, FEC_5_6, 17500, 20500},
> + { PSK_8, FEC_8_9, 19000, 22000},
> +};
> +
> +static struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_t_cnr_2_qual[] = {
> + /* from libdvbv5 source code, in milli db*/
> + { QPSK, FEC_1_2, 4100, 5900},
> + { QPSK, FEC_2_3, 6100, 9600},
> + { QPSK, FEC_3_4, 7200, 12400},
> + { QPSK, FEC_5_6, 8500, 15600},
> + { QPSK, FEC_7_8, 9200, 17500},
> +
> + { QAM_16, FEC_1_2, 9800, 11800},
> + { QAM_16, FEC_2_3, 12100, 15300},
> + { QAM_16, FEC_3_4, 13400, 18100},
> + { QAM_16, FEC_5_6, 14800, 21300},
> + { QAM_16, FEC_7_8, 15700, 23600},
> +
> + { QAM_64, FEC_1_2, 14000, 16000},
> + { QAM_64, FEC_2_3, 19900, 25400},
> + { QAM_64, FEC_3_4, 24900, 27900},
> + { QAM_64, FEC_5_6, 21300, 23300},
> + { QAM_64, FEC_7_8, 22000, 24000},
> +};
> +
> +struct vidtv_tuner_hardware_state {
> + bool asleep;
> + u32 lock_status;
> + u32 if_frequency;
> + u32 tuned_frequency;
> + u32 bandwidth;
> +};
> +
> +struct vidtv_tuner_dev {
> + struct dvb_frontend *fe;
> + struct vidtv_tuner_hardware_state hw_state;
> + struct vidtv_tuner_config config;
> +};
> +
> +static struct vidtv_tuner_dev*
> +vidtv_tuner_get_dev(struct dvb_frontend *fe)
> +{
> + struct i2c_client *client = fe->tuner_priv;
> +
> + return i2c_get_clientdata(client);
> +}
> +
> +static s32 vidtv_tuner_check_frequency_shift(struct dvb_frontend *fe)
> +{
> + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
> + struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> + struct vidtv_tuner_config config = tuner_dev->config;
> + u32 *valid_freqs = NULL;
> + u32 array_sz = 0;
> + u32 i;
> + u32 shift;
> +
> + switch (c->delivery_system) {
> + case SYS_DVBT:
> + case SYS_DVBT2:
> + valid_freqs = config.vidtv_valid_dvb_t_freqs;
> + array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_t_freqs);
> + break;
> + case SYS_DVBS:
> + case SYS_DVBS2:
> + valid_freqs = config.vidtv_valid_dvb_s_freqs;
> + array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_s_freqs);
> + break;
> + case SYS_DVBC_ANNEX_A:
> + valid_freqs = config.vidtv_valid_dvb_c_freqs;
> + array_sz = ARRAY_SIZE(config.vidtv_valid_dvb_c_freqs);
> + break;
> +
> + default:
> + pr_warn("%s: unsupported delivery system: %u\n",
> + __func__,
> + c->delivery_system);
> + break;

It should instead return -EINVAL here. Ok, it will end using array_sz = 0,
and return an error, but the best would be to just replace "break"
with "return -EINVAL", as it makes the code more readable.

> + }
> +
> + for (i = 0; i < array_sz; i++) {
> + shift = abs(c->frequency - valid_freqs[i]);
> +
> + if (!shift)
> + return 0;
> +
> + /*
> + * This will provide a value from 0 to 100 that would
> + * indicate how far is the tuned frequency from the
> + * right one.
> + */
> + if (shift < config.max_frequency_shift_hz)
> + return shift * 100 / config.max_frequency_shift_hz;
> + }
> +
> + return -1;

use return -EINVAL;

> +}
> +
> +static int
> +vidtv_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength)
> +{
> + struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> + struct vidtv_tuner_cnr_to_qual_s *cnr2qual = NULL;
> + u32 array_size = 0;
> + s32 shift;
> + u32 i;
> +
> + shift = vidtv_tuner_check_frequency_shift(fe);
> +
> + switch (c->delivery_system) {
> + case SYS_DVBT:
> + case SYS_DVBT2:
> + cnr2qual = vidtv_tuner_t_cnr_2_qual;
> + array_size = ARRAY_SIZE(vidtv_tuner_t_cnr_2_qual);
> + break;
> +
> + case SYS_DVBS:
> + cnr2qual = vidtv_tuner_s_cnr_2_qual;
> + array_size = ARRAY_SIZE(vidtv_tuner_s_cnr_2_qual);
> + break;
> +
> + case SYS_DVBS2:
> + cnr2qual = vidtv_tuner_s2_cnr_2_qual;
> + array_size = ARRAY_SIZE(vidtv_tuner_s2_cnr_2_qual);
> + break;
> +
> + case SYS_DVBC_ANNEX_A:
> + cnr2qual = vidtv_tuner_c_cnr_2_qual;
> + array_size = ARRAY_SIZE(vidtv_tuner_c_cnr_2_qual);
> + break;
> +
> + default:
> + pr_warn("%s: unsupported delivery system: %u\n",
> + __func__,
> + c->delivery_system);
> + return -EINVAL;
> + }
> +
> + for (i = 0; i <= array_size; i++) {
> + if (cnr2qual[i].modulation != c->modulation ||
> + cnr2qual[i].fec != c->fec_inner)
> + continue;
> +
> + if (!shift) {
> + *strength = cnr2qual[i].cnr_good;
> + return 0;
> + }
> + if (shift < 0) { /* Channel not tuned */
> + *strength = 0;
> + return 0;
> + }
> + /*
> + * Channel tuned at wrong frequency. Simulate that the
> + * Carrier S/N ratio is not too good.
> + */
> +
> + *strength = cnr2qual[i].cnr_ok -
> + (cnr2qual[i].cnr_good - cnr2qual[i].cnr_ok);
> + return 0;
> + }
> +
> + /*
> + * do a linear interpolation between 34dB and 10dB if we can't
> + * match against the table
> + */
> + *strength = 34 - 24 * shift / 100;
> + return 0;
> +}
> +
> +static int vidtv_tuner_init(struct dvb_frontend *fe)
> +{
> + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
> + struct vidtv_tuner_config config = tuner_dev->config;
> +
> + msleep_interruptible(config.mock_power_up_delay_msec);
> +
> + tuner_dev->hw_state.asleep = false;
> + tuner_dev->hw_state.if_frequency = 5000;
> +
> + return 0;
> +}
> +
> +static int vidtv_tuner_sleep(struct dvb_frontend *fe)
> +{
> + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
> +
> + tuner_dev->hw_state.asleep = true;
> + return 0;
> +}
> +
> +static int vidtv_tuner_suspend(struct dvb_frontend *fe)
> +{
> + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
> +
> + tuner_dev->hw_state.asleep = true;
> + return 0;
> +}
> +
> +static int vidtv_tuner_resume(struct dvb_frontend *fe)
> +{
> + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
> +
> + tuner_dev->hw_state.asleep = false;
> + return 0;
> +}
> +
> +static int vidtv_tuner_set_params(struct dvb_frontend *fe)
> +{
> + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
> + struct vidtv_tuner_config config = tuner_dev->config;
> + struct dtv_frontend_properties *c = &fe->dtv_property_cache;
> + u32 min_freq = fe->ops.tuner_ops.info.frequency_min_hz;
> + u32 max_freq = fe->ops.tuner_ops.info.frequency_max_hz;
> + u32 min_bw = fe->ops.tuner_ops.info.bandwidth_min;
> + u32 max_bw = fe->ops.tuner_ops.info.bandwidth_max;
> +
> + if (c->frequency < min_freq || c->frequency > max_freq ||
> + c->bandwidth_hz < min_bw || c->bandwidth_hz > max_bw) {
> + tuner_dev->hw_state.lock_status = 0;
> + return -EINVAL;
> + }
> +
> + tuner_dev->hw_state.tuned_frequency = c->frequency;
> + tuner_dev->hw_state.bandwidth = c->bandwidth_hz;
> + tuner_dev->hw_state.lock_status = TUNER_STATUS_LOCKED;
> +
> + msleep_interruptible(config.mock_tune_delay_msec);
> + return 0;
> +}
> +
> +static int vidtv_tuner_set_config(struct dvb_frontend *fe,
> + void *priv_cfg)
> +{
> + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
> +
> + memcpy(&tuner_dev->config, priv_cfg, sizeof(tuner_dev->config));
> +
> + return 0;
> +}
> +
> +static int vidtv_tuner_get_frequency(struct dvb_frontend *fe,
> + u32 *frequency)
> +{
> + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
> +
> + *frequency = tuner_dev->hw_state.tuned_frequency;
> +
> + return 0;
> +}
> +
> +static int vidtv_tuner_get_bandwidth(struct dvb_frontend *fe,
> + u32 *bandwidth)
> +{
> + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
> +
> + *bandwidth = tuner_dev->hw_state.bandwidth;
> +
> + return 0;
> +}
> +
> +static int vidtv_tuner_get_if_frequency(struct dvb_frontend *fe,
> + u32 *frequency)
> +{
> + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
> +
> + *frequency = tuner_dev->hw_state.if_frequency;
> +
> + return 0;
> +}
> +
> +static int vidtv_tuner_get_status(struct dvb_frontend *fe, u32 *status)
> +{
> + struct vidtv_tuner_dev *tuner_dev = vidtv_tuner_get_dev(fe);
> +
> + *status = tuner_dev->hw_state.lock_status;
> +
> + return 0;
> +}
> +
> +static const struct dvb_tuner_ops vidtv_tuner_ops = {
> + .init = vidtv_tuner_init,
> + .sleep = vidtv_tuner_sleep,
> + .suspend = vidtv_tuner_suspend,
> + .resume = vidtv_tuner_resume,
> + .set_params = vidtv_tuner_set_params,
> + .set_config = vidtv_tuner_set_config,
> + .get_bandwidth = vidtv_tuner_get_bandwidth,
> + .get_frequency = vidtv_tuner_get_frequency,
> + .get_if_frequency = vidtv_tuner_get_if_frequency,
> + .get_status = vidtv_tuner_get_status,
> + .get_rf_strength = vidtv_tuner_get_signal_strength
> +};
> +
> +static const struct i2c_device_id vidtv_tuner_i2c_id_table[] = {
> + {"vidtv_tuner", 0},
> + {}
> +};
> +MODULE_DEVICE_TABLE(i2c, vidtv_tuner_i2c_id_table);
> +
> +static int vidtv_tuner_i2c_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct vidtv_tuner_config *config = client->dev.platform_data;
> + struct dvb_frontend *fe = config->fe;
> + struct vidtv_tuner_dev *tuner_dev = NULL;
> +
> + tuner_dev = kzalloc(sizeof(*tuner_dev), GFP_KERNEL);
> + if (!tuner_dev)
> + return -ENOMEM;
> +
> + tuner_dev->fe = config->fe;
> + i2c_set_clientdata(client, tuner_dev);
> +
> + memcpy(&fe->ops.tuner_ops,
> + &vidtv_tuner_ops,
> + sizeof(struct dvb_tuner_ops));
> +
> + fe->tuner_priv = client;
> +
> + return 0;
> +}
> +
> +static int vidtv_tuner_i2c_remove(struct i2c_client *client)
> +{
> + struct vidtv_tuner_dev *tuner_dev = i2c_get_clientdata(client);
> + struct dvb_frontend *fe = tuner_dev->fe;
> +
> + memset(&fe->ops.tuner_ops, 0, sizeof(struct dvb_tuner_ops));
> + fe->tuner_priv = NULL;
> + kfree(tuner_dev);
> +
> + return 0;
> +}
> +
> +static struct i2c_driver vidtv_tuner_i2c_driver = {
> + .driver = {
> + .name = "vidtv_tuner",
> + .suppress_bind_attrs = true,
> + },
> + .probe = vidtv_tuner_i2c_probe,
> + .remove = vidtv_tuner_i2c_remove,
> + .id_table = vidtv_tuner_i2c_id_table,
> +};
> +module_i2c_driver(vidtv_tuner_i2c_driver);



Thanks,
Mauro

2020-05-02 06:00:30

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 03/11] media: vidtv: implement a demodulator driver

Em Sat, 2 May 2020 00:22:08 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> From: "Daniel W. S. Almeida" <[email protected]>
>
> Implement a I2C demodulator driver, simulating support for DVB-T, DVB-C
> and DVB-S.
>
> This demodulator will periodically check the signal quality against a table
> and drop the TS lock if it drops below a threshold value, regaining it in
> the event that the signal improves.
>
> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> ---
> drivers/media/test-drivers/vidtv/Makefile | 2 +-
> .../media/test-drivers/vidtv/vidtv_demod.c | 493 ++++++++++++++++++
> .../media/test-drivers/vidtv/vidtv_demod.h | 43 ++
> 3 files changed, 537 insertions(+), 1 deletion(-)
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_demod.c
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_demod.h
>
> diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
> index e625810a82603..36ba00ddc0d1e 100644
> --- a/drivers/media/test-drivers/vidtv/Makefile
> +++ b/drivers/media/test-drivers/vidtv/Makefile
> @@ -1,3 +1,3 @@
> # SPDX-License-Identifier: GPL-2.0
>
> -obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o
> +obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.c b/drivers/media/test-drivers/vidtv/vidtv_demod.c
> new file mode 100644
> index 0000000000000..e5f157e4bbe48
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.c
> @@ -0,0 +1,493 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * The Virtual DVB test driver serves as a reference DVB driver and helps
> + * validate the existing APIs in the media subsystem. It can also aid
> + * developers working on userspace applications.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + * Based on the example driver written by Emard <[email protected]>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/string.h>
> +#include <linux/slab.h>
> +#include <linux/workqueue.h>
> +#include <linux/random.h>
> +#include <linux/errno.h>
> +#include <linux/i2c.h>
> +#include <media/dvb_frontend.h>
> +#include "vidtv_demod.h"
> +
> +MODULE_DESCRIPTION("Virtual DVB Demodulator Driver");
> +MODULE_AUTHOR("Daniel W. S. Almeida");
> +MODULE_LICENSE("GPL");
> +
> +struct vidtv_demod_cnr_to_qual_s vidtv_demod_c_cnr_2_qual[] = {
> + /* from libdvbv5 source code, in milli db */
> + { QAM_256, FEC_NONE, 34000, 38000},
> + { QAM_64, FEC_NONE, 30000, 34000},
> +};
> +
> +struct vidtv_demod_cnr_to_qual_s vidtv_demod_s_cnr_2_qual[] = {
> + /* from libdvbv5 source code, in milli db */
> + { QPSK, FEC_1_2, 7000, 10000},
> + { QPSK, FEC_2_3, 9000, 12000},
> + { QPSK, FEC_3_4, 10000, 13000},
> + { QPSK, FEC_5_6, 11000, 14000},
> + { QPSK, FEC_7_8, 12000, 15000},
> +};
> +
> +struct vidtv_demod_cnr_to_qual_s vidtv_demod_s2_cnr_2_qual[] = {
> + /* from libdvbv5 source code, in milli db */
> + { QPSK, FEC_1_2, 9000, 12000},
> + { QPSK, FEC_2_3, 11000, 14000},
> + { QPSK, FEC_3_4, 12000, 15000},
> + { QPSK, FEC_5_6, 12000, 15000},
> + { QPSK, FEC_8_9, 13000, 16000},
> + { QPSK, FEC_9_10, 13500, 16500},
> + { PSK_8, FEC_2_3, 14500, 17500},
> + { PSK_8, FEC_3_4, 16000, 19000},
> + { PSK_8, FEC_5_6, 17500, 20500},
> + { PSK_8, FEC_8_9, 19000, 22000},
> +};
> +
> +static struct vidtv_demod_cnr_to_qual_s vidtv_demod_t_cnr_2_qual[] = {
> + /* from libdvbv5 source code, in milli db*/
> + { QPSK, FEC_1_2, 4100, 5900},
> + { QPSK, FEC_2_3, 6100, 9600},
> + { QPSK, FEC_3_4, 7200, 12400},
> + { QPSK, FEC_5_6, 8500, 15600},
> + { QPSK, FEC_7_8, 9200, 17500},
> +
> + { QAM_16, FEC_1_2, 9800, 11800},
> + { QAM_16, FEC_2_3, 12100, 15300},
> + { QAM_16, FEC_3_4, 13400, 18100},
> + { QAM_16, FEC_5_6, 14800, 21300},
> + { QAM_16, FEC_7_8, 15700, 23600},
> +
> + { QAM_64, FEC_1_2, 14000, 16000},
> + { QAM_64, FEC_2_3, 19900, 25400},
> + { QAM_64, FEC_3_4, 24900, 27900},
> + { QAM_64, FEC_5_6, 21300, 23300},
> + { QAM_64, FEC_7_8, 22000, 24000},
> +};
> +
> +static struct vidtv_demod_cnr_to_qual_s
> +*vidtv_match_cnr_s(struct dvb_frontend *fe)
> +{
> + struct dtv_frontend_properties *c;
> + struct vidtv_demod_cnr_to_qual_s *cnr2qual = NULL;
> + u32 array_size = 0;
> + u32 i;
> +
> + c = &fe->dtv_property_cache;
> +
> + switch (c->delivery_system) {
> + case SYS_DVBT:
> + case SYS_DVBT2:
> + cnr2qual = vidtv_demod_t_cnr_2_qual;
> + array_size = ARRAY_SIZE(vidtv_demod_t_cnr_2_qual);
> + break;
> +
> + case SYS_DVBS:
> + cnr2qual = vidtv_demod_s_cnr_2_qual;
> + array_size = ARRAY_SIZE(vidtv_demod_s_cnr_2_qual);
> + break;
> +
> + case SYS_DVBS2:
> + cnr2qual = vidtv_demod_s2_cnr_2_qual;
> + array_size = ARRAY_SIZE(vidtv_demod_s2_cnr_2_qual);
> + break;
> +
> + case SYS_DVBC_ANNEX_A:
> + cnr2qual = vidtv_demod_c_cnr_2_qual;
> + array_size = ARRAY_SIZE(vidtv_demod_c_cnr_2_qual);
> + break;
> +
> + default:
> + pr_warn("%s: unsupported delivery system: %u\n",
> + __func__,
> + c->delivery_system);
> + break;
> + }
> +
> + for (i = 0; i <= array_size; i++)
> + if (cnr2qual[i].modulation == c->modulation &&
> + cnr2qual[i].fec == c->fec_inner)
> + return &cnr2qual[i];
> +
> + return NULL; /* not found */
> +}
> +
> +static void vidtv_demod_poll_snr_handler(struct work_struct *work)
> +{
> + /*
> + * periodically check the signal quality and eventually
> + * lose the TS lock if it dips too low
> + */
> + struct vidtv_demod_state *state;
> + struct dtv_frontend_properties *c;
> + struct vidtv_demod_cnr_to_qual_s *cnr2qual = NULL;
> + struct vidtv_demod_config *config;
> + u16 snr = 0;
> +
> + state = container_of(work, struct vidtv_demod_state, poll_snr.work);
> + c = &state->frontend.dtv_property_cache;
> + config = &state->config;
> +
> + if (!state->frontend.ops.tuner_ops.get_rf_strength)
> + return;
> +
> + state->frontend.ops.tuner_ops.get_rf_strength(&state->frontend, &snr);
> +
> + cnr2qual = vidtv_match_cnr_s(&state->frontend);
> + if (!cnr2qual)
> + return;
> +
> + if (snr < cnr2qual->cnr_ok) {
> + /* eventually lose the TS lock */
> + if (prandom_u32_max(100) < config->drop_tslock_prob_on_low_snr)
> + state->status = 0;
> + } else {
> + /* recover if the signal improves */
> + if (prandom_u32_max(100) <
> + config->recover_tslock_prob_on_good_snr)
> + state->status = FE_HAS_SIGNAL |
> + FE_HAS_CARRIER |
> + FE_HAS_VITERBI |
> + FE_HAS_SYNC |
> + FE_HAS_LOCK;
> + }
> +
> + schedule_delayed_work(&state->poll_snr, msecs_to_jiffies(2000));

I would place the time (2000) on a #define at the top.

> +}
> +
> +static int vidtv_demod_read_status(struct dvb_frontend *fe,
> + enum fe_status *status)
> +{
> + struct vidtv_demod_state *state = fe->demodulator_priv;
> +
> + *status = state->status;
> +
> + return 0;
> +}

> +static int vidtv_demod_read_ber(struct dvb_frontend *fe, u32 *ber)
> +{
> + *ber = 0;
> + return 0;
> +}
> +
> +static int vidtv_demod_read_signal_strength(struct dvb_frontend *fe,
> + u16 *strength)
> +{
> + *strength = 0;
> + return 0;
> +}
> +
> +static int vidtv_demod_read_snr(struct dvb_frontend *fe, u16 *snr)
> +{
> + *snr = 0;
> + return 0;
> +}
> +
> +static int vidtv_demod_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
> +{
> + *ucblocks = 0;
> + return 0;
> +}

Hmm... I'm actually in doubt about the above legacy DVB-3 ops.

Let's keep it for now, but I may submit a patch to dvb-core for it
to use the data from the cache, if someone use those legacy ioctls
and the corresponding fe->ops is not defined by a demod.

> +
> +/*
> + * Should only be implemented if it actually reads something from the hardware.
> + * Also, it should check for the locks, in order to avoid report wrong data
> + * to userspace.
> + */

I would rewrite it as:

/*
* NOTE:
* This is implemented here just to be used as an example for real
* demod drivers.
*
* Should only be implemented if it actually reads something from the hardware.
* Also, it should check for the locks, in order to avoid report wrong data
* to userspace.
*/


> +static int vidtv_demod_get_frontend(struct dvb_frontend *fe,
> + struct dtv_frontend_properties *p)
> +{
> + return 0;
> +}
> +
> +static int vidtv_demod_set_frontend(struct dvb_frontend *fe)
> +{
> + struct vidtv_demod_state *state = fe->demodulator_priv;
> + struct vidtv_demod_cnr_to_qual_s *cnr2qual = NULL;
> + u32 tuner_status = 0;
> +
> + if (fe->ops.tuner_ops.set_params) {
> + fe->ops.tuner_ops.set_params(fe);
> +
> + /* store the CNR returned by the tuner */
> + fe->ops.tuner_ops.get_rf_strength(fe, &state->tuner_cnr);
> +
> + fe->ops.tuner_ops.get_status(fe, &tuner_status);
> + state->status = (state->tuner_cnr > 0) ? FE_HAS_SIGNAL |
> + FE_HAS_CARRIER |
> + FE_HAS_VITERBI |
> + FE_HAS_SYNC |
> + FE_HAS_LOCK :
> + 0;
> + cnr2qual = vidtv_match_cnr_s(fe);
> +
> + /* signal isn't good: might lose the lock eventually */
> + if (tuner_status == TUNER_STATUS_LOCKED &&
> + state->tuner_cnr < cnr2qual->cnr_good) {
> + schedule_delayed_work(&state->poll_snr,
> + msecs_to_jiffies(2000));

Use the same define as above mentioned for the msecs time.

> +
> + state->poll_snr_thread_running = true;
> + }
> +
> + if (fe->ops.i2c_gate_ctrl)
> + fe->ops.i2c_gate_ctrl(fe, 0);
> + }
> +
> + return 0;
> +}
> +
> +static int vidtv_demod_sleep(struct dvb_frontend *fe)
> +{
> + struct vidtv_demod_state *state = fe->demodulator_priv;
> +
> + if (state->poll_snr_thread_running) {
> + cancel_delayed_work_sync(&state->poll_snr);
> + state->poll_snr_thread_running = false;
> + state->poll_snr_thread_restart = true;
> + }
> + return 0;
> +}
> +
> +static int vidtv_demod_init(struct dvb_frontend *fe)
> +{
> + struct vidtv_demod_state *state = fe->demodulator_priv;
> + u32 tuner_status = 0;
> +
> + if (state->cold_start)
> + INIT_DELAYED_WORK(&state->poll_snr,
> + &vidtv_demod_poll_snr_handler);
> +
> + /*
> + * At resume, start the snr poll thread only if it was suspended with
> + * the thread running. Extra care should be taken here, as some tuner
> + * status change might happen at resume time (for example, due to a
> + * ioctl syscall to set_frontend, or due to a release syscall).

typo: "a ioctl" -> "an ioctl"

> + */
> + fe->ops.tuner_ops.get_status(fe, &tuner_status);
> +
> + if (tuner_status == TUNER_STATUS_LOCKED &&
> + state->poll_snr_thread_restart) {
> + schedule_delayed_work(&state->poll_snr,
> + msecs_to_jiffies(2000));

Same note about the timing constant. Keep all those "2000" inside
something like:

$define POLL_THREAD_TIME 2000 /* msecs */

> +
> + state->poll_snr_thread_restart = false;
> + }
> +
> + state->cold_start = false;
> + return 0;
> +}
> +


> +static int vidtv_demod_set_tone(struct dvb_frontend *fe,
> + enum fe_sec_tone_mode tone)
> +{
> + return 0;
> +}
> +
> +static int vidtv_demod_set_voltage(struct dvb_frontend *fe,
> + enum fe_sec_voltage voltage)
> +{
> + return 0;
> +}


It would be worth adding a note for each of the above stating
that they should be implemented only if if the demod has support
for DVB/S and/or DVB/S2

> +static void vidtv_demod_release(struct dvb_frontend *fe)
> +{
> + struct vidtv_demod_state *state = fe->demodulator_priv;
> +
> + if (state->poll_snr_thread_running)
> + cancel_delayed_work_sync(&state->poll_snr);
> +
> + kfree(state);
> +}
> +
> +static const struct dvb_frontend_ops vidtv_demod_ofdm_ops = {
> + .delsys = { SYS_DVBT },
> + .info = {
> + .name = "Dummy DVB-T",
> + .frequency_min_hz = 0,
> + .frequency_max_hz = 863250 * kHz,
> + .frequency_stepsize_hz = 62500,
> + .caps = FE_CAN_FEC_1_2 |
> + FE_CAN_FEC_2_3 |
> + FE_CAN_FEC_3_4 |
> + FE_CAN_FEC_4_5 |
> + FE_CAN_FEC_5_6 |
> + FE_CAN_FEC_6_7 |
> + FE_CAN_FEC_7_8 |
> + FE_CAN_FEC_8_9 |
> + FE_CAN_FEC_AUTO |
> + FE_CAN_QAM_16 |
> + FE_CAN_QAM_64 |
> + FE_CAN_QAM_AUTO |
> + FE_CAN_TRANSMISSION_MODE_AUTO |
> + FE_CAN_GUARD_INTERVAL_AUTO |
> + FE_CAN_HIERARCHY_AUTO,
> + },
> +
> + .release = vidtv_demod_release,
> +
> + .init = vidtv_demod_init,
> + .sleep = vidtv_demod_sleep,
> +
> + .set_frontend = vidtv_demod_set_frontend,
> + .get_frontend = vidtv_demod_get_frontend,
> +
> + .read_status = vidtv_demod_read_status,
> + .read_ber = vidtv_demod_read_ber,
> + .read_signal_strength = vidtv_demod_read_signal_strength,
> + .read_snr = vidtv_demod_read_snr,
> + .read_ucblocks = vidtv_demod_read_ucblocks,
> +};
> +
> +static const struct dvb_frontend_ops vidtv_demod_qam_ops = {
> + .delsys = { SYS_DVBC_ANNEX_A },
> + .info = {
> + .name = "Dummy DVB-C",
> + .frequency_min_hz = 51 * MHz,
> + .frequency_max_hz = 858 * MHz,
> + .frequency_stepsize_hz = 62500,
> + /* symbol_rate_min: SACLK/64 == (XIN/2)/64 */
> + .symbol_rate_min = (57840000 / 2) / 64,
> + .symbol_rate_max = (57840000 / 2) / 4, /* SACLK/4 */
> + .caps = FE_CAN_QAM_16 |
> + FE_CAN_QAM_32 |
> + FE_CAN_QAM_64 |
> + FE_CAN_QAM_128 |
> + FE_CAN_QAM_256 |
> + FE_CAN_FEC_AUTO |
> + FE_CAN_INVERSION_AUTO
> + },
> +
> + .release = vidtv_demod_release,
> +
> + .init = vidtv_demod_init,
> + .sleep = vidtv_demod_sleep,
> +
> + .set_frontend = vidtv_demod_set_frontend,
> + .get_frontend = vidtv_demod_get_frontend,
> +
> + .read_status = vidtv_demod_read_status,
> + .read_ber = vidtv_demod_read_ber,
> + .read_signal_strength = vidtv_demod_read_signal_strength,
> + .read_snr = vidtv_demod_read_snr,
> + .read_ucblocks = vidtv_demod_read_ucblocks,
> +};
> +
> +static const struct dvb_frontend_ops vidtv_demod_qpsk_ops = {
> + .delsys = { SYS_DVBS },
> + .info = {
> + .name = "Dummy DVB-S",
> + .frequency_min_hz = 950 * MHz,
> + .frequency_max_hz = 2150 * MHz,
> + .frequency_stepsize_hz = 250 * kHz,
> + .frequency_tolerance_hz = 29500 * kHz,
> + .symbol_rate_min = 1000000,
> + .symbol_rate_max = 45000000,
> + .caps = FE_CAN_INVERSION_AUTO |
> + FE_CAN_FEC_1_2 |
> + FE_CAN_FEC_2_3 |
> + FE_CAN_FEC_3_4 |
> + FE_CAN_FEC_5_6 |
> + FE_CAN_FEC_7_8 |
> + FE_CAN_FEC_AUTO |
> + FE_CAN_QPSK
> + },
> +
> + .release = vidtv_demod_release,
> +
> + .init = vidtv_demod_init,
> + .sleep = vidtv_demod_sleep,
> +
> + .set_frontend = vidtv_demod_set_frontend,
> + .get_frontend = vidtv_demod_get_frontend,
> +
> + .read_status = vidtv_demod_read_status,
> + .read_ber = vidtv_demod_read_ber,
> + .read_signal_strength = vidtv_demod_read_signal_strength,
> + .read_snr = vidtv_demod_read_snr,
> + .read_ucblocks = vidtv_demod_read_ucblocks,
> +
> + .set_voltage = vidtv_demod_set_voltage,
> + .set_tone = vidtv_demod_set_tone,
> +};

The recommended way is to place everything on a single entry:

static const struct dvb_frontend_ops vidtv_demod_ops = {
.delsys = {
SYS_DVBT,
SYS_DVBT2,
SYS_DVBC_ANNEX_A,
SYS_DVBS,
SYS_DVBS2,
},
.info = {
.name = "Dummy demod for DVB-T/T2/C/S/S2",
.frequency_min_hz = 51 * MHz,
.frequency_max_hz = 2150 * MHz,
.frequency_stepsize_hz = 62500,
.frequency_tolerance_hz = 29500 * kHz,
.symbol_rate_min = 1000000,
.symbol_rate_max = 45000000,

.caps = FE_CAN_FEC_1_2 |
FE_CAN_FEC_2_3 |
FE_CAN_FEC_3_4 |
FE_CAN_FEC_4_5 |
FE_CAN_FEC_5_6 |
FE_CAN_FEC_6_7 |
FE_CAN_FEC_7_8 |
FE_CAN_FEC_8_9 |
FE_CAN_FEC_AUTO |
FE_CAN_QAM_16 |
FE_CAN_QAM_64 |
FE_CAN_QAM_32 |
FE_CAN_QAM_128 |
FE_CAN_QAM_256 |
FE_CAN_QAM_AUTO |
FE_CAN_QPSK |
FE_CAN_FEC_AUTO |
FE_CAN_INVERSION_AUTO |
FE_CAN_TRANSMISSION_MODE_AUTO |
FE_CAN_GUARD_INTERVAL_AUTO |
FE_CAN_HIERARCHY_AUTO,
},

.release = vidtv_demod_release,

.init = vidtv_demod_init,
.sleep = vidtv_demod_sleep,

.set_frontend = vidtv_demod_set_frontend,
.get_frontend = vidtv_demod_get_frontend,

.read_status = vidtv_demod_read_status,
.read_ber = vidtv_demod_read_ber,
.read_signal_strength = vidtv_demod_read_signal_strength,
.read_snr = vidtv_demod_read_snr,
.read_ucblocks = vidtv_demod_read_ucblocks,


/* For DVB-S/S2 */
.set_voltage = vidtv_demod_set_voltage,
.set_tone = vidtv_demod_set_tone,
};

> +
> +static const struct i2c_device_id vidtv_demod_i2c_id_table[] = {
> + {"vidtv_demod", 0},
> + {}
> +};
> +MODULE_DEVICE_TABLE(i2c, vidtv_demod_i2c_id_table);
> +
> +static int vidtv_demod_i2c_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct vidtv_demod_config *config = client->dev.platform_data;
> + struct vidtv_demod_state *state;
> + const struct dvb_frontend_ops *ops;
> +
> + /* allocate memory for the internal state */
> + state = kzalloc(sizeof(*state), GFP_KERNEL);
> + if (!state)
> + return -ENOMEM;
> +
> + switch (config->chosen_delsys) {
> + case SYS_DVBT:
> + ops = &vidtv_demod_ofdm_ops;
> + break;
> + case SYS_DVBC_ANNEX_A:
> + ops = &vidtv_demod_qam_ops;
> + break;
> + case SYS_DVBS:
> + ops = &vidtv_demod_qpsk_ops;
> + break;
> + default:
> + pr_err("%s: Unsupported delivery system. Falling back to DVB-T",
> + __func__);
> + ops = &vidtv_demod_ofdm_ops;
> + break;
> + }

By placing everything into a single entry, you can get rid of the above.

Nowadays, most demods are multi-standard. So, let's teach people to write
just a single entry for it, instead of the legacy way of one entry per
each delivery system.

> +
> + /* create dvb_frontend */
> + memcpy(&state->frontend.ops,
> + ops,
> + sizeof(struct dvb_frontend_ops));
> +
> + state->frontend.demodulator_priv = state;
> + /* return the pointer to the bridge driver */
> + config->frontend = &state->frontend;
> + i2c_set_clientdata(client, state);
> +
> + return 0;
> +}
> +
> +static int vidtv_demod_i2c_remove(struct i2c_client *client)
> +{
> + struct vidtv_demod_state *state = i2c_get_clientdata(client);
> +
> + memset(&state->frontend.ops, 0, sizeof(struct dvb_frontend_ops));
> + state->frontend.demodulator_priv = NULL;
> + kfree(state);
> +
> + return 0;
> +}
> +
> +static struct i2c_driver vidtv_demod_i2c_driver = {
> + .driver = {
> + .name = "vidtv_demod",
> + .suppress_bind_attrs = true,
> + },
> + .probe = vidtv_demod_i2c_probe,
> + .remove = vidtv_demod_i2c_remove,
> + .id_table = vidtv_demod_i2c_id_table,
> +};
> +
> +module_i2c_driver(vidtv_demod_i2c_driver);
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.h b/drivers/media/test-drivers/vidtv/vidtv_demod.h
> new file mode 100644
> index 0000000000000..49c2a43f71661
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * The Virtual DTV test driver serves as a reference DVB driver and helps
> + * validate the existing APIs in the media subsystem. It can also aid
> + * developers working on userspace applications.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + * Based on the example driver written by Emard <[email protected]>
> + */
> +
> +#ifndef VIDTV_DEMOD_H
> +#define VIDTV_DEMOD_H
> +
> +#include <linux/dvb/frontend.h>
> +#include <media/dvb_frontend.h>
> +
> +struct vidtv_demod_cnr_to_qual_s {
> + /* attempt to use the same values as libdvbv5 */
> + u32 modulation;
> + u32 fec;
> + u32 cnr_ok, cnr_good;
> +};
> +
> +struct vidtv_demod_config {
> + struct dvb_frontend *frontend;
> + /* probability of losing the lock due to low snr */
> + u8 drop_tslock_prob_on_low_snr;
> + /* probability of recovering when the signal improves */
> + u8 recover_tslock_prob_on_good_snr;
> + u8 chosen_delsys;
> +};
> +
> +struct vidtv_demod_state {
> + struct dvb_frontend frontend;
> + struct vidtv_demod_config config;
> + struct delayed_work poll_snr;
> + enum fe_status status;
> + u16 tuner_cnr;
> + bool cold_start;
> + bool poll_snr_thread_running;
> + bool poll_snr_thread_restart;
> +};
> +#endif // VIDTV_DEMOD_H



Thanks,
Mauro

2020-05-02 06:04:44

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 04/11] media: vidtv: move config structs into a separate header

Em Sat, 2 May 2020 00:22:09 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> From: "Daniel W. S. Almeida" <[email protected]>
>
> Move config structs to a common header so they can be used by the bridge
> driver and by their respective drivers.
>
> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> ---
> .../media/test-drivers/vidtv/vidtv_config.h | 35 +++++++++++++++++++
> .../media/test-drivers/vidtv/vidtv_demod.c | 1 +
> .../media/test-drivers/vidtv/vidtv_demod.h | 9 -----
> .../media/test-drivers/vidtv/vidtv_tuner.c | 12 ++-----

Hmm... We generally use "foo.h" for kAPI definitions (like config structs)
and "foo-priv.h" for internal structs used within the driver.

So, I would be expecting a "vidtv_tuner.h" with kAPI definitions for
"vid_tuner.c" and a "vidtv_demod.h" for kAPI definitions for the
"vidtv_demod.c" driver.


> 4 files changed, 38 insertions(+), 19 deletions(-)
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_config.h
>
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_config.h b/drivers/media/test-drivers/vidtv/vidtv_config.h
> new file mode 100644
> index 0000000000000..7b95bf2444556
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_config.h
> @@ -0,0 +1,35 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * The Virtual DTV test driver serves as a reference DVB driver and helps
> + * validate the existing APIs in the media subsystem. It can also aid
> + * developers working on userspace applications.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#ifndef VIDTV_CONFIG_H
> +#define VIDTV_CONFIG_H
> +
> +#include <linux/types.h>
> +#include <media/dvb_frontend.h>
> +
> +struct vidtv_tuner_config {
> + struct dvb_frontend *fe;
> + u32 mock_power_up_delay_msec;
> + u32 mock_tune_delay_msec;
> + u32 vidtv_valid_dvb_t_freqs[8];
> + u32 vidtv_valid_dvb_c_freqs[8];
> + u32 vidtv_valid_dvb_s_freqs[8];
> + u8 max_frequency_shift_hz;
> +};
> +
> +struct vidtv_demod_config {
> + struct dvb_frontend *frontend;
> + /* probability of losing the lock due to low snr */
> + u8 drop_tslock_prob_on_low_snr;
> + /* probability of recovering when the signal improves */
> + u8 recover_tslock_prob_on_good_snr;
> + u8 chosen_delsys;
> +};
> +
> +#endif //VIDTV_CONFIG_H
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.c b/drivers/media/test-drivers/vidtv/vidtv_demod.c
> index e5f157e4bbe48..15436e565a7b0 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_demod.c
> +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.c
> @@ -19,6 +19,7 @@
> #include <linux/i2c.h>
> #include <media/dvb_frontend.h>
> #include "vidtv_demod.h"
> +#include "vidtv_config.h"
>
> MODULE_DESCRIPTION("Virtual DVB Demodulator Driver");
> MODULE_AUTHOR("Daniel W. S. Almeida");
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.h b/drivers/media/test-drivers/vidtv/vidtv_demod.h
> index 49c2a43f71661..269855efb77f3 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_demod.h
> +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.h
> @@ -21,15 +21,6 @@ struct vidtv_demod_cnr_to_qual_s {
> u32 cnr_ok, cnr_good;
> };
>
> -struct vidtv_demod_config {
> - struct dvb_frontend *frontend;
> - /* probability of losing the lock due to low snr */
> - u8 drop_tslock_prob_on_low_snr;
> - /* probability of recovering when the signal improves */
> - u8 recover_tslock_prob_on_good_snr;
> - u8 chosen_delsys;
> -};
> -
> struct vidtv_demod_state {
> struct dvb_frontend frontend;
> struct vidtv_demod_config config;
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_tuner.c b/drivers/media/test-drivers/vidtv/vidtv_tuner.c
> index a790508f935b3..ece4a94b0c3ac 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_tuner.c
> +++ b/drivers/media/test-drivers/vidtv/vidtv_tuner.c
> @@ -17,20 +17,12 @@
> #include <linux/types.h>
> #include <media/dvb_frontend.h>
>
> +#include "vidtv_config.h"
> +
> MODULE_DESCRIPTION("Virtual DTV Tuner");
> MODULE_AUTHOR("Daniel W. S. Almeida");
> MODULE_LICENSE("GPL");
>
> -struct vidtv_tuner_config {
> - struct dvb_frontend *fe;
> - u32 mock_power_up_delay_msec;
> - u32 mock_tune_delay_msec;
> - u32 vidtv_valid_dvb_t_freqs[8];
> - u32 vidtv_valid_dvb_c_freqs[8];
> - u32 vidtv_valid_dvb_s_freqs[8];
> - u8 max_frequency_shift_hz;
> -};
> -
> struct vidtv_tuner_cnr_to_qual_s {
> /* attempt to use the same values as libdvbv5 */
> u32 modulation;



Thanks,
Mauro

2020-05-02 06:32:28

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 05/11] media: vidtv: add a bridge driver

Em Sat, 2 May 2020 00:22:10 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> From: "Daniel W. S. Almeida" <[email protected]>
>
> Digital TV devices consist of several independent hardware components which
> are controlled by different drivers.
> Each media device is controlled by a group of cooperating drivers with the
> bridge driver as the main driver.
>
> This patch adds a bridge driver for the Virtual Digital TV driver [vidtv].
>
> The bridge driver binds to the other drivers, that is, vidtv_tuner and
> vidtv_demod and implements the digital demux logic, providing userspace
> with a MPEG Transport Stream.
>
> Move config structs to a common header so they can be used by the bridge
> driver and by their respective drivers.
>
> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> ---
> drivers/media/test-drivers/vidtv/Makefile | 2 +-
> .../media/test-drivers/vidtv/vidtv_bridge.c | 379 ++++++++++++++++++
> .../media/test-drivers/vidtv/vidtv_bridge.h | 37 ++
> 3 files changed, 417 insertions(+), 1 deletion(-)
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_bridge.c
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_bridge.h
>
> diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
> index 36ba00ddc0d1e..a9f1993dd5443 100644
> --- a/drivers/media/test-drivers/vidtv/Makefile
> +++ b/drivers/media/test-drivers/vidtv/Makefile
> @@ -1,3 +1,3 @@
> # SPDX-License-Identifier: GPL-2.0
>
> -obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o
> +obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c
> new file mode 100644
> index 0000000000000..05ca4027c869f
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c
> @@ -0,0 +1,379 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * The Virtual DTV test driver serves as a reference DVB driver and helps
> + * validate the existing APIs in the media subsystem. It can also aid
> + * developers working on userspace applications.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#include <linux/types.h>
> +#include <linux/moduleparam.h>
> +#include <linux/mutex.h>
> +#include <linux/workqueue.h>
> +#include <linux/time.h>
> +#include "vidtv_bridge.h"
> +
> +#define TS_BUF_MAX_SZ (128 * 188)
> +#define TUNER_DEFAULT_ADDR 0x68
> +#define DEMOD_DEFAULT_ADDR 0x60
> +
> +MODULE_AUTHOR("Daniel W. S. Almeida");
> +MODULE_LICENSE("GPL");
> +

> +static unsigned int drop_tslock_prob_on_low_snr;
> +module_param(drop_tslock_prob_on_low_snr, uint, 0644);
> +MODULE_PARM_DESC(drop_tslock_prob_on_low_snr,
> + "Probability of losing the TS lock if the signal quality is bad");
> +
> +static unsigned int recover_tslock_prob_on_good_snr;
> +module_param(recover_tslock_prob_on_good_snr, uint, 0644);
> +MODULE_PARM_DESC(recover_tslock_prob_on_good_snr,
> + "Probability recovering the TS lock when the signal improves");

The FS permissions should be reviewed. IMHO, we should allow the
group which owns the /dev to be able to change values when they can
be changed in runtime.

Yet, be sure that changing it on runtime won't cause race conditions.

For the two above, I guess the permissions for those should be, instead,
0664.

> +
> +static unsigned int mock_power_up_delay_msec;
> +module_param(mock_power_up_delay_msec, uint, 0644);
> +MODULE_PARM_DESC(mock_power_up_delay_msec, "Simulate a power up delay");

What's the sense of allowing changing it after the probe? I guess
permissions should be 0444 here.

> +
> +static unsigned int mock_tune_delay_msec;
> +module_param(mock_tune_delay_msec, uint, 0644);
> +MODULE_PARM_DESC(mock_tune_delay_msec, "Simulate a tune delay");

Same here: I guess 0664 would work better.

> +
> +static unsigned int vidtv_valid_dvb_t_freqs[8];
> +module_param_array(vidtv_valid_dvb_t_freqs, uint, NULL, 0644);
> +MODULE_PARM_DESC(vidtv_valid_dvb_t_freqs,
> + "Valid DVB-T frequencies to simulate");
> +
> +static unsigned int vidtv_valid_dvb_c_freqs[8];
> +module_param_array(vidtv_valid_dvb_c_freqs, uint, NULL, 0644);
> +MODULE_PARM_DESC(vidtv_valid_dvb_c_freqs,
> + "Valid DVB-C frequencies to simulate");
> +
> +static unsigned int vidtv_valid_dvb_s_freqs[8];
> +module_param_array(vidtv_valid_dvb_s_freqs, uint, NULL, 0644);
> +MODULE_PARM_DESC(vidtv_valid_dvb_s_freqs,
> + "Valid DVB-C frequencies to simulate");

Can those be changed in runtime without causing race conditions?

> +
> +static unsigned int max_frequency_shift_hz;
> +module_param(max_frequency_shift_hz, uint, 0644);
> +MODULE_PARM_DESC(max_frequency_shift_hz,
> + "Maximum shift in HZ allowed when tuning in a channel");

0664?

> +
> +static unsigned int chosen_delsys = SYS_DVBT;
> +module_param(chosen_delsys, uint, 0644);
> +MODULE_PARM_DESC(chosen_delsys,
> + "The delivery system to simulate. Currently supported: DVB-T, DVB-C, DVB-S");

Hmm... in order to allow changing this value on runtime, as you proposed,
you should use module_param_cb(). The callback would allow touching the
delsys only if the driver is not in use. Otherwise, it would return -EBUSY.
Also, permissions should be 0664.

Btw, after thinking a little bit about that, I would take one step back on
my demod review, changing it and the bridge's logic to allow chosen_delsys,
while keeping just one struct for DVB info parameters.

The way I see is that the logic here should be changed in order to use a
a bitmask for the chosen_delsys, like:

bit 0: DVB-T
bit 1: DVB-T2
bit 2: DVB-C
bit 3: DVB-S
bit 4: DVB-S2

Of course you will need to document the meaning for each bit somewhere.

The default should be to have all supported types enabled.

As there's no ops that would allow the bridge driver to change it
dynamically, you should instead implement the "chosen_delsys" directly
at the demod's driver.

> +static unsigned int ts_buf_sz = 20 * 188;
> +module_param(ts_buf_sz, uint, 0644);
> +MODULE_PARM_DESC(ts_buf_sz, "Optional size for the TS buffer");

Huh? Userspace can already change it via an ioctl. Why adding a parameter
for that?

> +
> +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nums);
> +
> +static int vidtv_start_streaming(struct vidtv_dvb *dvb)
> +{
> + WARN_ON(dvb->streaming);
> + dvb->streaming = true;
> +
> + return 0;
> +}
> +
> +static int vidtv_stop_streaming(struct vidtv_dvb *dvb)
> +{
> + /* mpeg thread will quit */
> + dvb->streaming = false;
> +
> + return 0;
> +}
> +
> +static int vidtv_start_feed(struct dvb_demux_feed *feed)
> +{
> + struct dvb_demux *demux = feed->demux;
> + struct vidtv_dvb *dvb = demux->priv;
> + int rc, ret;
> +
> + if (!demux->dmx.frontend)
> + return -EINVAL;
> +
> + mutex_lock(&dvb->feed_lock);
> +
> + dvb->nfeeds++;
> + rc = dvb->nfeeds;
> +
> + if (dvb->nfeeds == 1) {
> + ret = vidtv_start_streaming(dvb);
> + if (ret < 0)
> + rc = ret;
> + }
> +
> + mutex_unlock(&dvb->feed_lock);
> + return rc;
> +}
> +
> +static int vidtv_stop_feed(struct dvb_demux_feed *feed)
> +{
> + struct dvb_demux *demux = feed->demux;
> + struct vidtv_dvb *dvb = demux->priv;
> + int err = 0;
> +
> + mutex_lock(&dvb->feed_lock);
> + dvb->nfeeds--;
> +
> + if (!dvb->nfeeds)
> + err = vidtv_stop_streaming(dvb);
> +
> + mutex_unlock(&dvb->feed_lock);
> + return err;
> +}
> +
> +static int vidtv_master_xfer(struct i2c_adapter *i2c_adap,
> + struct i2c_msg msgs[],
> + int num)
> +{
> + return 0;
> +}
> +
> +static u32 vidtv_i2c_func(struct i2c_adapter *adapter)
> +{
> + return I2C_FUNC_I2C;
> +}
> +
> +struct i2c_algorithm vidtv_i2c_algorithm = {
> + .master_xfer = vidtv_master_xfer,
> + .functionality = vidtv_i2c_func,
> +};
> +
> +static int vidtv_bridge_i2c_register_adap(struct vidtv_dvb *dvb)
> +{
> + struct i2c_adapter *i2c_adapter = dvb->i2c_adapter;
> +
> + strscpy(i2c_adapter->name, "vidtv_i2c", sizeof(i2c_adapter->name));
> + i2c_adapter->owner = THIS_MODULE;
> + i2c_adapter->algo = &vidtv_i2c_algorithm;
> + i2c_adapter->algo_data = NULL;
> + i2c_adapter->timeout = 500;
> + i2c_adapter->retries = 3;
> + i2c_adapter->dev.parent = NULL;
> +
> + i2c_set_adapdata(i2c_adapter, dvb);
> + return i2c_add_adapter(dvb->i2c_adapter);
> +}
> +
> +static int vidtv_bridge_register_adap(struct vidtv_dvb *dvb)
> +{
> + int ret = 0;
> +
> + ret = dvb_register_adapter(&dvb->adapter,
> + KBUILD_MODNAME,
> + THIS_MODULE,
> + &dvb->i2c_adapter->dev,
> + adapter_nums);
> +
> + return ret;
> +}
> +
> +static int vidtv_bridge_dmx_init(struct vidtv_dvb *dvb)
> +{
> + dvb->demux.dmx.capabilities = DMX_TS_FILTERING |
> + DMX_SECTION_FILTERING;
> +
> + dvb->demux.priv = dvb;
> + dvb->demux.filternum = 256;
> + dvb->demux.feednum = 256;
> + dvb->demux.start_feed = vidtv_start_feed;
> + dvb->demux.stop_feed = vidtv_stop_feed;
> +
> + return dvb_dmx_init(&dvb->demux);
> +}
> +
> +static int vidtv_bridge_dmxdev_init(struct vidtv_dvb *dvb)
> +{
> + dvb->dmx_dev.filternum = 256;
> + dvb->dmx_dev.demux = &dvb->demux.dmx;
> + dvb->dmx_dev.capabilities = 0;
> +
> + return dvb_dmxdev_init(&dvb->dmx_dev, &dvb->adapter);
> +}
> +
> +static void vidtv_bridge_probe_demod(struct vidtv_dvb *dvb, u32 n)
> +{
> + struct vidtv_demod_config cfg = {0};
> +
> + cfg.drop_tslock_prob_on_low_snr = drop_tslock_prob_on_low_snr;
> + cfg.recover_tslock_prob_on_good_snr = recover_tslock_prob_on_good_snr;
> + cfg.chosen_delsys = chosen_delsys;
> +
> + dvb->i2c_client_demod[n] = dvb_module_probe("vidtv_demod",
> + NULL,
> + dvb->i2c_adapter,
> + DEMOD_DEFAULT_ADDR,
> + &cfg);
> +
> + /* retrieve a pointer to struct dvb_frontend */
> + dvb->fe[n] = cfg.frontend;
> +}
> +
> +static void vidtv_bridge_probe_tuner(struct vidtv_dvb *dvb, u32 n)
> +{
> + struct vidtv_tuner_config cfg = {0};
> +
> + cfg.fe = dvb->fe[n];
> + cfg.mock_power_up_delay_msec = mock_power_up_delay_msec;
> + cfg.mock_tune_delay_msec = mock_tune_delay_msec;
> +
> + memcpy(cfg.vidtv_valid_dvb_t_freqs,
> + vidtv_valid_dvb_t_freqs,
> + sizeof(vidtv_valid_dvb_t_freqs));
> +
> + memcpy(cfg.vidtv_valid_dvb_c_freqs,
> + vidtv_valid_dvb_c_freqs,
> + sizeof(vidtv_valid_dvb_c_freqs));
> +
> + memcpy(cfg.vidtv_valid_dvb_s_freqs,
> + vidtv_valid_dvb_s_freqs,
> + sizeof(vidtv_valid_dvb_s_freqs));
> +
> + cfg.max_frequency_shift_hz = max_frequency_shift_hz;
> +
> + dvb->i2c_client_tuner[n] = dvb_module_probe("vidtv_tuner",
> + NULL,
> + dvb->i2c_adapter,
> + TUNER_DEFAULT_ADDR,
> + &cfg);
> +}
> +
> +static int vidtv_bridge_dvb_init(struct vidtv_dvb *dvb)
> +{
> + int ret;
> + int i, j;
> +
> + ret = vidtv_bridge_i2c_register_adap(dvb);
> + if (ret < 0)
> + goto fail_i2c;
> +
> + ret = vidtv_bridge_register_adap(dvb);
> + if (ret < 0)
> + goto fail_adapter;
> +
> + vidtv_bridge_probe_demod(dvb, 0);
> + vidtv_bridge_probe_tuner(dvb, 0);
> +
> + for (i = 0; i < NUM_FE; ++i) {
> + ret = dvb_register_frontend(&dvb->adapter, dvb->fe[i]);
> + if (ret < 0)
> + goto fail_fe;
> + }
> +
> + ret = vidtv_bridge_dmx_init(dvb);
> + if (ret < 0)
> + goto fail_dmx;
> +
> + ret = vidtv_bridge_dmxdev_init(dvb);
> + if (ret < 0)
> + goto fail_dmx_dev;
> +
> + for (j = 0; j < NUM_FE; ++j) {
> + ret = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx,
> + &dvb->dmx_fe[j]);
> + if (ret < 0)
> + goto fail_dmx_conn;
> +
> + /*
> + * The source of the demux is a frontend connected
> + * to the demux.
> + */
> + dvb->dmx_fe[j].source = DMX_FRONTEND_0;
> + }
> +
> + return ret;
> +
> +fail_dmx_conn:
> + for (j = j - 1; j >= 0; --j)
> + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx,
> + &dvb->dmx_fe[j]);
> +fail_dmx_dev:
> + dvb_dmxdev_release(&dvb->dmx_dev);
> +fail_dmx:
> + dvb_dmx_release(&dvb->demux);
> +fail_fe:
> + for (i = i - 1; i >= 0; --i)
> + dvb_unregister_frontend(dvb->fe[i]);
> +
> +fail_adapter:
> + dvb_unregister_adapter(&dvb->adapter);
> +
> +fail_i2c:
> + i2c_del_adapter(dvb->i2c_adapter);
> +
> + return ret;
> +}
> +
> +static int vidtv_bridge_i2c_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + int ret;
> + struct vidtv_dvb *dvb;
> +
> + dvb = kzalloc(sizeof(*dvb), GFP_KERNEL);
> + if (!dvb)
> + return -ENOMEM;
> +
> + ret = vidtv_bridge_dvb_init(dvb);
> + if (ret < 0)
> + goto err_dvb;
> +
> + mutex_init(&dvb->feed_lock);
> +
> + i2c_set_clientdata(client, dvb);
> +
> + return ret;
> +
> +err_dvb:
> + kfree(dvb);
> + return ret;
> +}
> +
> +static int vidtv_bridge_i2c_remove(struct i2c_client *client)
> +{
> + struct vidtv_dvb *dvb;
> + u32 i;
> +
> + dvb = i2c_get_clientdata(client);
> +
> + mutex_destroy(&dvb->feed_lock);
> +
> + for (i = 0; i < NUM_FE; ++i)
> + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx,
> + &dvb->dmx_fe[i]);
> +
> + dvb_dmxdev_release(&dvb->dmx_dev);
> + dvb_dmx_release(&dvb->demux);
> +
> + for (i = 0; i < NUM_FE; ++i) {
> + dvb_unregister_frontend(dvb->fe[i]);
> + dvb_frontend_detach(dvb->fe[i]);
> + }
> +
> + dvb_unregister_adapter(&dvb->adapter);
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id vidtv_bridge_id_table[] = {
> + {"vidtv_bridge", 0},
> + {}
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, vidtv_bridge_id_table);
> +
> +static struct i2c_driver vidtv_bridge_driver = {
> + .driver = {
> + .name = "vidtv_bridge",
> + .suppress_bind_attrs = true,
> + },
> + .probe = vidtv_bridge_i2c_probe,
> + .remove = vidtv_bridge_i2c_remove,
> + .id_table = vidtv_bridge_id_table,
> +};
> +
> +module_i2c_driver(vidtv_bridge_driver);
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.h b/drivers/media/test-drivers/vidtv/vidtv_bridge.h
> new file mode 100644
> index 0000000000000..ef5c7cd2d64e3
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.h
> @@ -0,0 +1,37 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * The Virtual DTV test driver serves as a reference DVB driver and helps
> + * validate the existing APIs in the media subsystem. It can also aid
> + * developers working on userspace applications.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#ifndef VIDTV_BRIDGE_H
> +#define VIDTV_BRIDGE_H
> +
> +#define NUM_FE 1
> +
> +#include <linux/types.h>
> +#include <media/dvb_frontend.h>
> +#include <media/dvb_demux.h>
> +#include <media/dmxdev.h>
> +#include <linux/i2c.h>
> +
> +struct vidtv_dvb {
> + struct dvb_frontend *fe[NUM_FE];
> + struct dvb_adapter adapter;
> + struct dvb_demux demux;
> + struct dmxdev dmx_dev;
> + struct dmx_frontend dmx_fe[NUM_FE];
> + struct i2c_adapter *i2c_adapter;
> + struct i2c_client *i2c_client_demod[NUM_FE];
> + struct i2c_client *i2c_client_tuner[NUM_FE];
> +
> + u32 nfeeds;
> + struct mutex feed_lock; /* start/stop feed */
> +
> + bool streaming;
> +};
> +
> +#endif // VIDTV_BRIDGE_H



Thanks,
Mauro

2020-05-02 06:42:50

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 06/11] media: vidtv: add wrappers for memcpy and memset

Em Sat, 2 May 2020 00:22:11 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> From: "Daniel W. S. Almeida" <[email protected]>
>
> A lot of code in this driver is for serializing structures. This is
> error prone.
>
> Therefore, prevent buffer overflows by wrapping memcpy and memset,
> comparing the requested length against the buffer size.
>
> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> ---
> drivers/media/test-drivers/vidtv/Makefile | 3 ++
> .../media/test-drivers/vidtv/vidtv_common.c | 44 +++++++++++++++++++
> .../media/test-drivers/vidtv/vidtv_common.h | 28 ++++++++++++
> 3 files changed, 75 insertions(+)
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_common.c
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_common.h
>
> diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
> index a9f1993dd5443..9ea9485d42189 100644
> --- a/drivers/media/test-drivers/vidtv/Makefile
> +++ b/drivers/media/test-drivers/vidtv/Makefile
> @@ -1,3 +1,6 @@
> # SPDX-License-Identifier: GPL-2.0
>
> +vidtv_demod-objs := vidtv_common.o
> +vidtv_bridge-objs := vidtv_common.o
> +
> obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.c b/drivers/media/test-drivers/vidtv/vidtv_common.c
> new file mode 100644
> index 0000000000000..28f10630499a9
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_common.c
> @@ -0,0 +1,44 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * The Virtual DVB test driver serves as a reference DVB driver and helps
> + * validate the existing APIs in the media subsystem. It can also aid
> + * developers working on userspace applications.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#include <linux/types.h>
> +#include <linux/string.h>
> +#include <linux/printk.h>
> +
> +u32 vidtv_memcpy(void *to,
> + const void *from,
> + size_t len,
> + u32 offset,
> + u32 buf_sz)
> +{
> + if (buf_sz && offset + len > buf_sz) {
> + pr_err("%s: overflow detected, skipping. Try increasing the buffer size",
> + __func__);
> + return 0;

shouldn't it return an error?

> + }
> +
> + memcpy(to, from, len);
> + return len;
> +}
> +
> +u32 vidtv_memset(void *to,
> + int c,
> + size_t len,
> + u32 offset,
> + u32 buf_sz)
> +{
> + if (buf_sz && offset + len > buf_sz) {
> + pr_err("%s: overflow detected, skipping. Try increasing the buffer size",
> + __func__);
> + return 0;
> + }
> +
> + memset(to, c, len);
> + return len;
> +}
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.h b/drivers/media/test-drivers/vidtv/vidtv_common.h
> new file mode 100644
> index 0000000000000..64072c010dc66
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_common.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * The Virtual DVB test driver serves as a reference DVB driver and helps
> + * validate the existing APIs in the media subsystem. It can also aid
> + * developers working on userspace applications.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#ifndef VIDTV_COMMON_H
> +#define VIDTV_COMMON_H
> +
> +#include <linux/types.h>
> +#include <media/dvb_frontend.h>
> +
> +u32 vidtv_memcpy(void *to,
> + const void *from,
> + size_t len,
> + u32 offset,
> + u32 buf_sz);
> +
> +u32 vidtv_memset(void *to,
> + int c,
> + size_t len,
> + u32 offset,
> + u32 buf_sz);
> +
> +#endif // VIDTV_COMMON_H


On a generic note, I don't like seeing functions or macros like those
re-defining existing Kernel functions like memcpy(), memset(), etc.

This is actually a very common pattern when vendors try to submit
new drivers upstream: several of them have a generic code, and use an
OS-specific abstraction layer, with lots of defines, inline functions
and re-definitions for Kernel functions.

Before upstreaming a driver (or removing one from staging), the driver
should get rid of those.

On **this very specific case**, I see the value of having it there, as
you're not doing it as a normal Digital TV driver, but, instead, using
those in order to emulate an MPEG-TS encoding.

Yet, as this driver is meant to be a sort of "tutorial" for ones
implementing such features, please add a WARNING at both the header and
at the source code, saying that normal drivers should not do that,
explaining why, in this specific case (where you're simulating a MPEG-TS
in software) it makes sense to have such functions.

Thanks,
Mauro

2020-05-02 07:12:00

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 07/11] media: vidtv: add MPEG TS common code

Em Sat, 2 May 2020 00:22:12 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> From: "Daniel W. S. Almeida" <[email protected]>
>
> Add code to work with MPEG TS packets, such as TS headers, adaptation
> fields, PCR packets and NULL packets.
>
> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> ---
> drivers/media/test-drivers/vidtv/Makefile | 2 +-
> drivers/media/test-drivers/vidtv/vidtv_ts.c | 130 ++++++++++++++++++++
> drivers/media/test-drivers/vidtv/vidtv_ts.h | 103 ++++++++++++++++
> 3 files changed, 234 insertions(+), 1 deletion(-)
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.c
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.h
>
> diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
> index 9ea9485d42189..92001bc348615 100644
> --- a/drivers/media/test-drivers/vidtv/Makefile
> +++ b/drivers/media/test-drivers/vidtv/Makefile
> @@ -1,6 +1,6 @@
> # SPDX-License-Identifier: GPL-2.0
>
> vidtv_demod-objs := vidtv_common.o
> -vidtv_bridge-objs := vidtv_common.o
> +vidtv_bridge-objs := vidtv_common.o vidtv_ts.o
>
> obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_ts.c b/drivers/media/test-drivers/vidtv/vidtv_ts.c
> new file mode 100644
> index 0000000000000..f545c45c0fe7c
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_ts.c
> @@ -0,0 +1,130 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * The Virtual DVB test driver serves as a reference DVB driver and helps
> + * validate the existing APIs in the media subsystem. It can also aid
> + * developers working on userspace applications.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#include <linux/types.h>
> +#include <asm/byteorder.h>
> +#include "vidtv_ts.h"
> +#include "vidtv_common.h"
> +
> +static u32 vidtv_ts_write_pcr_bits(u8 *buf, u64 pcr)
> +{
> + /* Exact same from ffmpeg. PCR is a counter driven by a 27Mhz clock */
> + u64 pcr_low = pcr % 300, pcr_high = pcr / 300;
> +
> + *buf++ = pcr_high >> 25;
> + *buf++ = pcr_high >> 17;
> + *buf++ = pcr_high >> 9;
> + *buf++ = pcr_high >> 1;
> + *buf++ = pcr_high << 7 | pcr_low >> 8 | 0x7e;
> + *buf++ = pcr_low;
> +
> + return 6;
> +}
> +
> +void vidtv_ts_inc_cc(u8 *continuity_counter)
> +{
> + ++*continuity_counter;
> + if (*continuity_counter > TS_CC_MAX_VAL)
> + *continuity_counter = 0;
> +}
> +
> +u32 vidtv_ts_null_write_into(struct null_packet_write_args args)
> +{
> + u32 nbytes = 0;
> + struct vidtv_mpeg_ts ts_header = {0};
> +
> + ts_header.sync_byte = TS_SYNC_BYTE;
> + ts_header.pid = TS_NULL_PACKET_PID;
> + ts_header.payload = 1;
> + ts_header.continuity_counter = *args.continuity_counter;
> +
> + cpu_to_be16s(&ts_header.bitfield);
> +
> + /* copy TS header */
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + &ts_header,
> + sizeof(ts_header),
> + args.dest_offset + nbytes,
> + args.buf_sz);

Hmm... now I see why you're returning 0 to vidtv_memcpy().

Yet, if the buffer is full, you should just drop the entire package,
as otherwise you may end copying things that aren't multiple of 188 bytes,
causing sync issues at the client.

> +
> + be16_to_cpus(&ts_header.bitfield);
> +
> + vidtv_ts_inc_cc(args.continuity_counter);
> +
> + /* fill the rest with empty data */
> + nbytes += vidtv_memset(args.dest_buf + args.dest_offset + nbytes,
> + 0xff,
> + TS_PACKET_LEN - nbytes,
> + args.dest_offset + nbytes,
> + args.buf_sz);
> +
> + /* we should have written exactly _one_ 188byte packet */
> + WARN_ON(nbytes != TS_PACKET_LEN);

A WARN_ON() seems too severe here. Also, if something bad happens, it
will end causing lots of problems that can make the machine very slow,
ad this will flood dmesg.

So, the best would be to use, instead, dev_warn_ratelimited().

PS.: same notes here apply to the function below (and on next patches).

> +
> + return nbytes;
> +}
> +
> +u32 vidtv_ts_pcr_write_into(struct pcr_write_args args)
> +{
> + u32 nbytes = 0;
> + struct vidtv_mpeg_ts ts_header = {0};
> + struct vidtv_mpeg_ts_adaption ts_adap = {0};
> +
> + ts_header.sync_byte = TS_SYNC_BYTE;
> + ts_header.tei = 0;
> + ts_header.payload_start = 0;
> + ts_header.pid = args.pid;
> + ts_header.priority = 0;
> + ts_header.scrambling = 0;
> + /* cc is not incremented, see 13818-1 clause 2.4.3.3 */
> + ts_header.continuity_counter = *args.continuity_counter;
> + ts_header.payload = 0;
> + ts_header.adaptation_field = 1;
> +
> + /* 13818-1 clause 2.4.3.5 */
> + ts_adap.length = 183;
> + ts_adap.PCR = 1;
> +
> + cpu_to_be16s(&ts_header.bitfield);
> +
> + /* copy TS header */
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + &ts_header,
> + sizeof(ts_header),
> + args.dest_offset + nbytes,
> + args.buf_sz);
> +
> + be16_to_cpus(&ts_header.bitfield);
> +
> + /* write the adap after the TS header */
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + &ts_adap,
> + sizeof(ts_adap),
> + args.dest_offset + nbytes,
> + args.buf_sz);
> +
> + /* write the PCR optional */
> + cpu_to_be64s(&args.pcr);
> + nbytes += vidtv_ts_write_pcr_bits(args.dest_buf +
> + args.dest_offset +
> + nbytes,
> + args.pcr);
> + be64_to_cpus(&args.pcr);
> +
> + nbytes += vidtv_memset(args.dest_buf + args.dest_offset + nbytes,
> + 0xff,
> + TS_PACKET_LEN - nbytes,
> + args.dest_offset + nbytes,
> + args.buf_sz);
> +
> + /* we should have written exactly _one_ 188byte packet */
> + WARN_ON(nbytes != TS_PACKET_LEN);
> +
> + return nbytes;
> +}
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_ts.h b/drivers/media/test-drivers/vidtv/vidtv_ts.h
> new file mode 100644
> index 0000000000000..2c07bddc46119
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_ts.h
> @@ -0,0 +1,103 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * The Virtual DVB test driver serves as a reference DVB driver and helps
> + * validate the existing APIs in the media subsystem. It can also aid
> + * developers working on userspace applications.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#ifndef VIDTV_TS_H
> +#define VIDTV_TS_H
> +
> +#include <linux/types.h>
> +#include <asm/byteorder.h>
> +
> +#define TS_SYNC_BYTE 0x47
> +#define TS_PACKET_LEN 188
> +#define TS_PAYLOAD_LEN 184
> +#define TS_NULL_PACKET_PID 0x1fff
> +#define TS_CC_MAX_VAL 0x0f /* 4 bits */
> +#define TS_LAST_VALID_PID 8191
> +
> +struct vidtv_mpeg_ts_adaption {
> + u8 length;
> + struct {
> +#if defined(__LITTLE_ENDIAN_BITFIELD)
> + 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;
> +#elif defined(__BIG_ENDIAN_BITFIELD)
> + u8 discontinued:1;
> + u8 random_access:1;
> + u8 priority:1;
> + u8 PCR:1;
> + u8 OPCR:1;
> + u8 splicing_point:1;
> + u8 private_data:1;
> + u8 extension:1;
> +#else
> +#error "Please fix <asm/byteorder.h>"
> +#endif
> + } __packed;
> + u8 data[];
> +} __packed;
> +
> +struct vidtv_mpeg_ts {
> + u8 sync_byte;
> + union {
> + u16 bitfield;
> + struct {
> + u16 pid:13;
> + u16 priority:1;
> + u16 payload_start:1;
> + u16 tei:1;
> + } __packed;
> + } __packed;
> + struct {
> +#if defined(__LITTLE_ENDIAN_BITFIELD)
> + u8 continuity_counter:4;
> + u8 payload:1;
> + u8 adaptation_field:1;
> + u8 scrambling:2;
> +#elif defined(__BIG_ENDIAN_BITFIELD)
> + u8 scrambling:2;
> + u8 adaptation_field:1;
> + u8 payload:1;
> + u8 continuity_counter:4;
> +#else
> +#error "Please fix <asm/byteorder.h>"
> +#endif
> + } __packed;
> + struct vidtv_mpeg_ts_adaption adaption[];
> +} __packed;
> +
> +struct pcr_write_args {
> + void *dest_buf; /* The buffer to write into */
> + u32 dest_offset; /* The byte offset into the buffer */
> + u16 pid; /* the TS PID for the PCR packets */
> + u32 buf_sz; /* protect against overflow when this field is not zero */
> + u8 *continuity_counter;
> + u64 pcr; /* A sample from the system clock */
> +};
> +
> +struct null_packet_write_args {
> + void *dest_buf;/* The buffer to write into */
> + u32 dest_offset;/* The byte offset into the buffer */
> + u32 buf_sz; /* protect against overflow when this field is not zero */
> + u8 *continuity_counter;
> +};
> +
> +/* Increment the continuity counter */
> +void vidtv_ts_inc_cc(u8 *continuity_counter);
> +
> +u32 vidtv_ts_null_write_into(struct null_packet_write_args args);
> +
> +u32 vidtv_ts_pcr_write_into(struct pcr_write_args args);
> +
> +#endif //VIDTV_TS_H



Thanks,
Mauro

2020-05-02 21:14:43

by Daniel Almeida

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 05/11] media: vidtv: add a bridge driver

Hi Mauro, thanks for reviewing this.

On 5/2/20 3:30 AM, Mauro Carvalho Chehab wrote:
> Em Sat, 2 May 2020 00:22:10 -0300
> "Daniel W. S. Almeida" <[email protected]> escreveu:
>
>> From: "Daniel W. S. Almeida" <[email protected]>
>>
>> Digital TV devices consist of several independent hardware
components which
>> are controlled by different drivers.
>> Each media device is controlled by a group of cooperating drivers
with the
>> bridge driver as the main driver.
>>
>> This patch adds a bridge driver for the Virtual Digital TV driver
[vidtv].
>>
>> The bridge driver binds to the other drivers, that is, vidtv_tuner and
>> vidtv_demod and implements the digital demux logic, providing userspace
>> with a MPEG Transport Stream.
>>
>> Move config structs to a common header so they can be used by the bridge
>> driver and by their respective drivers.
>>
>> Signed-off-by: Daniel W. S. Almeida <[email protected]>
>> ---
>> drivers/media/test-drivers/vidtv/Makefile | 2 +-
>> .../media/test-drivers/vidtv/vidtv_bridge.c | 379 ++++++++++++++++++
>> .../media/test-drivers/vidtv/vidtv_bridge.h | 37 ++
>> 3 files changed, 417 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_bridge.c
>> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_bridge.h
>>
>> diff --git a/drivers/media/test-drivers/vidtv/Makefile
b/drivers/media/test-drivers/vidtv/Makefile
>> index 36ba00ddc0d1e..a9f1993dd5443 100644
>> --- a/drivers/media/test-drivers/vidtv/Makefile
>> +++ b/drivers/media/test-drivers/vidtv/Makefile
>> @@ -1,3 +1,3 @@
>> # SPDX-License-Identifier: GPL-2.0
>>
>> -obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o
>> +obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
>> diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c
b/drivers/media/test-drivers/vidtv/vidtv_bridge.c
>> new file mode 100644
>> index 0000000000000..05ca4027c869f
>> --- /dev/null
>> +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c
>> @@ -0,0 +1,379 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * The Virtual DTV test driver serves as a reference DVB driver and
helps
>> + * validate the existing APIs in the media subsystem. It can also aid
>> + * developers working on userspace applications.
>> + *
>> + * Written by Daniel W. S. Almeida <[email protected]>
>> + */
>> +
>> +#include <linux/types.h>
>> +#include <linux/moduleparam.h>
>> +#include <linux/mutex.h>
>> +#include <linux/workqueue.h>
>> +#include <linux/time.h>
>> +#include "vidtv_bridge.h"
>> +
>> +#define TS_BUF_MAX_SZ (128 * 188)
>> +#define TUNER_DEFAULT_ADDR 0x68
>> +#define DEMOD_DEFAULT_ADDR 0x60
>> +
>> +MODULE_AUTHOR("Daniel W. S. Almeida");
>> +MODULE_LICENSE("GPL");
>> +
>
>> +static unsigned int drop_tslock_prob_on_low_snr;
>> +module_param(drop_tslock_prob_on_low_snr, uint, 0644);
>> +MODULE_PARM_DESC(drop_tslock_prob_on_low_snr,
>> + "Probability of losing the TS lock if the signal quality is bad");
>> +
>> +static unsigned int recover_tslock_prob_on_good_snr;
>> +module_param(recover_tslock_prob_on_good_snr, uint, 0644);
>> +MODULE_PARM_DESC(recover_tslock_prob_on_good_snr,
>> + "Probability recovering the TS lock when the signal improves");
>
> The FS permissions should be reviewed. IMHO, we should allow the
> group which owns the /dev to be able to change values when they can
> be changed in runtime.
>
> Yet, be sure that changing it on runtime won't cause race conditions.
>
> For the two above, I guess the permissions for those should be, instead,
> 0664.
>
>> +
>> +static unsigned int mock_power_up_delay_msec;
>> +module_param(mock_power_up_delay_msec, uint, 0644);
>> +MODULE_PARM_DESC(mock_power_up_delay_msec, "Simulate a power up
delay");
>
> What's the sense of allowing changing it after the probe? I guess
> permissions should be 0444 here.
>
>> +
>> +static unsigned int mock_tune_delay_msec;
>> +module_param(mock_tune_delay_msec, uint, 0644);
>> +MODULE_PARM_DESC(mock_tune_delay_msec, "Simulate a tune delay");
>
> Same here: I guess 0664 would work better.
>
>> +
>> +static unsigned int vidtv_valid_dvb_t_freqs[8];
>> +module_param_array(vidtv_valid_dvb_t_freqs, uint, NULL, 0644);
>> +MODULE_PARM_DESC(vidtv_valid_dvb_t_freqs,
>> + "Valid DVB-T frequencies to simulate");
>> +
>> +static unsigned int vidtv_valid_dvb_c_freqs[8];
>> +module_param_array(vidtv_valid_dvb_c_freqs, uint, NULL, 0644);
>> +MODULE_PARM_DESC(vidtv_valid_dvb_c_freqs,
>> + "Valid DVB-C frequencies to simulate");
>> +
>> +static unsigned int vidtv_valid_dvb_s_freqs[8];
>> +module_param_array(vidtv_valid_dvb_s_freqs, uint, NULL, 0644);
>> +MODULE_PARM_DESC(vidtv_valid_dvb_s_freqs,
>> + "Valid DVB-C frequencies to simulate");
>
> Can those be changed in runtime without causing race conditions?
>
>> +
>> +static unsigned int max_frequency_shift_hz;
>> +module_param(max_frequency_shift_hz, uint, 0644);
>> +MODULE_PARM_DESC(max_frequency_shift_hz,
>> + "Maximum shift in HZ allowed when tuning in a channel");
>
> 0664?
>
>> +
>> +static unsigned int chosen_delsys = SYS_DVBT;
>> +module_param(chosen_delsys, uint, 0644);
>> +MODULE_PARM_DESC(chosen_delsys,
>> + "The delivery system to simulate. Currently supported: DVB-T,
DVB-C, DVB-S");
>
> Hmm... in order to allow changing this value on runtime, as you proposed,
> you should use module_param_cb(). The callback would allow touching the
> delsys only if the driver is not in use. Otherwise, it would return
-EBUSY.
> Also, permissions should be 0664.

Actually, my bad here. I used some other media driver as an example for
module_param(), I did not intend to be able to change any of these
during runtime at all, I only wanted to be able to set them at module
load time. In fact, IIRC, these parameters are used when first setting
up the driver and then they are not used anymore. Therefore, maybe "0"
is more fitting?

> Hmm... in order to allow changing this value on runtime, as you proposed,
> you should use module_param_cb(). The callback would allow touching the
> delsys only if the driver is not in use. Otherwise, it would return
-EBUSY.
> Also, permissions should be 0664.
>
> Btw, after thinking a little bit about that, I would take one step
back on
> my demod review, changing it and the bridge's logic to allow
chosen_delsys,
> while keeping just one struct for DVB info parameters.
>
> The way I see is that the logic here should be changed in order to use a
> a bitmask for the chosen_delsys, like:
>
> bit 0: DVB-T
> bit 1: DVB-T2
> bit 2: DVB-C
> bit 3: DVB-S
> bit 4: DVB-S2
>
> Of course you will need to document the meaning for each bit somewhere.
>
> The default should be to have all supported types enabled.
>
> As there's no ops that would allow the bridge driver to change it
> dynamically, you should instead implement the "chosen_delsys" directly
> at the demod's driver.

Given what I said above about it being a misunderstanding on my part, do
you still want to see this?

> while keeping just one struct for DVB info parameters.

What do you mean by this?


>> +static unsigned int ts_buf_sz = 20 * 188;
>> +module_param(ts_buf_sz, uint, 0644);
>> +MODULE_PARM_DESC(ts_buf_sz, "Optional size for the TS buffer");
>
> Huh? Userspace can already change it via an ioctl. Why adding a parameter
> for that?

This is actually misplaced: it was supposed to be in the last patch for
this series and probably got left behind as I was moving code around.

Maybe the name I chose was a bit confusing? I called the code
responsible for collecting ES data from different encoders into a single
buffer a "mux". Its buffer was thus named "ts_buf". This parameter
dictates the size allocated at runtime with vmalloc(). This buffer later
gets passed to dvb_dmx_swfilter().

The source code for this is in patch 11.

It is loosely based on what ffmpeg has going on at libavformat/mpegtsenc.c


- Daniel

2020-05-02 22:24:27

by Daniel Almeida

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 07/11] media: vidtv: add MPEG TS common code

Hi Mauro, thanks for reviewing this!

> Em Sat, 2 May 2020 00:22:12 -0300
> "Daniel W. S. Almeida" <[email protected]> escreveu:
>
>> From: "Daniel W. S. Almeida" <[email protected]>
>>
>> Add code to work with MPEG TS packets, such as TS headers, adaptation
>> fields, PCR packets and NULL packets.
>>
>> Signed-off-by: Daniel W. S. Almeida <[email protected]>
>> ---
>> drivers/media/test-drivers/vidtv/Makefile | 2 +-
>> drivers/media/test-drivers/vidtv/vidtv_ts.c | 130 ++++++++++++++++++++
>> drivers/media/test-drivers/vidtv/vidtv_ts.h | 103 ++++++++++++++++
>> 3 files changed, 234 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.c
>> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.h
>>
>> diff --git a/drivers/media/test-drivers/vidtv/Makefile
b/drivers/media/test-drivers/vidtv/Makefile
>> index 9ea9485d42189..92001bc348615 100644
>> --- a/drivers/media/test-drivers/vidtv/Makefile
>> +++ b/drivers/media/test-drivers/vidtv/Makefile
>> @@ -1,6 +1,6 @@
>> # SPDX-License-Identifier: GPL-2.0
>>
>> vidtv_demod-objs := vidtv_common.o
>> -vidtv_bridge-objs := vidtv_common.o
>> +vidtv_bridge-objs := vidtv_common.o vidtv_ts.o
>>
>> obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
>> diff --git a/drivers/media/test-drivers/vidtv/vidtv_ts.c
b/drivers/media/test-drivers/vidtv/vidtv_ts.c
>> new file mode 100644
>> index 0000000000000..f545c45c0fe7c
>> --- /dev/null
>> +++ b/drivers/media/test-drivers/vidtv/vidtv_ts.c
>> @@ -0,0 +1,130 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * The Virtual DVB test driver serves as a reference DVB driver and
helps
>> + * validate the existing APIs in the media subsystem. It can also aid
>> + * developers working on userspace applications.
>> + *
>> + * Written by Daniel W. S. Almeida <[email protected]>
>> + */
>> +
>> +#include <linux/types.h>
>> +#include <asm/byteorder.h>
>> +#include "vidtv_ts.h"
>> +#include "vidtv_common.h"
>> +
>> +static u32 vidtv_ts_write_pcr_bits(u8 *buf, u64 pcr)
>> +{
>> + /* Exact same from ffmpeg. PCR is a counter driven by a 27Mhz clock */
>> + u64 pcr_low = pcr % 300, pcr_high = pcr / 300;
>> +
>> + *buf++ = pcr_high >> 25;
>> + *buf++ = pcr_high >> 17;
>> + *buf++ = pcr_high >> 9;
>> + *buf++ = pcr_high >> 1;
>> + *buf++ = pcr_high << 7 | pcr_low >> 8 | 0x7e;
>> + *buf++ = pcr_low;
>> +
>> + return 6;
>> +}
>> +
>> +void vidtv_ts_inc_cc(u8 *continuity_counter)
>> +{
>> + ++*continuity_counter;
>> + if (*continuity_counter > TS_CC_MAX_VAL)
>> + *continuity_counter = 0;
>> +}
>> +
>> +u32 vidtv_ts_null_write_into(struct null_packet_write_args args)
>> +{
>> + u32 nbytes = 0;
>> + struct vidtv_mpeg_ts ts_header = {0};
>> +
>> + ts_header.sync_byte = TS_SYNC_BYTE;
>> + ts_header.pid = TS_NULL_PACKET_PID;
>> + ts_header.payload = 1;
>> + ts_header.continuity_counter = *args.continuity_counter;
>> +
>> + cpu_to_be16s(&ts_header.bitfield);
>> +
>> + /* copy TS header */
>> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
>> + &ts_header,
>> + sizeof(ts_header),
>> + args.dest_offset + nbytes,
>> + args.buf_sz);
>
> Hmm... now I see why you're returning 0 to vidtv_memcpy().
>
> Yet, if the buffer is full, you should just drop the entire package,
> as otherwise you may end copying things that aren't multiple of 188
bytes,
> causing sync issues at the client.

I'd like to provide a counterargument for this.

The way I am dealing with errors throughout vidtv so far is:
If we hit any of these WARN_ON macros, pr_err and the like, then all
bets are off. This means that the resulting stream will likely be
invalid and that something needs to be rewritten in the source code and
my main concern is then preventing the whole driver from crashing.

This is exactly the behavior that you see in vidtv_memcpy and
vidtv_memset: nothing gets written so we don't end up with an overflow,
a diagnostic message is printed and there are no attempts at recovery.

In this particular example, I compromised by allowing the size of the
buffer to be a module param, i.e.

>> +static unsigned int ts_buf_sz = 20 * 188;
>> +module_param(ts_buf_sz, uint, 0644);
>> +MODULE_PARM_DESC(ts_buf_sz, "Optional size for the TS buffer");

I think that what I am trying to say is better seen in the last patch
for this series: [RFC, WIP, v4 11/11] media: vidtv: Add a MPEG Transport
Stream Multiplexer.

The following takes place in vidtv_mux.c:

1. We wake up from sleep, take note of how long we slept for and store
it into "elapsed_time". This is usually between 10ms and 20ms.
2. We encode "elapsed_time" miliseconds worth of data into a buffer
3. We call dvb_dmx_swfilter(), passing a pointer to the buffer
4. We clear the buffer, sleep for a bit and then go back to 1.

If the buffer is somehow full then it means that we are trying to
simulate too many fake channels while allocating too little memory, so
either we scale down on the amount of fake channels or we choose another
value for the "ts_buf_sz" module_param.

I feel that this is better than adding more logic in an attempt to
circumvent the issue, specially since I can add more logic and still
fail due to my limited experience. The result is more bloat on the
source code for little gain.

> A WARN_ON() seems too severe here. Also, if something bad happens, it
> will end causing lots of problems that can make the machine very slow,
> ad this will flood dmesg.
>
> So, the best would be to use, instead, dev_warn_ratelimited().

You're right, I did not consider this. I will use dev_warn_ratelimited()
in place of WARN_ON in the next revisions.

2020-05-03 07:09:54

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 06/11] media: vidtv: add wrappers for memcpy and memset

Em Sat, 2 May 2020 08:40:38 +0200
Mauro Carvalho Chehab <[email protected]> escreveu:

> Em Sat, 2 May 2020 00:22:11 -0300
> "Daniel W. S. Almeida" <[email protected]> escreveu:
>

> > +u32 vidtv_memcpy(void *to,
> > + const void *from,
> > + size_t len,
> > + u32 offset,
> > + u32 buf_sz)
> > +{
> > + if (buf_sz && offset + len > buf_sz) {
> > + pr_err("%s: overflow detected, skipping. Try increasing the buffer size",
> > + __func__);
> > + return 0;
>
> shouldn't it return an error?
>
> > + }
> > +
> > + memcpy(to, from, len);
> > + return len;
> > +}

When trying to use your memset wrapper, I noticed a few issues there.

The first one is that you should not use __func__ directly at pr_* macros.

Instead, just ensure that you have a pr_fmt() macro that ill be adding
the driver's name and the function, e. g.:

#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__

Besides that, the parameter order sounded weird:

> > +u32 vidtv_memcpy(void *to,
> > + const void *from,
> > + size_t len,
> > + u32 offset,
> > + u32 buf_sz)

The "to", "offset" and "buf_sz" arguments refer to the "to" buffer,
while "from" and "len" refers to what will be copied from the "from"
into the "to" buffer. Please re-order it, placing first the "to"
args, then "from" arg, and finally the argument that applies to both,
e. g.:

size_t vidtv_memcpy(void *to, size_t to_offset, size_t to_size,
const void *from, size_t len)

(you should notice that I'm using size_t for all args there).

The same is also valid for the memset.

Finally, the places where this function is used require to pass twice
the offset (from patch 08/11):

> + nbytes += vidtv_memcpy(args.dest_buf +
> + args.dest_offset +
> + nbytes,
> + &ts_header,
> + sizeof(ts_header),
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);

That doesn't make much sense. I mean, if the arguments are "buf + offset",
one would expect that the "buf" would be the head of a buffer, and the
function would be adding the offset internally.

So, the best would be to re-define it like:

/**
* vidtv_memcpy() - wrapper routine to be used by MPEG-TS
* generator, in order to avoid going past the
* output buffer.
* @to: Starting element to where a MPEG-TS packet will
* be copied.
* @to_offset: Starting position of the @to buffer to be filled.
* @to_size: Size of the @to buffer.
* @from: Starting element of the buffer to be copied.
* @ten: Number of elements to be copy from @from buffer
* into @to+ @to_offset buffer.
*
* Note:
* Real digital TV demod drivers should not have memcpy
* wrappers. We use it here just because emulating a MPEG-TS
* generation at kernelspace require some extra care.
*
* Return:
* Returns the number of bytes
*/
size_t vidtv_memcpy(void *to, size_t to_offset, size_t to_size,
const void *from, size_t len)
{
if unlikely(to_offset + len > to_size) {
pr_err_ratelimited("overflow detected, skipping. Try increasing the buffer size\n");
return 0;
}
memcpy(to + to_offset, from, len);
return len;
}

Thanks,
Mauro

2020-05-03 07:53:36

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 08/11] media: vidtv: implement a PSI generator

Em Sat, 2 May 2020 00:22:13 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> From: "Daniel W. S. Almeida" <[email protected]>
>
> PSI packets contain general information about a MPEG Transport Stream.
> A PSI generator is needed so userspace apps can retrieve information
> about the Transport Stream and eventually tune into a (dummy) channel.
>
> Because the generator is implemented in a separate file, it can be
> reused elsewhere in the media subsystem.
>
> Currently this commit adds support for working with 3 PSI tables:
> PAT, PMT and SDT.
>
> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> ---
> drivers/media/test-drivers/vidtv/Makefile | 2 +-
> drivers/media/test-drivers/vidtv/vidtv_psi.c | 1137 ++++++++++++++++++
> drivers/media/test-drivers/vidtv/vidtv_psi.h | 357 ++++++
> 3 files changed, 1495 insertions(+), 1 deletion(-)
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_psi.c
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_psi.h
>
> diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
> index 92001bc348615..e4f744aa53136 100644
> --- a/drivers/media/test-drivers/vidtv/Makefile
> +++ b/drivers/media/test-drivers/vidtv/Makefile
> @@ -1,6 +1,6 @@
> # SPDX-License-Identifier: GPL-2.0
>
> vidtv_demod-objs := vidtv_common.o
> -vidtv_bridge-objs := vidtv_common.o vidtv_ts.o
> +vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o
>
> obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.c b/drivers/media/test-drivers/vidtv/vidtv_psi.c
> new file mode 100644
> index 0000000000000..191d37a248923
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_psi.c
> @@ -0,0 +1,1137 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * This file contains the logic to work with MPEG Program-Specific Information.
> + * These are defined both in ISO/IEC 13818-1 (systems) and ETSI EN 300 468.
> + * PSI is carried in the form of table structures, and although each table might
> + * technically be broken into one or more sections, we do not do this here,
> + * hence 'table' and 'section' are interchangeable for us.
> + *
> + * This code currently supports three tables: PAT, PMT and SDT. These are the
> + * bare minimum to get userspace to recognize our MPEG transport stream. It can
> + * be extended to support more PSI tables in the future.
> + *
> + * A note on endianness: MPEG layout is big-endian, therefore:
> + * - All fields spanning more than a byte must undergo 'cpu_to_beXX()' before
> + * serialization. These convertions are done in the *_write_into() functions.
> + *
> + * - All byte sized bitfields must have their ordering reversed if
> + * __LITTLE_ENDIAN_BITFIELD is defined.
> + *
> + * Written by: Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/types.h>
> +#include <linux/slab.h>
> +#include <linux/crc32.h>
> +#include <linux/string.h>
> +#include <linux/printk.h>
> +
> +#include "vidtv_psi.h"
> +#include "vidtv_common.h"
> +#include "vidtv_ts.h"
> +
> +#define CRC_SIZE_IN_BYTES 4
> +
> +static u32 vidtv_psi_ts_psi_write_stuffing(void *to,
> + u32 len,
> + u32 offset,
> + u32 buf_sz)
> +{
> + return vidtv_memset(to, 0xff, len, offset, buf_sz);
> +}

Instead, add thid define:

#define TS_FILL_BYTE 0xff

And just use vidtv_memset() when needed.



> +
> +static u32
> +vidtv_psi_ts_psi_write_into(struct psi_write_args args)
> +{
> + /*
> + * Packetize PSI sections into TS packets:
> + * push a TS header (4bytes) every 184 bytes
> + * manage the continuity_counter
> + * add stuffing after the CRC
> + */
> +
> + u32 nbytes_past_boundary = (args.dest_offset % TS_PACKET_LEN);
> + bool aligned = nbytes_past_boundary == 0;
> +
> + /*
> + * whether we need to fragment the data into multiple ts packets
> + * if we are aligned we need to spare one byte for the pointer_field
> + */
> + bool split = (aligned) ?
> + args.len > TS_PAYLOAD_LEN - 1 :
> + nbytes_past_boundary + args.len > TS_PACKET_LEN;
> +
> + /* how much we can write in this packet */
> + u32 payload_write_len = (split) ?
> + (aligned) ? TS_PAYLOAD_LEN :
> + TS_PACKET_LEN - nbytes_past_boundary :
> + args.len;
> +
> + struct psi_write_args new_args = {0};
> + struct vidtv_mpeg_ts ts_header = {0};
> +
> + u32 nbytes = 0; /* number of bytes written by this function */
> + u32 temp = 0;
> +
> + /* Just a sanity check, should not really happen because we stuff
> + * the packet when we finish a section, i.e. when we write the crc at
> + * the end. But if this happens then we have messed up the logic
> + * somewhere.
> + */
> + WARN_ON(args.new_psi_section && !aligned);

Please use a ratelimited printk instead (here and on all similar cases
at the TS generator).

Also, I think that, on such case, the driver should be filling the
remaining frame with pad bytes. E. g.:

/*
* Assuming that vidtv_memset() args from patch 06/11 were changed
* according with this prototype:
*/
size_t vidtv_memset(void *to, size_t to_offset, size_t to_size,
u8 c, size_t len);


#define TS_FILL_BYTE 0xff

if (args.new_psi_section && !aligned) {
pr_warn_ratelimit("Warning: PSI not aligned. Re-aligning it\n");

vidtv_memset(args.dest_buf,
args.dest_offset + nbytes_past_boundary,
args.dest_buf_sz,
TS_FILL_BYTE,
TS_PACKET_LEN - nbytes_past_boundary);
args.dest_offset += TS_PACKET_LEN - nbytes_past_boundary;
aligned = 1;
nbytes_past_boundary = 0;
}

> +
> + if (aligned) {
> + /* if at a packet boundary, write a new TS header */
> + ts_header.sync_byte = TS_SYNC_BYTE;
> + ts_header.tei = 0;
> + ts_header.payload_start = 1;
> + ts_header.pid = args.pid;
> + ts_header.priority = 0;
> + ts_header.scrambling = 0;
> + ts_header.continuity_counter = *args.continuity_counter;
> + ts_header.payload = 1;
> + /* no adaptation field */
> + ts_header.adaptation_field = 0;
> +
> + cpu_to_be16s(&ts_header.bitfield);
> +
> + /* copy the header */
> + nbytes += vidtv_memcpy(args.dest_buf +
> + args.dest_offset +
> + nbytes,
> + &ts_header,
> + sizeof(ts_header),
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + be16_to_cpus(&ts_header.bitfield);
> +
> + /*
> + * increment the countinuity counter since we have started
> + * a new packet
> + */
> + vidtv_ts_inc_cc(args.continuity_counter);
> + }
> +
> + if (args.new_psi_section) {
> + /* write the pointer_field in the first byte of the payload
> */
> + temp = vidtv_memset(args.dest_buf + args.dest_offset +
> nbytes,
> + 0x0,
> + 1,
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> + /* one byte was used by the pointer field*/
> + nbytes += temp;
> + if (payload_write_len == TS_PAYLOAD_LEN)
> + payload_write_len -= temp;
> + }
> +
> + /* write as much of the payload as we possibly can */
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + args.from,
> + payload_write_len,
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + if (split) {
> + /* 'payload_write_len' written from a total of 'len' requested*/
> + args.len -= payload_write_len;
> + /*
> + * recursively write the rest of the data until we do not
> + * need to split it anymore
> + */
> + memcpy(&new_args, &args, sizeof(struct psi_write_args));
> + new_args.from = args.from + payload_write_len;
> + new_args.dest_offset = args.dest_offset + nbytes;
> + new_args.new_psi_section = false;
> +
> + nbytes += vidtv_psi_ts_psi_write_into(new_args);
> + }
> +
> + /*
> + * as the CRC is last in the section, stuff the rest of the
> + * packet if there is any remaining space in there
> + */
> + if (args.is_crc)
> + nbytes += vidtv_psi_ts_psi_write_stuffing(args.dest_buf +
> + args.dest_offset +
> + nbytes,
> + TS_PACKET_LEN -
> + payload_write_len,
> + args.dest_offset +
> + nbytes,
> + args.dest_buf_sz);
> +
> + return nbytes;
> +}
> +
> +static u32 table_section_crc32_write_into(struct crc32_write_args args)
> +{
> + /* the CRC is the last entry in the section */
> + u32 nbytes = 0;
> + u32 crc;
> + struct psi_write_args psi_args = {0};
> +
> + crc = crc32_be(0, args.dest_buf, args.dest_offset);
> +
> + psi_args.dest_buf = args.dest_buf;
> + psi_args.from = &crc;
> + psi_args.len = CRC_SIZE_IN_BYTES;
> + psi_args.dest_offset = args.dest_offset;
> + psi_args.pid = args.pid;
> + psi_args.new_psi_section = false;
> + psi_args.continuity_counter = args.continuity_counter;
> + psi_args.is_crc = true;
> + psi_args.dest_buf_sz = args.dest_buf_sz;
> +
> + cpu_to_be32s(&crc);
> + nbytes += vidtv_psi_ts_psi_write_into(psi_args);
> + be32_to_cpus(&crc);
> +
> + return nbytes;
> +}
> +
> +struct vidtv_psi_desc *vidtv_psi_desc_init(struct vidtv_psi_desc *head,
> + u8 type,
> + u8 length)
> +{
> + struct vidtv_psi_desc *desc;
> +
> + /* alloc enough memory for the flexible array too */
> + desc = kzalloc(sizeof(*desc) + length, GFP_KERNEL);
> +
> + desc->type = type;
> + desc->length = length;
> +
> + if (head) {
> + while (head->next)
> + head = head->next;
> +
> + head->next = desc;
> + }
> +
> + return desc;
> +}
> +
> +void vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc)
> +{
> + struct vidtv_psi_desc *curr = desc;
> + struct vidtv_psi_desc *tmp = NULL;
> +
> + while (curr) {
> + tmp = curr;
> + curr = curr->next;
> + kfree(tmp);
> + }
> +}
> +
> +static u32
> +vidtv_psi_desc_comp_len(struct vidtv_psi_desc *desc)
> +{
> + u32 length = 0;
> +
> + if (!desc)
> + return 0;
> +
> + while (desc) {
> + length += desc->length;
> + desc = desc->next;
> + }
> +
> + return length;
> +}
> +
> +void vidtv_psi_desc_assign(struct vidtv_psi_desc **to,
> + struct vidtv_psi_desc *desc)
> +{
> + /*
> + * Caller must recompute the section length afterwards at some point
> + * This function transfers ownedship of desc.
> + * Start by cleaning the old data
> + */
> + if (*to)
> + vidtv_psi_desc_destroy(*to);
> +
> + *to = desc; /* reassign pointer */
> +}
> +
> +static void vidtv_psi_desc_to_be(struct vidtv_psi_desc *desc)
> +{
> + /*
> + * Convert descriptor endianness to big-endian on a field-by-field
> basis
> + * where applicable
> + */
> +
> + switch (desc->type) {
> + /* nothing do do */
> + case SERVICE_DESCRIPTOR:
> + break;
> + case REGISTRATION_DESCRIPTOR:
> + cpu_to_be32s(&((struct vidtv_psi_desc_registration *)
> + desc)->format_identifier);
> + pr_alert("%s: descriptor type %d found\n",
> + __func__,
> + desc->type);
> + pr_alert("%s: change 'additional_info' endianness before
> calling\n",
> + __func__);

The above pr_alert() calls sound weird. Why are you unconditionally
calling it (and still doing the BE conversion) for all
REGISTRATION_DESCRIPTOR types?

> + break;
> + default:
> + pr_err("%s: descriptor type %d not implemented, skipping\n",
> + __func__,
> + desc->type);

please use pr_*_ratelimited() everywhere, as otherwise the driver
could cause Kernel to be too slow if something bad happens.

> + break;
> + }
> +}
> +
> +static void vidtv_psi_desc_to_cpu(struct vidtv_psi_desc *desc)
> +{
> + /*
> + * Convert descriptor endianness to native on a field-by-field basis
> + * where applicable
> + */
> +
> + switch (desc->type) {
> + /* nothing do do */
> + case SERVICE_DESCRIPTOR:
> + break;
> + case REGISTRATION_DESCRIPTOR:
> + be32_to_cpus(&((struct vidtv_psi_desc_registration *)
> + desc)->format_identifier);
> + pr_alert("%s: descriptor type %d found\n",
> + __func__,
> + desc->type);
> + pr_alert("%s: change 'additional_info' endianness before
> calling\n",
> + __func__);

The above pr_alert() calls sound weird.

> + break;
> + default:
> + pr_err("%s: descriptor type %d not implemented, skipping\n",
> + __func__,
> + desc->type);
> + break;
> + }
> +}
> +
> +static u32 vidtv_psi_desc_write_into(struct desc_write_args args)
> +{
> + /* the number of bytes written by this function */
> + u32 nbytes = 0;
> + struct psi_write_args psi_args = {0};
> +
> + psi_args.dest_buf = args.dest_buf;
> + psi_args.from = args.desc;
> +
> + psi_args.len = sizeof_field(struct vidtv_psi_desc, type) +
> + sizeof_field(struct vidtv_psi_desc, length);
> +
> + psi_args.dest_offset = args.dest_offset;
> + psi_args.pid = args.pid;
> + psi_args.new_psi_section = false;
> + psi_args.continuity_counter = args.continuity_counter;
> + psi_args.is_crc = false;
> + psi_args.dest_buf_sz = args.dest_buf_sz;
> +
> + vidtv_psi_desc_to_be(args.desc);
> +
> + nbytes += vidtv_psi_ts_psi_write_into(psi_args);
> +
> + /* move 'from' pointer to point to u8 data[] */
> + psi_args.from = args.desc +
> + sizeof_field(struct vidtv_psi_desc, type) +
> + sizeof_field(struct vidtv_psi_desc, length) +
> + sizeof(struct vidtv_psi_desc *);
> +
> + psi_args.len = args.desc->length;
> + psi_args.dest_offset = args.dest_offset + nbytes;
> +
> + nbytes += vidtv_psi_ts_psi_write_into(psi_args);
> +
> + vidtv_psi_desc_to_cpu(args.desc);
> +
> + return nbytes;
> +}
> +
> +static u32
> +vidtv_psi_table_header_write_into(struct header_write_args args)
> +{
> + /* the number of bytes written by this function */
> + u32 nbytes = 0;
> + struct psi_write_args psi_args = {0};
> +
> + psi_args.dest_buf = args.dest_buf;
> + psi_args.from = args.h;
> + psi_args.len = sizeof(struct vidtv_psi_table_header);
> + psi_args.dest_offset = args.dest_offset;
> + psi_args.pid = args.pid;
> + psi_args.new_psi_section = true;
> + psi_args.continuity_counter = args.continuity_counter;
> + psi_args.is_crc = false;
> + psi_args.dest_buf_sz = args.dest_buf_sz;
> +
> + cpu_to_be16s(&args.h->bitfield);
> + cpu_to_be16s(&args.h->id);
> +
> + nbytes += vidtv_psi_ts_psi_write_into(psi_args);
> +
> + be16_to_cpus(&args.h->bitfield);
> + be16_to_cpus(&args.h->id);
> +
> + return nbytes;
> +}
> +
> +void
> +vidtv_psi_pat_table_comp_sec_len(struct vidtv_psi_table_pat *pat)
> +{
> + /* see ISO/IEC 13818-1 : 2000 p.43 */
> + u16 length = 0;
> + u32 i;
> +
> + /* from immediately after 'section_length' until
> 'last_section_number'*/
> + length += PAT_LEN_UNTIL_LAST_SECTION_NUMBER;
> +
> + /* do not count the pointer */
> + for (i = 0; i < pat->programs; ++i)
> + length += sizeof(struct vidtv_psi_table_pat_program) -
> + sizeof(struct vidtv_psi_table_pat_program *);
> +
> + length += CRC_SIZE_IN_BYTES;
> +
> + WARN_ON(length > MAX_SECTION_LEN);

Please get rid of all WARN_ON, in favor of pr_*_ratelimited() calls.

> + pat->header.section_length = length;
> +}
> +
> +void vidtv_psi_pmt_table_comp_sec_len(struct vidtv_psi_table_pmt *pmt)
> +{
> + /* see ISO/IEC 13818-1 : 2000 p.46 */
> + u16 length = 0;
> + struct vidtv_psi_table_pmt_stream *s = pmt->stream;
> +
> + /* from immediately after 'section_length' until
> 'program_info_length'*/
> + length += PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH;
> +
> + pmt->desc_length = vidtv_psi_desc_comp_len(pmt->descriptor);
> + length += pmt->desc_length;
> +
> + while (s) {
> + /* skip both pointers at the end */
> + length += sizeof(struct vidtv_psi_table_pmt_stream) -
> + sizeof(struct vidtv_psi_desc *) -
> + sizeof(struct vidtv_psi_table_pmt_stream *);
> +
> + s->desc_length = vidtv_psi_desc_comp_len(s->descriptor);
> + length += s->desc_length;
> +
> + s = s->next;
> + }
> +
> + length += CRC_SIZE_IN_BYTES;
> +
> + WARN_ON(length > MAX_SECTION_LEN);
> + pmt->header.section_length = length;
> +}
> +
> +void vidtv_psi_sdt_table_comp_sec_len(struct vidtv_psi_table_sdt *sdt)
> +{
> + /* see ETSI EN 300 468 V 1.10.1 p.24 */
> + u16 length = 0;
> + struct vidtv_psi_table_sdt_service *s = sdt->service;
> +
> + /*
> + * from immediately after 'section_length' until
> + * 'reserved_for_future_use'
> + */
> + length += SDT_LEN_UNTIL_RESERVED_FOR_FUTURE_USE;
> +
> + while (s) {
> + /* skip both pointers at the end */
> + length += sizeof(struct vidtv_psi_table_sdt_service) -
> + sizeof(struct vidtv_psi_desc *) -
> + sizeof(struct vidtv_psi_table_sdt_service *);
> +
> + s->desc_length = vidtv_psi_desc_comp_len(s->descriptor);
> + length += s->desc_length;
> +
> + s = s->next;
> + }
> +
> + length += CRC_SIZE_IN_BYTES;
> +
> + WARN_ON(length > MAX_SECTION_LEN);
> + sdt->header.section_length = length;
> +}
> +
> +struct vidtv_psi_table_pat_program*
> +vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head,
> + u16 service_id,
> + u16 pid)
> +{
> + /*
> + * if 'head' is attached to a table, caller should recompute
> + * the section length afterwards at some point
> + */
> + struct vidtv_psi_table_pat_program *program;
> +
> + program = kzalloc(sizeof(*program), GFP_KERNEL);
> +
> + program->service_id = service_id;
> + /* pid for the PMT section in the TS */
> + program->pid = pid;
> + program->next = NULL;
> + program->reserved = 0x7;
> +
> + if (head) {
> + while (head->next)
> + head = head->next;
> +
> + head->next = program;
> + }
> +
> + return program;
> +}
> +
> +void
> +vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p)
> +{
> + struct vidtv_psi_table_pat_program *curr = p;
> + struct vidtv_psi_table_pat_program *tmp = NULL;
> +
> + while (curr) {
> + tmp = curr;
> + curr = curr->next;
> + kfree(tmp);
> + }
> +}
> +
> +void
> +vidtv_psi_pat_program_assign(struct vidtv_psi_table_pat *pat,
> + struct vidtv_psi_table_pat_program *p)
> +{
> + /* This function transfers ownership of p to the table */
> +
> + u16 program_count = 0;
> + struct vidtv_psi_table_pat_program *program = p;
> + struct vidtv_psi_table_pat_program *temp = pat->program;
> +
> + while (program) {
> + ++program_count;
> + program = program->next;
> + }
> +
> + pat->programs = program_count;
> + pat->program = p;
> +
> + /* Recompute section length */
> + vidtv_psi_pat_table_comp_sec_len(pat);
> +
> + /* reassign if the new size is too big */
> + if (pat->header.section_length > MAX_SECTION_LEN)
> + vidtv_psi_pat_program_assign(pat, temp);
> + else
> + vidtv_psi_pat_program_destroy(temp);
> +}
> +
> +void vidtv_psi_pat_table_init(struct vidtv_psi_table_pat *pat,
> + bool update_version_num,
> + u16 transport_stream_id)
> +{
> + static u8 pat_version;
> +
> + pat->header.table_id = 0x0;
> + pat->header.syntax = 0x1;
> + pat->header.zero = 0x0;
> + pat->header.one = 0x03;
> +
> + /* transport stream ID, at will */
> + pat->header.id = transport_stream_id;
> + pat->header.current_next = 0x1;
> +
> + /* ETSI 300 468: indicates changes in the TS described by this
> table*/
> + if (update_version_num)
> + ++pat_version;
> +
> + pat->header.version = pat_version;
> +
> + pat->header.one2 = 0x03;
> + pat->header.section_id = 0x0;
> + pat->header.last_section = 0x0;
> +
> + pat->programs = 0;
> +
> + vidtv_psi_pat_table_comp_sec_len(pat);
> +}
> +
> +u32 vidtv_psi_pat_write_into(char *buf,
> + u32 offset,
> + struct vidtv_psi_table_pat *pat,
> + u32 buf_sz,
> + u8 *continuity_counter)
> +{
> + /* the number of bytes written by this function */
> + u32 nbytes = 0;
> + const u16 pat_pid = VIDTV_PAT_PID;
> +
> + struct vidtv_psi_table_pat_program *p = pat->program;
> + struct header_write_args h_args = {0};
> + struct psi_write_args args = {0};
> + struct crc32_write_args c_args = {0};
> +
> + vidtv_psi_pat_table_comp_sec_len(pat);
> +
> + h_args.dest_buf = buf;
> + h_args.dest_offset = offset;
> + h_args.h = &pat->header;
> + h_args.pid = pat_pid;
> + h_args.continuity_counter = continuity_counter;
> + h_args.dest_buf_sz = buf_sz;
> +
> + nbytes += vidtv_psi_table_header_write_into(h_args);
> +
> + /* note that the field 'u16 programs' is not really part of the PAT
> */ +
> + args.dest_buf = buf;
> + args.pid = pat_pid;
> + args.new_psi_section = false;
> + args.continuity_counter = continuity_counter;
> + args.is_crc = false;
> + args.dest_buf_sz = buf_sz;
> +
> + while (p) {
> + /* copy the PAT programs */
> + cpu_to_be16s(&p->service_id);
> + cpu_to_be16s(&p->bitfield);
> +
> + args.from = p;
> + /* skip the pointer */
> + args.len = sizeof(*p) -
> + sizeof(struct vidtv_psi_table_pat_program *);
> + args.dest_offset = offset + nbytes;
> +
> + nbytes += vidtv_psi_ts_psi_write_into(args);
> +
> + be16_to_cpus(&p->service_id);
> + be16_to_cpus(&p->bitfield);
> +
> + p = p->next;
> + }
> +
> + c_args.dest_buf = buf;
> + c_args.dest_offset = offset + nbytes;
> + c_args.pid = pat_pid;
> + c_args.continuity_counter = continuity_counter;
> + c_args.dest_buf_sz = buf_sz;
> +
> + nbytes += table_section_crc32_write_into(c_args);
> +
> + return nbytes;
> +}
> +
> +void
> +vidtv_psi_pat_table_destroy(struct vidtv_psi_table_pat *p)
> +{
> + vidtv_psi_pat_program_destroy(p->program);
> +}
> +
> +struct vidtv_psi_table_pmt_stream*
> +vidtv_psi_pmt_stream_init(struct vidtv_psi_table_pmt_stream *head,
> + enum vidtv_psi_stream_types stream_type,
> + u16 es_pid)
> +{
> + struct vidtv_psi_table_pmt_stream *stream;
> +
> + stream = kzalloc(sizeof(*stream), GFP_KERNEL);
> +
> + stream->type = stream_type;
> + stream->elementary_pid = es_pid;
> + stream->reserved = 0x07;
> +
> + stream->desc_length = vidtv_psi_desc_comp_len(stream->descriptor);
> +
> + stream->zero = 0x0;
> + stream->reserved2 = 0x0f;
> +
> + if (head) {
> + while (head->next)
> + head = head->next;
> +
> + head->next = stream;
> + }
> +
> + return stream;
> +}
> +
> +void vidtv_psi_pmt_stream_destroy(struct vidtv_psi_table_pmt_stream *s)
> +{
> + struct vidtv_psi_table_pmt_stream *curr_stream = s;
> + struct vidtv_psi_table_pmt_stream *tmp_stream = NULL;
> +
> + while (curr_stream) {
> + tmp_stream = curr_stream;
> + curr_stream = curr_stream->next;
> + kfree(tmp_stream);
> + }
> +}
> +
> +void vidtv_psi_pmt_stream_assign(struct vidtv_psi_table_pmt *pmt,
> + struct vidtv_psi_table_pmt_stream *s)
> +{
> + /* This function transfers ownership of s to the table */
> + struct vidtv_psi_table_pmt_stream *stream = s;
> + struct vidtv_psi_desc *desc = s->descriptor;
> + struct vidtv_psi_table_pmt_stream *temp = pmt->stream;
> +
> + while (stream)
> + stream = stream->next;
> +
> + while (desc)
> + desc = desc->next;
> +
> + pmt->stream = s;
> + /* Recompute section length */
> + vidtv_psi_pmt_table_comp_sec_len(pmt);
> +
> + /* reassign if the new size is too big */
> + if (pmt->header.section_length > MAX_SECTION_LEN)
> + vidtv_psi_pmt_stream_assign(pmt, temp);
> + else
> + vidtv_psi_pmt_stream_destroy(temp);
> +}
> +
> +u16 vidtv_psi_pmt_get_pid(struct vidtv_psi_table_pmt *section,
> + struct vidtv_psi_table_pat *pat)
> +{
> + struct vidtv_psi_table_pat_program *program = pat->program;
> +
> + /*
> + * service_id is the same as program_number in the
> + * corresponding program_map_section
> + * see ETSI EN 300 468 v1.15.1 p. 24
> + */
> + while (program) {
> + if (program->service_id == section->header.id)
> + return pat->program->pid;
> +
> + program = program->next;
> + }
> +
> + return TS_LAST_VALID_PID + 1; /* not found */
> +}
> +
> +void vidtv_psi_pmt_table_init(struct vidtv_psi_table_pmt *pmt,
> + bool update_version_num,
> + u16 program_number,
> + u16 pcr_pid)
> +{
> + static u8 pmt_version;
> +
> + pmt->header.table_id = 0x2;
> + pmt->header.syntax = 0x1;
> + pmt->header.zero = 0x0;
> + pmt->header.one = 0x3;
> +
> + pmt->header.id = program_number;
> + pmt->header.current_next = 0x1;
> +
> + /* ETSI 300 468: indicates changes in the TS described by this
> table*/
> + if (update_version_num)
> + ++pmt_version;
> +
> + pmt->header.version = pmt_version;
> +
> + pmt->header.one2 = 0x3;
> + pmt->header.section_id = 0;
> + pmt->header.last_section = 0;
> +
> + pmt->pcr_pid = (pcr_pid) ? pcr_pid : 0x1fff;
> + pmt->reserved2 = 0x03;
> +
> + pmt->reserved3 = 0x0f;
> + pmt->zero3 = 0x0;
> +
> + pmt->desc_length = vidtv_psi_desc_comp_len(pmt->descriptor);
> +
> + vidtv_psi_pmt_table_comp_sec_len(pmt);
> +}
> +
> +u32 vidtv_psi_pmt_write_into(char *buf,
> + u32 offset,
> + struct vidtv_psi_table_pmt *pmt,
> + u16 pid,
> + u32 buf_sz,
> + u8 *continuity_counter)
> +{
> + /* the number of bytes written by this function */
> + u32 nbytes = 0;
> + struct vidtv_psi_desc *table_descriptor = pmt->descriptor;
> + struct vidtv_psi_table_pmt_stream *stream = pmt->stream;
> + struct vidtv_psi_desc *stream_descriptor = (stream) ?
> + pmt->stream->descriptor :
> + NULL;
> +
> + struct header_write_args h_args = {0};
> + struct psi_write_args args = {0};
> + struct desc_write_args d_args = {0};
> + struct crc32_write_args c_args = {0};
> +
> + vidtv_psi_pmt_table_comp_sec_len(pmt);
> +
> + h_args.dest_buf = buf;
> + h_args.dest_offset = offset;
> + h_args.h = &pmt->header;
> + h_args.pid = pid;
> + h_args.continuity_counter = continuity_counter;
> + h_args.dest_buf_sz = buf_sz;
> +
> + nbytes += vidtv_psi_table_header_write_into(h_args);
> +
> + /* write the two bitfields */
> + cpu_to_be16s(&pmt->bitfield);
> + cpu_to_be16s(&pmt->bitfield2);
> +
> + args.dest_buf = buf;
> + args.from = pmt + sizeof(struct vidtv_psi_table_header);
> + args.len = sizeof_field(struct vidtv_psi_table_pmt, bitfield) +
> + sizeof_field(struct vidtv_psi_table_pmt, bitfield2);
> + args.dest_offset = offset + nbytes;
> + args.pid = pid;
> + args.new_psi_section = false;
> + args.continuity_counter = continuity_counter;
> + args.is_crc = false;
> + args.dest_buf_sz = buf_sz;
> +
> + nbytes += vidtv_psi_ts_psi_write_into(args);
> +
> + be16_to_cpus(&pmt->bitfield);
> + be16_to_cpus(&pmt->bitfield2);
> +
> + while (table_descriptor) {
> + /* write the descriptors, if any */
> + d_args.dest_buf = buf;
> + d_args.dest_offset = offset + nbytes;
> + d_args.desc = table_descriptor;
> + d_args.pid = pid;
> + d_args.continuity_counter = continuity_counter;
> + d_args.dest_buf_sz = buf_sz;
> +
> + vidtv_psi_desc_to_be(d_args.desc);
> + nbytes += vidtv_psi_desc_write_into(d_args);
> + vidtv_psi_desc_to_cpu(d_args.desc);
> +
> + table_descriptor = table_descriptor->next;
> + }
> +
> + while (stream) {
> + /* write the streams, if any */
> + args.from = stream;
> + args.len = sizeof_field(struct vidtv_psi_table_pmt_stream,
> + type) +
> + sizeof_field(struct vidtv_psi_table_pmt_stream,
> + bitfield) +
> + sizeof_field(struct vidtv_psi_table_pmt_stream,
> + bitfield2);
> + args.dest_offset = offset + nbytes;
> +
> + cpu_to_be16s(&stream->bitfield);
> + cpu_to_be16s(&stream->bitfield2);
> +
> + nbytes += vidtv_psi_ts_psi_write_into(args);
> +
> + be16_to_cpus(&stream->bitfield);
> + be16_to_cpus(&stream->bitfield2);
> +
> + while (stream_descriptor) {
> + /* write the stream descriptors, if any */
> + d_args.desc = stream_descriptor;
> + d_args.dest_offset = offset + nbytes;
> +
> + vidtv_psi_desc_to_be(d_args.desc);
> +
> + nbytes += vidtv_psi_desc_write_into(d_args);
> +
> + vidtv_psi_desc_to_cpu(d_args.desc);
> +
> + stream_descriptor = stream_descriptor->next;
> + }
> +
> + stream = stream->next;
> + }
> +
> + c_args.dest_buf = buf;
> + c_args.dest_offset = offset + nbytes;
> + c_args.pid = pid;
> + c_args.continuity_counter = continuity_counter;
> + c_args.dest_buf_sz = buf_sz;
> +
> + nbytes += table_section_crc32_write_into(c_args);
> +
> + return nbytes;
> +}
> +
> +void vidtv_psi_pmt_table_destroy(struct vidtv_psi_table_pmt *pmt)
> +{
> + struct vidtv_psi_desc *curr_desc = pmt->descriptor;
> + struct vidtv_psi_desc *tmp_desc = NULL;
> +
> + while (curr_desc) {
> + tmp_desc = curr_desc;
> + curr_desc = curr_desc->next;
> + vidtv_psi_desc_destroy(tmp_desc);
> + kfree(tmp_desc);
> + }
> +
> + vidtv_psi_pmt_stream_destroy(pmt->stream);
> +}
> +
> +void vidtv_psi_sdt_table_init(struct vidtv_psi_table_sdt *sdt,
> + bool update_version_num,
> + u16 transport_stream_id)
> +{
> + static u8 sdt_version;
> +
> + sdt->header.table_id = 0x42;
> +
> + sdt->header.one = 0x3;
> + sdt->header.zero = 0x1;
> + /*
> + * The PAT, PMT, and CAT all set this to 0.
> + * Other tables set this to 1.
> + */
> + sdt->header.syntax = 0x1;
> +
> + /*
> + * This is a 16-bit field which serves as a label for identification
> + * of the TS, about which the SDT informs, from any other multiplex
> + * within the delivery system.
> + */
> + sdt->header.id = transport_stream_id;
> + sdt->header.current_next = 0x1;
> +
> + /* ETSI 300 468: indicates changes in the TS described by this
> table*/
> + if (update_version_num)
> + ++sdt_version;
> +
> + sdt->header.version = sdt_version;
> +
> + sdt->header.one2 = 0x3;
> + sdt->header.section_id = 0;
> + sdt->header.last_section = 0;
> +
> + sdt->network_id = transport_stream_id;
> + sdt->reserved = 0xff;
> +
> + vidtv_psi_sdt_table_comp_sec_len(sdt);
> +}
> +
> +u32 vidtv_psi_sdt_write_into(char *buf,
> + u32 offset,
> + struct vidtv_psi_table_sdt *sdt,
> + u32 buf_sz,
> + u8 *continuity_counter)
> +{
> + u32 nbytes = 0; /* the number of bytes written */
> + u16 sdt_pid = VIDTV_SDT_PID; /* see ETSI EN 300 468 v1.15.1 p. 11 */
> +
> + struct vidtv_psi_table_sdt_service *service = sdt->service;
> + struct vidtv_psi_desc *service_desc = (sdt->service) ?
> + sdt->service->descriptor :
> + NULL;
> +
> + struct header_write_args h_args = {0};
> + struct psi_write_args args = {0};
> + struct desc_write_args d_args = {0};
> + struct crc32_write_args c_args = {0};
> +
> + vidtv_psi_sdt_table_comp_sec_len(sdt);
> +
> + h_args.dest_buf = buf;
> + h_args.dest_offset = offset;
> + h_args.h = &sdt->header;
> + h_args.pid = sdt_pid;
> + h_args.continuity_counter = continuity_counter;
> + h_args.dest_buf_sz = buf_sz;
> +
> + nbytes += vidtv_psi_table_header_write_into(h_args);
> +
> + args.dest_buf = buf;
> + args.from = sdt + sizeof(struct vidtv_psi_table_header);
> +
> + args.len = sizeof_field(struct vidtv_psi_table_sdt, network_id)
> +
> + sizeof_field(struct vidtv_psi_table_sdt, reserved);
> +
> + args.dest_offset = offset + nbytes;
> + args.pid = sdt_pid;
> + args.new_psi_section = false;
> + args.continuity_counter = continuity_counter;
> + args.is_crc = false;
> + args.dest_buf_sz = buf_sz;
> +
> + /* copy u16 network_id + u8 reserved)*/
> + cpu_to_be16s(&sdt->network_id);
> +
> + nbytes += vidtv_psi_ts_psi_write_into(args);
> +
> + be16_to_cpus(&sdt->network_id);
> +
> + while (service) {
> + /* copy the services, if any */
> + args.from = service;
> + /* skip both pointers at the end */
> + args.len = sizeof(struct vidtv_psi_table_sdt_service) -
> + sizeof(struct vidtv_psi_desc *) -
> + sizeof(struct vidtv_psi_table_sdt_service *);
> + args.dest_offset = offset + nbytes;
> +
> + cpu_to_be16s(&service->service_id);
> + cpu_to_be16s(&service->bitfield);
> +
> + nbytes += vidtv_psi_ts_psi_write_into(args);
> +
> + be16_to_cpus(&service->service_id);
> + be16_to_cpus(&service->bitfield);
> +
> + while (service_desc) {
> + /* copy the service descriptors, if any */
> + d_args.dest_buf = buf;
> + d_args.dest_offset = offset + nbytes;
> + d_args.desc = service_desc;
> + d_args.pid = sdt_pid;
> + d_args.continuity_counter = continuity_counter;
> + d_args.dest_buf_sz = buf_sz;
> +
> + vidtv_psi_desc_to_be(d_args.desc);
> +
> + nbytes += vidtv_psi_desc_write_into(d_args);
> +
> + vidtv_psi_desc_to_cpu(d_args.desc);
> +
> + service_desc = service_desc->next;
> + }
> +
> + service = service->next;
> + }
> +
> + c_args.dest_buf = buf;
> + c_args.dest_offset = offset + nbytes;
> + c_args.pid = sdt_pid;
> + c_args.continuity_counter = continuity_counter;
> + c_args.dest_buf_sz = buf_sz;
> +
> + nbytes += table_section_crc32_write_into(c_args);
> +
> + return nbytes;
> +}
> +
> +void vidtv_psi_sdt_table_destroy(struct vidtv_psi_table_sdt *sdt)
> +{
> + struct vidtv_psi_table_sdt_service *curr_service = sdt->service;
> + struct vidtv_psi_table_sdt_service *tmp_service = NULL;
> + struct vidtv_psi_desc *curr_desc = (sdt->service) ?
> + sdt->service->descriptor : NULL;
> + struct vidtv_psi_desc *tmp_desc = NULL;
> +
> + while (curr_service) {
> + curr_desc = curr_service->descriptor;
> +
> + while (curr_desc) {
> + /* clear all descriptors for the service */
> + tmp_desc = curr_desc;
> + curr_desc = curr_desc->next;
> + vidtv_psi_desc_destroy(tmp_desc);
> + kfree(tmp_desc);
> + }
> +
> + /* then clear the current service */
> + tmp_service = curr_service;
> + curr_service = curr_service->next;
> + kfree(tmp_service);
> + }
> +}
> +
> +struct vidtv_psi_table_sdt_service
> +*vidtv_psi_sdt_service_init(struct vidtv_psi_table_sdt_service *head,
> + u16 service_id)
> +{
> + /*
> + * if 'head' is attached to a table, caller should recompute
> + * the section length afterwards at some point
> + */
> + struct vidtv_psi_table_sdt_service *service;
> +
> + service = kzalloc(sizeof(*service), GFP_KERNEL);
> +
> + /*
> + * ETSI 300 468: this is a 16bit field which serves as a label to
> + * identify this service from any other service within the TS.
> + * The service id is the same as the program number in the
> + * corresponding program_map_section
> + */
> + service->service_id = service_id;
> + service->EIT_schedule = 0x0; /* TODO */
> + service->EIT_present_following = 0x0; /* TODO */
> + service->reserved = 0x3f; /* all bits on */
> + service->free_CA_mode = 0x0; /* not scrambled */
> + service->running_status = RUNNING;
> +
> + if (head) {
> + while (head->next)
> + head = head->next;
> +
> + head->next = service;
> + }
> +
> + return service;
> +}
> +
> +void
> +vidtv_psi_sdt_service_destroy(struct vidtv_psi_table_sdt_service *service)
> +{
> + struct vidtv_psi_table_sdt_service *curr = service;
> + struct vidtv_psi_table_sdt_service *tmp = NULL;
> +
> + while (curr) {
> + tmp = curr;
> + curr = curr->next;
> + kfree(tmp);
> + }
> +}
> +
> +void
> +vidtv_psi_sdt_service_assign(struct vidtv_psi_table_sdt *sdt,
> + struct vidtv_psi_table_sdt_service *service)
> +{
> + struct vidtv_psi_table_sdt_service *temp = sdt->service;
> +
> + sdt->service = service;
> +
> + /* recompute section length */
> + vidtv_psi_sdt_table_comp_sec_len(sdt);
> +
> + /* reassign if the new size is too big */
> + if (sdt->header.section_length > MAX_SECTION_LEN)
> + vidtv_psi_sdt_service_assign(sdt, temp);
> + else
> + vidtv_psi_sdt_service_destroy(temp);
> +}
> +
> +void
> +vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat,
> + struct vidtv_psi_table_pmt sec[])
> +
> +{
> + /*
> + * PMTs contain information about programs. For each program,
> + * there is one PMT section. This function will create a section
> + * for each program found in the PAT
> + */
> + struct vidtv_psi_table_pat_program *program = pat->program;
> + u32 i = 0;
> +
> + while (program) {
> + vidtv_psi_pmt_table_init(&sec[i],
> + false,
> + sec[i].header.id,
> + 0);
> +
> + ++i;
> + program = program->next;
> + }
> +}
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.h
> b/drivers/media/test-drivers/vidtv/vidtv_psi.h new file mode 100644
> index 0000000000000..c5c8c143f0e4a
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_psi.h
> @@ -0,0 +1,357 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * This file contains the logic to work with MPEG Program-Specific
> Information.
> + * These are defined both in ISO/IEC 13818-1 (systems) and ETSI EN 300 468.
> + * PSI is carried in the form of table structures, and although each table
> might
> + * technically be broken into one or more sections, we do not do this here,
> + * hence 'table' and 'section' are interchangeable for us.
> + *
> + * This code currently supports three tables: PAT, PMT and SDT. These are the
> + * bare minimum to get userspace to recognize our MPEG transport stream. It
> can
> + * be extended to support more PSI tables in the future.
> + *
> + * A note on endianness: MPEG layout is big-endian, therefore:
> + * - All fields spanning more than a byte must undergo 'cpu_to_beXX()' before
> + * serialization. These convertions are done in the *_write_into() functions.
> + *
> + * - All byte sized bitfields must have their ordering reversed if
> + * __LITTLE_ENDIAN_BITFIELD is defined.
> + *
> + * Written by: Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#ifndef VIDTV_PSI_H
> +#define VIDTV_PSI_H
> +
> +#include <linux/types.h>
> +#include <asm/byteorder.h>
> +
> +/*
> + * all section lengths start immediately after the 'section_length' field
> + * see ISO/IEC 13818-1 : 2000 and ETSI EN 300 468 V 1.10.1 for
> + * reference
> + */
> +#define PAT_LEN_UNTIL_LAST_SECTION_NUMBER 5
> +#define PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH 9
> +#define SDT_LEN_UNTIL_RESERVED_FOR_FUTURE_USE 8
> +#define MAX_SECTION_LEN 1021
> +#define VIDTV_PAT_PID 0
> +#define VIDTV_SDT_PID 0x0011
> +
> +enum vidtv_psi_descriptors {
> + REGISTRATION_DESCRIPTOR = 0x05,
> + SERVICE_DESCRIPTOR = 0x48,
> +};
> +
> +enum vidtv_psi_stream_types {
> + /* see ISO/IEC 13818-1 2000 p. 48 */
> + STREAM_PRIVATE_DATA = 0x06,
> +};
> +
> +struct vidtv_psi_desc {
> + u8 type;
> + u8 length;
> + struct vidtv_psi_desc *next;
> + u8 data[];
> +} __packed;
> +
> +struct vidtv_psi_desc_service {
> + u8 type;
> + u8 length;
> + struct vidtv_psi_desc *next;
> +
> + u8 service_type;
> + char *name;
> + char *name_emph;
> + char *provider;
> + char *provider_emph;
> +} __packed;
> +
> +struct vidtv_psi_desc_registration {
> + u8 type;
> + u8 length;
> + struct vidtv_psi_desc *next;
> +
> + /*
> + * The format_identifier is a 32-bit value obtained from a
> Registration
> + * Authority as designated by ISO/IEC JTC 1/SC 29.
> + */
> + u32 format_identifier;
> + /*
> + * The meaning of additional_identification_info bytes, if any, are
> + * defined by the assignee of that format_identifier, and once
> defined
> + * they shall not change.
> + */
> + u8 additional_identification_info[];
> +} __packed;
> +
> +struct vidtv_psi_table_header {
> + u8 table_id;
> + union {
> + u16 bitfield;
> + struct {
> + u8 syntax:1;
> + u8 zero:1;
> + u8 one:2;
> + u16 section_length:12;
> + } __packed;
> + } __packed;
> +
> + u16 id; /* TS ID */
> +#if defined(__LITTLE_ENDIAN_BITFIELD)
> + u8 current_next:1;
> + u8 version:5;
> + u8 one2:2;
> +#elif defined(__BIG_ENDIAN_BITFIELD)
> + u8 one2:2;
> + u8 version:5;
> + u8 current_next:1;
> +#else
> +#error "Please fix <asm/byteorder.h>"

I would change the message. I mean, if something has changed there,
and, for example, one of the defines was renamed, then the vidtv code
is the one that would require changes, not asm/byteorder.

> +#endif
> + u8 section_id; /* section_number */
> + u8 last_section; /* last_section_number */
> +} __packed;
> +
> +struct vidtv_psi_table_pat_program {
> + u16 service_id;
> + union {
> + u16 bitfield;
> + struct {
> + u8 reserved:3;
> + u16 pid:13;
> + } __packed;
> + } __packed;
> + struct vidtv_psi_table_pat_program *next;
> +} __packed;
> +
> +struct vidtv_psi_table_pat {
> + struct vidtv_psi_table_header header;
> + u16 programs;
> + struct vidtv_psi_table_pat_program *program;
> +} __packed;
> +
> +struct vidtv_psi_table_sdt_service {
> + u16 service_id;
> +#if defined(__LITTLE_ENDIAN_BITFIELD)
> + u8 EIT_present_following:1;
> + u8 EIT_schedule:1;
> + u8 reserved:6;
> +#elif defined(__BIG_ENDIAN_BITFIELD)
> + u8 reserved:6;
> + u8 EIT_schedule:1;
> + u8 EIT_present_following:1;
> +#else
> +#error "Please fix <asm/byteorder.h>"
> +#endif
> + union {
> + u16 bitfield;
> + struct {
> + u16 running_status:3;
> + u16 free_CA_mode:1;
> + u16 desc_length:12;
> + } __packed;
> + } __packed;
> + struct vidtv_psi_desc *descriptor;
> + struct vidtv_psi_table_sdt_service *next;
> +} __packed;
> +
> +struct vidtv_psi_table_sdt {
> + struct vidtv_psi_table_header header;
> + u16 network_id; /* original_network_id */
> + u8 reserved;
> + struct vidtv_psi_table_sdt_service *service;
> +} __packed;
> +
> +enum service_running_status {
> + RUNNING,
> +};
> +
> +enum service_type {
> + /* see ETSI EN 300 468 v1.15.1 p. 77 */
> + DIGITAL_TELEVISION_SERVICE = 0x1,
> +};
> +
> +struct vidtv_psi_table_pmt_stream {
> + u8 type;
> + union {
> + u16 bitfield;
> + struct {
> + u16 reserved:3;
> + u16 elementary_pid:13;
> + } __packed;
> + } __packed;
> + union {
> + u16 bitfield2;
> + struct {
> + u16 reserved2:4;
> + u16 zero:2;
> + u16 desc_length:10;
> + } __packed;
> + } __packed;
> + struct vidtv_psi_desc *descriptor;
> + struct vidtv_psi_table_pmt_stream *next;
> +} __packed;
> +
> +struct vidtv_psi_table_pmt {
> + struct vidtv_psi_table_header header;
> + union {
> + u16 bitfield;
> + struct {
> + u16 reserved2:3;
> + u16 pcr_pid:13;
> + } __packed;
> + } __packed;
> +
> + union {
> + u16 bitfield2;
> + struct {
> + u16 reserved3:4;
> + u16 zero3:2;
> + u16 desc_length:10; /* program_info_length */
> + } __packed;
> + } __packed;
> + struct vidtv_psi_desc *descriptor;
> + struct vidtv_psi_table_pmt_stream *stream;
> +} __packed;
> +
> +struct psi_write_args {
> + void *dest_buf; /* the buffer to write into */
> + void *from;
> + size_t len; /* how much to write */
> + u32 dest_offset; /* where to start writing in the buffer */
> + u16 pid; /* TS packet ID */
> + bool new_psi_section; /* set when starting a table section */
> + u8 *continuity_counter; /* TS: incremented on every new packet */
> + bool is_crc; /* set when writing the CRC at the end */
> + u32 dest_buf_sz; /* protect against overflow if not zero */
> +};
> +
> +struct desc_write_args {
> + void *dest_buf;
> + u32 dest_offset;
> + struct vidtv_psi_desc *desc;
> + u16 pid;
> + u8 *continuity_counter;
> + u32 dest_buf_sz;
> +};
> +
> +struct crc32_write_args {
> + void *dest_buf;
> + u32 dest_offset;
> + u16 pid;
> + u8 *continuity_counter;
> + u32 dest_buf_sz;
> +};
> +
> +struct header_write_args {
> + void *dest_buf;
> + u32 dest_offset;
> + struct vidtv_psi_table_header *h;
> + u16 pid;
> + u8 *continuity_counter;
> + u32 dest_buf_sz;
> +};
> +
> +struct vidtv_psi_desc *vidtv_psi_desc_init(struct vidtv_psi_desc *head,
> + u8 type,
> + u8 length);
> +
> +void vidtv_psi_pat_table_init(struct vidtv_psi_table_pat *pat,
> + bool update_version_num,
> + u16 transport_stream_id);
> +
> +struct vidtv_psi_table_pat_program*
> +vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head,
> + u16 service_id,
> + u16 pid);
> +
> +struct vidtv_psi_table_pmt_stream*
> +vidtv_psi_pmt_stream_init(struct vidtv_psi_table_pmt_stream *head,
> + enum vidtv_psi_stream_types stream_type,
> + u16 es_pid);
> +
> +void vidtv_psi_pmt_table_init(struct vidtv_psi_table_pmt *pmt,
> + bool update_version_num,
> + u16 program_number,
> + u16 pcr_pid);
> +
> +void
> +vidtv_psi_sdt_table_init(struct vidtv_psi_table_sdt *sdt,
> + bool update_version_num,
> + u16 transport_stream_id);
> +
> +struct vidtv_psi_table_sdt_service*
> +vidtv_psi_sdt_service_init(struct vidtv_psi_table_sdt_service *head,
> + u16 service_id);
> +
> +void
> +vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc);
> +
> +void
> +vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p);
> +
> +void
> +vidtv_psi_pat_table_destroy(struct vidtv_psi_table_pat *p);
> +
> +void
> +vidtv_psi_pmt_stream_destroy(struct vidtv_psi_table_pmt_stream *s);
> +
> +void
> +vidtv_psi_pmt_table_destroy(struct vidtv_psi_table_pmt *pmt);
> +
> +void
> +vidtv_psi_sdt_table_destroy(struct vidtv_psi_table_sdt *sdt);
> +
> +void
> +vidtv_psi_sdt_service_destroy(struct vidtv_psi_table_sdt_service *service);
> +
> +void
> +vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc);
> +
> +void
> +vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p);
> +
> +void
> +vidtv_psi_sdt_service_assign(struct vidtv_psi_table_sdt *sdt,
> + struct vidtv_psi_table_sdt_service *service);
> +
> +void vidtv_psi_desc_assign(struct vidtv_psi_desc **to,
> + struct vidtv_psi_desc *desc);
> +
> +void vidtv_psi_pat_program_assign(struct vidtv_psi_table_pat *pat,
> + struct vidtv_psi_table_pat_program *p);
> +
> +void vidtv_psi_pmt_stream_assign(struct vidtv_psi_table_pmt *pmt,
> + struct vidtv_psi_table_pmt_stream *s);
> +void
> +vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat,
> + struct vidtv_psi_table_pmt *sec);
> +
> +u16 vidtv_psi_pmt_get_pid(struct vidtv_psi_table_pmt *section,
> + struct vidtv_psi_table_pat *pat);
> +
> +void vidtv_psi_pat_table_comp_sec_len(struct vidtv_psi_table_pat *pat);
> +void vidtv_psi_pmt_table_comp_sec_len(struct vidtv_psi_table_pmt *pmt);
> +void vidtv_psi_sdt_table_comp_sec_len(struct vidtv_psi_table_sdt *sdt);
> +
> +u32 vidtv_psi_pat_write_into(char *buf,
> + u32 offset,
> + struct vidtv_psi_table_pat *pat,
> + u32 buf_sz,
> + u8 *continuity_counter);
> +
> +u32 vidtv_psi_sdt_write_into(char *buf,
> + u32 offset,
> + struct vidtv_psi_table_sdt *sdt,
> + u32 buf_sz,
> + u8 *continuity_counter);
> +
> +u32 vidtv_psi_pmt_write_into(char *buf,
> + u32 offset,
> + struct vidtv_psi_table_pmt *pmt,
> + u16 pid,
> + u32 buf_sz,
> + u8 *continuity_counter);
> +
> +#endif // VIDTV_PSI_H



Thanks,
Mauro

2020-05-03 08:24:10

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 09/11] media: vidtv: implement a PES packetizer

Em Sat, 2 May 2020 00:22:14 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> From: "Daniel W. S. Almeida" <[email protected]>
>
> Implement the PES logic to convert encoder data into MPEG TS packets.
> These TS packets can then be fed into a TS multiplexer and eventually
> into userspace.
>
> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> ---
> drivers/media/test-drivers/vidtv/Makefile | 2 +-
> .../media/test-drivers/vidtv/vidtv_common.c | 7 +
> .../media/test-drivers/vidtv/vidtv_common.h | 2 +
> drivers/media/test-drivers/vidtv/vidtv_pes.c | 429 ++++++++++++++++++
> drivers/media/test-drivers/vidtv/vidtv_pes.h | 185 ++++++++
> 5 files changed, 624 insertions(+), 1 deletion(-)
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_pes.c
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_pes.h
>
> diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
> index e4f744aa53136..e3a6540f50e87 100644
> --- a/drivers/media/test-drivers/vidtv/Makefile
> +++ b/drivers/media/test-drivers/vidtv/Makefile
> @@ -1,6 +1,6 @@
> # SPDX-License-Identifier: GPL-2.0
>
> vidtv_demod-objs := vidtv_common.o
> -vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o
> +vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o vidtv_pes.o
>
> obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.c b/drivers/media/test-drivers/vidtv/vidtv_common.c
> index 28f10630499a9..b1412b497e1e3 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_common.c
> +++ b/drivers/media/test-drivers/vidtv/vidtv_common.c
> @@ -42,3 +42,10 @@ u32 vidtv_memset(void *to,
> memset(to, c, len);
> return len;
> }
> +
> +u64 vidtv_extract_bits(u64 value, u8 start_bit, u8 nbits)
> +{
> + u64 mask = ((1 << nbits) - 1) << start_bit;
> +
> + return value & mask;
> +}


There are macros already for it, like:

include/linux/bits.h:#define BIT_MASK(nr) (UL(1) << ((nr) % BITS_PER_LONG))

So, please stick with the existing code there, as defining driver-specific
stuff like that is usually painful and may lead into errors.

(Btw, your "mask" definition doesn't work with u64)

> diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.h b/drivers/media/test-drivers/vidtv/vidtv_common.h
> index 64072c010dc66..3b68f95c5f6c8 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_common.h
> +++ b/drivers/media/test-drivers/vidtv/vidtv_common.h
> @@ -25,4 +25,6 @@ u32 vidtv_memset(void *to,
> u32 offset,
> u32 buf_sz);
>
> +u64 vidtv_extract_bits(u64 value, u8 start_bit, u8 nbits);
> +
> #endif // VIDTV_COMMON_H
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_pes.c b/drivers/media/test-drivers/vidtv/vidtv_pes.c
> new file mode 100644
> index 0000000000000..bc631bac07778
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_pes.c
> @@ -0,0 +1,429 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Vidtv serves as a reference DVB driver and helps validate the existing APIs
> + * in the media subsystem. It can also aid developers working on userspace
> + * applications.
> + *
> + * This file contains the logic to translate the ES data for one access unit
> + * from an encoder into MPEG TS packets. It does so by first encapsulating it
> + * with a PES header and then splitting it into TS packets.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#include <linux/types.h>
> +#include <linux/printk.h>
> +#include "vidtv_pes.h"
> +#include "vidtv_common.h"
> +#include "vidtv_ts.h"
> +
> +#define PRIVATE_STREAM_1_ID 0xbd /* private_stream_1. See SMPTE 302M-2007 p.6 */
> +#define PES_HEADER_MAX_STUFFING_BYTES 32
> +#define PES_TS_HEADER_MAX_STUFFING_BYTES 182
> +
> +static u32 vidtv_pes_op_get_regular_len(bool send_pts, bool send_dts)
> +{
> + u32 len = 0;
> +
> + /* the flags must always be sent */
> + len += sizeof(struct vidtv_pes_optional);
> +
> + /* From all optionals, we might send these for now */
> + if (send_pts && send_dts)
> + len += sizeof(struct vidtv_pes_optional_pts_dts);
> + else if (send_pts)
> + len += sizeof(struct vidtv_pes_optional_pts);
> +
> + return len;
> +}
> +
> +static u32 vidtv_pes_h_get_regular_len(bool send_pts, bool send_dts)
> +{
> + /* PES header length notwithstanding stuffing bytes */
> + u32 len = 0;
> +
> + len += sizeof(struct vidtv_mpeg_pes);
> + len += vidtv_pes_op_get_regular_len(send_pts, send_dts);
> +
> + return len;
> +}
> +
> +static u32 vidtv_pes_write_header_stuffing(struct vidtv_mpeg_pes *pes_h,
> + struct pes_header_write_args args)
> +{
> + /*
> + * This is a fixed 8-bit value equal to '1111 1111' that can be inserted
> + * by the encoder, for example to meet the requirements of the channel.
> + * It is discarded by the decoder. No more than 32 stuffing bytes shall
> + * be present in one PES packet header.
> + */
> +
> + WARN_ON(args.n_pes_h_s_bytes > PES_HEADER_MAX_STUFFING_BYTES);

As commented, don't use WARN_ON(). At most, you could use WARN_ON_ONCE(),
as otherwise, you may end by causing serious performance issues if
the code starts to produce a flood of warnings at the dmesg.

I would use pr_warn_ratelimit() on all such cases.

> +
> + if (args.n_pes_h_s_bytes > PES_HEADER_MAX_STUFFING_BYTES)
> + args.n_pes_h_s_bytes = PES_HEADER_MAX_STUFFING_BYTES;
> +
> + /* gives the length of the remainder of the PES header in bytes */
> + pes_h->length += args.n_pes_h_s_bytes;
> +
> + return vidtv_memset(args.dest_buf + args.dest_offset,
> + 0xff,
> + args.n_pes_h_s_bytes,
> + args.dest_offset,
> + args.dest_buf_sz);
> +}
> +
> +static u32 vidtv_pes_write_pts_dts(struct pes_header_write_args args)
> +{
> + u32 nbytes = 0; /* the number of bytes written by this function */
> +
> + struct vidtv_pes_optional_pts pts = {0};
> + struct vidtv_pes_optional_pts_dts pts_dts = {0};
> + void *op = NULL;
> + size_t op_sz = 0;
> +
> + if (!args.send_pts && args.send_dts)
> + return 0;
> +
> + /* see ISO/IEC 13818-1 : 2000 p. 32 */
> +
> + if (args.send_pts && args.send_dts) {
> + cpu_to_be64s(&args.pts);
> + cpu_to_be64s(&args.dts);
> + pts_dts.three = 0x3;
> +
> + pts_dts.pts1 = vidtv_extract_bits(args.pts, 30, 3);
> + pts_dts.marker1 = 0x1;
> + pts_dts.pts2 = vidtv_extract_bits(args.pts, 15, 15);
> + pts_dts.marker2 = 0x1;
> + pts_dts.pts3 = vidtv_extract_bits(args.pts, 0, 15);
> + pts_dts.marker3 = 0x1;
> +
> + pts_dts.one = 0x1;
> +
> + pts_dts.dts1 = vidtv_extract_bits(args.dts, 30, 3);
> + pts_dts.marker1 = 0x1;
> + pts_dts.dts2 = vidtv_extract_bits(args.dts, 15, 15);
> + pts_dts.marker2 = 0x1;
> + pts_dts.dts3 = vidtv_extract_bits(args.dts, 0, 15);
> + pts_dts.marker3 = 0x1;
> +
> + be64_to_cpus(&args.pts);
> + be64_to_cpus(&args.dts);
> +
> + op = &pts_dts;
> + op_sz = sizeof(pts_dts);
> +
> + } else if (args.send_pts) {
> + cpu_to_be64s(&args.pts);
> + pts.two = 0x2;
> + pts.pts1 = vidtv_extract_bits(args.pts, 30, 3);
> + pts.marker1 = 0x1;
> + pts.pts2 = vidtv_extract_bits(args.pts, 15, 15);
> + pts.marker2 = 0x1;
> + pts.pts3 = vidtv_extract_bits(args.pts, 0, 15);
> + pts.marker3 = 0x1;
> + be64_to_cpus(&args.pts);
> +
> + op = &pts;
> + op_sz = sizeof(pts);
> + }
> +
> + /* copy PTS/DTS optional */
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + op,
> + op_sz,
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + return nbytes;
> +}
> +
> +static u32 vidtv_pes_write_h(struct pes_header_write_args args)
> +{
> + u32 nbytes = 0; /* the number of bytes written by this function */
> +
> + struct vidtv_mpeg_pes pes_header = {0};
> + struct vidtv_pes_optional pes_optional = {0};
> + struct pes_header_write_args pts_dts_args = args;
> +
> + pes_header.packet_start_code_prefix = PES_START_CODE_PREFIX;
> +
> + pes_header.stream_id = (args.is_s302m_pkt) ?
> + PRIVATE_STREAM_1_ID :
> + args.stream_id;
> +
> + pes_header.length = vidtv_pes_op_get_regular_len(args.send_pts,
> + args.send_dts);
> +
> + pes_optional.two = 0x2;
> +
> + pes_optional.PTS_DTS = (args.send_pts && args.send_dts) ?
> + 0x3 :
> + (args.send_pts) ?
> + 0x2 :
> + 0x0;
> +
> + /* copy header */
> + cpu_to_be32s(&pes_header.bitfield);
> + cpu_to_be16s(&pes_header.length);
> +
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + &pes_header,
> + sizeof(pes_header),
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + be32_to_cpus(&pes_header.bitfield);
> + be16_to_cpus(&pes_header.length);

I don't like the idea of changing the "from" buffer endiannes, copy
and then restore it back to the original state. Is this really needed?

I would, instead, define:

struct pes_header {
...
__be32 bitfield;
__be16 length;
...
};

Then wherever you would touch them:

u32 bitfield;
u16 len;

/* Write into BE fields */
pes_header.bitfield = cpu_to_be32(bitfield);
pes_header.length = cpu_to_be16(len);

/* Read from BE fields */
bitfield = be32_to_cpu(pes_header.bitfield);
len = be16_to_cpu(pes_header.length);


As a side effect, when you use "__be16" and "__be32" types, gcc
and smatch/sparse will warn you if you mess with endiannes.

Same applies to similar code elsewhere.

> +
> + /* copy optional header */
> + cpu_to_be16s(&pes_optional.bitfield);
> +
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + &pes_optional,
> + sizeof(pes_optional),
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + be16_to_cpus(&pes_optional.bitfield);
> +
> + pts_dts_args.dest_offset = args.dest_offset + nbytes;
> + nbytes += vidtv_pes_write_pts_dts(pts_dts_args);
> +
> + /* write any PES header stuffing */
> + nbytes += vidtv_pes_write_header_stuffing(&pes_header, args);
> +
> + return nbytes;
> +}
> +
> +static u32 vidtv_pes_write_stuffing(struct vidtv_mpeg_ts *ts_h,
> + void *dest_buf,
> + u32 dest_offset,
> + u32 n_stuffing_bytes,
> + u32 buf_sz)
> +{
> + /*
> + * For Transport Stream packets carrying PES packets, stuffing is
> + * needed when there is insufficient PES packet data to completely
> + * fill the Transport Stream packet payload bytes. Stuffing is
> + * accomplished by defining an adaptation field longer than the sum of
> + * the lengths of the data elements in it, so that the payload bytes
> + * remaining after the adaptation field exactly accommodates the
> + * available PES packet data. The extra space in the adaptation field
> + * is filled with stuffing bytes.
> + *
> + */
> +
> + /* the number of bytes written by this function */
> + u32 nbytes = 0;
> + struct vidtv_mpeg_ts_adaption ts_adap = {0};
> +
> + if (!n_stuffing_bytes)
> + return nbytes;
> +
> + ts_h->adaptation_field = 1;
> +
> + WARN_ON(n_stuffing_bytes > PES_TS_HEADER_MAX_STUFFING_BYTES);
> +
> + if (n_stuffing_bytes > PES_TS_HEADER_MAX_STUFFING_BYTES)
> + n_stuffing_bytes = PES_TS_HEADER_MAX_STUFFING_BYTES;
> +
> + /* the AF will only be its 'length' field with a value of zero */
> + if (n_stuffing_bytes == 1) {
> + nbytes += vidtv_memset(dest_buf + dest_offset + nbytes,
> + 0,
> + n_stuffing_bytes,
> + dest_offset + nbytes,
> + buf_sz);
> +
> + return nbytes;
> + }
> +
> + n_stuffing_bytes -= sizeof(ts_adap);
> +
> + /* length _immediately_ following 'adaptation_field_length' */
> + ts_adap.length = sizeof(ts_adap) -
> + sizeof(ts_adap.length) +
> + n_stuffing_bytes;
> +
> + /* write the adap after the TS header */
> + nbytes += vidtv_memcpy(dest_buf + dest_offset + nbytes,
> + &ts_adap,
> + sizeof(ts_adap),
> + dest_offset + nbytes,
> + buf_sz);
> +
> + /* write the stuffing bytes */
> + nbytes += vidtv_memset(dest_buf + dest_offset + nbytes,
> + 0xff,
> + n_stuffing_bytes,
> + dest_offset + nbytes,
> + buf_sz);
> +
> + return nbytes;
> +}
> +
> +static u32 vidtv_pes_write_ts_h(struct pes_ts_header_write_args args)
> +{
> + /* number of bytes written by this function */
> + u32 nbytes = 0;
> + struct vidtv_mpeg_ts ts_header = {0};
> +
> + ts_header.sync_byte = TS_SYNC_BYTE;
> + ts_header.tei = 0;
> + ts_header.pid = args.pid;
> + ts_header.priority = 0;
> + ts_header.scrambling = 0; /* not scrambled */
> + ts_header.adaptation_field = 0;
> + ts_header.payload = 1;
> +
> + ts_header.payload_start = (!args.wrote_pes_header) ? 1 : 0;
> + ts_header.continuity_counter = *args.continuity_counter;
> +
> + vidtv_ts_inc_cc(args.continuity_counter);
> +
> + cpu_to_be16s(&ts_header.bitfield);
> +
> + /* write the TS header */
> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> + &ts_header,
> + sizeof(ts_header),
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + be16_to_cpus(&ts_header.bitfield);
> + /* write stuffing, if any */
> + nbytes += vidtv_pes_write_stuffing(&ts_header,
> + args.dest_buf,
> + args.dest_offset + nbytes,
> + args.n_stuffing_bytes,
> + args.dest_buf_sz);
> +
> + return nbytes;
> +}
> +
> +u32 vidtv_pes_write_into(struct pes_write_args args)
> +{
> + u32 nbytes_past_boundary = (args.dest_offset % TS_PACKET_LEN);
> + bool aligned = (nbytes_past_boundary == 0);
> +
> + struct pes_ts_header_write_args ts_header_args = {0};
> + struct pes_header_write_args pes_header_args = {0};
> +
> + /* number of bytes written by this function */
> + u32 nbytes = 0;
> + u32 remaining_len = args.access_unit_len;
> +
> + bool wrote_pes_header = false;
> + bool stuff = false;
> +
> + u32 available_space = 0;
> + u32 payload_write_len = 0;
> + u32 num_stuffing_bytes = 0;
> +
> + /* Just a sanity check, should not really happen because we stuff the
> + * TS packet when we finish writing the PES data, but if this happens
> + * then we have messed up the logic somewhere.
> + *
> + * Also note that, unlike packets for PSI data, we need to carry PES
> + * packets aligned with the payload of transport packets, that is the
> + * first byte of each PES header must be the first byte in the payload
> + * of a transport packet. As a consequence, the last byte of a PES
> + * packet must be the last byte of the payload of a transport packet.
> + */
> + WARN_ON(!aligned);

Same note as on a previous patch: if not aligned, you should re-align.

> +
> + if (args.send_dts && !args.send_pts) {
> + pr_warn("%s: forbidden value '01' for PTS_DTS flags", __func__);
> + args.send_pts = true;
> + args.pts = args.dts;
> + }
> +
> + /* see SMPTE 302M clause 6.4 */
> + if (args.is_s302m_pkt) {
> + args.send_dts = false;
> + args.send_pts = true;
> + }
> +
> + while (remaining_len) {
> + /*
> + * The amount of space initially available in the TS packet.
> + * if this is the beginning of the PES packet, we need to
> + * take into account the space needed for the TS header _and_
> + * for the PES header
> + */
> + available_space = (!wrote_pes_header) ?
> + TS_PAYLOAD_LEN -
> + vidtv_pes_h_get_regular_len(args.send_pts,
> + args.send_dts) :
> + TS_PAYLOAD_LEN;
> +
> + /* if the encoder has inserted stuffing bytes in the PES
> + * header, account for them.
> + */
> + available_space -= args.n_pes_h_s_bytes;
> +
> + /* whether we need to stuff the TS packet to align the buffer */
> + stuff = remaining_len < available_space;
> +
> + /*
> + * how much of the _actual_ payload we should write in this
> + * packet.
> + */
> + payload_write_len = (stuff) ?
> + remaining_len :
> + available_space;
> +
> + num_stuffing_bytes = available_space - payload_write_len;
> +
> + /* write ts header */
> + ts_header_args.dest_buf = args.dest_buf;
> + ts_header_args.dest_offset = args.dest_offset + nbytes;
> + ts_header_args.dest_buf_sz = args.dest_buf_sz;
> + ts_header_args.pid = args.pid;
> + ts_header_args.continuity_counter = args.continuity_counter;
> + ts_header_args.wrote_pes_header = wrote_pes_header;
> + ts_header_args.n_stuffing_bytes = num_stuffing_bytes;
> +
> + nbytes += vidtv_pes_write_ts_h(ts_header_args);
> +
> + if (!wrote_pes_header) {
> + /* write the PES header only once */
> + pes_header_args.dest_buf = args.dest_buf;
> +
> + pes_header_args.dest_offset = args.dest_offset +
> + nbytes;
> +
> + pes_header_args.dest_buf_sz = args.dest_buf_sz;
> + pes_header_args.is_s302m_pkt = args.is_s302m_pkt;
> + pes_header_args.send_pts = args.send_pts;
> + pes_header_args.pts = args.pts;
> + pes_header_args.send_dts = args.send_dts;
> + pes_header_args.dts = args.dts;
> + pes_header_args.stream_id = args.stream_id;
> + pes_header_args.n_pes_h_s_bytes = args.n_pes_h_s_bytes;
> +
> + nbytes += vidtv_pes_write_h(pes_header_args);
> + wrote_pes_header = true;
> + }
> +
> + /* write as much of the payload as we possibly can */
> + nbytes += vidtv_memcpy(args.dest_buf +
> + args.dest_offset +
> + nbytes,
> + args.from,
> + payload_write_len,
> + args.dest_offset + nbytes,
> + args.dest_buf_sz);
> +
> + args.from += payload_write_len;
> + args.dest_offset += nbytes;
> +
> + /* sanity check for underflow */
> + WARN_ON(remaining_len - payload_write_len > remaining_len);
> + remaining_len -= payload_write_len;
> + }
> +
> + return nbytes;
> +}
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_pes.h b/drivers/media/test-drivers/vidtv/vidtv_pes.h
> new file mode 100644
> index 0000000000000..00ede32adf476
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_pes.h
> @@ -0,0 +1,185 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Vidtv serves as a reference DVB driver and helps validate the existing APIs
> + * in the media subsystem. It can also aid developers working on userspace
> + * applications.
> + *
> + * This file contains the logic to translate the ES data for one access unit
> + * from an encoder into MPEG TS packets. It does so by first encapsulating it
> + * with a PES header and then splitting it into TS packets.
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#ifndef VIDTV_PES_H
> +#define VIDTV_PES_H
> +
> +#include <asm/byteorder.h>
> +#include <linux/types.h>
> +#include "vidtv_common.h"
> +
> +#define PES_MAX_LEN 65536 /* Set 'length' to 0 if greater */
> +#define PES_START_CODE_PREFIX 0x001 /* 00 00 01 */
> +
> +struct vidtv_pes_optional_pts {
> + struct {
> + #if defined(__LITTLE_ENDIAN_BITFIELD)
> + u8 marker3:1; /* always 0x1 */
> + u16 pts3:15;
> + u8 marker2:1; /* always 0x1 */
> + u16 pts2:15;
> + u8 marker1:1; /* always 0x1 */
> + u8 pts1:3;
> + u8 two:4; /* always 0010b */
> + #elif defined(__BIG_ENDIAN_BITFIELD)
> + u8 two:4; /* always 0010b */
> + u8 pts1:3;
> + u8 marker1:1; /* always 0x1 */
> + u16 pts2:15;
> + u8 marker2:1; /* always 0x1 */
> + u16 pts3:15;
> + u8 marker3:1; /* always 0x1 */
> + #else
> + #error "Please fix <asm/byteorder.h>"
> + #endif
> + } __packed;
> +} __packed;
> +
> +struct vidtv_pes_optional_pts_dts {
> + struct {
> + #if defined(__LITTLE_ENDIAN_BITFIELD)
> + u8 marker6:1; /* always 0x1 */
> + u16 dts3:15;
> + u8 marker5:1; /* always 0x1 */
> + u16 dts2:15;
> + u8 marker4:1; /* always 0x1 */
> + u8 dts1:3;
> + u8 one:4; /* always 0001b */
> + u8 marker3:1; /* always 0x1 */
> + u16 pts3:15;
> + u8 marker2:1; /* always 0x1 */
> + u16 pts2:15;
> + u8 marker1:1; /* always 0x1 */
> + u8 pts1:3;
> + u8 three:4; /* always 0011b */
> + #elif defined(__BIG_ENDIAN_BITFIELD)
> + u8 three:4; /* always 0011b */
> + u8 pts1:3;
> + u8 marker1:1; /* always 0x1 */
> + u16 pts2:15;
> + u8 marker2:1; /* always 0x1 */
> + u16 pts3:15;
> + u8 marker3:1; /* always 0x1 */
> + u8 one:4; /* always 0001b */
> + u8 dts1:3;
> + u8 marker4:1; /* always 0x1 */
> + u16 dts2:15;
> + u8 marker5:1; /* always 0x1 */
> + u16 dts3:15;
> + u8 marker6:1; /* always 0x1 */
> + #else
> + #error "Please fix <asm/byteorder.h>"
> + #endif
> + } __packed;
> +} __packed;
> +
> +struct vidtv_pes_optional {
> + union {
> + u16 bitfield;
> + struct {
> + u16 two:2; /* always 0x2*/
> + u16 PES_scrambling_control:2;
> + u16 PES_priority:1;
> + u16 data_alignment_indicator:1; /* ununsed for us */
> + u16 copyright:1;
> + u16 original_or_copy:1;
> + u16 PTS_DTS:2;
> + /* These flags show which components are actually
> + * present in the "optinal fields" in the optinal PES
> + * header and which are not. Vidtv currently does
> + * not need any of these.
> + */
> + u16 ESCR:1;
> + u16 ES_rate:1;
> + u16 DSM_trick_mode:1;
> + u16 additional_copy_info:1;
> + u16 PES_CRC:1;
> + u16 PES_extension:1;
> + } __packed;
> + } __packed;
> + u8 length;
> +} __packed;
> +
> +struct vidtv_mpeg_pes {
> + union {
> + u32 bitfield;
> + struct {
> + /* These two together make the 32-bit start-code */
> + u32 packet_start_code_prefix:24;
> + u32 stream_id:8;
> + } __packed;
> + } __packed;
> + /* after this field until the end of the PES data payload */
> + u16 length;
> + struct vidtv_pes_optional optional[];
> +} __packed;
> +
> +struct pes_header_write_args {
> + void *dest_buf;
> + u32 dest_offset;
> + u32 dest_buf_sz;
> + bool is_s302m_pkt;
> +
> + bool send_pts;
> + u64 pts;
> +
> + bool send_dts;
> + u64 dts;
> +
> + u16 stream_id;
> + /* might be used by an encoder if needed, gets discarded by decoder */
> + u32 n_pes_h_s_bytes;
> +};
> +
> +struct pes_ts_header_write_args {
> + void *dest_buf;
> + u32 dest_offset;
> + u32 dest_buf_sz;
> + u16 pid;
> + u8 *continuity_counter;
> + bool wrote_pes_header;
> + u32 n_stuffing_bytes;
> +};
> +
> +struct pes_write_args {
> + void *dest_buf; /* pointer to a program mux buffer */
> + void *from; /* pointer to the encoder buffer */
> +
> + /* the size of one access unit (with any headers it might need) */
> + u32 access_unit_len;
> +
> + u32 dest_offset; /* where to start writing in the program mux buffer */
> + u32 dest_buf_sz; /* how big is the program mux buffer */
> + u16 pid; /* TS packet ID */
> +
> + /* use SMPTE 302M to packetize the data */
> + bool is_s302m_pkt;
> +
> + u8 *continuity_counter; /* incremented for every TS packet */
> +
> + /* Examples: Audio streams (0xc0-0xdf), Video streams (0xe0-0xef) */
> + u16 stream_id;
> +
> + bool send_pts;
> + u64 pts;
> +
> + bool send_dts;
> + u64 dts;
> +
> + /* might be used by an encoder if needed, gets discarded by decoder */
> + u32 n_pes_h_s_bytes;
> +};
> +
> +u32 vidtv_pes_write_into(struct pes_write_args args);
> +
> +#endif // VIDTV_PES_H



Thanks,
Mauro

2020-05-03 09:00:42

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 10/11] media: vidtv: Implement a SMPTE 302M encoder

Em Sat, 2 May 2020 00:22:15 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> From: "Daniel W. S. Almeida" <[email protected]>
>
> Implement a S302M encoder to make it possible to insert PCM audio data
> in the generated MPEG Transport Stream.
>
> This shall enable passing an audio signal into userspace so it can be
> decoded and played by media software.
>
> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> ---
> drivers/media/test-drivers/vidtv/Makefile | 3 +-
> .../media/test-drivers/vidtv/vidtv_common.h | 2 +
> .../media/test-drivers/vidtv/vidtv_encoder.h | 103 +++
> .../media/test-drivers/vidtv/vidtv_s302m.c | 608 ++++++++++++++++++
> .../media/test-drivers/vidtv/vidtv_s302m.h | 99 +++
> 5 files changed, 814 insertions(+), 1 deletion(-)
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_encoder.h
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_s302m.c
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_s302m.h
>
> diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
> index e3a6540f50e87..c916eb19d73bb 100644
> --- a/drivers/media/test-drivers/vidtv/Makefile
> +++ b/drivers/media/test-drivers/vidtv/Makefile
> @@ -1,6 +1,7 @@
> # SPDX-License-Identifier: GPL-2.0
>
> vidtv_demod-objs := vidtv_common.o
> -vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o vidtv_pes.o
> +vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o vidtv_pes.o \
> + vidtv_s302m.o
>
> obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.h b/drivers/media/test-drivers/vidtv/vidtv_common.h
> index 3b68f95c5f6c8..170646497eb58 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_common.h
> +++ b/drivers/media/test-drivers/vidtv/vidtv_common.h
> @@ -13,6 +13,8 @@
> #include <linux/types.h>
> #include <media/dvb_frontend.h>
>
> +#define CLOCK_UNIT_90KHZ 90000
> +
> u32 vidtv_memcpy(void *to,
> const void *from,
> size_t len,
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_encoder.h b/drivers/media/test-drivers/vidtv/vidtv_encoder.h
> new file mode 100644
> index 0000000000000..f483200fd781c
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_encoder.h
> @@ -0,0 +1,103 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Vidtv serves as a reference DVB driver and helps validate the existing APIs
> + * in the media subsystem. It can also aid developers working on userspace
> + * applications.
> + *
> + * This file contains a generic encoder type that can provide data for a stream
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#ifndef VIDTV_ENCODER_H
> +#define VIDTV_ENCODER_H
> +
> +#include <linux/types.h>
> +
> +enum vidtv_encoder_id {
> + /* add IDs here when implementing new encoders */
> + S302M,
> +};
> +
> +struct vidtv_encoder {
> + /* so we can cast to a concrete implementation when needed */
> + enum vidtv_encoder_id id;
> + /* usually same as the stream name */
> + char *name;
> +
> + /* the encoder's internal buffer for the access units */
> + u8 *encoder_buf;
> + /* the encoder buffer size, in bytes */
> + u32 encoder_buf_sz;
> +
> + /* our byte position in the encoder buffer */
> + u32 encoder_buf_offset;
> +
> + /* how many samples we have encoded in total */
> + u32 sample_count;
> +
> + u32 previous_sample_count;
> +
> + /* the number of access units ready */
> + u32 nunits;
> + /* the number of samples per access unit */
> + u32 *samples_per_unit;
> + /* pts array mapping pts[i] -> AU[i] */
> + u64 *pts;
> + /* dts array mapping dts[i] -> AU[i] */
> + u64 *dts;
> + /* array mapping how many bytes were written per AU */
> + u32 *nbytes;
> + /* array keeping track of AU offsets in buffer */
> + u32 *offsets;
> +
> + /*
> + * the source of raw data to be encoded, encoder might set a default
> + * source if NULL
> + */
> + void *src_buf;
> + /* the source buffer size, in bytes */
> + u32 src_buf_sz;
> +
> + /* our byte position in the src buffer */
> + u32 src_buf_offset;
> +
> + bool video; /* either video or audio */
> + void *ctx; /* encoder-specific state */
> +
> + /* Examples: Audio streams (0xc0-0xdf), Video streams (0xe0-0xef) */
> + u16 stream_id;
> +
> + /* the TS PID to use for the elementary stream in this encoder */
> + u16 es_pid;
> +
> + /* prepare enough AUs for the given amount of time */
> + void *(*encode)(struct vidtv_encoder *e, u64 elapsed_time_usecs);
> +
> + /* clear the encoder output */
> + u8 (*clear)(struct vidtv_encoder *e);
> +
> + /* attempt to synchronize with the encoder below */
> + struct vidtv_encoder *sync;
> +
> + u32 sampling_rate_hz; /* or fps, if video */
> +
> + /* controls dynamic memory allocation for the arrays */
> + u8 access_unit_capacity;
> +
> + /*
> + * called when the encoder runs out of data
> + * the encoder might optionally wrap around the src data if this is not
> + * implemented
> + * this is so the source can read data in a piecemeal fashion instead
> + * of having to provide it all at once
> + */
> + void (*last_sample_cb)(u32 sample_no);
> +
> + /* destroy this encoder, freeing any allocated resources */
> + void (*destroy)(struct vidtv_encoder *e);
> +
> + struct vidtv_encoder *next;
> +};

Instead of adding one comment before each field at the struct, please
use kernel-doc markups, as dscribed at:

Documentation/doc-guide/kernel-doc.rst

This makes both the comments easy to read and the struct itself.

> +
> +#endif /* VIDTV_ENCODER_H */
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_s302m.c b/drivers/media/test-drivers/vidtv/vidtv_s302m.c
> new file mode 100644
> index 0000000000000..b08bfff7b8f27
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_s302m.c
> @@ -0,0 +1,608 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Vidtv serves as a reference DVB driver and helps validate the existing APIs
> + * in the media subsystem. It can also aid developers working on userspace
> + * applications.
> + *
> + * This file contains the code for an AES3 (also known as AES/EBU) encoder.
> + * It is based on EBU Tech 3250 and SMPTE 302M technical documents.
> + *
> + * This encoder currently supports 16bit AES3 subframes using 16bit signed
> + * integers.
> + *
> + * Note: AU stands for Access Unit, and AAU stands for Audio Access Unit
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#include <linux/types.h>
> +#include <linux/slab.h>
> +#include <linux/crc32.h>
> +#include <linux/vmalloc.h>
> +#include <linux/string.h>
> +#include <linux/kernel.h>
> +#include <linux/jiffies.h>
> +
> +#include "vidtv_s302m.h"
> +#include "vidtv_encoder.h"
> +#include "vidtv_common.h"
> +
> +#define S302M_SAMPLING_RATE_HZ 48000
> +
> +/* see EBU Tech 3250 2004 clause 4 */
> +#define CHANNEL_STATUS_BIT_LEN 92
> +
> +#define S302M_2CHANNELS 0
> +#define S302M_BITS_PER_SAMPLE_16 0
> +
> +/* see preamble definition in EBU Tech 3250 2004 clause 2.4 */
> +#define PREAMBLE_X_F0 0xe2 /* sub-frame 1 */
> +#define PREAMBLE_Y_F0 0xe4 /* sub-frame 2 */
> +#define PREAMBLE_Z_F0 0xe8 /* sub-frame 1 + block start */
> +/* F0 and F1 refer to whether the F bit was set in the previous sub-frame */
> +#define PREAMBLE_X_F1 0x1d /* sub-frame 1 */
> +#define PREAMBLE_Y_F1 0X1b /* sub-frame 2 */
> +#define PREAMBLE_Z_F1 0x17 /* sub-frame 1 + block start */
> +
> +#define S302M_BLOCK_SZ 192
> +#define S302M_SIN_LUT_SZ 1024
> +
> +static int s302m_sin_lut[S302M_SIN_LUT_SZ] = {

Please write a description about the contents of this buffer.

-

I didn't try to reproduce your audio. As you didn't mention what audio
was on this buffer, I'll do a comment that may or may not be applicable
by the time we merge this patch upstream.

Whatever is inside the buffer, It should be something compatible
with GPLv2.

So, assuming that the audio is something you recorded or generated
yourself, and that it doesn't use anything that has copyrights,
that should be OK, but the better is to clearly state about
its contents and its copyrights, like:

/*
* This buffer contains a PCM audio sample compatible with S302M.
* Its contents are the first 5 notes of the public domain's
* Beethoven Fifty symphony that I played on my guitar and
* I'm releasing it under public domain.
*/
static int s302m_sin_lut[S302M_SIN_LUT_SZ] = {

Or:

/*
* This buffer contains a PCM audio sample compatible with S302M.
* Its contents are sinusoidal waves that would reproduce the first
* three musical notes: "do", "re", "mi".
* Released under public domain.
*/
static int s302m_sin_lut[S302M_SIN_LUT_SZ] = {

Or:

/*
* This buffer contains a PCM audio sample compatible with S302M,
* with a 440Hz sinusoidal beep.
*/
static int s302m_sin_lut[S302M_SIN_LUT_SZ] = {

(or anything else that wouldn't be a problem for distros to ship
the Linux Kernel).

> + 0x8000, 0x80c9, 0x8192, 0x825b, 0x8324, 0x83ed, 0x84b6, 0x857e,
> + 0x8647, 0x8710, 0x87d9, 0x88a1, 0x896a, 0x8a32, 0x8afb, 0x8bc3,
> + 0x8c8b, 0x8d53, 0x8e1b, 0x8ee3, 0x8fab, 0x9072, 0x9139, 0x9201,
> + 0x92c7, 0x938e, 0x9455, 0x951b, 0x95e1, 0x96a7, 0x976d, 0x9833,
> + 0x98f8, 0x99bd, 0x9a82, 0x9b47, 0x9c0b, 0x9ccf, 0x9d93, 0x9e56,
> + 0x9f19, 0x9fdc, 0xa09f, 0xa161, 0xa223, 0xa2e5, 0xa3a6, 0xa467,
> + 0xa527, 0xa5e8, 0xa6a7, 0xa767, 0xa826, 0xa8e5, 0xa9a3, 0xaa61,
> + 0xab1f, 0xabdc, 0xac98, 0xad55, 0xae10, 0xaecc, 0xaf87, 0xb041,
> + 0xb0fb, 0xb1b5, 0xb26e, 0xb326, 0xb3de, 0xb496, 0xb54d, 0xb603,
> + 0xb6b9, 0xb76f, 0xb824, 0xb8d8, 0xb98c, 0xba3f, 0xbaf2, 0xbba4,
> + 0xbc56, 0xbd07, 0xbdb7, 0xbe67, 0xbf17, 0xbfc5, 0xc073, 0xc121,
> + 0xc1cd, 0xc279, 0xc325, 0xc3d0, 0xc47a, 0xc524, 0xc5cc, 0xc675,
> + 0xc71c, 0xc7c3, 0xc869, 0xc90f, 0xc9b3, 0xca57, 0xcafb, 0xcb9d,
> + 0xcc3f, 0xcce0, 0xcd81, 0xce20, 0xcebf, 0xcf5d, 0xcffb, 0xd097,
> + 0xd133, 0xd1ce, 0xd268, 0xd302, 0xd39a, 0xd432, 0xd4c9, 0xd55f,
> + 0xd5f5, 0xd689, 0xd71d, 0xd7b0, 0xd842, 0xd8d3, 0xd964, 0xd9f3,
> + 0xda82, 0xdb0f, 0xdb9c, 0xdc28, 0xdcb3, 0xdd3d, 0xddc7, 0xde4f,
> + 0xded7, 0xdf5d, 0xdfe3, 0xe068, 0xe0eb, 0xe16e, 0xe1f0, 0xe271,
> + 0xe2f1, 0xe370, 0xe3ee, 0xe46b, 0xe4e8, 0xe563, 0xe5dd, 0xe656,
> + 0xe6cf, 0xe746, 0xe7bc, 0xe831, 0xe8a6, 0xe919, 0xe98b, 0xe9fc,
> + 0xea6d, 0xeadc, 0xeb4a, 0xebb7, 0xec23, 0xec8e, 0xecf8, 0xed61,
> + 0xedc9, 0xee30, 0xee96, 0xeefa, 0xef5e, 0xefc1, 0xf022, 0xf083,
> + 0xf0e2, 0xf140, 0xf19d, 0xf1f9, 0xf254, 0xf2ae, 0xf307, 0xf35e,
> + 0xf3b5, 0xf40a, 0xf45f, 0xf4b2, 0xf504, 0xf555, 0xf5a5, 0xf5f3,
> + 0xf641, 0xf68d, 0xf6d8, 0xf722, 0xf76b, 0xf7b3, 0xf7fa, 0xf83f,
> + 0xf884, 0xf8c7, 0xf909, 0xf94a, 0xf989, 0xf9c8, 0xfa05, 0xfa41,
> + 0xfa7c, 0xfab6, 0xfaee, 0xfb26, 0xfb5c, 0xfb91, 0xfbc5, 0xfbf8,
> + 0xfc29, 0xfc59, 0xfc88, 0xfcb6, 0xfce3, 0xfd0e, 0xfd39, 0xfd62,
> + 0xfd89, 0xfdb0, 0xfdd5, 0xfdfa, 0xfe1d, 0xfe3e, 0xfe5f, 0xfe7e,
> + 0xfe9c, 0xfeb9, 0xfed5, 0xfeef, 0xff09, 0xff21, 0xff37, 0xff4d,
> + 0xff61, 0xff74, 0xff86, 0xff97, 0xffa6, 0xffb4, 0xffc1, 0xffcd,
> + 0xffd8, 0xffe1, 0xffe9, 0xfff0, 0xfff5, 0xfff9, 0xfffd, 0xfffe,
> + 0xffff, 0xfffe, 0xfffd, 0xfff9, 0xfff5, 0xfff0, 0xffe9, 0xffe1,
> + 0xffd8, 0xffcd, 0xffc1, 0xffb4, 0xffa6, 0xff97, 0xff86, 0xff74,
> + 0xff61, 0xff4d, 0xff37, 0xff21, 0xff09, 0xfeef, 0xfed5, 0xfeb9,
> + 0xfe9c, 0xfe7e, 0xfe5f, 0xfe3e, 0xfe1d, 0xfdfa, 0xfdd5, 0xfdb0,
> + 0xfd89, 0xfd62, 0xfd39, 0xfd0e, 0xfce3, 0xfcb6, 0xfc88, 0xfc59,
> + 0xfc29, 0xfbf8, 0xfbc5, 0xfb91, 0xfb5c, 0xfb26, 0xfaee, 0xfab6,
> + 0xfa7c, 0xfa41, 0xfa05, 0xf9c8, 0xf989, 0xf94a, 0xf909, 0xf8c7,
> + 0xf884, 0xf83f, 0xf7fa, 0xf7b3, 0xf76b, 0xf722, 0xf6d8, 0xf68d,
> + 0xf641, 0xf5f3, 0xf5a5, 0xf555, 0xf504, 0xf4b2, 0xf45f, 0xf40a,
> + 0xf3b5, 0xf35e, 0xf307, 0xf2ae, 0xf254, 0xf1f9, 0xf19d, 0xf140,
> + 0xf0e2, 0xf083, 0xf022, 0xefc1, 0xef5e, 0xeefa, 0xee96, 0xee30,
> + 0xedc9, 0xed61, 0xecf8, 0xec8e, 0xec23, 0xebb7, 0xeb4a, 0xeadc,
> + 0xea6d, 0xe9fc, 0xe98b, 0xe919, 0xe8a6, 0xe831, 0xe7bc, 0xe746,
> + 0xe6cf, 0xe656, 0xe5dd, 0xe563, 0xe4e8, 0xe46b, 0xe3ee, 0xe370,
> + 0xe2f1, 0xe271, 0xe1f0, 0xe16e, 0xe0eb, 0xe068, 0xdfe3, 0xdf5d,
> + 0xded7, 0xde4f, 0xddc7, 0xdd3d, 0xdcb3, 0xdc28, 0xdb9c, 0xdb0f,
> + 0xda82, 0xd9f3, 0xd964, 0xd8d3, 0xd842, 0xd7b0, 0xd71d, 0xd689,
> + 0xd5f5, 0xd55f, 0xd4c9, 0xd432, 0xd39a, 0xd302, 0xd268, 0xd1ce,
> + 0xd133, 0xd097, 0xcffb, 0xcf5d, 0xcebf, 0xce20, 0xcd81, 0xcce0,
> + 0xcc3f, 0xcb9d, 0xcafb, 0xca57, 0xc9b3, 0xc90f, 0xc869, 0xc7c3,
> + 0xc71c, 0xc675, 0xc5cc, 0xc524, 0xc47a, 0xc3d0, 0xc325, 0xc279,
> + 0xc1cd, 0xc121, 0xc073, 0xbfc5, 0xbf17, 0xbe67, 0xbdb7, 0xbd07,
> + 0xbc56, 0xbba4, 0xbaf2, 0xba3f, 0xb98c, 0xb8d8, 0xb824, 0xb76f,
> + 0xb6b9, 0xb603, 0xb54d, 0xb496, 0xb3de, 0xb326, 0xb26e, 0xb1b5,
> + 0xb0fb, 0xb041, 0xaf87, 0xaecc, 0xae10, 0xad55, 0xac98, 0xabdc,
> + 0xab1f, 0xaa61, 0xa9a3, 0xa8e5, 0xa826, 0xa767, 0xa6a7, 0xa5e8,
> + 0xa527, 0xa467, 0xa3a6, 0xa2e5, 0xa223, 0xa161, 0xa09f, 0x9fdc,
> + 0x9f19, 0x9e56, 0x9d93, 0x9ccf, 0x9c0b, 0x9b47, 0x9a82, 0x99bd,
> + 0x98f8, 0x9833, 0x976d, 0x96a7, 0x95e1, 0x951b, 0x9455, 0x938e,
> + 0x92c7, 0x9201, 0x9139, 0x9072, 0x8fab, 0x8ee3, 0x8e1b, 0x8d53,
> + 0x8c8b, 0x8bc3, 0x8afb, 0x8a32, 0x896a, 0x88a1, 0x87d9, 0x8710,
> + 0x8647, 0x857e, 0x84b6, 0x83ed, 0x8324, 0x825b, 0x8192, 0x80c9,
> + 0x8000, 0x7f36, 0x7e6d, 0x7da4, 0x7cdb, 0x7c12, 0x7b49, 0x7a81,
> + 0x79b8, 0x78ef, 0x7826, 0x775e, 0x7695, 0x75cd, 0x7504, 0x743c,
> + 0x7374, 0x72ac, 0x71e4, 0x711c, 0x7054, 0x6f8d, 0x6ec6, 0x6dfe,
> + 0x6d38, 0x6c71, 0x6baa, 0x6ae4, 0x6a1e, 0x6958, 0x6892, 0x67cc,
> + 0x6707, 0x6642, 0x657d, 0x64b8, 0x63f4, 0x6330, 0x626c, 0x61a9,
> + 0x60e6, 0x6023, 0x5f60, 0x5e9e, 0x5ddc, 0x5d1a, 0x5c59, 0x5b98,
> + 0x5ad8, 0x5a17, 0x5958, 0x5898, 0x57d9, 0x571a, 0x565c, 0x559e,
> + 0x54e0, 0x5423, 0x5367, 0x52aa, 0x51ef, 0x5133, 0x5078, 0x4fbe,
> + 0x4f04, 0x4e4a, 0x4d91, 0x4cd9, 0x4c21, 0x4b69, 0x4ab2, 0x49fc,
> + 0x4946, 0x4890, 0x47db, 0x4727, 0x4673, 0x45c0, 0x450d, 0x445b,
> + 0x43a9, 0x42f8, 0x4248, 0x4198, 0x40e8, 0x403a, 0x3f8c, 0x3ede,
> + 0x3e32, 0x3d86, 0x3cda, 0x3c2f, 0x3b85, 0x3adb, 0x3a33, 0x398a,
> + 0x38e3, 0x383c, 0x3796, 0x36f0, 0x364c, 0x35a8, 0x3504, 0x3462,
> + 0x33c0, 0x331f, 0x327e, 0x31df, 0x3140, 0x30a2, 0x3004, 0x2f68,
> + 0x2ecc, 0x2e31, 0x2d97, 0x2cfd, 0x2c65, 0x2bcd, 0x2b36, 0x2aa0,
> + 0x2a0a, 0x2976, 0x28e2, 0x284f, 0x27bd, 0x272c, 0x269b, 0x260c,
> + 0x257d, 0x24f0, 0x2463, 0x23d7, 0x234c, 0x22c2, 0x2238, 0x21b0,
> + 0x2128, 0x20a2, 0x201c, 0x1f97, 0x1f14, 0x1e91, 0x1e0f, 0x1d8e,
> + 0x1d0e, 0x1c8f, 0x1c11, 0x1b94, 0x1b17, 0x1a9c, 0x1a22, 0x19a9,
> + 0x1930, 0x18b9, 0x1843, 0x17ce, 0x1759, 0x16e6, 0x1674, 0x1603,
> + 0x1592, 0x1523, 0x14b5, 0x1448, 0x13dc, 0x1371, 0x1307, 0x129e,
> + 0x1236, 0x11cf, 0x1169, 0x1105, 0x10a1, 0x103e, 0xfdd, 0xf7c,
> + 0xf1d, 0xebf, 0xe62, 0xe06, 0xdab, 0xd51, 0xcf8, 0xca1,
> + 0xc4a, 0xbf5, 0xba0, 0xb4d, 0xafb, 0xaaa, 0xa5a, 0xa0c,
> + 0x9be, 0x972, 0x927, 0x8dd, 0x894, 0x84c, 0x805, 0x7c0,
> + 0x77b, 0x738, 0x6f6, 0x6b5, 0x676, 0x637, 0x5fa, 0x5be,
> + 0x583, 0x549, 0x511, 0x4d9, 0x4a3, 0x46e, 0x43a, 0x407,
> + 0x3d6, 0x3a6, 0x377, 0x349, 0x31c, 0x2f1, 0x2c6, 0x29d,
> + 0x276, 0x24f, 0x22a, 0x205, 0x1e2, 0x1c1, 0x1a0, 0x181,
> + 0x163, 0x146, 0x12a, 0x110, 0xf6, 0xde, 0xc8, 0xb2,
> + 0x9e, 0x8b, 0x79, 0x68, 0x59, 0x4b, 0x3e, 0x32,
> + 0x27, 0x1e, 0x16, 0xf, 0xa, 0x6, 0x2, 0x1,
> + 0x0, 0x1, 0x2, 0x6, 0xa, 0xf, 0x16, 0x1e,
> + 0x27, 0x32, 0x3e, 0x4b, 0x59, 0x68, 0x79, 0x8b,
> + 0x9e, 0xb2, 0xc8, 0xde, 0xf6, 0x110, 0x12a, 0x146,
> + 0x163, 0x181, 0x1a0, 0x1c1, 0x1e2, 0x205, 0x22a, 0x24f,
> + 0x276, 0x29d, 0x2c6, 0x2f1, 0x31c, 0x349, 0x377, 0x3a6,
> + 0x3d6, 0x407, 0x43a, 0x46e, 0x4a3, 0x4d9, 0x511, 0x549,
> + 0x583, 0x5be, 0x5fa, 0x637, 0x676, 0x6b5, 0x6f6, 0x738,
> + 0x77b, 0x7c0, 0x805, 0x84c, 0x894, 0x8dd, 0x927, 0x972,
> + 0x9be, 0xa0c, 0xa5a, 0xaaa, 0xafb, 0xb4d, 0xba0, 0xbf5,
> + 0xc4a, 0xca1, 0xcf8, 0xd51, 0xdab, 0xe06, 0xe62, 0xebf,
> + 0xf1d, 0xf7c, 0xfdd, 0x103e, 0x10a1, 0x1105, 0x1169, 0x11cf,
> + 0x1236, 0x129e, 0x1307, 0x1371, 0x13dc, 0x1448, 0x14b5, 0x1523,
> + 0x1592, 0x1603, 0x1674, 0x16e6, 0x1759, 0x17ce, 0x1843, 0x18b9,
> + 0x1930, 0x19a9, 0x1a22, 0x1a9c, 0x1b17, 0x1b94, 0x1c11, 0x1c8f,
> + 0x1d0e, 0x1d8e, 0x1e0f, 0x1e91, 0x1f14, 0x1f97, 0x201c, 0x20a2,
> + 0x2128, 0x21b0, 0x2238, 0x22c2, 0x234c, 0x23d7, 0x2463, 0x24f0,
> + 0x257d, 0x260c, 0x269b, 0x272c, 0x27bd, 0x284f, 0x28e2, 0x2976,
> + 0x2a0a, 0x2aa0, 0x2b36, 0x2bcd, 0x2c65, 0x2cfd, 0x2d97, 0x2e31,
> + 0x2ecc, 0x2f68, 0x3004, 0x30a2, 0x3140, 0x31df, 0x327e, 0x331f,
> + 0x33c0, 0x3462, 0x3504, 0x35a8, 0x364c, 0x36f0, 0x3796, 0x383c,
> + 0x38e3, 0x398a, 0x3a33, 0x3adb, 0x3b85, 0x3c2f, 0x3cda, 0x3d86,
> + 0x3e32, 0x3ede, 0x3f8c, 0x403a, 0x40e8, 0x4198, 0x4248, 0x42f8,
> + 0x43a9, 0x445b, 0x450d, 0x45c0, 0x4673, 0x4727, 0x47db, 0x4890,
> + 0x4946, 0x49fc, 0x4ab2, 0x4b69, 0x4c21, 0x4cd9, 0x4d91, 0x4e4a,
> + 0x4f04, 0x4fbe, 0x5078, 0x5133, 0x51ef, 0x52aa, 0x5367, 0x5423,
> + 0x54e0, 0x559e, 0x565c, 0x571a, 0x57d9, 0x5898, 0x5958, 0x5a17,
> + 0x5ad8, 0x5b98, 0x5c59, 0x5d1a, 0x5ddc, 0x5e9e, 0x5f60, 0x6023,
> + 0x60e6, 0x61a9, 0x626c, 0x6330, 0x63f4, 0x64b8, 0x657d, 0x6642,
> + 0x6707, 0x67cc, 0x6892, 0x6958, 0x6a1e, 0x6ae4, 0x6baa, 0x6c71,
> + 0x6d38, 0x6dfe, 0x6ec6, 0x6f8d, 0x7054, 0x711c, 0x71e4, 0x72ac,
> + 0x7374, 0x743c, 0x7504, 0x75cd, 0x7695, 0x775e, 0x7826, 0x78ef,
> + 0x79b8, 0x7a81, 0x7b49, 0x7c12, 0x7cdb, 0x7da4, 0x7e6d, 0x7f36
> +};
> +
> +static void vidtv_s302m_compute_nunits(struct vidtv_encoder *e)
> +{
> + /*
> + * match the amount of video units if we are trying to sync with a video
> + * encoder
> + */
> + if (e->sync && e->sync->video) {
> + e->nunits = e->sync->nunits;
> + return;
> + }
> +
> + /* otherwise return enough samples for the timedelta in one AU */
> + e->nunits = 1;
> +}
> +
> +static void
> +vidtv_s302m_compute_sample_count_v(struct vidtv_encoder *e)
> +{
> + /* compute sample count for VAU[i] in 'sync' */
> + u32 vau_duration_usecs;
> + u32 sample_duration_usecs;
> + u32 i;
> + u32 sample_count;
> + u32 s;
> +
> + vau_duration_usecs = USEC_PER_SEC / e->sync->sampling_rate_hz;
> + sample_duration_usecs = USEC_PER_SEC / e->sampling_rate_hz;
> +
> + for (i = 0; i < e->sync->nunits; ++i) {
> + sample_count = e->samples_per_unit[i];
> + s = DIV_ROUND_UP(vau_duration_usecs, sample_duration_usecs);
> + e->samples_per_unit[i] = s;
> + }
> +}
> +
> +static void
> +vidtv_s302m_compute_sample_count(struct vidtv_encoder *e,
> + u64 elapsed_time_usecs)
> +{
> + /* compute sample count for 'elapsed_time_usecs' */
> + u32 sample_duration_usecs = USEC_PER_SEC / e->sampling_rate_hz;
> +
> + e->samples_per_unit[0] = elapsed_time_usecs / sample_duration_usecs;
> +}
> +
> +static void vidtv_s302m_compute_pts(struct vidtv_encoder *e)
> +{
> + u32 count = e->previous_sample_count;
> + u32 i;
> +
> + for (i = 0; i < e->nunits; ++i) {
> + count += e->samples_per_unit[i];
> +
> + e->pts[i] = count *
> + CLOCK_UNIT_90KHZ / e->sampling_rate_hz;
> + }
> +}
> +
> +static void vidtv_s302m_compute_pts_v(struct vidtv_encoder *e)
> +{
> + u32 i;
> +
> + /* use the same pts for video */
> + for (i = 0; i < e->sync->nunits; ++i)
> + e->pts[i] = e->sync->pts[i];
> +}
> +
> +static bool vidtv_s302m_get_c_bit(struct vidtv_encoder *e)
> +{
> + /*
> + * see EBU Tech 3250 2004 clause 5.2.1: minimal implementation of
> + * channel status
> + */
> + struct vidtv_s302m_ctx *ctx = e->ctx;
> + bool start_was_z;
> +
> + start_was_z = ctx->last_start_preamble == PREAMBLE_Z_F0 ||
> + ctx->last_start_preamble == PREAMBLE_Z_F1;
> +
> + if (!start_was_z)
> + return false;
> +
> + ++ctx->current_c_bit;
> +
> + /* set the bit only if it is the first C bit after a Z preamble */
> + return !(ctx->current_c_bit % CHANNEL_STATUS_BIT_LEN);
> +}
> +
> +static s16 vidtv_s302m_get_sample(struct vidtv_encoder *e)
> +{
> + s16 ret;
> +
> + /* bug somewhere */
> + WARN_ON(e->src_buf_offset > e->src_buf_sz);
> +
> + if (e->src_buf_offset >= e->src_buf_sz) {
> + /* let the source know we are out of data */
> + if (e->last_sample_cb)
> + e->last_sample_cb(e->sample_count);
> +
> + e->src_buf_offset = 0;
> + }
> +
> + ret = *(s16 *)(e->src_buf + e->src_buf_offset);
> +
> + e->sample_count++;
> + e->src_buf_offset += sizeof(s16);
> +
> + return ret;
> +}
> +
> +static void vidtv_s302m_toggle_subframe(struct vidtv_encoder *e)
> +{
> + struct vidtv_s302m_ctx *ctx = e->ctx;
> +
> + ctx->is_subframe_a = !ctx->is_subframe_a;
> +}
> +
> +static bool vidtv_s302m_is_block_start(const struct vidtv_encoder *e)
> +{
> + return e->sample_count % S302M_BLOCK_SZ;
> +}
> +
> +static bool vidtv_s302m_get_f_bit(const struct vidtv_encoder *e)
> +{
> + const struct vidtv_s302m_ctx *ctx = e->ctx;
> +
> + return vidtv_s302m_is_block_start(e) && ctx->is_subframe_a;
> +}
> +
> +static u8 vidtv_s302m_get_preamble(struct vidtv_encoder *e)
> +{
> + /*
> + * some codecs might disregard the preambles (e.g. ffmpeg s302m), but
> + * we implement them according to the specs anyway, because some other
> + * codecs might rely on them.
> + */
> +
> + struct vidtv_s302m_ctx *ctx = e->ctx;
> +
> + bool start_was_x = ctx->last_start_preamble == PREAMBLE_X_F0 ||
> + ctx->last_start_preamble == PREAMBLE_X_F1;
> +
> + bool start_was_z = ctx->last_start_preamble == PREAMBLE_Z_F0 ||
> + ctx->last_start_preamble == PREAMBLE_Z_F1;
> +
> + bool is_block_start = vidtv_s302m_is_block_start(e);
> +
> + /* bug somewhere: a block always starts with an A subframe */
> + WARN_ON(!ctx->is_subframe_a && is_block_start);
> +
> + if (!ctx->is_subframe_a)
> + return (ctx->last_f) ? PREAMBLE_Y_F1 : PREAMBLE_Y_F0;
> +
> + if (start_was_x && is_block_start) {
> + if (ctx->last_f) {
> + ctx->last_start_preamble = PREAMBLE_Z_F0;
> + return PREAMBLE_Z_F0;
> + }
> +
> + ctx->last_start_preamble = PREAMBLE_Z_F1;
> + return PREAMBLE_Z_F1;
> + }
> +
> + if (start_was_z && is_block_start) {
> + if (ctx->last_f) {
> + ctx->last_start_preamble = PREAMBLE_X_F0;
> + return PREAMBLE_X_F0;
> + }
> +
> + ctx->last_start_preamble = PREAMBLE_X_F1;
> + return PREAMBLE_X_F1;
> + }
> +
> + return PREAMBLE_X_F0;
> +}
> +
> +static u32 vidtv_s302m_write_subframe(struct vidtv_encoder *e,
> + struct vidtv_s302m_subframe_16 *f)
> +{
> + u32 nbytes = 0;
> +
> + nbytes += vidtv_memcpy(e->encoder_buf + e->encoder_buf_offset,
> + f,
> + sizeof(*f),
> + e->encoder_buf_offset,
> + VIDTV_S302M_BUF_SZ);
> +
> + e->encoder_buf_offset += nbytes;
> +
> + return nbytes;
> +}
> +
> +static void vidtv_s302m_write_h(struct vidtv_encoder *e, u32 p_sz)
> +{
> + struct vidtv_smpte_s302m_es h = {0};
> + u32 nbytes = 0;
> +
> + h.bits_per_sample = S302M_BITS_PER_SAMPLE_16;
> + h.channel_identification = 0;
> + h.num_channels = S302M_2CHANNELS;
> + h.audio_packet_size = p_sz;
> +
> + cpu_to_be32s(&h.bitfield);
> +
> + nbytes += vidtv_memcpy(e->encoder_buf + e->encoder_buf_offset,
> + &h,
> + sizeof(h),
> + e->encoder_buf_offset,
> + e->encoder_buf_sz);
> +
> + be32_to_cpus(&h.bitfield);
> +
> + e->encoder_buf_offset += nbytes;
> +}
> +
> +static void vidtv_s302m_write_frames(struct vidtv_encoder *e)
> +{
> + u32 nbytes = 0;
> + u32 nbytes_per_unit = 0;
> + u32 preamble_bit_num = 0;
> + const u8 preamble_bit_count = 4;
> + u32 au_sz = 0;
> + struct vidtv_s302m_ctx *ctx = e->ctx;
> + struct vidtv_s302m_subframe_16 a = {0};
> + struct vidtv_s302m_subframe_16 b = {0};
> + u8 preamble_a = 0;
> + u8 preamble_b = 0;
> + u8 aux = 0;
> + s16 sample = 0;
> + bool v = true;
> + bool u = false;
> + bool c;
> + bool f;
> +
> + u32 i;
> + u32 j;
> +
> + ctx->is_subframe_a = true;
> +
> + for (i = 0; i < e->nunits; ++i) {
> + /* stereo: each sample will generate two subframes */
> + au_sz = e->samples_per_unit[i] *
> + sizeof(struct vidtv_s302m_subframe_16) *
> + 2;
> +
> + vidtv_s302m_write_h(e, au_sz);
> +
> + for (j = 0; j < e->samples_per_unit[i]; ++j) {
> + /* keep this in this order */
> + preamble_a = vidtv_s302m_get_preamble(e);
> + sample = cpu_to_le16(vidtv_s302m_get_sample(e));
> + c = vidtv_s302m_get_c_bit(e);
> + f = vidtv_s302m_get_f_bit(e);
> +
> + a.preamble = vidtv_extract_bits(preamble_a,
> + preamble_bit_num % 8,
> + preamble_bit_count);
> + a.aux = aux;
> + a.data_word = sample;
> + a.v = v;
> + a.u = u;
> + a.c = c;
> + a.f = f;
> +
> + vidtv_s302m_toggle_subframe(e);
> +
> + preamble_b = vidtv_s302m_get_preamble(e);
> + c = vidtv_s302m_get_c_bit(e);
> + f = vidtv_s302m_get_f_bit(e);
> +
> + b.preamble = vidtv_extract_bits(preamble_b,
> + preamble_bit_num % 8,
> + preamble_bit_count);
> + b.aux = aux;
> + b.data_word = sample;
> + b.v = v;
> + b.u = u;
> + b.c = c;
> + b.f = f;
> +
> + preamble_bit_num += preamble_bit_count;
> +
> + nbytes_per_unit += vidtv_s302m_write_subframe(e, &a);
> + nbytes_per_unit += vidtv_s302m_write_subframe(e, &b);
> +
> + nbytes += nbytes_per_unit;
> +
> + vidtv_s302m_toggle_subframe(e);
> + }
> +
> + e->nbytes[i] = nbytes;
> +
> + /* did we write more bytes than we initially computed? */
> + WARN_ON(au_sz != nbytes_per_unit);
> +
> + e->offsets[i] = nbytes_per_unit;
> + nbytes_per_unit = 0;
> + }
> +}
> +
> +static void *vidtv_s302m_encode(struct vidtv_encoder *e, u64 elapsed_time)
> +{
> + /*
> + * According to SMPTE 302M, an audio access unit is specified as those
> + * AES3 words that are associated with a corresponding video frame.
> + * Therefore we should write one AAU for every VAU in the corresponding
> + * video encoder ('sync'), using the same values for PTS as used by the
> + * video encoder.
> + *
> + * I assume that it is also possible to send audio without any
> + * associated video, as in a radio-like service. If this is the case,
> + * we are sending only _one_ AAU with enough audio data for
> + * 'elapsed_time' instead, computing the value for PTS manually.
> + */
> +
> + vidtv_s302m_compute_nunits(e);
> +
> + if (e->sync && e->sync->video) {
> + vidtv_s302m_compute_sample_count_v(e);
> + vidtv_s302m_compute_pts_v(e);
> + } else {
> + vidtv_s302m_compute_sample_count(e, elapsed_time);
> + vidtv_s302m_compute_pts(e);
> + }
> +
> + vidtv_s302m_write_frames(e);
> +
> + return e->encoder_buf;
> +}
> +
> +static u8 vidtv_s302m_clear(struct vidtv_encoder *e)
> +{
> + u8 ret = e->nunits;
> +
> + e->nunits = 0;
> + memset(e->samples_per_unit, 0, e->access_unit_capacity);
> + memset(e->nbytes, 0, e->access_unit_capacity);
> + memset(e->offsets, 0, e->access_unit_capacity);
> + memset(e->pts, 0, e->access_unit_capacity);
> + memset(e->dts, 0, e->access_unit_capacity);
> +
> + return ret;
> +}
> +
> +struct vidtv_encoder
> +*vidtv_s302m_encoder_init(struct vidtv_s302m_encoder_init_args args)
> +{
> + struct vidtv_encoder *e = kzalloc(sizeof(e), GFP_KERNEL);
> + u32 priv_sz = sizeof(struct vidtv_s302m_ctx);
> +
> + if (args.sync)
> + args.access_unit_capacity = args.sync->access_unit_capacity;
> +
> + e->id = S302M;
> +
> + e->name = kzalloc(strlen(args.name), GFP_KERNEL);
> + strncpy(e->name, args.name, strlen(e->name));
> +
> + e->encoder_buf = vzalloc(VIDTV_S302M_BUF_SZ);
> + e->encoder_buf_sz = VIDTV_S302M_BUF_SZ;
> + e->encoder_buf_offset = 0;
> +
> + e->sample_count = 0;
> + e->previous_sample_count = 0;
> + e->nunits = 0;
> +
> + e->samples_per_unit = kcalloc(args.access_unit_capacity,
> + sizeof(u32),
> + GFP_KERNEL);
> +
> + e->pts = kcalloc(args.access_unit_capacity, sizeof(u64), GFP_KERNEL);
> + e->dts = kcalloc(args.access_unit_capacity, sizeof(u64), GFP_KERNEL);
> +
> + e->nbytes = kcalloc(args.access_unit_capacity,
> + sizeof(u32),
> + GFP_KERNEL);
> +
> + e->offsets = kcalloc(args.access_unit_capacity,
> + sizeof(u32),
> + GFP_KERNEL);
> +
> + e->src_buf = (args.src_buf) ? args.src_buf : &s302m_sin_lut;
> + e->src_buf_sz = (args.src_buf) ? args.src_buf_sz : S302M_SIN_LUT_SZ;
> + e->src_buf_offset = 0;
> +
> + e->video = false;
> + e->ctx = kzalloc(priv_sz, GFP_KERNEL);
> +
> + e->encode = vidtv_s302m_encode;
> + e->clear = vidtv_s302m_clear;
> +
> + e->es_pid = args.es_pid;
> +
> + e->sync = args.sync;
> + e->sampling_rate_hz = S302M_SAMPLING_RATE_HZ;
> + e->access_unit_capacity = args.access_unit_capacity;
> +
> + /* we will wrap around 'src' if this is NULL */
> + e->last_sample_cb = args.last_sample_cb;
> +
> + e->destroy = vidtv_s302m_encoder_destroy;
> +
> + if (args.head) {
> + while (args.head->next)
> + args.head = args.head->next;
> +
> + args.head->next = e;
> + }
> +
> + e->next = NULL;
> +
> + return e;
> +}
> +
> +void vidtv_s302m_encoder_destroy(struct vidtv_encoder *e)
> +{
> + WARN_ON(e->id != S302M);
> + kfree(e->name);
> + kfree(e->encoder_buf);
> + kfree(e->samples_per_unit);
> + kfree(e->pts);
> + kfree(e->dts);
> + kfree(e->nbytes);
> + kfree(e->offsets);
> + kfree(e->ctx);
> + kfree(e);
> +}
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_s302m.h b/drivers/media/test-drivers/vidtv/vidtv_s302m.h
> new file mode 100644
> index 0000000000000..bfb8b0c80eddf
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_s302m.h
> @@ -0,0 +1,99 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Vidtv serves as a reference DVB driver and helps validate the existing APIs
> + * in the media subsystem. It can also aid developers working on userspace
> + * applications.
> + *
> + * This file contains the code for an AES3 (also known as AES/EBU) encoder.
> + * It is based on EBU Tech 3250 and SMPTE 302M technical documents.
> + *
> + * This encoder currently supports 16bit AES3 subframes using 16bit signed
> + * integers.
> + *
> + * Note: AU stands for Access Unit, and AAU stands for Audio Access Unit
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#ifndef VIDTV_S302M_H
> +#define VIDTV_S302M_H
> +
> +#include <linux/types.h>
> +#include <asm/byteorder.h>
> +#include "vidtv_encoder.h"
> +
> +/* see SMPTE 302M 2007 clause 7.3 */
> +#define VIDTV_S302M_BUF_SZ 65024
> +
> +/* see ETSI TS 102 154 v.1.2.1 clause 7.3.5 */
> +#define VIDTV_S302M_FORMAT_IDENTIFIER 0x42535344
> +
> +struct vidtv_s302m_ctx {
> + struct vidtv_encoder *enc;
> +
> + /* whether the F bit was set for the last sub-frame */
> + bool last_f;
> + /* either X or Z */
> + u8 last_start_preamble;
> + /* are we writing an A subframe now? */
> + bool is_subframe_a;
> + /* we send a bit per subframe */
> + u8 current_c_bit;
> +};
> +
> +struct vidtv_smpte_s302m_es {
> + union {
> + u32 bitfield;
> + struct {
> + u16 audio_packet_size:16;
> + u16 num_channels:2;
> + u16 channel_identification:8;
> + u16 bits_per_sample:2; /* 0x0 for 16bits */
> + u16 zero:4;
> + } __packed;
> + } __packed;
> +} __packed;
> +
> +struct vidtv_s302m_subframe_16 {
> +#if defined(__LITTLE_ENDIAN_BITFIELD)
> + u8 f:1;
> + u8 c:1;
> + u8 u:1;
> + u8 v:1;
> + u16 data_word:16; /* little endian, use cpu_to_le before writing */
> + u8 zero:4;
> + u8 aux:4;
> + u8 preamble:4;
> +#elif defined(__BIG_ENDIAN_BITFIELD)
> + u8 preamble:4;
> + u8 aux:4;
> + u8 zero:4;
> + u16 data_word:16; /* little endian */
> + u8 v:1;
> + u8 u:1;
> + u8 c:1;
> + u8 f:1;
> +#else
> +#error "Please fix <asm/byteorder.h>"
> +#endif
> +}__packed;
> +
> +struct vidtv_s302m_encoder_init_args {
> + char *name;
> + void *src_buf;
> + u32 src_buf_sz;
> + u16 es_pid;
> + struct vidtv_encoder *sync;
> + u8 access_unit_capacity;
> + void (*last_sample_cb)(u32 sample_no);
> +
> + /* optionally chain to this encoder */
> + struct vidtv_encoder *head;
> +};
> +
> +struct vidtv_encoder
> +*vidtv_s302m_encoder_init(struct vidtv_s302m_encoder_init_args args);
> +
> +void vidtv_s302m_encoder_destroy(struct vidtv_encoder *encoder);
> +
> +#endif /* VIDTV_S302M_H */



Thanks,
Mauro

2020-05-03 09:15:37

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 11/11] media: vidtv: Add a MPEG Transport Stream Multiplexer

Em Sat, 2 May 2020 00:22:16 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> From: "Daniel W. S. Almeida" <[email protected]>
>
> Add a MPEG Transport Stream multiplexer responsible for polling encoders,
> interleaving packets, padding the resulting stream with NULL packets if
> necessary and then delivering the resulting TS packets to the bridge
> driver so it can feed the demux.
>
> This patch includes a "channel" abstraction, which attempts to map a
> MPEG service into a struct that vidtv can work with.
>
> When vidtv boots, it will create some hardcoded channels:
>
> -Their services will be concatenated to populate the SDT.
> -Their programs will be concatenated to populate the PAT
> -For each program in the PAT, a PMT section will be created
> -The PMT section for a channel will be assigned its streams.
> -Every stream will have its corresponding encoder polled to produce TS packets
> -These packets may be interleaved by the mux and then delivered to the bridg
>
> Signed-off-by: Daniel W. S. Almeida <[email protected]>

The same notes I made on previous patches apply here.

> ---
> drivers/media/test-drivers/vidtv/Makefile | 2 +-
> .../media/test-drivers/vidtv/vidtv_bridge.c | 67 ++-
> .../media/test-drivers/vidtv/vidtv_bridge.h | 2 +
> .../media/test-drivers/vidtv/vidtv_channel.c | 326 ++++++++++++++
> .../media/test-drivers/vidtv/vidtv_channel.h | 66 +++
> .../media/test-drivers/vidtv/vidtv_common.h | 3 +
> drivers/media/test-drivers/vidtv/vidtv_mux.c | 423 ++++++++++++++++++
> drivers/media/test-drivers/vidtv/vidtv_mux.h | 105 +++++
> drivers/media/test-drivers/vidtv/vidtv_psi.c | 18 +
> drivers/media/test-drivers/vidtv/vidtv_psi.h | 5 +
> 10 files changed, 1014 insertions(+), 3 deletions(-)
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_channel.c
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_channel.h
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_mux.c
> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_mux.h
>
> diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile
> index c916eb19d73bb..a1d29001fffe3 100644
> --- a/drivers/media/test-drivers/vidtv/Makefile
> +++ b/drivers/media/test-drivers/vidtv/Makefile
> @@ -2,6 +2,6 @@
>
> vidtv_demod-objs := vidtv_common.o
> vidtv_bridge-objs := vidtv_common.o vidtv_ts.o vidtv_psi.o vidtv_pes.o \
> - vidtv_s302m.o
> + vidtv_s302m.o vidtv_channel.o vidtv_mux.o
>
> obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c
> index 05ca4027c869f..c9876372fdebd 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_bridge.c
> +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c
> @@ -13,8 +13,10 @@
> #include <linux/workqueue.h>
> #include <linux/time.h>
> #include "vidtv_bridge.h"
> +#include "vidtv_ts.h"
> +#include "vidtv_mux.h"
>
> -#define TS_BUF_MAX_SZ (128 * 188)
> +#define TS_BUF_MAX_SZ (128 * TS_PACKET_LEN)
> #define TUNER_DEFAULT_ADDR 0x68
> #define DEMOD_DEFAULT_ADDR 0x60
>
> @@ -64,16 +66,63 @@ module_param(chosen_delsys, uint, 0644);
> MODULE_PARM_DESC(chosen_delsys,
> "The delivery system to simulate. Currently supported: DVB-T, DVB-C, DVB-S");
>
> -static unsigned int ts_buf_sz = 20 * 188;
> +static unsigned int ts_buf_sz = 20 * TS_PACKET_LEN;
> module_param(ts_buf_sz, uint, 0644);
> MODULE_PARM_DESC(ts_buf_sz, "Optional size for the TS buffer");
>
> DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nums);
>
> +/*
> + * Influences the signal acquisition time. See ISO/IEC 13818-1 : 2000. p. 113.
> + */
> +static unsigned int si_period_msec = 40;
> +module_param(si_period_msec, uint, 0644);
> +MODULE_PARM_DESC(si_period_msec, "How often to send SI packets. Default: 40ms");
> +
> +static unsigned int pcr_period_msec = 40;
> +module_param(pcr_period_msec, uint, 0644);
> +MODULE_PARM_DESC(pcr_period_msec, "How often to send PCR packets. Default: 40ms");
> +
> +static unsigned int mux_rate_kbytes_sec = 4096;
> +module_param(mux_rate_kbytes_sec, uint, 0644);
> +MODULE_PARM_DESC(mux_rate_kbytes_sec, "Optional mux rate: will pad stream if below");
> +
> +static unsigned int pcr_pid = 0x200
> +module_param(pcr_pid, uint, 0644);
> +MODULE_PARM_DESC(pcr_pid, "Optional PCR PID for all channels: defaults to 0x200");
> +

Same comments I made on a past patch about module parameters apply here.

Also, for the last two, I would remove the "Optional" word. All modprobe
parameters are optional ;-)

> +static bool vidtv_bridge_check_demod_lock(struct vidtv_dvb *dvb, u32 n)
> +{
> + enum fe_status status;
> +
> + dvb->fe[n]->ops.read_status(dvb->fe[n], &status);
> +
> + return status == FE_HAS_SIGNAL |
> + FE_HAS_CARRIER |
> + FE_HAS_VITERBI |
> + FE_HAS_SYNC |
> + FE_HAS_LOCK;
> +}
> +
> +static void
> +vidtv_bridge_on_new_pkts_avail(void *priv, u8 *buf, u32 npkts)
> +{
> + /*
> + * called on a separate thread by the mux when new packets become
> + * available
> + */
> + struct vidtv_dvb *dvb = (struct vidtv_dvb *)priv;
> +
> + /* drop packets if we lose the lock */
> + if (vidtv_bridge_check_demod_lock(dvb, 0))
> + dvb_dmx_swfilter_packets(&dvb->demux, buf, npkts);
> +}
> +
> static int vidtv_start_streaming(struct vidtv_dvb *dvb)
> {
> WARN_ON(dvb->streaming);
> dvb->streaming = true;
> + vidtv_mux_start_thread(dvb->mux);
>
> return 0;
> }
> @@ -82,6 +131,7 @@ static int vidtv_stop_streaming(struct vidtv_dvb *dvb)
> {
> /* mpeg thread will quit */
> dvb->streaming = false;
> + vidtv_mux_stop_thread(dvb->mux);
>
> return 0;
> }
> @@ -313,6 +363,7 @@ static int vidtv_bridge_i2c_probe(struct i2c_client *client,
> {
> int ret;
> struct vidtv_dvb *dvb;
> + struct vidtv_mux_init_args mux_args = {0};
>
> dvb = kzalloc(sizeof(*dvb), GFP_KERNEL);
> if (!dvb)
> @@ -324,6 +375,16 @@ static int vidtv_bridge_i2c_probe(struct i2c_client *client,
>
> mutex_init(&dvb->feed_lock);
>
> + mux_args.mux_rate_kbytes_sec = mux_rate_kbytes_sec;
> + mux_args.on_new_packets_available_cb = vidtv_bridge_on_new_pkts_avail;
> + mux_args.ts_buf_sz = ts_buf_sz;
> + mux_args.pcr_period_usecs = pcr_period_msecs * 1000;
> + mux_args.si_period_usecs = si_period_msecs * 1000;
> + mux_args.pcr_pid = pcr_pid;
> + mux_args.priv = dvb;
> +
> + dvb->mux = vidtv_mux_init(mux_args);
> +
> i2c_set_clientdata(client, dvb);
>
> return ret;
> @@ -340,6 +401,8 @@ static int vidtv_bridge_i2c_remove(struct i2c_client *client)
>
> dvb = i2c_get_clientdata(client);
>
> + vidtv_mux_destroy(dvb->mux);
> +
> mutex_destroy(&dvb->feed_lock);
>
> for (i = 0; i < NUM_FE; ++i)
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.h b/drivers/media/test-drivers/vidtv/vidtv_bridge.h
> index ef5c7cd2d64e3..f5e6931058c9c 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_bridge.h
> +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.h
> @@ -17,6 +17,7 @@
> #include <media/dvb_demux.h>
> #include <media/dmxdev.h>
> #include <linux/i2c.h>
> +#include "vidtv_mux.h"
>
> struct vidtv_dvb {
> struct dvb_frontend *fe[NUM_FE];
> @@ -32,6 +33,7 @@ struct vidtv_dvb {
> struct mutex feed_lock; /* start/stop feed */
>
> bool streaming;
> + struct vidtv_mux *mux;
> };
>
> #endif // VIDTV_BRIDGE_H
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_channel.c b/drivers/media/test-drivers/vidtv/vidtv_channel.c
> new file mode 100644
> index 0000000000000..3e89a40eb7ca6
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_channel.c
> @@ -0,0 +1,326 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Vidtv serves as a reference DVB driver and helps validate the existing APIs
> + * in the media subsystem. It can also aid developers working on userspace
> + * applications.
> + *
> + * This file contains the code for a 'channel' abstraction.
> + *
> + * When vidtv boots, it will create some hardcoded channels.
> + * Their services will be concatenated to populate the SDT.
> + * Their programs will be concatenated to populate the PAT
> + * For each program in the PAT, a PMT section will be created
> + * The PMT section for a channel will be assigned its streams.
> + * Every stream will have its corresponding encoder polled to produce TS packets
> + * These packets may be interleaved by the mux and then delivered to the bridge
> + *
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#include <linux/types.h>
> +#include <linux/slab.h>
> +#include "vidtv_channel.h"
> +#include "vidtv_psi.h"
> +#include "vidtv_encoder.h"
> +#include "vidtv_mux.h"
> +#include "vidtv_common.h"
> +#include "vidtv_s302m.h"
> +
> +static void vidtv_channel_encoder_destroy(struct vidtv_encoder *e)
> +{
> + struct vidtv_encoder *curr = e;
> +
> + while (curr) {
> + /* forward the call to the derived type */
> + curr->destroy(curr);
> + curr = curr->next;
> + }
> +}
> +
> +static struct vidtv_channel
> +*vidtv_channel_s302m_init(struct vidtv_channel *head)
> +{
> + /* init an audio only channel with a s302m encoder */
> + const u16 s302m_service_id = 0x880;
> + const u16 s302m_program_num = 0x880;
> + const u16 s302m_program_pid = 0x101; /* packet id for PMT*/
> + const u16 s302m_es_pid = 0x111; /* packet id for the ES */
> + const u16 s302m_pes_audio_stream_id = 0xbd; /* PES: private_stream_1 */
> +
> + struct vidtv_channel *s302m = kzalloc(sizeof(*s302m), GFP_KERNEL);
> + struct vidtv_psi_table_sdt_service *s302m_service;
> + struct vidtv_psi_desc_service *s302m_s_desc;
> + struct vidtv_s302m_encoder_init_args encoder_args = {0};
> +
> + s302m_service = vidtv_psi_sdt_service_init(NULL, s302m_service_id);
> +
> + s302m_s_desc = (struct vidtv_psi_desc_service *)
> + vidtv_psi_desc_init(NULL,
> + SERVICE_DESCRIPTOR,
> + sizeof(*s302m_s_desc));
> +
> + s302m_s_desc->name = "Sine Wave PCM Audio";
> + s302m_s_desc->service_type = DIGITAL_TELEVISION_SERVICE;
> +
> + s302m_s_desc->length = sizeof(s302m_s_desc->service_type)
> + + strlen(s302m_s_desc->name)
> + + strlen(s302m_s_desc->name_emph)
> + + strlen(s302m_s_desc->provider)
> + + strlen(s302m_s_desc->provider_emph);
> +
> + vidtv_psi_desc_assign(&s302m_service->descriptor,
> + (struct vidtv_psi_desc *)
> + s302m_s_desc);
> +
> + s302m->transport_stream_id = TRANSPORT_STREAM_ID;
> +
> + s302m->program = vidtv_psi_pat_program_init(NULL,
> + s302m_service_id,
> + s302m_program_pid);
> +
> + s302m->program_num = s302m_program_num;
> +
> + s302m->streams = vidtv_psi_pmt_stream_init(NULL,
> + STREAM_PRIVATE_DATA,
> + s302m_pes_audio_stream_id);
> +
> + encoder_args.access_unit_capacity = 16;
> + encoder_args.es_pid = s302m_es_pid;
> +
> + s302m->encoders = vidtv_s302m_encoder_init(encoder_args);
> +
> + if (head) {
> + while (head->next)
> + head = head->next;
> +
> + head->next = s302m;
> + }
> +
> + return s302m;
> +}
> +
> +static struct vidtv_psi_table_sdt_service
> +*vidtv_channel_sdt_serv_cat_into_new(struct vidtv_channel *channels)
> +{
> + struct vidtv_channel *cur_chnl = channels;
> + struct vidtv_psi_table_sdt_service *curr = NULL;
> + struct vidtv_psi_table_sdt_service *head = NULL;
> + struct vidtv_psi_table_sdt_service *tail = NULL;
> + u16 service_id;
> +
> + while (cur_chnl) {
> + curr = cur_chnl->service;
> + service_id = curr->service_id;
> +
> + if (!curr)
> + continue;
> +
> + while (curr->next) {
> + tail = vidtv_psi_sdt_service_init(tail, service_id);
> +
> + if (!head)
> + head = tail;
> +
> + curr = curr->next;
> + }
> +
> + cur_chnl = cur_chnl->next;
> + }
> +
> + return head;
> +}
> +
> +static struct vidtv_psi_table_pat_program*
> +vidtv_channel_pat_prog_cat_into_new(struct vidtv_channel *channels)
> +{
> + struct vidtv_channel *cur_chnl = channels;
> + struct vidtv_psi_table_pat_program *curr = NULL;
> + struct vidtv_psi_table_pat_program *head = NULL;
> + struct vidtv_psi_table_pat_program *tail = NULL;
> +
> + while (cur_chnl) {
> + curr = cur_chnl->program;
> +
> + if (!curr)
> + continue;
> +
> + while (curr->next) {
> + tail = vidtv_psi_pat_program_init(tail,
> + curr->service_id,
> + curr->pid);
> +
> + if (!head)
> + head = tail;
> +
> + curr = curr->next;
> + }
> +
> + cur_chnl = cur_chnl->next;
> + }
> +
> + return head;
> +}
> +
> +static void
> +vidtv_channel_pmt_match_sections(struct vidtv_channel *channels,
> + struct vidtv_psi_table_pmt sections[],
> + u32 nsections)
> +{
> + struct vidtv_psi_table_pmt *curr_section = NULL;
> + struct vidtv_channel *cur_chnl = channels;
> + u32 j;
> +
> + while (cur_chnl) {
> + for (j = 0; j < nsections; ++j) {
> + curr_section = &sections[j];
> +
> + if (!curr_section)
> + continue;
> +
> + /* we got a match */
> + if (curr_section->header.id == cur_chnl->program_num) {
> + vidtv_psi_pmt_stream_assign(curr_section,
> + cur_chnl->streams);
> + break;
> + }
> + }
> +
> + cur_chnl = cur_chnl->next;
> + }
> +}
> +
> +void vidtv_channel_si_init(struct vidtv_mux *m)
> +{
> + struct vidtv_psi_table_pat *pat = m->si.pat;
> + struct vidtv_psi_table_sdt *sdt = m->si.sdt;
> +
> + struct vidtv_psi_table_pmt *pmt_sections = m->si.pmt_secs;
> +
> + struct vidtv_psi_table_pat_program *programs = NULL;
> + struct vidtv_psi_table_sdt_service *services = NULL;
> +
> + bool update_version_num = false;
> +
> + vidtv_psi_pat_table_init(pat,
> + update_version_num,
> + TRANSPORT_STREAM_ID);
> +
> + vidtv_psi_sdt_table_init(sdt,
> + update_version_num,
> + TRANSPORT_STREAM_ID);
> +
> + programs = vidtv_channel_pat_prog_cat_into_new(m->channels);
> + services = vidtv_channel_sdt_serv_cat_into_new(m->channels);
> +
> + /* assemble all programs and assign to PAT */
> + vidtv_psi_pat_program_assign(pat, programs);
> +
> + /* assemble all services and assign to SDT */
> + vidtv_psi_sdt_service_assign(sdt, services);
> +
> + /* a section for each program_id */
> + pmt_sections = kcalloc(pat->programs,
> + sizeof(struct vidtv_psi_table_pmt),
> + GFP_KERNEL);
> +
> + vidtv_psi_pmt_create_sec_for_each_pat_entry(pat,
> + pmt_sections);
> +
> + vidtv_channel_pmt_match_sections(m->channels,
> + pmt_sections,
> + pat->programs);
> +}
> +
> +void vidtv_channel_si_destroy(struct vidtv_mux *m)
> +{
> + u32 i;
> +
> + vidtv_psi_pat_table_destroy(m->si.pat);
> +
> + for (i = 0; i < m->si.num_pmt_sections; ++i)
> + vidtv_psi_pmt_table_destroy(&m->si.pmt_secs[i]);
> +
> + kfree(m->si.pmt_secs);
> + vidtv_psi_sdt_table_destroy(m->si.sdt);
> +}
> +
> +void vidtv_channels_init(struct vidtv_mux *m)
> +{
> + /* we only have a single channel for now */
> + m->channels = vidtv_channel_s302m_init(NULL);
> +}
> +
> +void vidtv_channels_destroy(struct vidtv_mux *m)
> +{
> + struct vidtv_channel *curr = m->channels;
> +
> + while (curr) {
> + vidtv_psi_sdt_service_destroy(curr->service);
> + vidtv_psi_pat_program_destroy(curr->program);
> + vidtv_psi_pmt_stream_destroy(curr->streams);
> + vidtv_channel_encoder_destroy(curr->encoders);
> + curr = curr->next;
> + }
> +}
> +
> +static void
> +vidtv_channels_add_registration_s302m(struct vidtv_psi_table_pmt *sec)
> +{
> + struct vidtv_psi_desc_registration *s302m_r_desc;
> +
> + /* there might be some descriptors there already */
> + struct vidtv_psi_desc *parent = sec->descriptor;
> +
> + s302m_r_desc = (struct vidtv_psi_desc_registration *)
> + vidtv_psi_desc_init(parent,
> + REGISTRATION_DESCRIPTOR,
> + sizeof(*s302m_r_desc));
> +
> + s302m_r_desc->format_identifier = VIDTV_S302M_FORMAT_IDENTIFIER;
> +
> + if (!parent)
> + vidtv_psi_desc_assign(&sec->descriptor,
> + (struct vidtv_psi_desc *)s302m_r_desc);
> +
> + /* we are adding to the table, so recompute the length */
> + vidtv_psi_pmt_table_comp_sec_len(sec);
> +}
> +
> +void vidtv_channels_add_registration_descs(struct vidtv_mux *m)
> +{
> + /*
> + * Some formats might need a registration descriptor to be recognized.
> + * S302M needs it, and ffmpeg actually checks for it, so add such
> + * descriptor at the PMT section that contains the stream
> + */
> + struct vidtv_channel *cur_chnl = m->channels;
> + struct vidtv_encoder *e = NULL;
> + struct vidtv_psi_table_pmt *sec = NULL;
> +
> + while (cur_chnl) {
> + e = cur_chnl->encoders;
> + sec = vidtv_psi_find_pmt_sec(m->si.pmt_secs,
> + m->si.pat->programs,
> + cur_chnl->program_num);
> +
> + /* bug somewhere */
> + WARN_ON(!sec);
> + if (!sec)
> + continue;
> +
> + while (e) {
> + switch (e->id) {
> + case S302M:
> + vidtv_channels_add_registration_s302m(sec);
> + break;
> + default:
> + break;
> + }
> +
> + e = e->next;
> + }
> +
> + cur_chnl = cur_chnl->next;
> + }
> +}
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_channel.h b/drivers/media/test-drivers/vidtv/vidtv_channel.h
> new file mode 100644
> index 0000000000000..02141c4c732f0
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_channel.h
> @@ -0,0 +1,66 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Vidtv serves as a reference DVB driver and helps validate the existing APIs
> + * in the media subsystem. It can also aid developers working on userspace
> + * applications.
> + *
> + * This file contains the code for a 'channel' abstraction.
> + *
> + * When vidtv boots, it will create some hardcoded channels.
> + * Their services will be concatenated to populate the SDT.
> + * Their programs will be concatenated to populate the PAT
> + * For each program in the PAT, a PMT section will be created
> + * The PMT section for a channel will be assigned its streams.
> + * Every stream will have its corresponding encoder polled to produce TS packets
> + * These packets may be interleaved by the mux and then delivered to the bridge
> + *
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#ifndef VIDTV_CHANNEL_H
> +#define VIDTV_CHANNEL_H
> +
> +#include <linux/types.h>
> +#include "vidtv_psi.h"
> +#include "vidtv_encoder.h"
> +#include "vidtv_mux.h"
> +
> +struct vidtv_channel {
> + /* a number to identify the TS, chosen at will */
> + u16 transport_stream_id;
> +
> + /* will be concatenated into the SDT */
> + struct vidtv_psi_table_sdt_service *service;
> +
> + /* the link between SDT, PAT and PMT */
> + u16 program_num;
> +
> + /*
> + * a single program with one or more streams associated with it.
> + * Will be concatenated into the PAT
> + */
> + struct vidtv_psi_table_pat_program *program;
> +
> + /*
> + * one or more streams associated with the program
> + * Will populate the PMT section for this program
> + */
> + struct vidtv_psi_table_pmt_stream *streams;
> +
> + /* a list of encoders, one for each stream */
> + struct vidtv_encoder *encoders;
> +
> + struct vidtv_channel *next;
> +};
> +
> +/* init SI data from the channels */
> +void vidtv_channel_si_init(struct vidtv_mux *m);
> +void vidtv_channel_si_destroy(struct vidtv_mux *m);
> +
> +void vidtv_channels_init(struct vidtv_mux *m);
> +void vidtv_channels_destroy(struct vidtv_mux *m);
> +
> +void vidtv_channels_add_registration_descs(struct vidtv_mux *m);
> +
> +#endif //VIDTV_CHANNEL_H
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.h b/drivers/media/test-drivers/vidtv/vidtv_common.h
> index 170646497eb58..64c85503d4113 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_common.h
> +++ b/drivers/media/test-drivers/vidtv/vidtv_common.h
> @@ -14,6 +14,9 @@
> #include <media/dvb_frontend.h>
>
> #define CLOCK_UNIT_90KHZ 90000
> +#define CLOCK_UNIT_27MHZ 27000000
> +#define SLEEP_USECS 10000
> +#define TRANSPORT_STREAM_ID 0x744
>
> u32 vidtv_memcpy(void *to,
> const void *from,
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_mux.c b/drivers/media/test-drivers/vidtv/vidtv_mux.c
> new file mode 100644
> index 0000000000000..6d553be27622f
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_mux.c
> @@ -0,0 +1,423 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Vidtv serves as a reference DVB driver and helps validate the existing APIs
> + * in the media subsystem. It can also aid developers working on userspace
> + * applications.
> + *
> + * This file contains the multiplexer logic for TS packets from different
> + * elementary streams
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#include <linux/types.h>
> +#include <linux/slab.h>
> +#include <linux/jiffies.h>
> +#include <linux/kernel.h>
> +#include "vidtv_mux.h"
> +#include "vidtv_ts.h"
> +#include "vidtv_pes.h"
> +#include "vidtv_encoder.h"
> +#include "vidtv_channel.h"
> +#include "vidtv_common.h"
> +#include "vidtv_psi.h"
> +
> +static struct vidtv_mux_pid_ctx
> +*vidtv_mux_get_pid_ctx(struct vidtv_mux *m, u16 pid)
> +{
> + struct vidtv_mux_pid_ctx *ctx;
> +
> + hash_for_each_possible(m->pid_ctx, ctx, h, pid)
> + if (ctx->pid == pid)
> + return ctx;
> +
> + return NULL;
> +}
> +
> +static struct vidtv_mux_pid_ctx
> +*vidtv_mux_create_pid_ctx_once(struct vidtv_mux *m, u16 pid)
> +{
> + struct vidtv_mux_pid_ctx *ctx;
> +
> + ctx = vidtv_mux_get_pid_ctx(m, pid);
> +
> + if (ctx)
> + goto end;
> +
> + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> + ctx->pid = pid;
> + ctx->cc = 0;
> + hash_add(m->pid_ctx, &ctx->h, pid);
> +
> +end:
> + return ctx;
> +}
> +
> +static void vidtv_mux_pid_ctx_init(struct vidtv_mux *m)
> +{
> + struct vidtv_psi_table_pat_program *p = m->si.pat->program;
> +
> + hash_init(m->pid_ctx);
> + /* push the pcr pid ctx */
> + vidtv_mux_create_pid_ctx_once(m, m->pcr_pid);
> + /* push the null packet pid ctx */
> + vidtv_mux_create_pid_ctx_once(m, TS_NULL_PACKET_PID);
> + /* push the PAT pid ctx */
> + vidtv_mux_create_pid_ctx_once(m, VIDTV_PAT_PID);
> + /* push the SDT pid ctx */
> + vidtv_mux_create_pid_ctx_once(m, VIDTV_SDT_PID);
> +
> + /* add a ctx for all PMT sections */
> + while (p) {
> + vidtv_mux_create_pid_ctx_once(m, p->pid);
> + p = p->next;
> + }
> +}
> +
> +static void vidtv_mux_pid_ctx_destroy(struct vidtv_mux *m)
> +{
> + int bkt;
> + struct vidtv_mux_pid_ctx *ctx;
> +
> + hash_for_each(m->pid_ctx, bkt, ctx, h) {
> + kfree(ctx);
> + }
> +}
> +
> +static void vidtv_mux_update_clk(struct vidtv_mux *m)
> +{
> + /* call this at every thread iteration */
> + u64 elapsed_time;
> +
> + /* this will not hold a value yet if we have just started */
> + m->timing.past_jiffies = m->timing.current_jiffies ?
> + m->timing.current_jiffies :
> + get_jiffies_64();
> +
> + m->timing.current_jiffies = get_jiffies_64();
> +
> + elapsed_time = jiffies_to_usecs(m->timing.current_jiffies -
> + m->timing.past_jiffies);
> +
> + /* update the 27Mhz clock proportionally to the elapsed time */
> + m->timing.clk += (CLOCK_UNIT_27MHZ / USEC_PER_SEC) * elapsed_time;
> +}
> +
> +static u32 vidtv_mux_push_si(struct vidtv_mux *m)
> +{
> + u32 initial_offset = m->ts_buf_offset;
> + struct vidtv_mux_pid_ctx *pat_ctx, *pmt_ctx, *sdt_ctx;
> + u32 nbytes; /* the number of bytes written by this function */
> + u16 pmt_pid;
> + u32 i;
> +
> + pat_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_PAT_PID);
> + sdt_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_SDT_PID);
> +
> + m->ts_buf_offset += vidtv_psi_pat_write_into(m->ts_buf,
> + m->ts_buf_offset,
> + m->si.pat,
> + m->ts_buf_sz,
> + &pat_ctx->cc);
> +
> + for (i = 0; i < m->si.num_pmt_sections; ++i) {
> + pmt_pid = vidtv_psi_pmt_get_pid(&m->si.pmt_secs[i],
> + m->si.pat);
> +
> + /* not found */
> + WARN_ON(pmt_pid > TS_LAST_VALID_PID);
> + if (pmt_pid > TS_LAST_VALID_PID)
> + continue;
> +
> + pmt_ctx = vidtv_mux_get_pid_ctx(m, pmt_pid);
> +
> + /* write each section into buffer */
> + m->ts_buf_offset += vidtv_psi_pmt_write_into(m->ts_buf,
> + m->ts_buf_offset,
> + &m->si.pmt_secs[i],
> + pmt_pid,
> + m->ts_buf_sz,
> + &pmt_ctx->cc);
> + }
> +
> + m->ts_buf_offset += vidtv_psi_sdt_write_into(m->ts_buf,
> + m->ts_buf_offset,
> + m->si.sdt,
> + m->ts_buf_sz,
> + &sdt_ctx->cc);
> +
> + nbytes = m->ts_buf_offset - initial_offset;
> + return nbytes;
> +}
> +
> +static u32 vidtv_mux_push_pcr(struct vidtv_mux *m)
> +{
> + struct pcr_write_args args = {0};
> + struct vidtv_mux_pid_ctx *ctx;
> +
> + ctx = vidtv_mux_get_pid_ctx(m, m->pcr_pid);
> + args.dest_buf = m->ts_buf;
> + args.pid = m->pcr_pid;
> + args.buf_sz = m->ts_buf_sz;
> + args.continuity_counter = &ctx->cc;
> +
> + /* the 27Mhz clock will feed both parts of the PCR bitfield */
> + args.pcr = m->timing.clk;
> +
> + return vidtv_ts_pcr_write_into(args);
> +}
> +
> +static bool vidtv_mux_should_push_pcr(struct vidtv_mux *m)
> +{
> + u64 next_pcr_at;
> +
> + next_pcr_at = m->timing.start_jiffies +
> + usecs_to_jiffies(m->num_streamed_pcr *
> + m->timing.pcr_period_usecs);
> +
> + return time_after64(m->timing.current_jiffies, next_pcr_at);
> +}
> +
> +static bool vidtv_mux_should_push_si(struct vidtv_mux *m)
> +{
> + u64 next_si_at;
> +
> + next_si_at = m->timing.start_jiffies +
> + usecs_to_jiffies(m->num_streamed_si *
> + m->timing.si_period_usecs);
> +
> + return time_after64(m->timing.current_jiffies, next_si_at);
> +}
> +
> +static u32 vidtv_mux_packetize_access_units(struct vidtv_mux *m,
> + struct vidtv_encoder *e)
> +{
> + /* the number of bytes written by this function */
> + u32 nbytes = 0;
> + struct pes_write_args args = {0};
> + u32 initial_offset = m->ts_buf_offset;
> +
> + u32 i;
> + u8 *buf;
> + struct vidtv_mux_pid_ctx *pid_ctx;
> +
> + pid_ctx = vidtv_mux_create_pid_ctx_once(m, e->es_pid);
> +
> + args.dest_buf = m->ts_buf;
> + args.dest_buf_sz = m->ts_buf_sz;
> + args.pid = e->es_pid;
> + args.is_s302m_pkt = (e->id == S302M);
> + args.continuity_counter = &pid_ctx->cc;
> + args.send_pts = true;
> +
> + for (i = 0; i < e->nunits; ++i) {
> + buf = e->encoder_buf + e->offsets[i];
> + args.from = buf;
> + args.access_unit_len = e->nbytes[i];
> + args.dest_offset = m->ts_buf_offset;
> + args.pts = e->pts[i];
> +
> + m->ts_buf_offset += vidtv_pes_write_into(args);
> + }
> +
> + /* clear the encoder state once we have written the current ES data */
> + e->clear(e);
> +
> + nbytes = m->ts_buf_offset - initial_offset;
> + return nbytes;
> +}
> +
> +static u32 vidtv_mux_poll_encoders(struct vidtv_mux *m)
> +{
> + u32 nbytes = 0;
> + struct vidtv_channel *cur_chnl = m->channels;
> + struct vidtv_encoder *e = NULL;
> +
> + u64 elapsed_time_usecs = jiffies_to_usecs(m->timing.current_jiffies -
> + m->timing.past_jiffies);
> + while (cur_chnl) {
> + e = cur_chnl->encoders;
> +
> + while (e) {
> + /* encode for 'elapsed_time_usecs' */
> + e->encode(e, elapsed_time_usecs);
> + /* get the TS packets into the mux buffer */
> + nbytes += vidtv_mux_packetize_access_units(m, e);
> + /* grab next encoder */
> + e = e->next;
> + }
> +
> + /* grab the next channel */
> + cur_chnl = cur_chnl->next;
> + }
> +
> + return nbytes;
> +}
> +
> +static u32 vidtv_mux_pad_with_nulls(struct vidtv_mux *m, u32 npkts)
> +{
> + struct null_packet_write_args args = {0};
> + u32 initial_offset = m->ts_buf_offset;
> + u32 nbytes; /* the number of bytes written by this function */
> + u32 i;
> + struct vidtv_mux_pid_ctx *ctx;
> +
> + ctx = vidtv_mux_get_pid_ctx(m, TS_NULL_PACKET_PID);
> +
> + args.dest_buf = m->ts_buf;
> + args.buf_sz = m->ts_buf_sz;
> + args.continuity_counter = &ctx->cc;
> + args.dest_offset = m->ts_buf_offset;
> +
> + for (i = 0; i < npkts; ++i) {
> + m->ts_buf_offset += vidtv_ts_null_write_into(args);
> + args.dest_offset = m->ts_buf_offset;
> + }
> +
> + nbytes = m->ts_buf_offset - initial_offset;
> +
> + /* sanity check */
> + WARN_ON(nbytes != npkts * TS_PACKET_LEN);
> +
> + return nbytes;
> +}
> +
> +static u32 vidtv_mux_check_mux_rate(struct vidtv_mux *m)
> +{
> + /*
> + * attempt to maintain a constant mux rate, padding with null packets
> + * if needed
> + */
> +
> + u32 nbytes = 0; /* the number of bytes written by this function */
> +
> + u64 nbytes_expected; /* the number of bytes we should have written */
> + u64 nbytes_streamed; /* the number of bytes we actually wrote */
> + u32 num_null_pkts; /* number of null packets to bridge the gap */
> +
> + u64 elapsed_time_usecs = jiffies_to_usecs(m->timing.current_jiffies -
> + m->timing.past_jiffies);
> +
> + nbytes_expected = (m->mux_rate_kbytes_sec / 1000) / USEC_PER_SEC;
> + nbytes_expected *= elapsed_time_usecs;
> +
> + nbytes_streamed = m->num_streamed_pkts * TS_PACKET_LEN;
> +
> + if (nbytes_streamed < nbytes_expected) {
> + /* can't write half a packet: roundup to a 188 multiple */
> + nbytes_expected = roundup(nbytes_expected, TS_PACKET_LEN);
> + num_null_pkts = nbytes_expected / TS_PACKET_LEN;
> + nbytes += vidtv_mux_pad_with_nulls(m, num_null_pkts);
> + }
> +
> + return nbytes;
> +}
> +
> +static void vidtv_mux_clear(struct vidtv_mux *m)
> +{
> + /* clear the packets currently in the mux */
> + memset(m->ts_buf, 0, m->ts_buf_sz);
> + /* point to the beginning of the buffer again */
> + m->ts_buf_offset = 0;
> +}
> +
> +static void vidtv_mux_tick(struct work_struct *work)
> +{
> + struct vidtv_mux *m = container_of(work,
> + struct vidtv_mux,
> + mpeg_thread);
> + u32 nbytes;
> + u32 npkts;
> +
> + while (m->streaming) {
> + nbytes = 0;
> +
> + vidtv_mux_update_clk(m);
> +
> + if (vidtv_mux_should_push_pcr(m))
> + nbytes += vidtv_mux_push_pcr(m);
> +
> + if (vidtv_mux_should_push_si(m))
> + nbytes += vidtv_mux_push_si(m);
> +
> + nbytes += vidtv_mux_poll_encoders(m);
> +
> + nbytes += vidtv_mux_check_mux_rate(m);
> +
> + npkts = nbytes / TS_PACKET_LEN;
> + /* if the buffer is not aligned there is a bug somewhere */
> + WARN_ON(nbytes % TS_PACKET_LEN);
> +
> + if (m->on_new_packets_available_cb)
> + m->on_new_packets_available_cb(m->priv,
> + m->ts_buf,
> + npkts);
> +
> + m->num_streamed_pkts += npkts;
> + vidtv_mux_clear(m);
> +
> + usleep_range(SLEEP_USECS, 2 * SLEEP_USECS);
> + }
> +}
> +
> +void vidtv_mux_start_thread(struct vidtv_mux *m)
> +{
> + WARN_ON(m->streaming);
> +
> + if (m->streaming)
> + return;
> +
> + m->streaming = true;
> + m->timing.start_jiffies = get_jiffies_64();
> + schedule_work(&m->mpeg_thread);
> +}
> +
> +void vidtv_mux_stop_thread(struct vidtv_mux *m)
> +{
> + /* thread will quit */
> + m->streaming = false;
> +}
> +
> +struct vidtv_mux *vidtv_mux_init(struct vidtv_mux_init_args args)
> +{
> + struct vidtv_mux *m = kzalloc(sizeof(*m), GFP_KERNEL);
> +
> + m->timing.pcr_period_usecs = args.pcr_period_usecs;
> + m->timing.si_period_usecs = args.si_period_usecs;
> +
> + m->mux_rate_kbytes_sec = args.mux_rate_kbytes_sec;
> +
> + m->on_new_packets_available_cb = args.on_new_packets_available_cb;
> +
> + m->ts_buf = vmalloc(args.ts_buf_sz);
> + m->ts_buf_sz = args.ts_buf_sz;
> +
> + m->si.pat = kzalloc(sizeof(*m->si.pat), GFP_KERNEL);
> + m->si.sdt = kzalloc(sizeof(*m->si.sdt), GFP_KERNEL);
> +
> + vidtv_channels_init(m);
> +
> + /* will alloc data for pmt_sections after initializing pat */
> + vidtv_channel_si_init(m);
> +
> + INIT_WORK(&m->mpeg_thread, vidtv_mux_tick);
> +
> + m->pcr_pid = args.pcr_pid;
> + m->priv = args.priv;
> +
> + vidtv_mux_pid_ctx_init(m);
> + vidtv_channels_add_registration_descs(m);
> +
> + return m;
> +}
> +
> +void vidtv_mux_destroy(struct vidtv_mux *m)
> +{
> + vidtv_mux_pid_ctx_destroy(m);
> + vidtv_channel_si_destroy(m);
> + vidtv_channels_destroy(m);
> + kfree(m->si.sdt);
> + kfree(m->si.pat);
> + vfree(m->ts_buf);
> + kfree(m);
> +}
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_mux.h b/drivers/media/test-drivers/vidtv/vidtv_mux.h
> new file mode 100644
> index 0000000000000..1dffa2010d518
> --- /dev/null
> +++ b/drivers/media/test-drivers/vidtv/vidtv_mux.h
> @@ -0,0 +1,105 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Vidtv serves as a reference DVB driver and helps validate the existing APIs
> + * in the media subsystem. It can also aid developers working on userspace
> + * applications.
> + *
> + * This file contains the multiplexer logic for TS packets from different
> + * elementary streams
> + *
> + * Written by Daniel W. S. Almeida <[email protected]>
> + */
> +
> +#ifndef VIDTV_MUX_H
> +#define VIDTV_MUX_H
> +
> +#include <linux/types.h>
> +#include <linux/hashtable.h>
> +#include <linux/workqueue.h>
> +#include "vidtv_psi.h"
> +struct vidtv_mux_timing {
> + u64 start_jiffies;
> + u64 current_jiffies;
> + u64 past_jiffies;
> +
> + /* a 27Mhz clock from which we will drive the PCR */
> + u64 clk;
> +
> + u64 pcr_period_usecs;
> + u64 si_period_usecs;
> +};
> +
> +struct vidtv_mux_si {
> + /* the SI tables */
> + struct vidtv_psi_table_pat *pat;
> + struct vidtv_psi_table_pmt *pmt_secs; /* the PMT sections */
> + /* as many sections as programs in the PAT */
> + u16 num_pmt_sections;
> + struct vidtv_psi_table_sdt *sdt;
> +};
> +
> +struct vidtv_mux_pid_ctx {
> + u16 pid;
> + u8 cc; /* continuity counter */
> + struct hlist_node h;
> +};
> +
> +struct vidtv_mux {
> + struct vidtv_mux_timing timing;
> +
> + /* the bit rate for the TS, in kbytes */
> + u32 mux_rate_kbytes_sec;
> +
> + /* a hash table to keep track of per-PID metadata */
> + DECLARE_HASHTABLE(pid_ctx, 3);
> +
> + /* a callback to inform of new TS packets ready */
> + void (*on_new_packets_available_cb)(void *priv, u8 *buf, u32 npackets);
> +
> + /* the TS buffer */
> + u8 *ts_buf;
> +
> + /* the TS buffer size */
> + u32 ts_buf_sz;
> +
> + /* where we are in the TS buffer now */
> + u32 ts_buf_offset;
> +
> + /* a list of channels */
> + struct vidtv_channel *channels;
> +
> + struct vidtv_mux_si si;
> + u64 num_streamed_pcr;
> + u64 num_streamed_si;
> +
> + /* total number of packets streamed */
> + u64 num_streamed_pkts;
> +
> + struct work_struct mpeg_thread;
> +
> + /* whether to keep running the main loop */
> + bool streaming;
> +
> + /* the pcr PID for _all_ channels */
> + u16 pcr_pid;
> +
> + void *priv;
> +};
> +
> +struct vidtv_mux_init_args {
> + u32 mux_rate_kbytes_sec;
> + void (*on_new_packets_available_cb)(void *priv, u8 *buf, u32 npackets);
> + u32 ts_buf_sz;
> + u64 pcr_period_usecs;
> + u64 si_period_usecs;
> + u16 pcr_pid;
> + void *priv;
> +};
> +
> +struct vidtv_mux *vidtv_mux_init(struct vidtv_mux_init_args args);
> +void vidtv_mux_destroy(struct vidtv_mux *m);
> +
> +void vidtv_mux_start_thread(struct vidtv_mux *m);
> +void vidtv_mux_stop_thread(struct vidtv_mux *m);
> +
> +#endif //VIDTV_MUX_H
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.c b/drivers/media/test-drivers/vidtv/vidtv_psi.c
> index 191d37a248923..ac7a52f03843a 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_psi.c
> +++ b/drivers/media/test-drivers/vidtv/vidtv_psi.c
> @@ -1135,3 +1135,21 @@ vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat,
> program = program->next;
> }
> }
> +
> +struct vidtv_psi_table_pmt
> +*vidtv_psi_find_pmt_sec(struct vidtv_psi_table_pmt pmt_sections[],
> + u16 nsections,
> + u16 program_num)
> +{
> + /* find the PMT section associated with 'program_num' */
> + struct vidtv_psi_table_pmt *sec = NULL;
> + u32 i;
> +
> + for (i = 0; i < nsections; ++i) {
> + sec = &pmt_sections[i];
> + if (sec->header.id == program_num)
> + return sec;
> + }
> +
> + return NULL;
> +}
> diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.h b/drivers/media/test-drivers/vidtv/vidtv_psi.h
> index c5c8c143f0e4a..7aa502b89a2e2 100644
> --- a/drivers/media/test-drivers/vidtv/vidtv_psi.h
> +++ b/drivers/media/test-drivers/vidtv/vidtv_psi.h
> @@ -354,4 +354,9 @@ u32 vidtv_psi_pmt_write_into(char *buf,
> u32 buf_sz,
> u8 *continuity_counter);
>
> +struct vidtv_psi_table_pmt
> +*vidtv_psi_find_pmt_sec(struct vidtv_psi_table_pmt *pmt_sections,
> + u16 nsections,
> + u16 program_num);
> +
> #endif // VIDTV_PSI_H



Thanks,
Mauro

2020-05-03 09:54:19

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 07/11] media: vidtv: add MPEG TS common code

Em Sat, 2 May 2020 19:22:06 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> Hi Mauro, thanks for reviewing this!
>
> > Em Sat, 2 May 2020 00:22:12 -0300
> > "Daniel W. S. Almeida" <[email protected]> escreveu:
> >
> >> From: "Daniel W. S. Almeida" <[email protected]>
> >>
> >> Add code to work with MPEG TS packets, such as TS headers, adaptation
> >> fields, PCR packets and NULL packets.
> >>
> >> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> >> ---
> >> drivers/media/test-drivers/vidtv/Makefile | 2 +-
> >> drivers/media/test-drivers/vidtv/vidtv_ts.c | 130 ++++++++++++++++++++
> >> drivers/media/test-drivers/vidtv/vidtv_ts.h | 103 ++++++++++++++++
> >> 3 files changed, 234 insertions(+), 1 deletion(-)
> >> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.c
> >> create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.h
> >>
> >> diff --git a/drivers/media/test-drivers/vidtv/Makefile
> b/drivers/media/test-drivers/vidtv/Makefile
> >> index 9ea9485d42189..92001bc348615 100644
> >> --- a/drivers/media/test-drivers/vidtv/Makefile
> >> +++ b/drivers/media/test-drivers/vidtv/Makefile
> >> @@ -1,6 +1,6 @@
> >> # SPDX-License-Identifier: GPL-2.0
> >>
> >> vidtv_demod-objs := vidtv_common.o
> >> -vidtv_bridge-objs := vidtv_common.o
> >> +vidtv_bridge-objs := vidtv_common.o vidtv_ts.o
> >>
> >> obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o
> >> diff --git a/drivers/media/test-drivers/vidtv/vidtv_ts.c
> b/drivers/media/test-drivers/vidtv/vidtv_ts.c
> >> new file mode 100644
> >> index 0000000000000..f545c45c0fe7c
> >> --- /dev/null
> >> +++ b/drivers/media/test-drivers/vidtv/vidtv_ts.c
> >> @@ -0,0 +1,130 @@
> >> +// SPDX-License-Identifier: GPL-2.0
> >> +/*
> >> + * The Virtual DVB test driver serves as a reference DVB driver and
> helps
> >> + * validate the existing APIs in the media subsystem. It can also aid
> >> + * developers working on userspace applications.
> >> + *
> >> + * Written by Daniel W. S. Almeida <[email protected]>
> >> + */
> >> +
> >> +#include <linux/types.h>
> >> +#include <asm/byteorder.h>
> >> +#include "vidtv_ts.h"
> >> +#include "vidtv_common.h"
> >> +
> >> +static u32 vidtv_ts_write_pcr_bits(u8 *buf, u64 pcr)
> >> +{
> >> + /* Exact same from ffmpeg. PCR is a counter driven by a 27Mhz clock */
> >> + u64 pcr_low = pcr % 300, pcr_high = pcr / 300;
> >> +
> >> + *buf++ = pcr_high >> 25;
> >> + *buf++ = pcr_high >> 17;
> >> + *buf++ = pcr_high >> 9;
> >> + *buf++ = pcr_high >> 1;
> >> + *buf++ = pcr_high << 7 | pcr_low >> 8 | 0x7e;
> >> + *buf++ = pcr_low;
> >> +
> >> + return 6;
> >> +}
> >> +
> >> +void vidtv_ts_inc_cc(u8 *continuity_counter)
> >> +{
> >> + ++*continuity_counter;
> >> + if (*continuity_counter > TS_CC_MAX_VAL)
> >> + *continuity_counter = 0;
> >> +}
> >> +
> >> +u32 vidtv_ts_null_write_into(struct null_packet_write_args args)
> >> +{
> >> + u32 nbytes = 0;
> >> + struct vidtv_mpeg_ts ts_header = {0};
> >> +
> >> + ts_header.sync_byte = TS_SYNC_BYTE;
> >> + ts_header.pid = TS_NULL_PACKET_PID;
> >> + ts_header.payload = 1;
> >> + ts_header.continuity_counter = *args.continuity_counter;
> >> +
> >> + cpu_to_be16s(&ts_header.bitfield);
> >> +
> >> + /* copy TS header */
> >> + nbytes += vidtv_memcpy(args.dest_buf + args.dest_offset + nbytes,
> >> + &ts_header,
> >> + sizeof(ts_header),
> >> + args.dest_offset + nbytes,
> >> + args.buf_sz);
> >
> > Hmm... now I see why you're returning 0 to vidtv_memcpy().
> >
> > Yet, if the buffer is full, you should just drop the entire package,
> > as otherwise you may end copying things that aren't multiple of 188
> bytes,
> > causing sync issues at the client.
>
> I'd like to provide a counterargument for this.
>
> The way I am dealing with errors throughout vidtv so far is:
> If we hit any of these WARN_ON macros, pr_err and the like, then all
> bets are off. This means that the resulting stream will likely be
> invalid

Losing some data is not really a big issue.

I mean, a stream with some dropped packages is acceptable. That
actually happens in practice, as sometimes userspace just can't read
everything in time.

The only thing is that, if buffer overflow conditions are detected
(and allowed - se more below), the code should ensure that the package
counters should be updated, so the next frame will show a
discontinuity. This way, userspace can detect packet losses.

> and that something needs to be rewritten in the source code and
> my main concern is then preventing the whole driver from crashing.

A WARN_ON() won't prevent a crash. It will just output (very noisly)
stuff at dmesg. This can actually cause a crash: if someone is using
a serial console, for example, the amount of data at dmesg will
be so much that the machine will become unresponsive.

Btw, when WARN_ON() is detecting something that would cause crashes,
it should use this pattern:

if (WARN_ON(...))
return -EINVAL; // or some other error code like -ENOMEM

> This is exactly the behavior that you see in vidtv_memcpy and
> vidtv_memset: nothing gets written so we don't end up with an overflow,
> a diagnostic message is printed and there are no attempts at recovery.

Yeah, after looking how you're using it, I'm ok with the way you're
using vidtv_memcpy (but see the comments I posted today about them).

>
> In this particular example, I compromised by allowing the size of the
> buffer to be a module param, i.e.
>
> >> +static unsigned int ts_buf_sz = 20 * 188;
> >> +module_param(ts_buf_sz, uint, 0644);
> >> +MODULE_PARM_DESC(ts_buf_sz, "Optional size for the TS buffer");

(Unrelated to this discussion)
What happens if userspace sets ts_buf_sz < 188? What happens if
ts_buf_sz is bigger than the available RAM size? For sure you need
a callback there in order validate its result and ensure it would
between a certain range that would make sense.

>
> I think that what I am trying to say is better seen in the last patch
> for this series: [RFC, WIP, v4 11/11] media: vidtv: Add a MPEG Transport
> Stream Multiplexer.
>
> The following takes place in vidtv_mux.c:
>
> 1. We wake up from sleep, take note of how long we slept for and store
> it into "elapsed_time". This is usually between 10ms and 20ms.
> 2. We encode "elapsed_time" miliseconds worth of data into a buffer
> 3. We call dvb_dmx_swfilter(), passing a pointer to the buffer
> 4. We clear the buffer, sleep for a bit and then go back to 1.
>
> If the buffer is somehow full then it means that we are trying to
> simulate too many fake channels while allocating too little memory, so
> either we scale down on the amount of fake channels or we choose another
> value for the "ts_buf_sz" module_param.
>
> I feel that this is better than adding more logic in an attempt to
> circumvent the issue, specially since I can add more logic and still
> fail due to my limited experience. The result is more bloat on the
> source code for little gain.

Ok, there are some different situations buffer overflow conditions:

1) userspace may not be calling read() fast enough;
2) the buffer is too small for a single stream;
3) the buffer will support a limited number of streams.

The code should work fine with condition (1), not causing warnings
or errors.

The minimal buffer size should be big enough to avoid condition (2).

Different strategies could be used to handle condition (3).

-

Let's forget for a while the Kernel driver, focusing on the other
side of the coin:

This driver is meant to allow testing userspace programs, emulating
a real hardware.

With real hardware, the TV board will receive a signal with some
noise. The signal is encoded with several error detection mechanisms.

When a board hardware detects that a package has issues, it will
discard it. That's why some packets have a continuity_counter,
while others are just continuously repeated.

So, from this aspect, having some packages dropped from a stream
is actually not a bad thing.

Back to the implementation part, in order to simulate a package
drop, we could simply have a rand() call that would be randomly
dropping some packages. Or, we could set the driver to produce
more streams that it would fit into the MPEG-TS stream.

For now, I would just do the latter: if the buffer is too small,
just drop an entire package, preserving the alignment.

>
> > A WARN_ON() seems too severe here. Also, if something bad happens, it
> > will end causing lots of problems that can make the machine very slow,
> > ad this will flood dmesg.
> >
> > So, the best would be to use, instead, dev_warn_ratelimited().
>
> You're right, I did not consider this. I will use dev_warn_ratelimited()
> in place of WARN_ON in the next revisions.

yeah, dev_warn_ratelimited() would work. You could also use
WARN_ON_ONCE(), as probably there's not much value on keep
repeating the message every time.

Thanks,
Mauro

2020-05-06 06:58:57

by Daniel Almeida

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 09/11] media: vidtv: implement a PES packetizer

Hi Mauro,


> As commented, don't use WARN_ON(). At most, you could use WARN_ON_ONCE(),
> as otherwise, you may end by causing serious performance issues if
> the code starts to produce a flood of warnings at the dmesg.
>
> I would use pr_warn_ratelimit() on all such cases.
>

OK.




> I don't like the idea of changing the "from" buffer endiannes, copy
> and then restore it back to the original state. Is this really needed?
>
> I would, instead, define:
>
> struct pes_header {
> ...
> __be32 bitfield;
> __be16 length;
> ...
> };
>
> Then wherever you would touch them:
>
> u32 bitfield;
> u16 len;
>
> /* Write into BE fields */
> pes_header.bitfield = cpu_to_be32(bitfield);
> pes_header.length = cpu_to_be16(len);
>
> /* Read from BE fields */
> bitfield = be32_to_cpu(pes_header.bitfield);
> len = be16_to_cpu(pes_header.length);
>
>
> As a side effect, when you use "__be16" and "__be32" types, gcc
> and smatch/sparse will warn you if you mess with endiannes.
>
> Same applies to similar code elsewhere.
>

I don't like it either, it is error prone. I did not know about this
other possibility. Does this work for _bitfields_ though?

I think the authors for libdvbv5 used unions precisely so bswap() could
be called on a 'bitfield' member and from then on the bitfields could be
accessed directly, e.g.:

union {
u16 bitfield; <-- call bswap() on this
struct {
--> then use these directly:
u8 syntax:1;
u8 zero:1;
u8 one:2;
u16 section_length:12;
} __packed;
} __packed

At least that's what I understood.

I found this:
https://lwn.net/Articles/741762/

Maybe *_get_bits, *_replace_bits is the equivalent that I should use for bitfields?

Because I'd rather not do this:

> u32 bitfield;
> /* Write into BE fields */
> pes_header.bitfield = cpu_to_be32(bitfield);

Since I'd have to write the (many!) bitwise operations myself and I'm
sure I will mess this up at _some_ point.



thanks,
- Daniel


2020-05-06 07:08:07

by Daniel Almeida

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 11/11] media: vidtv: Add a MPEG Transport Stream Multiplexer

Hi Mauro! Thank you for reviewing this!


>> Add a MPEG Transport Stream multiplexer responsible for polling encoders,
>> interleaving packets, padding the resulting stream with NULL packets if
>> necessary and then delivering the resulting TS packets to the bridge
>> driver so it can feed the demux.
>>
>> This patch includes a "channel" abstraction, which attempts to map a
>> MPEG service into a struct that vidtv can work with.
>>
>> When vidtv boots, it will create some hardcoded channels:
>>
>> -Their services will be concatenated to populate the SDT.
>> -Their programs will be concatenated to populate the PAT
>> -For each program in the PAT, a PMT section will be created
>> -The PMT section for a channel will be assigned its streams.
>> -Every stream will have its corresponding encoder polled to produce
>> TS packets
>> -These packets may be interleaved by the mux and then delivered to
>> the bridg
>>
>> Signed-off-by: Daniel W. S. Almeida <[email protected]>
>
> The same notes I made on previous patches apply here.

I did not understand this. Do you mean to say that I should remove these
dashes in the beginning of the lines?


thanks
- Daniel

2020-05-06 08:38:26

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 08/11] media: vidtv: implement a PSI generator

Em Wed, 6 May 2020 03:28:17 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> >> + /* Just a sanity check, should not really happen because we stuff
> >> + * the packet when we finish a section, i.e. when we write the crc at
> >> + * the end. But if this happens then we have messed up the logic
> >> + * somewhere.
> >> + */
> >> + WARN_ON(args.new_psi_section && !aligned);
> >
> > Please use a ratelimited printk instead (here and on all similar cases
> > at the TS generator).
> >
> > Also, I think that, on such case, the driver should be filling the
> > remaining frame with pad bytes. E. g.:
> >
> > /*
> > * Assuming that vidtv_memset() args from patch 06/11 were changed
> > * according with this prototype:
> > */
> > size_t vidtv_memset(void *to, size_t to_offset, size_t to_size,
> > u8 c, size_t len);
> >
> >
> > #define TS_FILL_BYTE 0xff
> >
> > if (args.new_psi_section && !aligned) {
> > pr_warn_ratelimit("Warning: PSI not aligned. Re-aligning it\n");
> >
> > vidtv_memset(args.dest_buf,
> > args.dest_offset + nbytes_past_boundary,
> > args.dest_buf_sz,
> > TS_FILL_BYTE,
> > TS_PACKET_LEN - nbytes_past_boundary);
> > args.dest_offset += TS_PACKET_LEN - nbytes_past_boundary;
> > aligned = 1;
> > nbytes_past_boundary = 0;
> > }
> >
>
> Sure, that's fine then! Just to be clear this should not happen at all,
> because the writes should go through one of these four functions (IIRC!):
>
> u32 vidtv_ts_null_write_into(struct null_packet_write_args args)
> u32 vidtv_ts_pcr_write_into(struct pcr_write_args args)
>
> ...which will write a single packet at a time, thus leaving the buffer
> aligned if it was already aligned to begin with,
>
> and
>
> u32 vidtv_pes_write_into(struct pes_write_args args)
> static u32
> vidtv_psi_ts_psi_write_into(struct psi_write_args args)
>
> ...which will pad when they finish writing a PES packet or a table
> section, respectively.
>
> I left these warnings behind as a way to warn me if the padding logic
> itself is broken.

OK!

> > Please use a ratelimited printk instead (here and on all similar cases
> > at the TS generator).
>
> OK.
>
>
>
> >> +static void vidtv_psi_desc_to_be(struct vidtv_psi_desc *desc)
> >> +{
> >> + /*
> >> + * Convert descriptor endianness to big-endian on a field-by-field
> >> basis
> >> + * where applicable
> >> + */
> >> +
> >> + switch (desc->type) {
> >> + /* nothing do do */
> >> + case SERVICE_DESCRIPTOR:
> >> + break;
> >> + case REGISTRATION_DESCRIPTOR:
> >> + cpu_to_be32s(&((struct vidtv_psi_desc_registration *)
> >> + desc)->format_identifier);
> >> + pr_alert("%s: descriptor type %d found\n",
> >> + __func__,
> >> + desc->type);
> >> + pr_alert("%s: change 'additional_info' endianness before
> >> calling\n",
> >> + __func__);
> >
> > The above pr_alert() calls sound weird. Why are you unconditionally
> > calling it (and still doing the BE conversion) for all
> > REGISTRATION_DESCRIPTOR types?
>
> To be honest, I did not know what to do. Here's what 13818-1 has to say
> about registration descriptors:
>
> >2.6.9
> > Semantic definition of fields in registration descriptor
> >format_identifier – The format_identifier is a 32-bit value obtained
> >from a Registration Authority as designated by
> >ISO/IEC JTC 1/SC 29.
> >additional_identification_info – The meaning of
> >additional_identification_info bytes, if any, are defined by the
> >assignee of that format_identifier, and once defined they shall not change.
>
> So I took the cue from libdvbv5 and defined the following struct for it,
> with a flexible array member at the end:

André (who re-wrote the libdvbv5 parsers to be more generic)
preferred the approach of keeping the structs in CPU-endian, as it
makes easier from application PoV, as the conversion happens only once
at the library.

That's not the case here. We can fill the structs in big endian,
converting to CPU-endian only on the few places we may need to read
back from it.

>
> struct vidtv_psi_desc_registration {
> u8 type;
> u8 length;
> struct vidtv_psi_desc *next;
>
> /*
> * The format_identifier is a 32-bit value obtained from a Registration
> * Authority as designated by ISO/IEC JTC 1/SC 29.
> */
> u32 format_identifier;
> /*
> * The meaning of additional_identification_info bytes, if any, are
> * defined by the assignee of that format_identifier, and once defined
> * they shall not change.
> */
> u8 additional_identification_info[];
> } __packed
>
>
> As you know, I was changing the endianness from host to BE before
> serializing and then changing back from BE to host. Given the struct
> definition above, there was no way to do this to the
> 'additional_identification_info' member, since we do not know its layout.
>
> If we go with your approach instead (i.e. using __be16, __be32...etc)
> then I think we can remove these two functions (and more)

Yep.

Thanks,
Mauro

2020-05-06 09:01:13

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 09/11] media: vidtv: implement a PES packetizer

Em Wed, 6 May 2020 03:55:48 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> Hi Mauro,
>
>
> > As commented, don't use WARN_ON(). At most, you could use WARN_ON_ONCE(),
> > as otherwise, you may end by causing serious performance issues if
> > the code starts to produce a flood of warnings at the dmesg.
> >
> > I would use pr_warn_ratelimit() on all such cases.
> >
>
> OK.
>
>
>
>
> > I don't like the idea of changing the "from" buffer endiannes, copy
> > and then restore it back to the original state. Is this really needed?
> >
> > I would, instead, define:
> >
> > struct pes_header {
> > ...
> > __be32 bitfield;
> > __be16 length;
> > ...
> > };
> >
> > Then wherever you would touch them:
> >
> > u32 bitfield;
> > u16 len;
> >
> > /* Write into BE fields */
> > pes_header.bitfield = cpu_to_be32(bitfield);
> > pes_header.length = cpu_to_be16(len);
> >
> > /* Read from BE fields */
> > bitfield = be32_to_cpu(pes_header.bitfield);
> > len = be16_to_cpu(pes_header.length);
> >
> >
> > As a side effect, when you use "__be16" and "__be32" types, gcc
> > and smatch/sparse will warn you if you mess with endiannes.
> >
> > Same applies to similar code elsewhere.
> >
>
> I don't like it either, it is error prone. I did not know about this
> other possibility. Does this work for _bitfields_ though?

See my comment below.

> I think the authors for libdvbv5 used unions precisely so bswap() could
> be called on a 'bitfield' member and from then on the bitfields could be
> accessed directly, e.g.:
>
> union {
> u16 bitfield; <-- call bswap() on this
> struct {
> --> then use these directly:
> u8 syntax:1;
> u8 zero:1;
> u8 one:2;
> u16 section_length:12;
> } __packed;
> } __packed
>
> At least that's what I understood.

You should double-check the structs from the specs. If I'm not mistaken,
bytes were swapped on some places. As I commented for patch 08/11,
the focus there were to make life simpler for userspace, and not to
store a precise copy of the byte order.

>
> I found this:
> https://lwn.net/Articles/741762/
>
> Maybe *_get_bits, *_replace_bits is the equivalent that I should use for bitfields?

I never used them, but, based on their definition:

static __always_inline base type##_get_bits(__##type v, base field) \
{ \
return (from(v) & field)/field_multiplier(field); \
}

Calling be16_get_bits should do the right cast to the type.

I don't know what the "from()" and "to()" macros would do.

I guess you will need to do some tests to see if this works as
expected.

>
> Because I'd rather not do this:
>
> > u32 bitfield;
> > /* Write into BE fields */
> > pes_header.bitfield = cpu_to_be32(bitfield);
>
> Since I'd have to write the (many!) bitwise operations myself and I'm
> sure I will mess this up at _some_ point.

If you mess up, gcc (and/or smatch) will complain. I mean,

if bitfield is declared as __be32, if you do:

u32 bitfield;
pes_header.bitfield = bitfield;

this will produce warnings.

Thanks,
Mauro

2020-05-06 09:06:43

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 11/11] media: vidtv: Add a MPEG Transport Stream Multiplexer

Em Wed, 6 May 2020 04:05:25 -0300
"Daniel W. S. Almeida" <[email protected]> escreveu:

> Hi Mauro! Thank you for reviewing this!
>
>
> >> Add a MPEG Transport Stream multiplexer responsible for polling encoders,
> >> interleaving packets, padding the resulting stream with NULL packets if
> >> necessary and then delivering the resulting TS packets to the bridge
> >> driver so it can feed the demux.
> >>
> >> This patch includes a "channel" abstraction, which attempts to map a
> >> MPEG service into a struct that vidtv can work with.
> >>
> >> When vidtv boots, it will create some hardcoded channels:
> >>
> >> -Their services will be concatenated to populate the SDT.
> >> -Their programs will be concatenated to populate the PAT
> >> -For each program in the PAT, a PMT section will be created
> >> -The PMT section for a channel will be assigned its streams.
> >> -Every stream will have its corresponding encoder polled to produce
> >> TS packets
> >> -These packets may be interleaved by the mux and then delivered to
> >> the bridg
> >>
> >> Signed-off-by: Daniel W. S. Almeida <[email protected]>
> >
> > The same notes I made on previous patches apply here.
>
> I did not understand this. Do you mean to say that I should remove these
> dashes in the beginning of the lines?

No. I just meant to say that I won't be repeating the comments I made
about WARN_ON, bit order, and other generic comments on other patches
that will also apply here :-)

Thanks,
Mauro

2020-05-06 11:23:47

by Daniel Almeida

[permalink] [raw]
Subject: Re: [RFC, WIP, v4 08/11] media: vidtv: implement a PSI generator

Hi Mauro,



>> +static u32
>> +vidtv_psi_ts_psi_write_into(struct psi_write_args args)
>> +{
>> + /*
>> + * Packetize PSI sections into TS packets:
>> + * push a TS header (4bytes) every 184 bytes
>> + * manage the continuity_counter
>> + * add stuffing after the CRC
>> + */
>> +
>> + u32 nbytes_past_boundary = (args.dest_offset % TS_PACKET_LEN);
>> + bool aligned = nbytes_past_boundary == 0;
>> +
>> + /*
>> + * whether we need to fragment the data into multiple ts packets
>> + * if we are aligned we need to spare one byte for the pointer_field
>> + */
>> + bool split = (aligned) ?
>> + args.len > TS_PAYLOAD_LEN - 1 :
>> + nbytes_past_boundary + args.len > TS_PACKET_LEN;
>> +
>> + /* how much we can write in this packet */
>> + u32 payload_write_len = (split) ?
>> + (aligned) ? TS_PAYLOAD_LEN :
>> + TS_PACKET_LEN - nbytes_past_boundary :
>> + args.len;
>> +
>> + struct psi_write_args new_args = {0};
>> + struct vidtv_mpeg_ts ts_header = {0};
>> +
>> + u32 nbytes = 0; /* number of bytes written by this function */
>> + u32 temp = 0;
>> +
>> + /* Just a sanity check, should not really happen because we stuff
>> + * the packet when we finish a section, i.e. when we write the crc at
>> + * the end. But if this happens then we have messed up the logic
>> + * somewhere.
>> + */
>> + WARN_ON(args.new_psi_section && !aligned);
>
> Please use a ratelimited printk instead (here and on all similar cases
> at the TS generator).
>
> Also, I think that, on such case, the driver should be filling the
> remaining frame with pad bytes. E. g.:
>
> /*
> * Assuming that vidtv_memset() args from patch 06/11 were changed
> * according with this prototype:
> */
> size_t vidtv_memset(void *to, size_t to_offset, size_t to_size,
> u8 c, size_t len);
>
>
> #define TS_FILL_BYTE 0xff
>
> if (args.new_psi_section && !aligned) {
> pr_warn_ratelimit("Warning: PSI not aligned. Re-aligning it\n");
>
> vidtv_memset(args.dest_buf,
> args.dest_offset + nbytes_past_boundary,
> args.dest_buf_sz,
> TS_FILL_BYTE,
> TS_PACKET_LEN - nbytes_past_boundary);
> args.dest_offset += TS_PACKET_LEN - nbytes_past_boundary;
> aligned = 1;
> nbytes_past_boundary = 0;
> }
>

Sure, that's fine then! Just to be clear this should not happen at all,
because the writes should go through one of these four functions (IIRC!):

u32 vidtv_ts_null_write_into(struct null_packet_write_args args)
u32 vidtv_ts_pcr_write_into(struct pcr_write_args args)

...which will write a single packet at a time, thus leaving the buffer
aligned if it was already aligned to begin with,

and

u32 vidtv_pes_write_into(struct pes_write_args args)
static u32
vidtv_psi_ts_psi_write_into(struct psi_write_args args)

...which will pad when they finish writing a PES packet or a table
section, respectively.

I left these warnings behind as a way to warn me if the padding logic
itself is broken.



> Please use a ratelimited printk instead (here and on all similar cases
> at the TS generator).

OK.



>> +static void vidtv_psi_desc_to_be(struct vidtv_psi_desc *desc)
>> +{
>> + /*
>> + * Convert descriptor endianness to big-endian on a field-by-field
>> basis
>> + * where applicable
>> + */
>> +
>> + switch (desc->type) {
>> + /* nothing do do */
>> + case SERVICE_DESCRIPTOR:
>> + break;
>> + case REGISTRATION_DESCRIPTOR:
>> + cpu_to_be32s(&((struct vidtv_psi_desc_registration *)
>> + desc)->format_identifier);
>> + pr_alert("%s: descriptor type %d found\n",
>> + __func__,
>> + desc->type);
>> + pr_alert("%s: change 'additional_info' endianness before
>> calling\n",
>> + __func__);
>
> The above pr_alert() calls sound weird. Why are you unconditionally
> calling it (and still doing the BE conversion) for all
> REGISTRATION_DESCRIPTOR types?

To be honest, I did not know what to do. Here's what 13818-1 has to say
about registration descriptors:

>2.6.9
> Semantic definition of fields in registration descriptor
>format_identifier – The format_identifier is a 32-bit value obtained
>from a Registration Authority as designated by
>ISO/IEC JTC 1/SC 29.
>additional_identification_info – The meaning of
>additional_identification_info bytes, if any, are defined by the
>assignee of that format_identifier, and once defined they shall not change.

So I took the cue from libdvbv5 and defined the following struct for it,
with a flexible array member at the end:

struct vidtv_psi_desc_registration {
u8 type;
u8 length;
struct vidtv_psi_desc *next;

/*
* The format_identifier is a 32-bit value obtained from a Registration
* Authority as designated by ISO/IEC JTC 1/SC 29.
*/
u32 format_identifier;
/*
* The meaning of additional_identification_info bytes, if any, are
* defined by the assignee of that format_identifier, and once defined
* they shall not change.
*/
u8 additional_identification_info[];
} __packed


As you know, I was changing the endianness from host to BE before
serializing and then changing back from BE to host. Given the struct
definition above, there was no way to do this to the
'additional_identification_info' member, since we do not know its layout.

If we go with your approach instead (i.e. using __be16, __be32...etc)
then I think we can remove these two functions (and more)


thanks,
- Daniel