From patchwork Mon Apr 6 23:20:50 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 62889 Received: from vger.kernel.org ([209.132.180.67]) by www.linuxtv.org with esmtp (Exim 4.92) (envelope-from ) id 1jLb0f-009Kbx-V5; Mon, 06 Apr 2020 23:18:30 +0000 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726571AbgDFXVM (ORCPT + 1 other); Mon, 6 Apr 2020 19:21:12 -0400 Received: from mail-qt1-f193.google.com ([209.85.160.193]:34304 "EHLO mail-qt1-f193.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726254AbgDFXVL (ORCPT ); Mon, 6 Apr 2020 19:21:11 -0400 Received: by mail-qt1-f193.google.com with SMTP id 14so1357410qtp.1; Mon, 06 Apr 2020 16:21:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=qYLbut/xX+hjmPe9RzWA90KmfgDd7VcddBEKSA5kQ5c=; b=Sy5/SLnkA+S9bkiFDB2dfPAELaCJGzkQc6kHCaXQngzyYZ0prZ0RlndfkF6lPoyBRN bTYOGckd/UNuBPFrPOmCeXv4tPQTaWmwftmDtfDwHEdlIoAx7Gzig4V7uHqWER5VP8Hp GJAlYD0wgGNnORPAzRpTqGQutMi47FtxEjbl8L1aybXAfGdhNg0nZz4D4zAJ+Ky2h1Mr 4dKZGrA7r0v3jMatsjRtc+agfb5v7oifmHTY7fiwXoRdYbMxObraspqBfZWv2Z+9RxJU zlde1Y2RTMA8J5y2Dd5t0vO2WhgYMRX7ExauHvv9+7gRN3g+nuSiUuX39ichdi2ANR9R dF6Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=qYLbut/xX+hjmPe9RzWA90KmfgDd7VcddBEKSA5kQ5c=; b=mCGju8h1bLWJG/Nc9jkPJ9iXkoNpVuqLebCfnF5K/ffYyBmHlWK4BOSfpX2I+IJfjy KgqXzmBZUvNQDduA+PkRpU3rcYRL7ebskMy01qKgHO2kXkoX4L233TbWuJIoCv2N03ph nLsgkyY9YeCvsB7OvBBEUHDjuMu78sJKk4pNmhKVR/dzO6w7kcU2KCAWyZgh9EVJOqQN ykY0OC9LAzCxUke38kOB+lYcEojOgdYMDT0h8c2iq4Aekpk8nEGu7TPUJvNoxzOOhOXL gQ8mWEbEPq0KIg0jzmgnRCZZla3fPmOSktm+kdMSb2SlcTQiQ/DUGFvGuIFpOYuD0lH6 NIlw== X-Gm-Message-State: AGi0PuavTkHX9W2cXn+OplMSAxRTbsSJ3VSGUjqhtuAjzPNTVVxt8qfD HE2gP+XmP60jA9rAH/EVR00= X-Google-Smtp-Source: APiQypKFyGFHbTWdKNBlwKIiKmcWH5TlE7cQwKz3a2q7rHfDcj87BH2mA0YWa2SsZHsZsuKR9Bdh8A== X-Received: by 2002:ac8:1684:: with SMTP id r4mr1974418qtj.287.1586215271062; Mon, 06 Apr 2020 16:21:11 -0700 (PDT) Received: from localhost.localdomain ([2804:14d:72b1:8920:da15:c0bd:33c1:e2ad]) by smtp.gmail.com with ESMTPSA id u26sm1490978qku.54.2020.04.06.16.21.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 06 Apr 2020 16:21:10 -0700 (PDT) From: "Daniel W. S. Almeida" X-Google-Original-From: Daniel W. S. Almeida To: mchehab+huawei@kernel.org, sean@mess.org, kstewart@linuxfoundation.org, allison@lohutok.net, tglx@linutronix.de Cc: "Daniel W. S. Almeida" , linux-media@vger.kernel.org, skhan@linuxfoundation.org, linux-kernel-mentees@lists.linuxfoundation.org, linux-kernel@vger.kernel.org Subject: [RFC, WIP, v3 1/6] media: vidtv: add Kconfig entry Date: Mon, 6 Apr 2020 20:20:50 -0300 Message-Id: <20200406232055.1023946-2-dwlsalmeida@gmail.com> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200406232055.1023946-1-dwlsalmeida@gmail.com> References: <20200406232055.1023946-1-dwlsalmeida@gmail.com> MIME-Version: 1.0 Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: "Daniel W. S. Almeida" 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 --- drivers/media/test_drivers/Kconfig | 10 ++++++++++ drivers/media/test_drivers/Makefile | 1 + drivers/media/test_drivers/vidtv/Kconfig | 11 +++++++++++ drivers/media/test_drivers/vidtv/Makefile | 2 ++ 4 files changed, 24 insertions(+) create mode 100644 drivers/media/test_drivers/vidtv/Kconfig create mode 100644 drivers/media/test_drivers/vidtv/Makefile diff --git a/drivers/media/test_drivers/Kconfig b/drivers/media/test_drivers/Kconfig index 9f4a9cfbacc9c..997594ab3842a 100644 --- a/drivers/media/test_drivers/Kconfig +++ b/drivers/media/test_drivers/Kconfig @@ -6,6 +6,10 @@ menuconfig V4L_TEST_DRIVERS bool "V4L test drivers" depends on VIDEO_DEV +menuconfig DVB_TEST_DRIVERS + bool "DVB test drivers" + depends on DVB_CORE && MEDIA_SUPPORT && I2C + if V4L_TEST_DRIVERS source "drivers/media/test_drivers/vimc/Kconfig" @@ -25,4 +29,10 @@ source "drivers/media/test_drivers/vicodec/Kconfig" endif #V4L_TEST_DRIVERS +if DVB_TEST_DRIVERS + +source "drivers/media/test_drivers/vidtv/Kconfig" + +endif #DVB_TEST_DRIVERS + endif #MEDIA_TEST_SUPPORT diff --git a/drivers/media/test_drivers/Makefile b/drivers/media/test_drivers/Makefile index 74410d3a9f2d2..9f0e4ebb2efe7 100644 --- a/drivers/media/test_drivers/Makefile +++ b/drivers/media/test_drivers/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_VIDEO_VIMC) += vimc/ obj-$(CONFIG_VIDEO_VIVID) += vivid/ obj-$(CONFIG_VIDEO_VIM2M) += vim2m.o obj-$(CONFIG_VIDEO_VICODEC) += vicodec/ +obj-$(CONFIG_DVB_VIDTV) += vidtv/ diff --git a/drivers/media/test_drivers/vidtv/Kconfig b/drivers/media/test_drivers/vidtv/Kconfig new file mode 100644 index 0000000000000..22c4fd39461f1 --- /dev/null +++ b/drivers/media/test_drivers/vidtv/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_VIDTV + tristate "Virtual DVB Driver (vidtv)" + depends on DVB_CORE && MEDIA_SUPPORT && I2C + help + The virtual DVB test driver serves as a reference DVB driver and helps + validate the existing APIs in the media subsystem. It can also aid developers + working on userspace applications. + + + When in doubt, say N. diff --git a/drivers/media/test_drivers/vidtv/Makefile b/drivers/media/test_drivers/vidtv/Makefile new file mode 100644 index 0000000000000..d1558d84eeaed --- /dev/null +++ b/drivers/media/test_drivers/vidtv/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 + From patchwork Mon Apr 6 23:20:51 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 62891 Received: from vger.kernel.org ([209.132.180.67]) by www.linuxtv.org with esmtp (Exim 4.92) (envelope-from ) id 1jLb0k-009KcO-Ks; Mon, 06 Apr 2020 23:18:35 +0000 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726589AbgDFXVR (ORCPT + 1 other); Mon, 6 Apr 2020 19:21:17 -0400 Received: from mail-qt1-f195.google.com ([209.85.160.195]:46815 "EHLO mail-qt1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726254AbgDFXVQ (ORCPT ); Mon, 6 Apr 2020 19:21:16 -0400 Received: by mail-qt1-f195.google.com with SMTP id g7so1281203qtj.13; Mon, 06 Apr 2020 16:21:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=+VMaTuc10hSoiRD6+x+8mC2OdvaoKo4RiTSdbwDjF38=; b=c0R9AICq79klmjoqrbWkP9ORFlRPlegMbkzZWWAGIBWs9CxaGLE+oLfRdxnYy0sqkE wPV9iuV3SR0C936/W/r0E+9BWg1s1Wnwjmb5gQugEOe45SMR3uaXV3MiDKsbe0iGgbr7 PsN2bpOook6o7XAvJ2Ge5ey6g6il2kQ3gvtHSdAiOGxy6dyYiBQYYcjMTLYTFreM5UZz 8AqF9e6UkxeThamW9Hv4qmQ2kOmhvJqFEjDwrvyFnG8eFPJFYY1NOPLIDN6ZJ6dS0zN9 +iVuXR2kAXq8+KtUj7sCYThtaVY35Hmyxi27EgrM3uWViYvYeln60jgjaVHuy+UBCK7u QHGw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=+VMaTuc10hSoiRD6+x+8mC2OdvaoKo4RiTSdbwDjF38=; b=tXOBA9CzUmvGqbUnIbrtn1i8+hWv1wD0VrdgFZbWBiI9M8cvzzt29DSC3Jt+YAMkGd H1jDqWjBu1KQxdjdmC8VQ60XcSkifDYueMMaGSBsS5nxwbYw5uOL5DyKpXxD7foxtkp5 9sCaDCp4ANQW6eqWG/BA/UDMC8AIue333PqK/lnXDWK79Vrh6CzZJw5VIhpjn7IVl2Pf Au7i3eIpLUW+e0epTNLKeH3bNmn/VxodhKd9PsxgXihkqJF2T9KGg/siZ5v1DY2nM8sa bVQzvXYbmRKdvLmpEwOSmkXTQ1B9AZE6PZ9UePfRO9FM4rQOA9YzNGhHu1nwM8wxBSeG hklg== X-Gm-Message-State: AGi0Pua+WP/vVwaHpYOmbTWaT4HXcQylJrqqiJtI9HWqpRjJJ0QhwTOi MlLMHzPInBKGsLXY6jg8kbEBwA0nxMU= X-Google-Smtp-Source: APiQypICxixTiS8Ll53VAvX/xmQ75np9biT22Mi/YwhH98C6nA5VeBZbIRFtW6qQl1uwAgI+BzTa/g== X-Received: by 2002:ac8:6d3b:: with SMTP id r27mr2074068qtu.55.1586215274801; Mon, 06 Apr 2020 16:21:14 -0700 (PDT) Received: from localhost.localdomain ([2804:14d:72b1:8920:da15:c0bd:33c1:e2ad]) by smtp.gmail.com with ESMTPSA id u26sm1490978qku.54.2020.04.06.16.21.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 06 Apr 2020 16:21:14 -0700 (PDT) From: "Daniel W. S. Almeida" X-Google-Original-From: Daniel W. S. Almeida To: mchehab+huawei@kernel.org, sean@mess.org, kstewart@linuxfoundation.org, allison@lohutok.net, tglx@linutronix.de Cc: "Daniel W. S. Almeida" , linux-media@vger.kernel.org, skhan@linuxfoundation.org, linux-kernel-mentees@lists.linuxfoundation.org, linux-kernel@vger.kernel.org Subject: [RFC, WIP, v3 2/6] media: vidtv: implement a tuner driver Date: Mon, 6 Apr 2020 20:20:51 -0300 Message-Id: <20200406232055.1023946-3-dwlsalmeida@gmail.com> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200406232055.1023946-1-dwlsalmeida@gmail.com> References: <20200406232055.1023946-1-dwlsalmeida@gmail.com> MIME-Version: 1.0 Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: "Daniel W. S. Almeida" 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 --- drivers/media/test_drivers/vidtv/Makefile | 1 + .../media/test_drivers/vidtv/vidtv_tuner.c | 411 ++++++++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 drivers/media/test_drivers/vidtv/vidtv_tuner.c diff --git a/drivers/media/test_drivers/vidtv/Makefile b/drivers/media/test_drivers/vidtv/Makefile index d1558d84eeaed..e625810a82603 100644 --- a/drivers/media/test_drivers/vidtv/Makefile +++ b/drivers/media/test_drivers/vidtv/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o diff --git a/drivers/media/test_drivers/vidtv/vidtv_tuner.c b/drivers/media/test_drivers/vidtv/vidtv_tuner.c new file mode 100644 index 0000000000000..c948daa66ec73 --- /dev/null +++ b/drivers/media/test_drivers/vidtv/vidtv_tuner.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Virtual DVB test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * The vidtv tuner should support common TV standards such as + * DVB-T/T2/S/S2, ISDB-T and ATSC when completed. + * + * Written by Daniel W. S. Almeida + */ + +#include +#include +#include +#include +#include +#include + +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); From patchwork Mon Apr 6 23:20:52 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 62895 Received: from vger.kernel.org ([209.132.180.67]) by www.linuxtv.org with esmtp (Exim 4.92) (envelope-from ) id 1jLb14-009KdH-Jd; Mon, 06 Apr 2020 23:18:55 +0000 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726619AbgDFXVW (ORCPT + 1 other); Mon, 6 Apr 2020 19:21:22 -0400 Received: from mail-qv1-f66.google.com ([209.85.219.66]:45090 "EHLO mail-qv1-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726609AbgDFXVU (ORCPT ); Mon, 6 Apr 2020 19:21:20 -0400 Received: by mail-qv1-f66.google.com with SMTP id g4so938320qvo.12; Mon, 06 Apr 2020 16:21:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=2MaBBtlcwrQR73kUqcQ71IliJ78QCREkk/vMNb4lqjI=; b=lu6tQh87CK4IGQ5ZuOri4TIgzTQK0x92XhWrlfuFPBWDEZLNSVaN0WJ2mOCRPFj/Vu 6eN6cT3vHT4n5+uiW1Veo90DGRJvgYpdbLK/w+O+u3hO8hFltHMrRi76APSGkCBbiLuB o7p+CQ7ChSD7QUdaptisswic0P3xct/rHm/UJg9JLQ7AzqDwQimlBhiyjtxzgni+9JpL eLA74HXflPlx+VwEts0yMtSWer79myp51jcMQlbuKCA6mYaINSkR+UsLTiefV2nzNyVz vws+p5luY3UzqpJXaJKQkYK4xr7GcXWAzD2X110+5SEIUdOXdqood13c3DEfFtEaTiqg maCQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=2MaBBtlcwrQR73kUqcQ71IliJ78QCREkk/vMNb4lqjI=; b=r+nK+sk33l2yZSrrznKxL+XStxkByH+x/vWMz1e+4wMnVYr7UvqDwMLhCptxIG5iJe EK2xLP2wfCSe1pVw/Jc1Vo7Qg6KecP7UAzCOXyA4lSjCnzNQt5FVHrz+L5caYw7N30JU fpD3Ujzlf703XaAE82BPwQBHUlFswzXNBOSrbjh1eg5HnkdRmx0R5lBLkW4kLYP8Bgoi RzWCJy4JIVnr1KzCdMLvMmt3r11noyGxCDiOpJVIjZeS9fwpN8Ef2aNbSzYFqeXXLP88 GBEcYlygur1LngW0tsJ1LP4RV2VXRKQPZdlrHOE3c5p8Vu5Py532H2JDhL6YD64C7kkg bAOw== X-Gm-Message-State: AGi0Puai+RofP7jNLMf368nFH5sy5vnDCNS2VAYvvUkZlKwkwI4xRDV1 W5BxDLxkVOfEhlhKx+ShC2LAJXj52CM= X-Google-Smtp-Source: APiQypJb6/FG3OJzFK7dzxPRZX6MSkzonV2j5W3pFKWjsygp4EcFn+azdvtdtlf//ABVftcQ6kRmJg== X-Received: by 2002:a0c:a602:: with SMTP id s2mr2303910qva.222.1586215278507; Mon, 06 Apr 2020 16:21:18 -0700 (PDT) Received: from localhost.localdomain ([2804:14d:72b1:8920:da15:c0bd:33c1:e2ad]) by smtp.gmail.com with ESMTPSA id u26sm1490978qku.54.2020.04.06.16.21.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 06 Apr 2020 16:21:17 -0700 (PDT) From: "Daniel W. S. Almeida" X-Google-Original-From: Daniel W. S. Almeida To: mchehab+huawei@kernel.org, sean@mess.org, kstewart@linuxfoundation.org, allison@lohutok.net, tglx@linutronix.de Cc: "Daniel W. S. Almeida" , linux-media@vger.kernel.org, skhan@linuxfoundation.org, linux-kernel-mentees@lists.linuxfoundation.org, linux-kernel@vger.kernel.org Subject: [RFC, WIP, v3 3/6] media: vidtv: implement a demodulator driver Date: Mon, 6 Apr 2020 20:20:52 -0300 Message-Id: <20200406232055.1023946-4-dwlsalmeida@gmail.com> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200406232055.1023946-1-dwlsalmeida@gmail.com> References: <20200406232055.1023946-1-dwlsalmeida@gmail.com> MIME-Version: 1.0 Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: "Daniel W. S. Almeida" 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 --- drivers/media/test_drivers/vidtv/Makefile | 2 +- .../media/test_drivers/vidtv/vidtv_demod.c | 493 ++++++++++++++++++ .../media/test_drivers/vidtv/vidtv_demod.h | 43 ++ 3 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 drivers/media/test_drivers/vidtv/vidtv_demod.c create mode 100644 drivers/media/test_drivers/vidtv/vidtv_demod.h diff --git a/drivers/media/test_drivers/vidtv/Makefile b/drivers/media/test_drivers/vidtv/Makefile index e625810a82603..36ba00ddc0d1e 100644 --- a/drivers/media/test_drivers/vidtv/Makefile +++ b/drivers/media/test_drivers/vidtv/Makefile @@ -1,3 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o +obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o diff --git a/drivers/media/test_drivers/vidtv/vidtv_demod.c b/drivers/media/test_drivers/vidtv/vidtv_demod.c new file mode 100644 index 0000000000000..803fe1a89b7ec --- /dev/null +++ b/drivers/media/test_drivers/vidtv/vidtv_demod.c @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * The Virtual DVB test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Written by Daniel W. S. Almeida + * Based on the example driver written by Emard + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + * Based on the example driver written by Emard + */ + +#ifndef VIDTV_DEMOD_H +#define VIDTV_DEMOD_H + +#include +#include + +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 From patchwork Mon Apr 6 23:20:53 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 62894 Received: from vger.kernel.org ([209.132.180.67]) by www.linuxtv.org with esmtp (Exim 4.92) (envelope-from ) id 1jLb12-009KdH-8r; Mon, 06 Apr 2020 23:18:52 +0000 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726651AbgDFXV0 (ORCPT + 1 other); Mon, 6 Apr 2020 19:21:26 -0400 Received: from mail-qk1-f194.google.com ([209.85.222.194]:46377 "EHLO mail-qk1-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726254AbgDFXVZ (ORCPT ); Mon, 6 Apr 2020 19:21:25 -0400 Received: by mail-qk1-f194.google.com with SMTP id u4so18140535qkj.13; Mon, 06 Apr 2020 16:21:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=Zzy0jUAN6C0zTtF/FNa2E2Oxqy5UozoTv/zAp7PZcIQ=; b=XGbiOEfJL6pGapcLc5L9/FeYZBi0NixGDzc424P2toevx3seiigVTVktJs4QWubHZP kJg1QDWLNE4wlBoWo6xYvEJfg/oQFv9w8g56HJeitLtkQr65ncXCI1M9XDljfH9q0FS0 JgaWMg4Hltse/gjOyY5437JBLbOK62F+bFsl2aT6/Y/+VVSljXkumnVvIF1h0JScodxI 71vrVqHd7r5Yrzc70cD7EArkHysQNxtDgbih7dCfFl5RF+Ly+obHCJWr/pOStnNS93DU lqsGFjnKDaoxEvsIOg74dgSUz6YC/0ISApbiBPbNx7vD2DhJkRCELGYZcRs1a6lccOhR urPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=Zzy0jUAN6C0zTtF/FNa2E2Oxqy5UozoTv/zAp7PZcIQ=; b=KDvOAkd6yGCyA0gtle4xxi9DrE2zezYfmAa0dvLdwjRVhna40fvizTsRJFxGYuAvUQ 3Gr1H49P52LNwuF81SdSPgbRd2VMUN9vo6Yqhciir3jk0K7SGNL9tCDXC6lVQZb7mg7G 5vlD16u4WBOIg8c4Sw9uHDPIVtGWg7+yzxsC1rLtSrVjvzvM3gra22gud67U1C5AXcto w2XteopHQJSDF/5KXwiK8b6MHdznYvMwr/nJv1bjcF5FgGnbOQIK2a68p7Fze0ie/ajx u/LdUnUYnmFnIwtk882PNyzANZR8xBE4IJmkBCvoe9CZCr9uT7qPjrG4UqCw2h3Q/A73 4prQ== X-Gm-Message-State: AGi0PuYq4Wa8mAsHZsJemUovkfR6m3sS9+PHHlf1r9llGgX85+QGaJyE wyy3UoHIyF+SZXk6b9fIZ8U= X-Google-Smtp-Source: APiQypKtVFd9J70zZECiq8yIsZQ3b70yB+cb3CTbb4jkA2+o1kPkdd+irnhf2dtJCT+awq5cBy7ijg== X-Received: by 2002:a37:a84f:: with SMTP id r76mr8764345qke.370.1586215282864; Mon, 06 Apr 2020 16:21:22 -0700 (PDT) Received: from localhost.localdomain ([2804:14d:72b1:8920:da15:c0bd:33c1:e2ad]) by smtp.gmail.com with ESMTPSA id u26sm1490978qku.54.2020.04.06.16.21.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 06 Apr 2020 16:21:21 -0700 (PDT) From: "Daniel W. S. Almeida" X-Google-Original-From: Daniel W. S. Almeida To: mchehab+huawei@kernel.org, sean@mess.org, kstewart@linuxfoundation.org, allison@lohutok.net, tglx@linutronix.de Cc: "Daniel W. S. Almeida" , linux-media@vger.kernel.org, skhan@linuxfoundation.org, linux-kernel-mentees@lists.linuxfoundation.org, linux-kernel@vger.kernel.org Subject: [RFC, WIP, v3 4/6] media: vidtv: implement a PSI generator Date: Mon, 6 Apr 2020 20:20:53 -0300 Message-Id: <20200406232055.1023946-5-dwlsalmeida@gmail.com> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200406232055.1023946-1-dwlsalmeida@gmail.com> References: <20200406232055.1023946-1-dwlsalmeida@gmail.com> MIME-Version: 1.0 Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: "Daniel W. S. Almeida" 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 --- drivers/media/test_drivers/vidtv/Makefile | 4 +- .../media/test_drivers/vidtv/vidtv_common.c | 44 + .../media/test_drivers/vidtv/vidtv_common.h | 71 ++ drivers/media/test_drivers/vidtv/vidtv_psi.c | 960 ++++++++++++++++++ drivers/media/test_drivers/vidtv/vidtv_psi.h | 294 ++++++ 5 files changed, 1372 insertions(+), 1 deletion(-) create mode 100644 drivers/media/test_drivers/vidtv/vidtv_common.c create mode 100644 drivers/media/test_drivers/vidtv/vidtv_common.h create mode 100644 drivers/media/test_drivers/vidtv/vidtv_psi.c create mode 100644 drivers/media/test_drivers/vidtv/vidtv_psi.h diff --git a/drivers/media/test_drivers/vidtv/Makefile b/drivers/media/test_drivers/vidtv/Makefile index 36ba00ddc0d1e..690420a7c904b 100644 --- a/drivers/media/test_drivers/vidtv/Makefile +++ b/drivers/media/test_drivers/vidtv/Makefile @@ -1,3 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o +vidtv_demod-objs := vidtv_common.o vidtv_psi.o + +obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o diff --git a/drivers/media/test_drivers/vidtv/vidtv_common.c b/drivers/media/test_drivers/vidtv/vidtv_common.c new file mode 100644 index 0000000000000..62713284e14d9 --- /dev/null +++ b/drivers/media/test_drivers/vidtv/vidtv_common.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Virtual DTV test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Written by Daniel W. S. Almeida + */ + +#include +#include +#include + +u32 vidtv_memcpy(void *to, + const void *from, + size_t len, + u32 offset, + u32 buf_sz) +{ + if (buf_sz && offset + len > buf_sz) { + pr_err("%s: overflow detected, skipping. Try increasing the buffer size", + __func__); + return 0; + } + + memcpy(to, from, len); + return len; +} + +u32 vidtv_memset(void *to, + int c, + size_t len, + u32 offset, + u32 buf_sz) +{ + if (buf_sz && offset + len > buf_sz) { + pr_err("%s: overflow detected, skipping. Try increasing the buffer size", + __func__); + return 0; + } + + memset(to, c, len); + return len; +} diff --git a/drivers/media/test_drivers/vidtv/vidtv_common.h b/drivers/media/test_drivers/vidtv/vidtv_common.h new file mode 100644 index 0000000000000..43269833ee866 --- /dev/null +++ b/drivers/media/test_drivers/vidtv/vidtv_common.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * The Virtual DTV test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Written by Daniel W. S. Almeida + */ + +#ifndef VIDTV_COMMON_H +#define VIDTV_COMMON_H + +#include +#include + +#define CRC_SIZE_IN_BYTES 32 +#define TS_SYNC_BYTE 0x47 +#define TS_PACKET_LEN 188 +#define TS_PAYLOAD_LEN 184 +#define LAST_VALID_TS_PID 8191 + +/* to be used by both PSI and ES */ +struct vidtv_mpeg_ts_adaption { + u8 length; + struct { + u8 extension:1; + u8 private_data:1; + u8 splicing_point:1; + u8 OPCR:1; + u8 PCR:1; + u8 priority:1; + u8 random_access:1; + u8 discontinued:1; + } __packed; + u8 data[]; +} __packed; + +/* to be used by both PSI and ES */ +struct vidtv_mpeg_ts { + u8 sync_byte; + union { + u16 bitfield; + struct { + u16 pid:13; + u16 priority:1; + u16 payload_start:1; + u16 tei:1; + } __packed; + } __packed; + struct { + u8 continuity_counter:4; + u8 payload:1; + u8 adaptation_field:1; + u8 scrambling:2; + } __packed; + struct vidtv_mpeg_ts_adaption adaption[]; +} __packed; + +u32 vidtv_memcpy(void *to, + const void *from, + size_t len, + u32 offset, + u32 buf_sz); + +u32 vidtv_memset(void *to, + int c, + size_t len, + u32 offset, + u32 buf_sz); + +#endif // VIDTV_COMMON_H diff --git a/drivers/media/test_drivers/vidtv/vidtv_psi.c b/drivers/media/test_drivers/vidtv/vidtv_psi.c new file mode 100644 index 0000000000000..70fc6289407a0 --- /dev/null +++ b/drivers/media/test_drivers/vidtv/vidtv_psi.c @@ -0,0 +1,960 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include + +#include "vidtv_common.h" +#include "vidtv_psi.h" + +static u32 vidtv_psi_ts_psi_write_stuffing(void *to, + u32 len, + u32 offset, + u32 buf_sz) +{ + return vidtv_memset(to, 0xff, len, offset, buf_sz); +} + +static u32 +vidtv_psi_ts_psi_write_into(struct psi_write_args args) +{ + /* + * Packetize PSI sections into TS packets: + * push a TS header (4bytes) every 184 bytes + * manage the continuity_counter + * add stuffing after the CRC + */ + + u32 nbytes_past_boundary = (args.offset % TS_PACKET_LEN); + bool aligned = nbytes_past_boundary == 0; + bool split = args.len > TS_PAYLOAD_LEN; + u32 payload_write_len = (split) ? TS_PAYLOAD_LEN : args.len; + + struct psi_write_args new_args = {0}; + struct vidtv_mpeg_ts ts_header = {0}; + + u32 nbytes = 0; /* number of bytes written by this function */ + u32 temp; + + if (args.new_psi_section && !aligned) { + /* + * must pad the buffer with the complement to get a + * multiple of 188 + */ + nbytes += vidtv_psi_ts_psi_write_stuffing(args.to + + args.offset + + nbytes, + TS_PACKET_LEN - + nbytes_past_boundary, + args.offset + nbytes, + args.buf_sz); + + /* + * if we were not at a packet boundary, we are now after + * stuffing the buffer with 0xff + */ + aligned = true; + } + + if (aligned) { + /* if at a packet boundary, write a new TS header */ + ts_header.sync_byte = TS_SYNC_BYTE; + ts_header.tei = 0; + ts_header.payload_start = 1; + ts_header.pid = args.pid; + ts_header.priority = 0; + ts_header.scrambling = 0; /* not scrambled */ + ts_header.continuity_counter = *args.continuity_counter; + ts_header.payload_start = 0; /* no adaption for now */ + + /* copy the header minus the adaption pointer*/ + nbytes += vidtv_memcpy(args.to + args.offset + nbytes, + &ts_header, + sizeof(ts_header), + args.offset + nbytes, + args.buf_sz); + } + + if (args.new_psi_section) { + /* write the pointer_field in the first byte of the payload */ + temp = vidtv_memset(args.to + args.offset + nbytes, + 0x0, + 1, + args.offset + nbytes, + args.buf_sz); + /* one byte was used by the pointer field*/ + nbytes += temp; + payload_write_len -= temp; + } + + /* write as much of the payload as we possibly can */ + nbytes += vidtv_memcpy(args.to + args.offset + nbytes, + args.from, + payload_write_len, + args.offset + nbytes, + args.buf_sz); + + if (split) { + /* + * next TS packet keeps the same PID, but increments the + * counter + */ + ++(*args.continuity_counter); + /* 'nbytes' written from a total of 'len' requested*/ + args.len -= nbytes; + /* + * recursively write the rest of the data until we do not + * need to split it anymore + */ + memcpy(&new_args, &args, sizeof(struct psi_write_args)); + new_args.from = args.from + nbytes; + new_args.offset = args.offset + nbytes; + new_args.new_psi_section = false; + + nbytes += vidtv_psi_ts_psi_write_into(new_args); + } + + /* + * as the CRC is last in the section, stuff the rest of the + * packet if there is any remaining space in there + */ + if (args.is_crc) + nbytes += vidtv_psi_ts_psi_write_stuffing(args.to + nbytes, + TS_PAYLOAD_LEN - + nbytes, + args.offset + nbytes, + args.buf_sz); + + return nbytes; +} + +static u32 table_section_crc32_write_into(struct crc32_write_args args) +{ + /* the CRC is the last entry in the section */ + u32 nbytes = 0; + u32 crc; + struct psi_write_args psi_args = {0}; + + crc = crc32(0, args.to, args.offset); + + psi_args.to = args.to; + psi_args.from = &crc; + psi_args.len = CRC_SIZE_IN_BYTES; + psi_args.offset = args.offset; + psi_args.pid = args.pid; + psi_args.new_psi_section = false; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = true; + psi_args.buf_sz = args.buf_sz; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + return nbytes; +} + +struct vidtv_psi_desc *vidtv_psi_desc_init(struct vidtv_psi_desc *head, + u8 type, + u8 length) +{ + struct vidtv_psi_desc *desc; + + /* alloc enough memory for the flexible array too */ + desc = kzalloc(sizeof(*desc) + length, GFP_KERNEL); + + desc->type = type; + desc->length = length; + + if (head) { + while (head->next) + head = head->next; + + head->next = desc; + } + + return desc; +} + +void vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc) +{ + struct vidtv_psi_desc *curr = desc; + struct vidtv_psi_desc *tmp = NULL; + + while (curr) { + tmp = curr; + curr = curr->next; + kfree(tmp); + } +} + +static u32 +vidtv_psi_desc_comp_len(struct vidtv_psi_desc *desc) +{ + u32 length = 0; + + if (!desc) + return 0; + + while (desc) { + length += desc->length; + desc = desc->next; + } + + return length; +} + +void vidtv_psi_desc_assign(struct vidtv_psi_desc **to, + struct vidtv_psi_desc *desc, + u16 *desc_length) +{ + /* + * This function transfers ownedship of desc. + * Start by cleaning the old data + */ + if (*to) + vidtv_psi_desc_destroy(*to); + + *desc_length = vidtv_psi_desc_comp_len(desc); + *to = desc; /* reassign pointer */ +} + +static u32 vidtv_psi_desc_write_into(struct desc_write_args args) +{ + u32 nbytes = 0; /* the number of bytes written by this function */ + struct psi_write_args psi_args = {0}; + + psi_args.to = args.to; + psi_args.from = args.desc; + psi_args.len = sizeof_field(struct vidtv_psi_desc, type) + + sizeof_field(struct vidtv_psi_desc, length); + psi_args.offset = args.offset; + psi_args.pid = args.pid; + psi_args.new_psi_section = false; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = false; + psi_args.buf_sz = args.buf_sz; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + /* move 'from' pointer to point to u8 data[] */ + psi_args.from = args.desc + nbytes + sizeof(struct vidtv_psi_desc *); + psi_args.len = args.desc->length; + psi_args.offset = args.offset + nbytes; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + return nbytes; +} + +static u32 +vidtv_psi_table_header_write_into(struct header_write_args args) +{ + /* the number of bytes written by this function */ + u32 nbytes = 0; + struct psi_write_args psi_args = {0}; + + psi_args.to = args.to; + psi_args.from = args.h; + psi_args.len = sizeof(struct vidtv_psi_table_header); + psi_args.offset = args.offset; + psi_args.pid = args.pid; + psi_args.new_psi_section = true; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = false; + psi_args.buf_sz = args.buf_sz; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + return nbytes; +} + +static u16 +vidtv_psi_pat_table_comp_sec_len(struct vidtv_psi_table_pat *pat) +{ + /* see ISO/IEC 13818-1 : 2000 p.43 */ + u16 length = 0; + u32 i; + + /* from immediately after 'section_length' until 'last_section_number'*/ + length += PAT_LEN_UNTIL_LAST_SECTION_NUMBER; + + /* do not count the pointer */ + for (i = 0; i < pat->programs; ++i) + length += sizeof(struct vidtv_psi_table_pat_program) - + sizeof(struct vidtv_psi_table_pat_program *); + + length += CRC_SIZE_IN_BYTES; + + WARN_ON(length > PAT_MAX_SECTION_LEN); + return length; +} + +static u16 +vidtv_psi_pmt_table_comp_sec_len(struct vidtv_psi_table_pmt *pmt) +{ + /* see ISO/IEC 13818-1 : 2000 p.46 */ + u16 length = 0; + struct vidtv_psi_table_pmt_stream *s = pmt->stream; + + /* from immediately after 'section_length' until 'program_info_length'*/ + length += PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH; + + /* do not fail if 'desc_length' has not been computed yet */ + length += vidtv_psi_desc_comp_len(pmt->descriptor); + length += pmt->desc_length; + + while (s) { + /* skip both pointers at the end */ + length += sizeof(struct vidtv_psi_table_pmt_stream) - + sizeof(struct vidtv_psi_desc *) - + sizeof(struct vidtv_psi_table_pmt_stream *); + + length += vidtv_psi_desc_comp_len(s->descriptor); + s = s->next; + } + + length += CRC_SIZE_IN_BYTES; + + WARN_ON(length > PMT_MAX_SECTION_LEN); + return length; +} + +static u16 +vidtv_psi_sdt_table_comp_sec_len +(struct vidtv_psi_table_sdt *sdt) +{ + /* see ETSI EN 300 468 V 1.10.1 p.24 */ + u16 length = 0; + struct vidtv_psi_table_sdt_service *s = sdt->service; + + /* + * from immediately after 'section_length' until + * 'reserved_for_future_use' + */ + length += SDT_LEN_UNTIL_RESERVED_FOR_FUTURE_USE; + + while (s) { + /* skip both pointers at the end */ + length += sizeof(struct vidtv_psi_table_pmt_stream) - + sizeof(struct vidtv_psi_desc *) - + sizeof(struct vidtv_psi_table_pmt_stream *); + /* do not fail if 'desc_length' has not been computed yet */ + length += vidtv_psi_desc_comp_len(s->descriptor); + + s = s->next; + } + + length += CRC_SIZE_IN_BYTES; + + WARN_ON(length > SDT_MAX_SECTION_LEN); + return length; +} + +struct vidtv_psi_table_pat_program* +vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head, + u16 service_id, + u16 pid) +{ + struct vidtv_psi_table_pat_program *program; + + program = kzalloc(sizeof(*program), GFP_KERNEL); + + program->service_id = service_id; + program->pid = pid; /* pid for the PMT section in the TS */ + program->next = NULL; + program->reserved = 0x7; + + if (head) { + while (head->next) + head = head->next; + + head->next = program; + } + + return program; +} + +void +vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p) +{ + struct vidtv_psi_table_pat_program *curr = p; + struct vidtv_psi_table_pat_program *tmp = NULL; + + while (curr) { + tmp = curr; + curr = curr->next; + kfree(tmp); + } +} + +void +vidtv_psi_pat_program_assign(struct vidtv_psi_table_pat *pat, + struct vidtv_psi_table_pat_program *p) +{ + /* This function transfers ownership of p to the table */ + + u16 program_count = 0; + struct vidtv_psi_table_pat_program *program = p; + struct vidtv_psi_table_pat_program *temp = pat->program; + + while (program) { + ++program_count; + program = program->next; + } + + pat->programs = program_count; + pat->program = p; + + /* Recompute section length */ + pat->header.section_length = vidtv_psi_pat_table_comp_sec_len(pat); + + /* do not break userspace: reassign if the new size is too big */ + if (pat->header.section_length > PAT_MAX_SECTION_LEN) + vidtv_psi_pat_program_assign(pat, temp); + else + vidtv_psi_pat_program_destroy(temp); +} + +void vidtv_psi_pat_table_init(struct vidtv_psi_table_pat *pat, + bool update_version_num, + u16 transport_stream_id) +{ + static u8 pat_version; + + pat->header.table_id = 0x0; + pat->header.syntax = 0x1; + pat->header.zero = 0x0; + pat->header.one = 0x03; + + pat->header.id = transport_stream_id; /* transport stream ID, at will */ + pat->header.current_next = 0x1; + + /* ETSI 300 468: indicates changes in the TS described by this table*/ + if (update_version_num) + ++pat_version; + + pat->header.version = pat_version; + + pat->header.one2 = 0x03; + pat->header.section_id = 0x0; + pat->header.last_section = 0x0; + + pat->programs = 0; + + pat->header.section_length = vidtv_psi_pat_table_comp_sec_len(pat); +} + +u32 vidtv_psi_pat_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_pat *pat, + u32 buf_sz) +{ + u32 nbytes = 0; /* the number of bytes written by this function */ + u8 continuity_counter = 0; + const u16 pat_pid = pat->header.table_id; /* always 0x0 */ + + struct vidtv_psi_table_pat_program *p = pat->program; + struct header_write_args h_args = {0}; + struct psi_write_args args = {0}; + struct crc32_write_args c_args = {0}; + + h_args.to = buf; + h_args.offset = offset; + h_args.h = &pat->header; + h_args.pid = pat_pid; + h_args.continuity_counter = &continuity_counter; + h_args.buf_sz = buf_sz; + + nbytes += vidtv_psi_table_header_write_into(h_args); + + args.to = buf; + args.from = pat + sizeof(struct vidtv_psi_table_header), + args.len = sizeof(pat->programs); + args.offset = offset + nbytes; + args.pid = pat_pid; + args.new_psi_section = false; + args.continuity_counter = &continuity_counter; + args.is_crc = false; + args.buf_sz = buf_sz; + + nbytes += vidtv_psi_ts_psi_write_into(args); + + while (p) { + args.from = p; + /* skip the pointer */ + args. len = sizeof(*p) - + sizeof(struct vidtv_psi_table_pat_program *); + args.offset = offset + nbytes; + + nbytes += vidtv_psi_ts_psi_write_into(args); + p = p->next; + } + + c_args.to = buf; + c_args.offset = offset + nbytes; + c_args.pid = pat_pid; + c_args.continuity_counter = &continuity_counter; + c_args.buf_sz = buf_sz; + + nbytes += table_section_crc32_write_into(c_args); + + return nbytes; +} + +void +vidtv_psi_pat_table_destroy(struct vidtv_psi_table_pat *p) +{ + vidtv_psi_pat_program_destroy(p->program); +} + +struct vidtv_psi_table_pmt_stream* +vidtv_psi_pmt_stream_init(struct vidtv_psi_table_pmt_stream *head, + enum vidtv_psi_stream_types stream_type, + u16 es_pid) +{ + struct vidtv_psi_table_pmt_stream *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + + stream->type = stream_type; + stream->elementary_pid = es_pid; + stream->reserved = 0x07; + + stream->desc_length = vidtv_psi_desc_comp_len(stream->descriptor); + + stream->zero = 0x0; + stream->reserved2 = 0x0f; + + if (head) { + while (head->next) + head = head->next; + + head->next = stream; + } + + return stream; +} + +void vidtv_psi_pmt_stream_destroy(struct vidtv_psi_table_pmt_stream *s) +{ + struct vidtv_psi_table_pmt_stream *curr_stream = s; + struct vidtv_psi_table_pmt_stream *tmp_stream = NULL; + + while (curr_stream) { + tmp_stream = curr_stream; + curr_stream = curr_stream->next; + kfree(tmp_stream); + } +} + +void vidtv_psi_pmt_stream_assign(struct vidtv_psi_table_pmt *pmt, + struct vidtv_psi_table_pmt_stream *s) +{ + /* This function transfers ownership of s to the table */ + struct vidtv_psi_table_pmt_stream *stream = s; + struct vidtv_psi_desc *desc = s->descriptor; + struct vidtv_psi_table_pmt_stream *temp = pmt->stream; + + while (stream) + stream = stream->next; + + while (desc) + desc = desc->next; + + pmt->stream = s; + /* Recompute section length */ + pmt->header.section_length = vidtv_psi_pmt_table_comp_sec_len(pmt); + + /* do not break userspace: reassign if the new size is too big */ + if (pmt->header.section_length > PMT_MAX_SECTION_LEN) + vidtv_psi_pmt_stream_assign(pmt, temp); + else + vidtv_psi_pmt_stream_destroy(temp); +} + +u16 vidtv_psi_pmt_get_pid(struct vidtv_psi_table_pmt *section, + struct vidtv_psi_table_pat *pat) +{ + struct vidtv_psi_table_pat_program *program = pat->program; + + /* + * service_id is the same as program_number in the + * corresponding program_map_section + * see ETSI EN 300 468 v1.15.1 p. 24 + */ + while (program) + if (program->service_id == section->header.id) + return pat->program->pid; + + return LAST_VALID_TS_PID + 1; /* not found */ +} + +void vidtv_psi_pmt_table_init(struct vidtv_psi_table_pmt *pmt, + bool update_version_num, + u16 program_number, + u16 pcr_pid) +{ + static u8 pmt_version; + + pmt->header.table_id = 0x2; + pmt->header.syntax = 0x1; + pmt->header.zero = 0x0; + pmt->header.one = 0x3; + + pmt->header.id = program_number; + pmt->header.current_next = 0x1; + + /* ETSI 300 468: indicates changes in the TS described by this table*/ + if (update_version_num) + ++pmt_version; + + pmt->header.version = pmt_version; + + pmt->header.one2 = 0x3; + pmt->header.section_id = 0; + pmt->header.last_section = 0; + + pmt->pcr_pid = (pcr_pid) ? pcr_pid : 0x1fff; + pmt->reserved2 = 0x03; + + pmt->reserved3 = 0x0f; + pmt->zero3 = 0x0; + + pmt->desc_length = vidtv_psi_desc_comp_len(pmt->descriptor); + + pmt->header.section_length = vidtv_psi_pmt_table_comp_sec_len(pmt); +} + +u32 vidtv_psi_pmt_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_pmt *pmt, + u16 pid, + u32 buf_sz) +{ + u32 nbytes = 0; /* the number of bytes written by this function */ + u8 continuity_counter = 0; + struct vidtv_psi_desc *table_descriptor = pmt->descriptor; + struct vidtv_psi_table_pmt_stream *stream = pmt->stream; + struct vidtv_psi_desc *stream_descriptor = (stream) ? + pmt->stream->descriptor : + NULL; + + struct header_write_args h_args = {0}; + struct psi_write_args args = {0}; + struct desc_write_args d_args = {0}; + struct crc32_write_args c_args = {0}; + + h_args.to = buf; + h_args.offset = offset; + h_args.h = &pmt->header; + h_args.pid = pid; + h_args.continuity_counter = &continuity_counter; + h_args.buf_sz = buf_sz; + + nbytes += vidtv_psi_table_header_write_into(h_args); + + args.to = buf; + args.from = pmt + sizeof(struct vidtv_psi_table_header); + args.len = sizeof_field(struct vidtv_psi_table_pmt, bitfield) + + sizeof_field(struct vidtv_psi_table_pmt, bitfield2); + args.offset = offset + nbytes; + args.pid = pid; + args.new_psi_section = false; + args.continuity_counter = &continuity_counter; + args.is_crc = false; + args.buf_sz = buf_sz; + + nbytes += vidtv_psi_ts_psi_write_into(args); + + while (table_descriptor) { + d_args.to = buf; + d_args.offset = offset + nbytes; + d_args.desc = table_descriptor; + d_args.pid = pid; + d_args.continuity_counter = &continuity_counter; + d_args.buf_sz = buf_sz; + + nbytes += vidtv_psi_desc_write_into(d_args); + + table_descriptor = table_descriptor->next; + } + + while (stream) { + args.from = stream; + args.len = sizeof_field(struct vidtv_psi_table_pmt_stream, + type) + + sizeof_field(struct vidtv_psi_table_pmt_stream, + bitfield) + + sizeof_field(struct vidtv_psi_table_pmt_stream, + bitfield2); + args.offset = offset + nbytes; + + nbytes += vidtv_psi_ts_psi_write_into(args); + + while (stream_descriptor) { + d_args.desc = stream_descriptor; + d_args.offset = offset + nbytes; + nbytes += vidtv_psi_desc_write_into(d_args); + + stream_descriptor = stream_descriptor->next; + } + + stream = stream->next; + } + + c_args.to = buf; + c_args.offset = offset + nbytes; + c_args.pid = pid; + c_args.continuity_counter = &continuity_counter; + c_args.buf_sz = buf_sz; + + nbytes += table_section_crc32_write_into(c_args); + + return nbytes; +} + +void vidtv_psi_pmt_table_destroy(struct vidtv_psi_table_pmt *pmt) +{ + struct vidtv_psi_desc *curr_desc = pmt->descriptor; + struct vidtv_psi_desc *tmp_desc = NULL; + + while (curr_desc) { + tmp_desc = curr_desc; + curr_desc = curr_desc->next; + vidtv_psi_desc_destroy(tmp_desc); + kfree(tmp_desc); + } + + vidtv_psi_pmt_stream_destroy(pmt->stream); +} + +void vidtv_psi_sdt_table_init(struct vidtv_psi_table_sdt *sdt, + bool update_version_num, + u16 transport_stream_id) +{ + static u8 sdt_version; + + sdt->header.table_id = 0x42; + + sdt->header.one = 0x3; + sdt->header.zero = 0x1; + /* + * The PAT, PMT, and CAT all set this to 0. + * Other tables set this to 1. + */ + sdt->header.syntax = 0x1; + + /* + * This is a 16-bit field which serves as a label for identification + * of the TS, about which the SDT informs, from any other multiplex + * within the delivery system. + */ + sdt->header.id = transport_stream_id; + sdt->header.current_next = 0x1; + + /* ETSI 300 468: indicates changes in the TS described by this table*/ + if (update_version_num) + ++sdt_version; + + sdt->header.version = sdt_version; + + sdt->header.one2 = 0x3; + sdt->header.section_id = 0; + sdt->header.last_section = 0; + + sdt->network_id = transport_stream_id; + sdt->reserved = 0xff; + + sdt->header.section_length = vidtv_psi_sdt_table_comp_sec_len(sdt); +} + +u32 vidtv_psi_sdt_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_sdt *sdt, + u32 buf_sz) +{ + u32 nbytes = 0; /* the number of bytes written */ + u16 sdt_pid = 0x11; /* see ETSI EN 300 468 v1.15.1 p. 11 */ + u8 continuity_counter = 0; + + struct vidtv_psi_table_sdt_service *service = sdt->service; + struct vidtv_psi_desc *service_desc = (sdt->service) ? + sdt->service->descriptor : + NULL; + + struct header_write_args h_args = {0}; + struct psi_write_args args = {0}; + struct desc_write_args d_args = {0}; + struct crc32_write_args c_args = {0}; + + h_args.to = buf; + h_args.offset = offset; + h_args.h = &sdt->header; + h_args.pid = sdt_pid; + h_args.continuity_counter = &continuity_counter; + h_args.buf_sz = buf_sz; + + nbytes += vidtv_psi_table_header_write_into(h_args); + + args.to = buf; + args.from = sdt + sizeof(struct vidtv_psi_table_header); + args.len = sizeof_field(struct vidtv_psi_table_sdt, network_id) + + sizeof_field(struct vidtv_psi_table_sdt, reserved); + args.offset = offset + nbytes; + args.pid = sdt_pid; + args.new_psi_section = false; + args.continuity_counter = &continuity_counter; + args.is_crc = false; + args.buf_sz = buf_sz; + + /* copy u16 network_id + u8 reserved)*/ + nbytes += vidtv_psi_ts_psi_write_into(args); + + while (service) { + args.from = service; + /* skip both pointers at the end */ + args.len = sizeof(struct vidtv_psi_table_sdt_service) - + sizeof(struct vidtv_psi_desc *) - + sizeof(struct vidtv_psi_table_sdt_service *); + args.offset = offset + nbytes; + + nbytes += vidtv_psi_ts_psi_write_into(args); + + while (service_desc) { + d_args.to = buf; + d_args.offset = offset + nbytes; + d_args.desc = service_desc; + d_args.pid = sdt_pid; + d_args.continuity_counter = &continuity_counter; + d_args.buf_sz = buf_sz; + + nbytes += vidtv_psi_desc_write_into(d_args); + + service_desc = service_desc->next; + } + + service = service->next; + } + + c_args.to = buf; + c_args.offset = offset + nbytes; + c_args.pid = sdt_pid; + c_args.continuity_counter = &continuity_counter; + c_args.buf_sz = buf_sz; + + nbytes += table_section_crc32_write_into(c_args); + + return nbytes; +} + +void vidtv_psi_sdt_table_destroy(struct vidtv_psi_table_sdt *sdt) +{ + struct vidtv_psi_table_sdt_service *curr_service = sdt->service; + struct vidtv_psi_table_sdt_service *tmp_service = NULL; + struct vidtv_psi_desc *curr_desc = (sdt->service) ? + sdt->service->descriptor : NULL; + struct vidtv_psi_desc *tmp_desc = NULL; + + while (curr_service) { + curr_desc = curr_service->descriptor; + + while (curr_desc) { + /* clear all descriptors for the service */ + tmp_desc = curr_desc; + curr_desc = curr_desc->next; + vidtv_psi_desc_destroy(tmp_desc); + kfree(tmp_desc); + } + + /* then clear the current service */ + tmp_service = curr_service; + curr_service = curr_service->next; + kfree(tmp_service); + } +} + +struct vidtv_psi_table_sdt_service* +vidtv_psi_sdt_service_init(struct vidtv_psi_table_sdt_service *head, + u16 service_id) +{ + struct vidtv_psi_table_sdt_service *service; + + service = kzalloc(sizeof(*service), GFP_KERNEL); + + /* + * ETSI 300 468: this is a 16bit field which serves as a label to + * identify this service from any other service within the TS. + * The service id is the same as the program number in the + * corresponding program_map_section + */ + service->service_id = service_id; + service->EIT_schedule = 0x0; /* TODO */ + service->EIT_present_following = 0x0; /* TODO */ + service->reserved = 0x3f; /* all bits on */ + service->free_CA_mode = 0x0; /* not scrambled */ + service->running_status = RUNNING; + + if (head) { + while (head->next) + head = head->next; + + head->next = service; + } + + return service; +} + +void +vidtv_psi_sdt_service_destroy(struct vidtv_psi_table_sdt_service *service) +{ + struct vidtv_psi_table_sdt_service *curr = service; + struct vidtv_psi_table_sdt_service *tmp = NULL; + + while (curr) { + tmp = curr; + curr = curr->next; + kfree(tmp); + } +} + +void +vidtv_psi_sdt_service_assign(struct vidtv_psi_table_sdt *sdt, + struct vidtv_psi_table_sdt_service *service) +{ + struct vidtv_psi_table_sdt_service *temp = sdt->service; + + sdt->service = service; + + sdt->header.section_length = vidtv_psi_sdt_table_comp_sec_len(sdt); + + /* do not break userspace: reassign if the new size is too big */ + if (sdt->header.section_length > SDT_MAX_SECTION_LEN) + vidtv_psi_sdt_service_assign(sdt, temp); + else + vidtv_psi_sdt_service_destroy(temp); +} + +void +vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat, + struct vidtv_psi_table_pmt *sec) + +{ + /* + * PMTs contain information about programs. For each program, + * there is one PMT + */ + struct vidtv_psi_table_pat_program *program = pat->program; + u32 i = 0; + + while (program) { + vidtv_psi_pmt_table_init(&sec[i], + false, + sec[i].header.id, + 0); + + ++i; + program = program->next; + } +} diff --git a/drivers/media/test_drivers/vidtv/vidtv_psi.h b/drivers/media/test_drivers/vidtv/vidtv_psi.h new file mode 100644 index 0000000000000..0336934b3aba2 --- /dev/null +++ b/drivers/media/test_drivers/vidtv/vidtv_psi.h @@ -0,0 +1,294 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef VIDTV_PSI_H +#define VIDTV_PSI_H + +#include + +/* + * all section lengths start immediately after the 'section_length' field + * see ISO/IEC 13818-1 : 2000 and ETSI EN 300 468 V 1.10.1 for + * reference + */ +#define PAT_LEN_UNTIL_LAST_SECTION_NUMBER 5 +#define PAT_MAX_SECTION_LEN 1021 +#define PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH 9 +#define PMT_MAX_SECTION_LEN 1021 +#define SDT_LEN_UNTIL_RESERVED_FOR_FUTURE_USE 8 +#define SDT_MAX_SECTION_LEN 1021 + +enum vidtv_psi_descriptors { + SERVICE_DESCRIPTOR = 0x48, +}; + +enum vidtv_psi_stream_types { + ISO_IEC_13818_3_AUDIO = 0x4, +}; + +struct vidtv_psi_desc { + u8 type; + u8 length; + struct vidtv_psi_desc *next; + u8 data[]; +} __packed; + +struct vidtv_psi_desc_service { + u8 type; + u8 length; + struct dvb_desc *next; + + u8 service_type; + char *name; + char *name_emph; + char *provider; + char *provider_emph; +} __packed; + +struct vidtv_psi_table_header { + u8 table_id; + union { + u16 bitfield; + struct { + u16 section_length:12; + u8 one:2; + u8 zero:1; + u8 syntax:1; + } __packed; + } __packed; + u16 id; /* TS ID */ + u8 current_next:1; + u8 version:5; + u8 one2:2; + + u8 section_id; /* section_number */ + u8 last_section; /* last_section_number */ +} __packed; + +struct vidtv_psi_table_pat_program { + u16 service_id; + union { + u16 bitfield; + struct { + u16 pid:13; + u8 reserved:3; + } __packed; + } __packed; + struct vidtv_psi_table_pat_program *next; +} __packed; + +struct vidtv_psi_table_pat { + struct vidtv_psi_table_header header; + u16 programs; + struct vidtv_psi_table_pat_program *program; +} __packed; + +struct vidtv_psi_table_sdt_service { + u16 service_id; + u8 EIT_present_following:1; + u8 EIT_schedule:1; + u8 reserved:6; + union { + u16 bitfield; + struct { + u16 desc_length:12; + u16 free_CA_mode:1; + u16 running_status:3; + } __packed; + } __packed; + struct vidtv_psi_desc *descriptor; + struct vidtv_psi_table_sdt_service *next; +} __packed; + +struct vidtv_psi_table_sdt { + struct vidtv_psi_table_header header; + u16 network_id; + u8 reserved; + struct vidtv_psi_table_sdt_service *service; +} __packed; + +enum service_running_status { + RUNNING, +}; + +enum service_type { + /* see ETSI EN 300 468 v1.15.1 p. 77 */ + DIGITAL_TELEVISION_SERVICE = 0x1, +}; + +struct vidtv_psi_table_pmt_stream { + u8 type; + union { + u16 bitfield; + struct { + u16 elementary_pid:13; + u16 reserved:3; + } __packed; + } __packed; + union { + u16 bitfield2; + struct { + u16 desc_length:10; + u16 zero:2; + u16 reserved2:4; + } __packed; + } __packed; + struct vidtv_psi_desc *descriptor; + struct vidtv_psi_table_pmt_stream *next; +} __packed; + +struct vidtv_psi_table_pmt { + struct vidtv_psi_table_header header; + union { + u16 bitfield; + struct { + u16 pcr_pid:13; + u16 reserved2:3; + } __packed; + } __packed; + + union { + u16 bitfield2; + struct { + u16 desc_length:10; + u16 zero3:2; + u16 reserved3:4; + } __packed; + } __packed; + struct vidtv_psi_desc *descriptor; + struct vidtv_psi_table_pmt_stream *stream; +} __packed; + +struct psi_write_args { + void *to; + void *from; + size_t len; /* how much to write */ + u32 offset; /* where to start writing in the buffer */ + u16 pid; /* TS packet ID */ + bool new_psi_section; /* set when starting a table section */ + u8 *continuity_counter; /* TS: incremented when section gets split */ + bool is_crc; /* set when writing the CRC at the end */ + u32 buf_sz; /* protect against overflow when this field is not zero */ +}; + +struct desc_write_args { + void *to; + u32 offset; + struct vidtv_psi_desc *desc; + u16 pid; + u8 *continuity_counter; + u32 buf_sz; /* protect against overflow when this field is not zero */ +}; + +struct crc32_write_args { + void *to; + u32 offset; + u16 pid; + u8 *continuity_counter; + u32 buf_sz; /* protect against overflow when this field is not zero */ +}; + +struct header_write_args { + void *to; + u32 offset; + struct vidtv_psi_table_header *h; + u16 pid; + u8 *continuity_counter; + u32 buf_sz; /* protect against overflow when this field is not zero */ +}; + +struct vidtv_psi_desc *vidtv_psi_desc_init(struct vidtv_psi_desc *head, + u8 type, + u8 length); + +void vidtv_psi_pat_table_init(struct vidtv_psi_table_pat *pat, + bool update_version_num, + u16 transport_stream_id); + +struct vidtv_psi_table_pat_program* +vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head, + u16 service_id, + u16 pid); + +struct vidtv_psi_table_pmt_stream* +vidtv_psi_pmt_stream_init(struct vidtv_psi_table_pmt_stream *head, + enum vidtv_psi_stream_types stream_type, + u16 es_pid); + +void vidtv_psi_pmt_table_init(struct vidtv_psi_table_pmt *pmt, + bool update_version_num, + u16 program_number, + u16 pcr_pid); + +void +vidtv_psi_sdt_table_init(struct vidtv_psi_table_sdt *sdt, + bool update_version_num, + u16 transport_stream_id); + +struct vidtv_psi_table_sdt_service* +vidtv_psi_sdt_service_init(struct vidtv_psi_table_sdt_service *head, + u16 service_id); + +void +vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc); + +void +vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p); + +void +vidtv_psi_pat_table_destroy(struct vidtv_psi_table_pat *p); + +void +vidtv_psi_pmt_stream_destroy(struct vidtv_psi_table_pmt_stream *s); + +void +vidtv_psi_pmt_table_destroy(struct vidtv_psi_table_pmt *pmt); + +void +vidtv_psi_sdt_table_destroy(struct vidtv_psi_table_sdt *sdt); + +void +vidtv_psi_sdt_service_destroy(struct vidtv_psi_table_sdt_service *service); + +void +vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc); + +void +vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p); + +void +vidtv_psi_sdt_service_assign(struct vidtv_psi_table_sdt *sdt, + struct vidtv_psi_table_sdt_service *service); + +void vidtv_psi_desc_assign(struct vidtv_psi_desc **to, + struct vidtv_psi_desc *desc, + u16 *desc_length); + +void vidtv_psi_pat_program_assign(struct vidtv_psi_table_pat *pat, + struct vidtv_psi_table_pat_program *p); + +void vidtv_psi_pmt_stream_assign(struct vidtv_psi_table_pmt *pmt, + struct vidtv_psi_table_pmt_stream *s); +void +vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat, + struct vidtv_psi_table_pmt *sec); + +u16 vidtv_psi_pmt_get_pid(struct vidtv_psi_table_pmt *section, + struct vidtv_psi_table_pat *pat); + +u32 vidtv_psi_pat_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_pat *pat, + u32 buf_sz); + +u32 vidtv_psi_sdt_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_sdt *sdt, + u32 buf_sz); + +u32 vidtv_psi_pmt_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_pmt *pmt, + u16 pid, + u32 buf_sz); + +#endif // VIDTV_PSI_H From patchwork Mon Apr 6 23:20:54 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 62892 Received: from vger.kernel.org ([209.132.180.67]) by www.linuxtv.org with esmtp (Exim 4.92) (envelope-from ) id 1jLb0x-009Kcj-96; Mon, 06 Apr 2020 23:18:47 +0000 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726679AbgDFXV3 (ORCPT + 1 other); Mon, 6 Apr 2020 19:21:29 -0400 Received: from mail-qt1-f194.google.com ([209.85.160.194]:40915 "EHLO mail-qt1-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726254AbgDFXV1 (ORCPT ); Mon, 6 Apr 2020 19:21:27 -0400 Received: by mail-qt1-f194.google.com with SMTP id y25so1315907qtv.7; Mon, 06 Apr 2020 16:21:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=OaKLagKkBXSlUggDF7Q3Hul7FW0QuHIxEa1ITJQXhiM=; b=u6C+F8HxVrCGoXEr+POxhMSGbQuO5uamUz8SuihoKeW60L7JOw8gBldwwXhVS2xe5O xdYQK120VYk7HrHHvo1k7Ak5fLLASA5R+t+cRxYHJJ9IZvgKg5QZASj4xQUFOX1BG52Y aZ35nxwzdd5+Woc1qx0sfQN37AoT5upfIcjbPWqPmZiy4C4EeOw7xPAE1yKER13gbNdw UcFuZyKMaxeYL4qqKJo8B3JS4InbSykT83nm0JHDii/XIBtZN1encFXlg/0HR2condsf 1F1Ogzut5RR2bKRa2XdJEjsBwVOMAwm7Hu8KNKW5hqCU4QxauGizsc10XdQbQcPI96g5 mYWw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=OaKLagKkBXSlUggDF7Q3Hul7FW0QuHIxEa1ITJQXhiM=; b=C6Y1+SMrDiGsajb/zKq0sur7gJrq0gpD2onFF9PWfaAI28Wiaj7NGDQwxwja/pwV52 Lf0689IXz+q4uKVGmoA/yqONUiKTnFnUglMgfxYHuy2RW40rU0rp6oqGpd9+jZBeFL8s hARzww4iKrYxcNpshGCPKMeUB78PXumNekjfkNO1eG21Wcfw2BL5Utn/fj8hRVXdgKiK CeOW9zV0rr9VPJFUIl8pWaBrz5Jeb+MYRXIjFKLpsx1jjUVr/48Kyd7+tx/XiVEo1JLs vExZHwqfExZXs/vFeX8uF0J8CAJrJqCsZ5AiD5qeokfnNLq1cxFF+/+DmkB+3B3pVcmM hrWQ== X-Gm-Message-State: AGi0Pua+N1nVyPJrwiahlODxX1d5fRTh2uGhqDpq1/KfQui1cq/Yc6Ba ya1ztQ2tspzDZBDrPnEQHxk= X-Google-Smtp-Source: APiQypKIMzGPMnyM6M/gB4c56C+5YWQnNPvoaJcP4Nep+NhXWW08QMvd1RS7NBLpPunHLyKhw7KNIw== X-Received: by 2002:ac8:224c:: with SMTP id p12mr2021756qtp.32.1586215286610; Mon, 06 Apr 2020 16:21:26 -0700 (PDT) Received: from localhost.localdomain ([2804:14d:72b1:8920:da15:c0bd:33c1:e2ad]) by smtp.gmail.com with ESMTPSA id u26sm1490978qku.54.2020.04.06.16.21.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 06 Apr 2020 16:21:26 -0700 (PDT) From: "Daniel W. S. Almeida" X-Google-Original-From: Daniel W. S. Almeida To: mchehab+huawei@kernel.org, sean@mess.org, kstewart@linuxfoundation.org, allison@lohutok.net, tglx@linutronix.de Cc: "Daniel W. S. Almeida" , linux-media@vger.kernel.org, skhan@linuxfoundation.org, linux-kernel-mentees@lists.linuxfoundation.org, linux-kernel@vger.kernel.org Subject: [RFC, WIP, v3 5/6] media: vidtv: move config structs into a common header Date: Mon, 6 Apr 2020 20:20:54 -0300 Message-Id: <20200406232055.1023946-6-dwlsalmeida@gmail.com> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200406232055.1023946-1-dwlsalmeida@gmail.com> References: <20200406232055.1023946-1-dwlsalmeida@gmail.com> MIME-Version: 1.0 Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: "Daniel W. S. Almeida" 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 --- .../media/test_drivers/vidtv/vidtv_common.h | 19 +++++++++++++++++++ .../media/test_drivers/vidtv/vidtv_demod.h | 9 --------- .../media/test_drivers/vidtv/vidtv_tuner.c | 12 ++---------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/drivers/media/test_drivers/vidtv/vidtv_common.h b/drivers/media/test_drivers/vidtv/vidtv_common.h index 43269833ee866..e6b36429cc8de 100644 --- a/drivers/media/test_drivers/vidtv/vidtv_common.h +++ b/drivers/media/test_drivers/vidtv/vidtv_common.h @@ -56,6 +56,25 @@ struct vidtv_mpeg_ts { struct vidtv_mpeg_ts_adaption adaption[]; } __packed; +struct vidtv_tuner_config { + struct dvb_frontend *fe; + u32 mock_power_up_delay_msec; + u32 mock_tune_delay_msec; + u32 vidtv_valid_dvb_t_freqs[8]; + u32 vidtv_valid_dvb_c_freqs[8]; + u32 vidtv_valid_dvb_s_freqs[8]; + u8 max_frequency_shift_hz; +}; + +struct vidtv_demod_config { + struct dvb_frontend *frontend; + /* probability of losing the lock due to low snr */ + u8 drop_tslock_prob_on_low_snr; + /* probability of recovering when the signal improves */ + u8 recover_tslock_prob_on_good_snr; + u8 chosen_delsys; +}; + u32 vidtv_memcpy(void *to, const void *from, size_t len, diff --git a/drivers/media/test_drivers/vidtv/vidtv_demod.h b/drivers/media/test_drivers/vidtv/vidtv_demod.h index 49c2a43f71661..269855efb77f3 100644 --- a/drivers/media/test_drivers/vidtv/vidtv_demod.h +++ b/drivers/media/test_drivers/vidtv/vidtv_demod.h @@ -21,15 +21,6 @@ struct vidtv_demod_cnr_to_qual_s { u32 cnr_ok, cnr_good; }; -struct vidtv_demod_config { - struct dvb_frontend *frontend; - /* probability of losing the lock due to low snr */ - u8 drop_tslock_prob_on_low_snr; - /* probability of recovering when the signal improves */ - u8 recover_tslock_prob_on_good_snr; - u8 chosen_delsys; -}; - struct vidtv_demod_state { struct dvb_frontend frontend; struct vidtv_demod_config config; diff --git a/drivers/media/test_drivers/vidtv/vidtv_tuner.c b/drivers/media/test_drivers/vidtv/vidtv_tuner.c index c948daa66ec73..8b1befc861e33 100644 --- a/drivers/media/test_drivers/vidtv/vidtv_tuner.c +++ b/drivers/media/test_drivers/vidtv/vidtv_tuner.c @@ -17,20 +17,12 @@ #include #include +#include "vidtv_common.h" + MODULE_DESCRIPTION("Virtual DTV Tuner"); MODULE_AUTHOR("Daniel W. S. Almeida"); MODULE_LICENSE("GPL"); -struct vidtv_tuner_config { - struct dvb_frontend *fe; - u32 mock_power_up_delay_msec; - u32 mock_tune_delay_msec; - u32 vidtv_valid_dvb_t_freqs[8]; - u32 vidtv_valid_dvb_c_freqs[8]; - u32 vidtv_valid_dvb_s_freqs[8]; - u8 max_frequency_shift_hz; -}; - struct vidtv_tuner_cnr_to_qual_s { /* attempt to use the same values as libdvbv5 */ u32 modulation; From patchwork Mon Apr 6 23:20:55 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 62893 Received: from vger.kernel.org ([209.132.180.67]) by www.linuxtv.org with esmtp (Exim 4.92) (envelope-from ) id 1jLb0y-009Kcj-4o; Mon, 06 Apr 2020 23:18:48 +0000 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726701AbgDFXVc (ORCPT + 1 other); Mon, 6 Apr 2020 19:21:32 -0400 Received: from mail-qt1-f196.google.com ([209.85.160.196]:34329 "EHLO mail-qt1-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726254AbgDFXVc (ORCPT ); Mon, 6 Apr 2020 19:21:32 -0400 Received: by mail-qt1-f196.google.com with SMTP id 14so1357989qtp.1; Mon, 06 Apr 2020 16:21:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=PpbyX1WuBtdSzHfRR+tKl8K9IZRMVMgL6KyqM10pbN8=; b=rCggRTnN02uDxBY/f9OOz1UmYTUdtTGvMKs966ayc8CPoBJglKnIh9QLniFs2CSktG uMqZOYZeOTbQySfYWKtFq3ckjJaYA3+sPV+YAmPy5dmIC76Z41zMCt+kIu6g98Jp6coS Tp1lo4C2n4UblcR63/ghKnjgpuKp/sCQiWDGSuBP9/rEHu+gChLXx54qzLWAo66UzyRa LrRPnckvvkDsJYKvMB6PpgxmVQDMSNoosSLYR2cCrm+bYXMmqxQp8L4JGcs2nBbtNTG2 zpFyUXziEaCfsoGAYMtS7wjmywY0mZL1/YaFlNdJQ3Dyrqfrzrhf56umIv4CgNSqkJHP vmmQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=PpbyX1WuBtdSzHfRR+tKl8K9IZRMVMgL6KyqM10pbN8=; b=ZPI8uY9s64pHKBtH6i5gBYvLhBuSK/Rrhy+FMARMsTBCew+lFF4oq/NaKccS5XN4er 4MmtHj1ajLFeS9jwDlW7FmBSNxh/aQXl48nJsj8wbKwMXlUb9rt1ivZxMGO6fKxjBKfM lqSRRnDR7Rpg6Y/k8N23nMG7IYuPH7fc+yA9PMJo8dtVUJiFMu/kHTNcALR26kic0TA3 IHExaqoIngWHhHmTagpFMjVa15fkWbkI+RXhr45yrJO3AByjPBemE5UZRfnlfwpGhDUR hI+hhVj6HUxV0XKHGr2U8Vl4z0ueiDawsSbquOMi/kg1ISIWA7Q919KfQmuspAI+rvqw 7vBQ== X-Gm-Message-State: AGi0PuZpEiaiZDfCFufi48QPH/8jnkIYyMfw0pOuXkR1jU832PwsTxxt SiFcttWAfNO50LdQCM6orTs= X-Google-Smtp-Source: APiQypLIeVLaERRDCmncNcmvxqYrnc5srewvRoL1cBlZEdkHzct66DcySE6mJJU4ey3rZPBlQcOcGA== X-Received: by 2002:ac8:6d0b:: with SMTP id o11mr1989482qtt.324.1586215290328; Mon, 06 Apr 2020 16:21:30 -0700 (PDT) Received: from localhost.localdomain ([2804:14d:72b1:8920:da15:c0bd:33c1:e2ad]) by smtp.gmail.com with ESMTPSA id u26sm1490978qku.54.2020.04.06.16.21.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 06 Apr 2020 16:21:29 -0700 (PDT) From: "Daniel W. S. Almeida" X-Google-Original-From: Daniel W. S. Almeida To: mchehab+huawei@kernel.org, sean@mess.org, kstewart@linuxfoundation.org, allison@lohutok.net, tglx@linutronix.de Cc: "Daniel W. S. Almeida" , linux-media@vger.kernel.org, skhan@linuxfoundation.org, linux-kernel-mentees@lists.linuxfoundation.org, linux-kernel@vger.kernel.org Subject: [RFC, WIP, v3 6/6] media: vidtv: add a bridge driver Date: Mon, 6 Apr 2020 20:20:55 -0300 Message-Id: <20200406232055.1023946-7-dwlsalmeida@gmail.com> X-Mailer: git-send-email 2.26.0 In-Reply-To: <20200406232055.1023946-1-dwlsalmeida@gmail.com> References: <20200406232055.1023946-1-dwlsalmeida@gmail.com> MIME-Version: 1.0 Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: "Daniel W. S. Almeida" Digital TV devices consist of several independent hardware components which are controlled by different drivers. Each media device is controlled by a group of cooperating drivers with the bridge driver as the main driver. This patch adds a bridge driver for the Virtual Digital TV driver [vidtv]. The bridge driver binds to the other drivers, that is, vidtv_tuner and vidtv_demod and implements the digital demux logic, providing userspace with a MPEG Transport Stream. Signed-off-by: Daniel W. S. Almeida --- drivers/media/test_drivers/vidtv/Makefile | 3 +- .../media/test_drivers/vidtv/vidtv_bridge.c | 736 ++++++++++++++++++ .../media/test_drivers/vidtv/vidtv_bridge.h | 51 ++ .../media/test_drivers/vidtv/vidtv_common.h | 15 + 4 files changed, 804 insertions(+), 1 deletion(-) create mode 100644 drivers/media/test_drivers/vidtv/vidtv_bridge.c create mode 100644 drivers/media/test_drivers/vidtv/vidtv_bridge.h diff --git a/drivers/media/test_drivers/vidtv/Makefile b/drivers/media/test_drivers/vidtv/Makefile index 690420a7c904b..296e591883c5e 100644 --- a/drivers/media/test_drivers/vidtv/Makefile +++ b/drivers/media/test_drivers/vidtv/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 vidtv_demod-objs := vidtv_common.o vidtv_psi.o +vidtv_bridge-objs := vidtv_common.o vidtv_psi.o -obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o +obj-$(CONFIG_DVB_VIDTV) += vidtv_tuner.o vidtv_demod.o vidtv_bridge.o diff --git a/drivers/media/test_drivers/vidtv/vidtv_bridge.c b/drivers/media/test_drivers/vidtv/vidtv_bridge.c new file mode 100644 index 0000000000000..a3dfd913d1e83 --- /dev/null +++ b/drivers/media/test_drivers/vidtv/vidtv_bridge.c @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Virtual DTV test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Written by Daniel W. S. Almeida + */ + +#include +#include +#include +#include +#include +#include "vidtv_bridge.h" +#include "vidtv_common.h" +#include "vidtv_psi.h" + +#define TS_BUF_MAX_SZ (128 * TS_PACKET_LEN) + +MODULE_AUTHOR("Daniel W. S. Almeida"); +MODULE_LICENSE("GPL"); + +static unsigned int drop_tslock_prob_on_low_snr; +module_param(drop_tslock_prob_on_low_snr, uint, 0644); +MODULE_PARM_DESC(drop_tslock_prob_on_low_snr, + "Probability of losing the TS lock if the signal quality is bad"); + +static unsigned int recover_tslock_prob_on_good_snr; +module_param(recover_tslock_prob_on_good_snr, uint, 0644); +MODULE_PARM_DESC(recover_tslock_prob_on_good_snr, + "Probability recovering the TS lock when the signal improves"); + +static unsigned int mock_power_up_delay_msec; +module_param(mock_power_up_delay_msec, uint, 0644); +MODULE_PARM_DESC(mock_power_up_delay_msec, "Simulate a power up delay"); + +static unsigned int mock_tune_delay_msec; +module_param(mock_tune_delay_msec, uint, 0644); +MODULE_PARM_DESC(mock_tune_delay_msec, "Simulate a tune delay"); + +static unsigned int vidtv_valid_dvb_t_freqs[8]; +module_param_array(vidtv_valid_dvb_t_freqs, uint, NULL, 0644); +MODULE_PARM_DESC(vidtv_valid_dvb_t_freqs, + "Valid DVB-T frequencies to simulate"); + +static unsigned int vidtv_valid_dvb_c_freqs[8]; +module_param_array(vidtv_valid_dvb_c_freqs, uint, NULL, 0644); +MODULE_PARM_DESC(vidtv_valid_dvb_c_freqs, + "Valid DVB-C frequencies to simulate"); + +static unsigned int vidtv_valid_dvb_s_freqs[8]; +module_param_array(vidtv_valid_dvb_s_freqs, uint, NULL, 0644); +MODULE_PARM_DESC(vidtv_valid_dvb_s_freqs, + "Valid DVB-C frequencies to simulate"); + +static unsigned int max_frequency_shift_hz; +module_param(max_frequency_shift_hz, uint, 0644); +MODULE_PARM_DESC(max_frequency_shift_hz, + "Maximum shift in HZ allowed when tuning in a channel"); + +static unsigned int chosen_delsys = SYS_DVBT; +module_param(chosen_delsys, uint, 0644); +MODULE_PARM_DESC(chosen_delsys, + "The delivery system to simulate. Currently supported: DVB-T, DVB-C, DVB-S"); + +static unsigned int ts_buf_sz = 20 * TS_PACKET_LEN; +module_param(ts_buf_sz, uint, 0644); +MODULE_PARM_DESC(ts_buf_sz, "Optional size for the TS buffer"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nums); + +/* + * Influences the signal acquisition time. See ISO/IEC 13818-1 : 2000. p. 113. + */ +static unsigned int psi_freq_hz = 25; +module_param(psi_freq_hz, uint, 0644); +MODULE_PARM_DESC(psi_freq_hz, "Simulate a given PSI frequency"); + +static unsigned int mpeg_thread_freq_hz = 100; +module_param(mpeg_thread_freq_hz, uint, 0644); +MODULE_PARM_DESC(mpeg_thread_freq_hz, + "Simulate a given loop frequency for the MPEG thread"); + +static int vidtv_start_streaming(struct vidtv_dvb *dvb) +{ + /* if already streaming, then this call is probably a mistake */ + WARN_ON(dvb->streaming); + + dvb->streaming = true; + schedule_work(&dvb->mpeg_thread); + + return 0; +} + +static int vidtv_stop_streaming(struct vidtv_dvb *dvb) +{ + /* mpeg thread will quit */ + dvb->streaming = false; + + return 0; +} + +static int vidtv_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct vidtv_dvb *dvb = demux->priv; + int rc, ret; + + if (!demux->dmx.frontend) + return -EINVAL; + + mutex_lock(&dvb->feed_lock); + + dvb->nfeeds++; + rc = dvb->nfeeds; + + if (dvb->nfeeds == 1) { + ret = vidtv_start_streaming(dvb); + if (ret < 0) + rc = ret; + } + + mutex_unlock(&dvb->feed_lock); + return rc; +} + +static int vidtv_stop_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct vidtv_dvb *dvb = demux->priv; + int err = 0; + + mutex_lock(&dvb->feed_lock); + dvb->nfeeds--; + + if (!dvb->nfeeds) + err = vidtv_stop_streaming(dvb); + + mutex_unlock(&dvb->feed_lock); + return err; +} + +static void vidtv_bridge_channels_init(struct vidtv_dvb *dvb) +{ + /* these channels will be used to populate the MPEG PSI tables */ + + const u16 pac_service_id = 0x880; + const u16 pac_program_num = 0x880; + const u16 pac_program_pid = 0x101; /* packet id for PMT*/ + const u16 pac_audio_stream_id = 0x111; + + struct vidtv_channel pac; /* PCM Audio Channel */ + struct vidtv_psi_table_sdt_service *pac_service; + struct vidtv_psi_desc_service *pac_s_desc; + u16 desc_length; + + pac_service = vidtv_psi_sdt_service_init(NULL, pac_service_id); + + pac_s_desc = (struct vidtv_psi_desc_service *) + vidtv_psi_desc_init(NULL, + SERVICE_DESCRIPTOR, + sizeof(*pac_s_desc)); + + pac_s_desc->name = "Sine Wave PCM Audio"; + pac_s_desc->service_type = DIGITAL_TELEVISION_SERVICE; + + pac_s_desc->length = sizeof(pac_s_desc->service_type) + + strlen(pac_s_desc->name) + + strlen(pac_s_desc->name_emph) + + strlen(pac_s_desc->provider) + + strlen(pac_s_desc->provider_emph); + + vidtv_psi_desc_assign(&pac_service->descriptor, + (struct vidtv_psi_desc *) + pac_s_desc, + &desc_length); + + pac_service->desc_length = desc_length; + + pac.transport_stream_id = TRANSPORT_STREAM_ID; + + pac.program = vidtv_psi_pat_program_init(NULL, + pac_service_id, + pac_program_pid); + + pac.program_num = pac_program_num; + pac.streams = vidtv_psi_pmt_stream_init(NULL, + ISO_IEC_13818_3_AUDIO, + pac_audio_stream_id); + + dvb->channels[0] = pac; +} + +static void +vidtv_bridge_channels_destroy(struct vidtv_dvb *dvb) +{ + u32 i; + struct vidtv_channel *curr; + + for (i = 0; i < NUM_CHANNELS; ++i) { + curr = &dvb->channels[i]; + vidtv_psi_sdt_service_destroy(curr->service); + vidtv_psi_pat_program_destroy(curr->program); + vidtv_psi_pmt_stream_destroy(curr->streams); + } +} + +static struct vidtv_psi_table_sdt_service* +vidtv_bridge_sdt_serv_cat_into_new(struct vidtv_channel chnls[NUM_CHANNELS]) +{ + u32 i; + struct vidtv_psi_table_sdt_service *curr = NULL; + struct vidtv_psi_table_sdt_service *head = NULL; + struct vidtv_psi_table_sdt_service *tail = NULL; + u16 service_id; + + for (i = 0; i < NUM_CHANNELS; ++i) { + curr = chnls[i].service; + service_id = curr->service_id; + + if (!curr) + continue; + + while (curr->next) { + tail = vidtv_psi_sdt_service_init(tail, service_id); + + if (!head) + head = tail; + + curr = curr->next; + } + } + + return head; +} + +static struct vidtv_psi_table_pat_program* +vidtv_bridge_pat_prog_cat_into_new(struct vidtv_channel chnls[NUM_CHANNELS]) +{ + u32 i; + struct vidtv_psi_table_pat_program *curr = NULL; + struct vidtv_psi_table_pat_program *head = NULL; + struct vidtv_psi_table_pat_program *tail = NULL; + + for (i = 0; i < NUM_CHANNELS; ++i) { + curr = chnls[i].program; + + if (!curr) + continue; + + while (curr->next) { + tail = vidtv_psi_pat_program_init(tail, + curr->service_id, + curr->pid); + + if (!head) + head = tail; + + curr = curr->next; + } + } + + return head; +} + +static void +vidtv_bridge_pmt_match_sections(struct vidtv_channel chnls[NUM_CHANNELS], + struct vidtv_psi_table_pmt *sections, + u32 nsections) +{ + struct vidtv_psi_table_pmt *curr_section = NULL; + u32 i, j; + + for (i = 0; i < NUM_CHANNELS; ++i) { + for (j = 0; j < nsections; ++j) { + curr_section = §ions[j]; + + if (!curr_section) + continue; + + /* we got a match */ + if (curr_section->header.id == + chnls[i].program_num) { + vidtv_psi_pmt_stream_assign(curr_section, + chnls[i].streams); + break; + } + } + } +} + +static void vidtv_bridge_mpeg_tables_init(struct vidtv_dvb *dvb) +{ + struct vidtv_psi_table_pat *pat = dvb->pat; + struct vidtv_psi_table_sdt *sdt = dvb->sdt; + + struct vidtv_psi_table_pmt *pmt_sections = dvb->pmt_sections; + + struct vidtv_psi_table_pat_program *programs = NULL; + struct vidtv_psi_table_sdt_service *services = NULL; + + bool update_version_num = false; + + vidtv_psi_pat_table_init(pat, + update_version_num, + TRANSPORT_STREAM_ID); + + vidtv_psi_sdt_table_init(sdt, + update_version_num, + TRANSPORT_STREAM_ID); + + programs = vidtv_bridge_pat_prog_cat_into_new(dvb->channels); + services = vidtv_bridge_sdt_serv_cat_into_new(dvb->channels); + + /* assemble all programs and assign to PAT */ + vidtv_psi_pat_program_assign(pat, programs); + + /* assemble all services and assign to SDT */ + vidtv_psi_sdt_service_assign(sdt, services); + + /* a section for each program_id */ + pmt_sections = kcalloc(pat->programs, + sizeof(struct vidtv_psi_table_pmt), + GFP_KERNEL); + + vidtv_psi_pmt_create_sec_for_each_pat_entry(pat, + pmt_sections); + + vidtv_bridge_pmt_match_sections(dvb->channels, + pmt_sections, + pat->programs); +} + +static void vidtv_bridge_mpeg_tables_destroy(struct vidtv_dvb *dvb) +{ + u32 i; + + vidtv_psi_pat_table_destroy(dvb->pat); + + for (i = 0; i < dvb->num_pmt_sections; ++i) + vidtv_psi_pmt_table_destroy(&dvb->pmt_sections[i]); + + vidtv_psi_sdt_table_destroy(dvb->sdt); +} + +static bool vidtv_bridge_check_demod_lock(struct vidtv_dvb *dvb, u32 n) +{ + enum fe_status status; + + dvb->fe[n]->ops.read_status(dvb->fe[n], &status); + + return status == FE_HAS_LOCK; /* all other flags will be set */ +} + +static bool vidtv_bridge_should_push_psi(struct vidtv_dvb *dvb, + unsigned long elapsed_time) +{ + unsigned long psi_period_in_jiffies; + unsigned long next_psi_at; + + psi_period_in_jiffies = usecs_to_jiffies(USECS_IN_SEC / psi_freq_hz); + next_psi_at = dvb->stream_start_jiffies + + (dvb->num_streamed_psi * psi_period_in_jiffies); + + /* if this is in the past, it is time to push PSI packets again */ + return elapsed_time > next_psi_at; +} + +static void vidtv_bridge_mpeg_push_psi(struct vidtv_dvb *dvb, + u8 *buf, + u32 *buffer_offset) +{ + u16 pmt_pid; + u32 i; + + *buffer_offset += vidtv_psi_pat_write_into(buf, + *buffer_offset, + dvb->pat, + dvb->ts_buffer_sz); + + for (i = 0; i < dvb->num_pmt_sections; ++i) { + pmt_pid = vidtv_psi_pmt_get_pid(&dvb->pmt_sections[i], + dvb->pat); + + /* not found */ + WARN_ON(pmt_pid > LAST_VALID_TS_PID); + + /* write each section into buffer */ + *buffer_offset += + vidtv_psi_pmt_write_into(buf, + *buffer_offset, + &dvb->pmt_sections[i], + pmt_pid, + dvb->ts_buffer_sz); + } + + *buffer_offset += vidtv_psi_sdt_write_into(buf, + *buffer_offset, + dvb->sdt, + dvb->ts_buffer_sz); + + dvb->num_streamed_psi++; +} + +static void vidtv_bridge_mpeg_tick(struct work_struct *work) +{ + struct vidtv_dvb *dvb = container_of(work, + struct vidtv_dvb, + mpeg_thread); + + const int SLEEP_USECS = USECS_IN_SEC / mpeg_thread_freq_hz; + char *buf = dvb->ts_buffer; + u32 buffer_offset; + unsigned long elapsed_time = 0; + + dvb->stream_start_jiffies = jiffies; + + while (dvb->streaming && vidtv_bridge_check_demod_lock(dvb, 0)) { + memset(buf, 0, dvb->ts_buffer_sz); + buffer_offset = 0; + + if (vidtv_bridge_should_push_psi(dvb, elapsed_time)) + vidtv_bridge_mpeg_push_psi(dvb, buf, &buffer_offset); + + /* + * just a sanity check, should not happen because we check + * for overflow before writing + */ + WARN_ON(buffer_offset > dvb->ts_buffer_sz); + + /* if packets are not aligned by now, something is also wrong */ + WARN_ON(buffer_offset % TS_PACKET_LEN); + + dvb_dmx_swfilter_packets(&dvb->demux, + buf, + buffer_offset / TS_PACKET_LEN); + + usleep_range(SLEEP_USECS, SLEEP_USECS + (SLEEP_USECS / 10)); + elapsed_time = dvb->stream_start_jiffies - jiffies; + } + + dvb->streaming = false; /* demod lost the lock */ + dvb->stream_start_jiffies = 0; + dvb->num_streamed_psi = 0; +} + +static int vidtv_master_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msgs[], + int num) +{ + return 0; +} + +static u32 vidtv_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +struct i2c_algorithm vidtv_i2c_algorithm = { + .master_xfer = vidtv_master_xfer, + .functionality = vidtv_i2c_func, +}; + +static int vidtv_bridge_i2c_register_adap(struct vidtv_dvb *dvb) +{ + struct i2c_adapter *i2c_adapter = dvb->i2c_adapter; + + strscpy(i2c_adapter->name, "vidtv_i2c", sizeof(i2c_adapter->name)); + i2c_adapter->owner = THIS_MODULE; + i2c_adapter->algo = &vidtv_i2c_algorithm; + i2c_adapter->algo_data = NULL; + i2c_adapter->timeout = 500; + i2c_adapter->retries = 3; + i2c_adapter->dev.parent = NULL; + + i2c_set_adapdata(i2c_adapter, dvb); + return i2c_add_adapter(dvb->i2c_adapter); +} + +static int vidtv_bridge_register_adap(struct vidtv_dvb *dvb) +{ + int ret = 0; + + ret = dvb_register_adapter(&dvb->adapter, + KBUILD_MODNAME, + THIS_MODULE, + &dvb->i2c_adapter->dev, + adapter_nums); + + return ret; +} + +static int vidtv_bridge_dmx_init(struct vidtv_dvb *dvb) +{ + dvb->demux.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING; + + dvb->demux.priv = dvb; + dvb->demux.filternum = 256; + dvb->demux.feednum = 256; + dvb->demux.start_feed = vidtv_start_feed; + dvb->demux.stop_feed = vidtv_stop_feed; + + return dvb_dmx_init(&dvb->demux); +} + +static int vidtv_bridge_dmxdev_init(struct vidtv_dvb *dvb) +{ + dvb->dmx_dev.filternum = 256; + dvb->dmx_dev.demux = &dvb->demux.dmx; + dvb->dmx_dev.capabilities = 0; + + return dvb_dmxdev_init(&dvb->dmx_dev, &dvb->adapter); +} + +static void vidtv_bridge_probe_demod(struct vidtv_dvb *dvb, u32 n) +{ + struct vidtv_demod_config cfg = {0}; + + cfg.drop_tslock_prob_on_low_snr = drop_tslock_prob_on_low_snr; + cfg.recover_tslock_prob_on_good_snr = recover_tslock_prob_on_good_snr; + cfg.chosen_delsys = chosen_delsys; + + dvb->i2c_client_demod[n] = dvb_module_probe("vidtv_demod", + NULL, + dvb->i2c_adapter, + DEMOD_DEFAULT_ADDR, + &cfg); + + /* retrieve a pointer to struct dvb_frontend */ + dvb->fe[n] = cfg.frontend; +} + +static void vidtv_bridge_probe_tuner(struct vidtv_dvb *dvb, u32 n) +{ + struct vidtv_tuner_config cfg = {0}; + + cfg.fe = dvb->fe[n]; + cfg.mock_power_up_delay_msec = mock_power_up_delay_msec; + cfg.mock_tune_delay_msec = mock_tune_delay_msec; + + memcpy(cfg.vidtv_valid_dvb_t_freqs, + vidtv_valid_dvb_t_freqs, + sizeof(vidtv_valid_dvb_t_freqs)); + + memcpy(cfg.vidtv_valid_dvb_c_freqs, + vidtv_valid_dvb_c_freqs, + sizeof(vidtv_valid_dvb_c_freqs)); + + memcpy(cfg.vidtv_valid_dvb_s_freqs, + vidtv_valid_dvb_s_freqs, + sizeof(vidtv_valid_dvb_s_freqs)); + + cfg.max_frequency_shift_hz = max_frequency_shift_hz; + + dvb->i2c_client_tuner[n] = dvb_module_probe("vidtv_tuner", + NULL, + dvb->i2c_adapter, + TUNER_DEFAULT_ADDR, + &cfg); +} + +static int vidtv_bridge_dvb_init(struct vidtv_dvb *dvb) +{ + int ret; + int i, j; + + ret = vidtv_bridge_i2c_register_adap(dvb); + if (ret < 0) + goto fail_i2c; + + ret = vidtv_bridge_register_adap(dvb); + if (ret < 0) + goto fail_adapter; + + vidtv_bridge_probe_demod(dvb, 0); + vidtv_bridge_probe_tuner(dvb, 0); + + for (i = 0; i < NUM_FE; ++i) { + ret = dvb_register_frontend(&dvb->adapter, dvb->fe[i]); + if (ret < 0) + goto fail_fe; + } + + ret = vidtv_bridge_dmx_init(dvb); + if (ret < 0) + goto fail_dmx; + + ret = vidtv_bridge_dmxdev_init(dvb); + if (ret < 0) + goto fail_dmx_dev; + + for (j = 0; j < NUM_FE; ++j) { + ret = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, + &dvb->dmx_fe[j]); + if (ret < 0) + goto fail_dmx_conn; + + /* + * The source of the demux is a frontend connected + * to the demux. + */ + dvb->dmx_fe[j].source = DMX_FRONTEND_0; + } + + return ret; + +fail_dmx_conn: + for (j = j - 1; j >= 0; --j) + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, + &dvb->dmx_fe[j]); +fail_dmx_dev: + dvb_dmxdev_release(&dvb->dmx_dev); +fail_dmx: + dvb_dmx_release(&dvb->demux); +fail_fe: + for (i = i - 1; i >= 0; --i) + dvb_unregister_frontend(dvb->fe[i]); + +fail_adapter: + dvb_unregister_adapter(&dvb->adapter); + +fail_i2c: + i2c_del_adapter(dvb->i2c_adapter); + + return ret; +} + +static int vidtv_bridge_ts_buffer_init(struct vidtv_dvb *dvb) +{ + u32 nbytes_past_boundary; + + if (ts_buf_sz > TS_BUF_MAX_SZ) + ts_buf_sz = TS_BUF_MAX_SZ; + + nbytes_past_boundary = ts_buf_sz % TS_PACKET_LEN; + + /* round to the nearest multiple of 188 */ + if (nbytes_past_boundary) + ts_buf_sz += TS_PACKET_LEN - nbytes_past_boundary; + + dvb->ts_buffer = kzalloc(ts_buf_sz, GFP_KERNEL); + if (!dvb->ts_buffer) + return -ENOMEM; + + dvb->ts_buffer_sz = ts_buf_sz; + + return 0; +} + +static void vidtv_bridge_ts_buffer_destroy(struct vidtv_dvb *dvb) +{ + kfree(dvb->ts_buffer); + dvb->ts_buffer_sz = 0; +} + +static int vidtv_bridge_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct vidtv_dvb *dvb; + + dvb = kzalloc(sizeof(*dvb), GFP_KERNEL); + if (!dvb) + return -ENOMEM; + + ret = vidtv_bridge_dvb_init(dvb); + if (ret < 0) + goto err_dvb; + + ret = vidtv_bridge_ts_buffer_init(dvb); + if (ret < 0) + goto err_ts_buf; + + vidtv_bridge_channels_init(dvb); + vidtv_bridge_mpeg_tables_init(dvb); + + mutex_init(&dvb->feed_lock); + + INIT_WORK(&dvb->mpeg_thread, vidtv_bridge_mpeg_tick); + i2c_set_clientdata(client, dvb); + return ret; + +err_ts_buf: +err_dvb: + kfree(dvb); + return ret; +} + +static int vidtv_bridge_i2c_remove(struct i2c_client *client) +{ + struct vidtv_dvb *dvb; + u32 i; + + dvb = i2c_get_clientdata(client); + + for (i = 0; i < NUM_FE; ++i) + dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, + &dvb->dmx_fe[i]); + + dvb_dmxdev_release(&dvb->dmx_dev); + dvb_dmx_release(&dvb->demux); + + for (i = 0; i < NUM_FE; ++i) { + dvb_unregister_frontend(dvb->fe[i]); + dvb_frontend_detach(dvb->fe[i]); + } + + dvb_unregister_adapter(&dvb->adapter); + + vidtv_bridge_mpeg_tables_destroy(dvb); + vidtv_bridge_channels_destroy(dvb); + vidtv_bridge_ts_buffer_destroy(dvb); + + return 0; +} + +static const struct i2c_device_id vidtv_bridge_id_table[] = { + {"vidtv_bridge", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, vidtv_bridge_id_table); + +static struct i2c_driver vidtv_bridge_driver = { + .driver = { + .name = "vidtv_bridge", + .suppress_bind_attrs = true, + }, + .probe = vidtv_bridge_i2c_probe, + .remove = vidtv_bridge_i2c_remove, + .id_table = vidtv_bridge_id_table, +}; + +module_i2c_driver(vidtv_bridge_driver); diff --git a/drivers/media/test_drivers/vidtv/vidtv_bridge.h b/drivers/media/test_drivers/vidtv/vidtv_bridge.h new file mode 100644 index 0000000000000..4958e5c73e512 --- /dev/null +++ b/drivers/media/test_drivers/vidtv/vidtv_bridge.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * The Virtual DTV test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Written by Daniel W. S. Almeida + */ + +#ifndef VIDTV_BRIDGE_H +#define VIDTV_BRIDGE_H + +#define NUM_FE 1 + +#include +#include +#include +#include +#include +#include "vidtv_common.h" + +struct vidtv_dvb { + struct dvb_frontend *fe[NUM_FE]; + struct dvb_adapter adapter; + struct dvb_demux demux; + struct dmxdev dmx_dev; + struct dmx_frontend dmx_fe[NUM_FE]; + struct i2c_adapter *i2c_adapter; + struct i2c_client *i2c_client_demod[NUM_FE]; + struct i2c_client *i2c_client_tuner[NUM_FE]; + + struct vidtv_psi_table_pat *pat; + struct vidtv_psi_table_pmt *pmt_sections; + u16 num_pmt_sections; /* as many sections as programs in the PAT */ + struct vidtv_psi_table_sdt *sdt; + + struct vidtv_channel channels[NUM_CHANNELS]; + + u32 nfeeds; + struct mutex feed_lock; /* start/stop feed */ + + u8 *ts_buffer; + u32 ts_buffer_sz; + + struct work_struct mpeg_thread; + bool streaming; + unsigned long stream_start_jiffies; + u64 num_streamed_psi; +}; + +#endif // VIDTV_BRIDGE_H diff --git a/drivers/media/test_drivers/vidtv/vidtv_common.h b/drivers/media/test_drivers/vidtv/vidtv_common.h index e6b36429cc8de..d2c869cffe35b 100644 --- a/drivers/media/test_drivers/vidtv/vidtv_common.h +++ b/drivers/media/test_drivers/vidtv/vidtv_common.h @@ -18,6 +18,12 @@ #define TS_PACKET_LEN 188 #define TS_PAYLOAD_LEN 184 #define LAST_VALID_TS_PID 8191 +#define USECS_IN_SEC 1000000 +#define TUNER_DEFAULT_ADDR 0x68 +#define DEMOD_DEFAULT_ADDR 0x60 +#define NUM_CHANNELS 1 +#define TRANSPORT_STREAM_ID 0x744 /* a single stream */ + /* to be used by both PSI and ES */ struct vidtv_mpeg_ts_adaption { @@ -75,6 +81,15 @@ struct vidtv_demod_config { u8 chosen_delsys; }; +struct vidtv_channel { + u16 transport_stream_id; + struct vidtv_psi_table_sdt_service *service; + u16 program_num; + /* a single program with one or more streams associated with it */ + struct vidtv_psi_table_pat_program *program; + struct vidtv_psi_table_pmt_stream *streams; +}; + u32 vidtv_memcpy(void *to, const void *from, size_t len,