From patchwork Wed May 20 07:03:26 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 209768 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.6 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9C1BCC433E1 for ; Wed, 20 May 2020 07:03:56 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 707C3207D3 for ; Wed, 20 May 2020 07:03:56 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="rXmbNI5g" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726745AbgETHDw (ORCPT ); Wed, 20 May 2020 03:03:52 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:42884 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726714AbgETHDv (ORCPT ); Wed, 20 May 2020 03:03:51 -0400 Received: from mail-qt1-x842.google.com (mail-qt1-x842.google.com [IPv6:2607:f8b0:4864:20::842]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9AFEDC061A0E; Wed, 20 May 2020 00:03:51 -0700 (PDT) Received: by mail-qt1-x842.google.com with SMTP id v4so1791033qte.3; Wed, 20 May 2020 00:03:51 -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=eeh2SkWyijymo9cnU9Av2Bb+Xmr6PIKb3ksPyQrjT9I=; b=rXmbNI5gTGNWRLF1wQUtZB2LyQn9YD0sJqIGZWkRqGtwkDOBKvaBMdgvnpUbLgj/+U pJQVcb1EaEb5DLkdmo+gg13eAxhIoAbD5gMoS26twMxEIzDYl1Hnc/TtPDxKzZmMFeKn N91hA9RBq50RxHpQlpgpO5ahVMveCM0rKslW6nQHPF74oebQhnCTbfb6en4W4fW5Ap24 /nCeLCTsodI5Qls8gfMO4mjVpfna0X7Qbxd2bjuLTkMtuBL+o1f5YeQ4K5OA3/JM2BrN gsoBMXi1eZBOFzLUrwgRjTVRBnK7e9r2i0dMVWGOt7dD/aU6Q8iplIGvXMF2Q+2cyymR 4AGw== 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=eeh2SkWyijymo9cnU9Av2Bb+Xmr6PIKb3ksPyQrjT9I=; b=pQTbNKW6+SR3JEW7qXWDCXktW/IGFWSb0UWAv9qlV8qd9MkpQSidytSp+WuweWdOjn RTAwtxQPSRsGqhYqzRxPzKxfg+Sn48wPAuxynTsj0Fkg1aUUyP4aN4xlOLbCQgcx4x1x 42XjcT80CBxf/SdTyh4BsMnn2+kp5oWTQ8QzZyie2itjTE3gipscOQEeOfTrIdbUBwfn EhMgoae39cTCwpUv4nKLM1Sp0TnfUTHEIuwDUtLqg8Bzo4gdkT+CxHlziRpSa0Wo+s46 USRBiBd5L7HOb3YTaMir3WM1V/q9Q8yBw7boiC9dTMzLwpLOPQEJU+v9jRC490E9Smfk eMIw== X-Gm-Message-State: AOAM532qhnFC0vUAIKdw3OVSFuAdXJJ5fjUhLyETYGVd+X5XMW0r3WsK wIPJ63QRp0eeN0BOzr8je9A= X-Google-Smtp-Source: ABdhPJzg55T5Hytku6Km669TJZy80Av/Kmb3uoctl1YzFg6pEG93n6yW/jL0bHG8a/zd8WgY/RmnFw== X-Received: by 2002:ac8:945:: with SMTP id z5mr4022225qth.16.1589958230665; Wed, 20 May 2020 00:03:50 -0700 (PDT) Received: from localhost.localdomain ([2804:14d:72b1:8920:a2ce:f815:f14d:bfac]) by smtp.gmail.com with ESMTPSA id i23sm1598893qke.65.2020.05.20.00.03.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 May 2020 00:03:50 -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, v6 02/10] media: vidtv: implement a tuner driver Date: Wed, 20 May 2020 04:03:26 -0300 Message-Id: <20200520070334.1778751-3-dwlsalmeida@gmail.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200520070334.1778751-1-dwlsalmeida@gmail.com> References: <20200520070334.1778751-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 | 3 + .../media/test-drivers/vidtv/vidtv_tuner.c | 408 ++++++++++++++++++ .../media/test-drivers/vidtv/vidtv_tuner.h | 26 ++ 3 files changed, 437 insertions(+) create mode 100644 drivers/media/test-drivers/vidtv/vidtv_tuner.c create mode 100644 drivers/media/test-drivers/vidtv/vidtv_tuner.h diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile index d1558d84eeaed..58e022c094e5e 100644 --- a/drivers/media/test-drivers/vidtv/Makefile +++ b/drivers/media/test-drivers/vidtv/Makefile @@ -1,2 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 +dvb-vidtv-tuner-objs := vidtv_tuner.o + +obj-$(CONFIG_DVB_VIDTV) += dvb-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..3f2b4b9a6d568 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_tuner.c @@ -0,0 +1,408 @@ +// 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 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vidtv_tuner.h" + +MODULE_DESCRIPTION("Virtual DVB Tuner"); +MODULE_AUTHOR("Daniel W. S. Almeida"); +MODULE_LICENSE("GPL"); + +struct vidtv_tuner_cnr_to_qual_s { + /* attempt to use the same values as libdvbv5 */ + u32 modulation; + u32 fec; + u32 cnr_ok, cnr_good; +}; + +static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_c_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QAM_256, FEC_NONE, 34000, 38000}, + { QAM_64, FEC_NONE, 30000, 34000}, +}; + +static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QPSK, FEC_1_2, 7000, 10000}, + + { QPSK, FEC_2_3, 9000, 12000}, + { QPSK, FEC_3_4, 10000, 13000}, + { QPSK, FEC_5_6, 11000, 14000}, + + { QPSK, FEC_7_8, 12000, 15000}, +}; + +static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_s2_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QPSK, FEC_1_2, 9000, 12000}, + { QPSK, FEC_2_3, 11000, 14000}, + { QPSK, FEC_3_4, 12000, 15000}, + { QPSK, FEC_5_6, 12000, 15000}, + { QPSK, FEC_8_9, 13000, 16000}, + { QPSK, FEC_9_10, 13500, 16500}, + { PSK_8, FEC_2_3, 14500, 17500}, + { PSK_8, FEC_3_4, 16000, 19000}, + { PSK_8, FEC_5_6, 17500, 20500}, + { PSK_8, FEC_8_9, 19000, 22000}, +}; + +static const struct vidtv_tuner_cnr_to_qual_s vidtv_tuner_t_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db*/ + { QPSK, FEC_1_2, 4100, 5900}, + { QPSK, FEC_2_3, 6100, 9600}, + { QPSK, FEC_3_4, 7200, 12400}, + { QPSK, FEC_5_6, 8500, 15600}, + { QPSK, FEC_7_8, 9200, 17500}, + + { QAM_16, FEC_1_2, 9800, 11800}, + { QAM_16, FEC_2_3, 12100, 15300}, + { QAM_16, FEC_3_4, 13400, 18100}, + { QAM_16, FEC_5_6, 14800, 21300}, + { QAM_16, FEC_7_8, 15700, 23600}, + + { QAM_64, FEC_1_2, 14000, 16000}, + { QAM_64, FEC_2_3, 19900, 25400}, + { QAM_64, FEC_3_4, 24900, 27900}, + { QAM_64, FEC_5_6, 21300, 23300}, + { QAM_64, FEC_7_8, 22000, 24000}, +}; + +struct vidtv_tuner_hardware_state { + 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); + + return -EINVAL; + } + + 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 -EINVAL; +} + +static int +vidtv_tuner_get_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct dtv_frontend_properties *c = &fe->dtv_property_cache; + const 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_ratelimited("%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); diff --git a/drivers/media/test-drivers/vidtv/vidtv_tuner.h b/drivers/media/test-drivers/vidtv/vidtv_tuner.h new file mode 100644 index 0000000000000..46407739f5d44 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_tuner.h @@ -0,0 +1,26 @@ +/* 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_TUNER_H +#define VIDTV_TUNER_H + +#include +#include + +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; +}; + +#endif //VIDTV_TUNER_H From patchwork Wed May 20 07:03:27 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 209767 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.6 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EB378C433E2 for ; Wed, 20 May 2020 07:04:01 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B9A0420756 for ; Wed, 20 May 2020 07:04:01 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Vp2Nb3v7" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726766AbgETHD4 (ORCPT ); Wed, 20 May 2020 03:03:56 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:42898 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726714AbgETHDz (ORCPT ); Wed, 20 May 2020 03:03:55 -0400 Received: from mail-qk1-x743.google.com (mail-qk1-x743.google.com [IPv6:2607:f8b0:4864:20::743]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C3D8EC061A0E; Wed, 20 May 2020 00:03:55 -0700 (PDT) Received: by mail-qk1-x743.google.com with SMTP id 142so2576858qkl.6; Wed, 20 May 2020 00:03:55 -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=WKL/OALCC72asuyabvcbfE6MUUWvMbmr8JW8DOP0Sws=; b=Vp2Nb3v7CpCqdveFbBqeFlyPGcCl8UhLZUm16YQ49L124BWaCtoweQjV+pAln5Oyca /5eyAMUpPLvDPez3Hhi516Q2SP0RGNAnSPZwQvFQouDiGiO/N+wyuNn/yw1Lglz1YI8r k2KMwhqnpErFN+g5HFp8hHpAtMPouND+vo1v3uauxbFA+ZcpkSaGqXFR82I7ApXaxfm6 cAl0w8+GvCSphtCvd1cKwye5+MzYhJWbdbrqJUsa479T9OnlVRrnVmmNda3pqjkkXHgI L1FWzHFTidsK5newE/gn8djNWm/GlOft9MZPQ7kYG4A8wUpxg8pl0619sIYd/1dD88Wa bL9Q== 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=WKL/OALCC72asuyabvcbfE6MUUWvMbmr8JW8DOP0Sws=; b=GizZp6HP99vhFqoVf4IwbZf3vMBrTdYXVXUNP1zHeS3rQnKksvTfET44KOacrruiv1 bmCqMOmm8CCU8wcXCgP8IfhAE1/Uuwgqq7u2s9V0bLSUiya7o0zVgj3fByu0LtujK2Jn NLaECznB8C4b8jhcadFxyFKz1D8wCZcMpx+DtBoBv6zS4COGFqgxNufI1Zt3OZ5NWKFg cJGJaW6eDV9rJ/Ekw0wcgJLYZ5Ii22QDYBcpt3Rxfcwm+tpXgv3WLBg3NcSncYBOPzMK wWOxc0Fs+Hsq8lxXVmnY+l+ZMaDS5ZrN3Qx2T433Zb4/B4WpQ+bXdkUWM5Z3tK0JvHVi wXwA== X-Gm-Message-State: AOAM531xAP29/0RT2EIhibNGR9bcEkM0HVG02nCEeXcCaxbsnyFor7O6 AOGsfWAxQA7M1FJzQPp9U3o= X-Google-Smtp-Source: ABdhPJzjSLgg9df2qBqD90inK0gZ/+smL2Dh03BSVFzfHMA0d+uOycgHn4R3Leco/CBRgDIrZkLj1g== X-Received: by 2002:a05:620a:1312:: with SMTP id o18mr3352832qkj.277.1589958234752; Wed, 20 May 2020 00:03:54 -0700 (PDT) Received: from localhost.localdomain ([2804:14d:72b1:8920:a2ce:f815:f14d:bfac]) by smtp.gmail.com with ESMTPSA id i23sm1598893qke.65.2020.05.20.00.03.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 May 2020 00:03:54 -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, v6 03/10] media: vidtv: implement a demodulator driver Date: Wed, 20 May 2020 04:03:27 -0300 Message-Id: <20200520070334.1778751-4-dwlsalmeida@gmail.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200520070334.1778751-1-dwlsalmeida@gmail.com> References: <20200520070334.1778751-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 | 3 +- .../media/test-drivers/vidtv/vidtv_demod.c | 444 ++++++++++++++++++ .../media/test-drivers/vidtv/vidtv_demod.h | 41 ++ 3 files changed, 487 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 58e022c094e5e..21e50c11c94d0 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 dvb-vidtv-tuner-objs := vidtv_tuner.o +dvb-vidtv-demod-objs := vidtv_demod.o -obj-$(CONFIG_DVB_VIDTV) += dvb-vidtv-tuner.o +obj-$(CONFIG_DVB_VIDTV) += dvb-vidtv-tuner.o dvb-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..15912e063cf5a --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.c @@ -0,0 +1,444 @@ +// 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 +#include + +#include "vidtv_demod.h" + +// FIXME: just added a random value here +#define POLL_THRD_TIME 10 /* ms */ + +static const struct vidtv_demod_cnr_to_qual_s vidtv_demod_c_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QAM_256, FEC_NONE, 34000, 38000}, + { QAM_64, FEC_NONE, 30000, 34000}, +}; + +static const struct vidtv_demod_cnr_to_qual_s vidtv_demod_s_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QPSK, FEC_1_2, 7000, 10000}, + { QPSK, FEC_2_3, 9000, 12000}, + { QPSK, FEC_3_4, 10000, 13000}, + { QPSK, FEC_5_6, 11000, 14000}, + { QPSK, FEC_7_8, 12000, 15000}, +}; + +static const struct vidtv_demod_cnr_to_qual_s vidtv_demod_s2_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db */ + { QPSK, FEC_1_2, 9000, 12000}, + { QPSK, FEC_2_3, 11000, 14000}, + { QPSK, FEC_3_4, 12000, 15000}, + { QPSK, FEC_5_6, 12000, 15000}, + { QPSK, FEC_8_9, 13000, 16000}, + { QPSK, FEC_9_10, 13500, 16500}, + { PSK_8, FEC_2_3, 14500, 17500}, + { PSK_8, FEC_3_4, 16000, 19000}, + { PSK_8, FEC_5_6, 17500, 20500}, + { PSK_8, FEC_8_9, 19000, 22000}, +}; + +static const struct vidtv_demod_cnr_to_qual_s vidtv_demod_t_cnr_2_qual[] = { + /* from libdvbv5 source code, in milli db*/ + { QPSK, FEC_1_2, 4100, 5900}, + { QPSK, FEC_2_3, 6100, 9600}, + { QPSK, FEC_3_4, 7200, 12400}, + { QPSK, FEC_5_6, 8500, 15600}, + { QPSK, FEC_7_8, 9200, 17500}, + + { QAM_16, FEC_1_2, 9800, 11800}, + { QAM_16, FEC_2_3, 12100, 15300}, + { QAM_16, FEC_3_4, 13400, 18100}, + { QAM_16, FEC_5_6, 14800, 21300}, + { QAM_16, FEC_7_8, 15700, 23600}, + + { QAM_64, FEC_1_2, 14000, 16000}, + { QAM_64, FEC_2_3, 19900, 25400}, + { QAM_64, FEC_3_4, 24900, 27900}, + { QAM_64, FEC_5_6, 21300, 23300}, + { QAM_64, FEC_7_8, 22000, 24000}, +}; + +static const struct vidtv_demod_cnr_to_qual_s +*vidtv_match_cnr_s(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *c; + const 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_ratelimited("%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; + const 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); + 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(POLL_THRD_TIME)); +} + +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; +} + +/* + * NOTE: + * This is implemented here just to be used as an example for real + * demod drivers. + * + * Should only be implemented if it actually reads something from the hardware. + * Also, it should check for the locks, in order to avoid report wrong data + * to userspace. + */ +static int vidtv_demod_get_frontend(struct dvb_frontend *fe, + struct dtv_frontend_properties *p) +{ + return 0; +} + +static int vidtv_demod_set_frontend(struct dvb_frontend *fe) +{ + struct vidtv_demod_state *state = fe->demodulator_priv; + const 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(POLL_THRD_TIME)); + + state->poll_snr_thread_running = true; + } + + if (fe->ops.i2c_gate_ctrl) + fe->ops.i2c_gate_ctrl(fe, 0); + } + + return 0; +} + +static int vidtv_demod_sleep(struct dvb_frontend *fe) +{ + struct vidtv_demod_state *state = fe->demodulator_priv; + + if (state->poll_snr_thread_running) { + cancel_delayed_work_sync(&state->poll_snr); + state->poll_snr_thread_running = false; + state->poll_snr_thread_restart = true; + } + return 0; +} + +static int vidtv_demod_init(struct dvb_frontend *fe) +{ + struct vidtv_demod_state *state = fe->demodulator_priv; + u32 tuner_status = 0; + + if (state->cold_start) + INIT_DELAYED_WORK(&state->poll_snr, + &vidtv_demod_poll_snr_handler); + + /* + * At resume, start the snr poll thread only if it was suspended with + * the thread running. Extra care should be taken here, as some tuner + * status change might happen at resume time (for example, due to an + * 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(POLL_THRD_TIME)); + + state->poll_snr_thread_restart = false; + } + + state->cold_start = false; + return 0; +} + +/* + * NOTE: + * This is implemented here just to be used as an example for real + * demod drivers. + * + * Should only be implemented if the demod has support for DVB-S or DVB-S2 + */ +static int vidtv_demod_set_tone(struct dvb_frontend *fe, + enum fe_sec_tone_mode tone) +{ + return 0; +} + +/* + * NOTE: + * This is implemented here just to be used as an example for real + * demod drivers. + * + * Should only be implemented if the demod has support for DVB-S or DVB-S2 + */ +static int vidtv_demod_set_voltage(struct dvb_frontend *fe, + enum fe_sec_voltage voltage) +{ + return 0; +} + +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_ops = { + .delsys = { + SYS_DVBT, + SYS_DVBT2, + SYS_DVBC_ANNEX_A, + SYS_DVBS, + SYS_DVBS2, + }, + + .info = { + .name = "Dummy demod for DVB-T/T2/C/S/S2", + .frequency_min_hz = 51 * MHz, + .frequency_max_hz = 2150 * MHz, + .frequency_stepsize_hz = 62500, + .frequency_tolerance_hz = 29500 * kHz, + .symbol_rate_min = 1000000, + .symbol_rate_max = 45000000, + + .caps = FE_CAN_FEC_1_2 | + FE_CAN_FEC_2_3 | + FE_CAN_FEC_3_4 | + FE_CAN_FEC_4_5 | + FE_CAN_FEC_5_6 | + FE_CAN_FEC_6_7 | + FE_CAN_FEC_7_8 | + FE_CAN_FEC_8_9 | + FE_CAN_FEC_AUTO | + FE_CAN_QAM_16 | + FE_CAN_QAM_64 | + FE_CAN_QAM_32 | + FE_CAN_QAM_128 | + FE_CAN_QAM_256 | + FE_CAN_QAM_AUTO | + FE_CAN_QPSK | + FE_CAN_FEC_AUTO | + FE_CAN_INVERSION_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = vidtv_demod_release, + + .init = vidtv_demod_init, + .sleep = vidtv_demod_sleep, + + .set_frontend = vidtv_demod_set_frontend, + .get_frontend = vidtv_demod_get_frontend, + + .read_status = vidtv_demod_read_status, + .read_ber = vidtv_demod_read_ber, + .read_signal_strength = vidtv_demod_read_signal_strength, + .read_snr = vidtv_demod_read_snr, + .read_ucblocks = vidtv_demod_read_ucblocks, + + /* For DVB-S/S2 */ + .set_voltage = vidtv_demod_set_voltage, + .set_tone = vidtv_demod_set_tone, +}; + +static const struct i2c_device_id vidtv_demod_i2c_id_table[] = { + {"vidtv_demod", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, vidtv_demod_i2c_id_table); + +static int vidtv_demod_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct vidtv_demod_state *state; + + /* allocate memory for the internal state */ + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + /* create dvb_frontend */ + memcpy(&state->frontend.ops, + &vidtv_demod_ops, + sizeof(struct dvb_frontend_ops)); + + state->frontend.demodulator_priv = state; + 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); + +MODULE_DESCRIPTION("Virtual DVB Demodulator Driver"); +MODULE_AUTHOR("Daniel W. S. Almeida"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/test-drivers/vidtv/vidtv_demod.h b/drivers/media/test-drivers/vidtv/vidtv_demod.h new file mode 100644 index 0000000000000..b14957d9d4ea0 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_demod.h @@ -0,0 +1,41 @@ +/* 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 { + /* 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; +}; + +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 Wed May 20 07:03:30 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 209766 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.6 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0EED8C433E1 for ; Wed, 20 May 2020 07:04:13 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D013120884 for ; Wed, 20 May 2020 07:04:12 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="hL3uKUZC" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726835AbgETHEI (ORCPT ); Wed, 20 May 2020 03:04:08 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:42932 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726714AbgETHEH (ORCPT ); Wed, 20 May 2020 03:04:07 -0400 Received: from mail-qk1-x742.google.com (mail-qk1-x742.google.com [IPv6:2607:f8b0:4864:20::742]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 66BBCC061A0E; Wed, 20 May 2020 00:04:07 -0700 (PDT) Received: by mail-qk1-x742.google.com with SMTP id 142so2577241qkl.6; Wed, 20 May 2020 00:04:07 -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=Eu6zyVAv2okqAu2MpDIX4+uWLByOhOoVUD/ukYkdvtU=; b=hL3uKUZC+ShBYI7GA2vsr0K3vXSxPUe7eJnW9YA+ujZoBCfPsgwzaUXtKgFI+xOghY iCWRM0fxH7gdulYBcV0mF1Ts0QxaPzpbgbiv4PCwITXw6+ZkasZUaQsVqKbOE25wWz78 Sob0yDX+m5VKXwPz3UwvMm6lQt4cHDW6ObTQYACHxsIZU5IKa/EtxGsMT9dPRtQLF4b3 7QEiSK1jEHocuxR45g/jXgFGz8p1wYo7vzQJb6TL6wKIDZcZflaljAnLPi9whUvyo16x mgv26ZTYU0x2X7/1nkLz+vAVpugybaMT5RqoytZWl9e4dCtn8aRYicNc2Hfla8SE6tJ2 jRHQ== 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=Eu6zyVAv2okqAu2MpDIX4+uWLByOhOoVUD/ukYkdvtU=; b=mzURbuSg2AMbGj46ALOpVNEhUsl3MPcAK6hAUhytXQiicQYIkluvshn+oIrQyU8pEM PwTMyJ3KuqvahD2IC3/572e2LuL/lccF+o2I/so8BNhpPRzgmYmJBiNMtWEshW76jBlW bhKVdxPF9Csgk1D7DYqhF+9ODve2VmJJSZOvELnT5JdMIrLM/Ujzgn6MTQnyEg1gCE/h 1vUQPnbeCu+YctWJHecKDQrAkgd12Itn/mFHVw25YQwljxu4VSyu0gJWe1lImdfTrmuz ash3WdfzWnha/SbIqlAYlohM0N4wJh2sbji9fz/SRkJ0F4+/4olFyiabqgE2ds69B53+ h+5w== X-Gm-Message-State: AOAM532ez6BQavnXeqfD6RvBDVE0GI/0sB/5PSDkTOzO6hFqQEsjeDuX DR01Yh6FuYARa5OqfsKDSZw= X-Google-Smtp-Source: ABdhPJwIM5NOT97Nxp2fyd1Y2xqqHdqgG0DhKVvamSgFiegtoWtz85BmA/oPp1PqnYHz1325W8Qh9A== X-Received: by 2002:a37:a7c8:: with SMTP id q191mr3272536qke.214.1589958246511; Wed, 20 May 2020 00:04:06 -0700 (PDT) Received: from localhost.localdomain ([2804:14d:72b1:8920:a2ce:f815:f14d:bfac]) by smtp.gmail.com with ESMTPSA id i23sm1598893qke.65.2020.05.20.00.04.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 May 2020 00:04:05 -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, v6 06/10] media: vidtv: add MPEG TS common code Date: Wed, 20 May 2020 04:03:30 -0300 Message-Id: <20200520070334.1778751-7-dwlsalmeida@gmail.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200520070334.1778751-1-dwlsalmeida@gmail.com> References: <20200520070334.1778751-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 code to work with MPEG TS packets, such as TS headers, adaptation fields, PCR packets and NULL packets. Signed-off-by: Daniel W. S. Almeida --- drivers/media/test-drivers/vidtv/vidtv_ts.c | 157 ++++++++++++++++++++ drivers/media/test-drivers/vidtv/vidtv_ts.h | 120 +++++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.c create mode 100644 drivers/media/test-drivers/vidtv/vidtv_ts.h diff --git a/drivers/media/test-drivers/vidtv/vidtv_ts.c b/drivers/media/test-drivers/vidtv/vidtv_ts.c new file mode 100644 index 0000000000000..77908c4e85eef --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_ts.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Virtual DVB test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Written by Daniel W. S. Almeida + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__ + +#include +#include +#include +#include + +#include "vidtv_ts.h" +#include "vidtv_common.h" + +static u32 vidtv_ts_write_pcr_bits(u8 *to, u32 to_offset, u64 pcr) +{ + /* Exact same from ffmpeg. PCR is a counter driven by a 27Mhz clock */ + u64 pcr_low = pcr % 300; + u64 pcr_high = pcr / 300; + u8 *buf = to + to_offset; + + *buf++ = pcr_high >> 25; + *buf++ = pcr_high >> 17; + *buf++ = pcr_high >> 9; + *buf++ = pcr_high >> 1; + *buf++ = pcr_high << 7 | pcr_low >> 8 | 0x7e; + *buf++ = pcr_low; + + return 6; +} + +void vidtv_ts_inc_cc(u8 *continuity_counter) +{ + ++*continuity_counter; + if (*continuity_counter > TS_CC_MAX_VAL) + *continuity_counter = 0; +} + +u32 vidtv_ts_null_write_into(struct null_packet_write_args args) +{ + u32 nbytes = 0; + struct vidtv_mpeg_ts ts_header = {0}; + + ts_header.sync_byte = TS_SYNC_BYTE; + ts_header.pid = TS_NULL_PACKET_PID; + ts_header.payload = 1; + ts_header.continuity_counter = *args.continuity_counter; + + cpu_to_be16s(&ts_header.bitfield); + + /* copy TS header */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.buf_sz, + &ts_header, + sizeof(ts_header)); + + be16_to_cpus(&ts_header.bitfield); + + /* + * Note: + * This will cause a discontinuity if the buffer is full, + * effectively dropping the packet, since nothing will get written + * but the continuity counter will still be incremented. This way, + * userspace can detect packet losses. + */ + vidtv_ts_inc_cc(args.continuity_counter); + + /* fill the rest with empty data */ + nbytes += vidtv_memset(args.dest_buf, + args.dest_offset + nbytes, + args.buf_sz, + TS_FILL_BYTE, + TS_PACKET_LEN - nbytes); + + /* we should have written exactly _one_ 188byte packet */ + if (nbytes != TS_PACKET_LEN) + pr_warn_ratelimited("Expected exactly %d bytes, got %d\n", + TS_PACKET_LEN, + nbytes); + + return nbytes; +} + +u32 vidtv_ts_pcr_write_into(struct pcr_write_args args) +{ + u32 nbytes = 0; + struct vidtv_mpeg_ts ts_header = {0}; + struct vidtv_mpeg_ts_adaption ts_adap = {0}; + + ts_header.sync_byte = TS_SYNC_BYTE; + ts_header.tei = 0; + ts_header.payload_start = 0; + ts_header.pid = args.pid; + ts_header.priority = 0; + ts_header.scrambling = 0; + /* cc is not incremented, but we need it. see 13818-1 clause 2.4.3.3 */ + ts_header.continuity_counter = *args.continuity_counter; + ts_header.payload = 0; + ts_header.adaptation_field = 1; + + /* 13818-1 clause 2.4.3.5 */ + ts_adap.length = 183; + ts_adap.PCR = 1; + + /* + * Note: + * Nothing will get written if the buffer is full, this will + * essentially drop the packet. The continuity counter is not + * updated for PCR packets, so it will be as if no PCR packet was + * sent at all. + */ + + cpu_to_be16s(&ts_header.bitfield); + + /* copy TS header */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.buf_sz, + &ts_header, + sizeof(ts_header)); + + be16_to_cpus(&ts_header.bitfield); + + /* write the adap after the TS header */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.buf_sz, + &ts_adap, + sizeof(ts_adap)); + + /* write the PCR optional */ + cpu_to_be64s(&args.pcr); + nbytes += vidtv_ts_write_pcr_bits(args.dest_buf, + args.dest_offset + nbytes, + args.pcr); + be64_to_cpus(&args.pcr); + + nbytes += vidtv_memset(args.dest_buf, + args.dest_offset + nbytes, + args.buf_sz, + TS_FILL_BYTE, + TS_PACKET_LEN - nbytes); + + /* we should have written exactly _one_ 188byte packet */ + if (nbytes != TS_PACKET_LEN) + pr_warn_ratelimited("Expected exactly %d bytes, got %d\n", + TS_PACKET_LEN, + nbytes); + + return nbytes; +} diff --git a/drivers/media/test-drivers/vidtv/vidtv_ts.h b/drivers/media/test-drivers/vidtv/vidtv_ts.h new file mode 100644 index 0000000000000..b6c638d13d14f --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_ts.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * The Virtual DVB test driver serves as a reference DVB driver and helps + * validate the existing APIs in the media subsystem. It can also aid + * developers working on userspace applications. + * + * Written by Daniel W. S. Almeida + */ + +#ifndef VIDTV_TS_H +#define VIDTV_TS_H + +#include +#include + +#define TS_SYNC_BYTE 0x47 +#define TS_PACKET_LEN 188 +#define TS_PAYLOAD_LEN 184 +#define TS_NULL_PACKET_PID 0x1fff +#define TS_CC_MAX_VAL 0x0f /* 4 bits */ +#define TS_LAST_VALID_PID 8191 +#define TS_FILL_BYTE 0xff /* the byte used in packet stuffing */ + +struct vidtv_mpeg_ts_adaption { + u8 length; + struct { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 extension:1; + u8 private_data:1; + u8 splicing_point:1; + u8 OPCR:1; + u8 PCR:1; + u8 priority:1; + u8 random_access:1; + u8 discontinued:1; +#elif defined(__BIG_ENDIAN_BITFIELD) + u8 discontinued:1; + u8 random_access:1; + u8 priority:1; + u8 PCR:1; + u8 OPCR:1; + u8 splicing_point:1; + u8 private_data:1; + u8 extension:1; +#else +#error "Unknown bitfield ordering" +#endif + } __packed; + u8 data[]; +} __packed; + +struct vidtv_mpeg_ts { + u8 sync_byte; + union { + u16 bitfield; + struct { + u16 pid:13; + u16 priority:1; + u16 payload_start:1; + u16 tei:1; + } __packed; + } __packed; + struct { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 continuity_counter:4; + u8 payload:1; + u8 adaptation_field:1; + u8 scrambling:2; +#elif defined(__BIG_ENDIAN_BITFIELD) + u8 scrambling:2; + u8 adaptation_field:1; + u8 payload:1; + u8 continuity_counter:4; +#else +#error "Unknown bitfield ordering" +#endif + } __packed; + struct vidtv_mpeg_ts_adaption adaption[]; +} __packed; + +/** + * struct pcr_write_args - Arguments for the pcr_write_into function. + * @dest_buf: The buffer to write into. + * @dest_offset: The byte offset into the buffer. + * @pid: The TS PID for the PCR packets. + * @buf_sz: The size of the buffer in bytes. + * @countinuity_counter: The TS continuity_counter. + * @pcr: A sample from the system clock. + */ +struct pcr_write_args { + void *dest_buf; + u32 dest_offset; + u16 pid; + u32 buf_sz; + u8 *continuity_counter; + u64 pcr; +}; + +/** + * struct null_packet_write_args - Arguments for the null_write_into function + * @dest_buf: The buffer to write into. + * @dest_offset: The byte offset into the buffer. + * @buf_sz: The size of the buffer in bytes. + * @countinuity_counter: The TS continuity_counter. + */ +struct null_packet_write_args { + void *dest_buf; + u32 dest_offset; + u32 buf_sz; + u8 *continuity_counter; +}; + +/* Increment the continuity counter */ +void vidtv_ts_inc_cc(u8 *continuity_counter); + +u32 vidtv_ts_null_write_into(struct null_packet_write_args args); + +u32 vidtv_ts_pcr_write_into(struct pcr_write_args args); + +#endif //VIDTV_TS_H From patchwork Wed May 20 07:03:31 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 209765 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.6 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9BCABC433DF for ; Wed, 20 May 2020 07:04:17 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 64C4920888 for ; Wed, 20 May 2020 07:04:17 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="m2AOsMKJ" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726850AbgETHEN (ORCPT ); Wed, 20 May 2020 03:04:13 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:42948 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726714AbgETHEM (ORCPT ); Wed, 20 May 2020 03:04:12 -0400 Received: from mail-qk1-x743.google.com (mail-qk1-x743.google.com [IPv6:2607:f8b0:4864:20::743]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A3000C061A0E; Wed, 20 May 2020 00:04:12 -0700 (PDT) Received: by mail-qk1-x743.google.com with SMTP id s1so2553402qkf.9; Wed, 20 May 2020 00:04:12 -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=mJaZFu8McWaOUnoSKg9YQTxctJ8piA8/3a2sbTFnGew=; b=m2AOsMKJ9PqR6DzyawZCijNwu9ye3t1Mm7kzm1JjJhi1edYREXVmucwmuSfFbL6eIf jtfVBmyv5RkdQrq4MMdYyBk7eko+D6ek58KCHowT9J4z88OD2Ddq1q00EFQvlqc3QQ+h Q0wfQoN/7Q9dts/Ex+e7dkQwLU0rKN4mGHqsr+y2cSUcz6Kd30nUYdBCnkDiKU6lbRPL 3DVrVXvTvKcU5/HWLMN1UvpXmV4Gt7XWco2IvjuVA8i8hrfY0hkildoblOzGeUe37DX6 aZQdp9UsM4hQIWMs3qwpOKq6aoQAmi2V7aR0FQS1QW8A3DKCfkZRCJfapnucJKvi7NPV 1TyA== 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=mJaZFu8McWaOUnoSKg9YQTxctJ8piA8/3a2sbTFnGew=; b=hm7GBhqlptbqLKzHbMvFNphgQ9Ngk9df+gIRAGd7rOYcrfaKW+9f4CzniYo9Aj/l6d nd41NeHVxgDmyE+MeW7FjXINpusHGtdTUbi/BmNwSyI33X358xAjL1CgR3J2dw+AXc0G K3LMUkIWcOY/gvj//tGdLlM1FqmWlj1U/c6a6fpiSm7x9F0HAsgskMUKDHBUtjFDbksa 78Vi8GeNY9Akk1oWZv71ypdHCCQ1ezi93E3Qp18OeMl9oTggBww6VU+wwpSXuJv3eL5X 77ibzUsEZBsRimb+rwcbtozz6UGRiX6Jahfx6f/QQVcyAevQoOZSHG6FrwRcGWk2/L37 Y2YQ== X-Gm-Message-State: AOAM530nQR3+9PfIpZ3zmX6ECP2VwRpmpsno2EYF54Hn+RkZDna8bwwf 1T+DRN9bYTxwPVRW1H+3umQ= X-Google-Smtp-Source: ABdhPJw3d3dcI80NRqapX+hItbi0XuhRxJ0wtz+0K1+Gsq+bIJnHEbQF1kD82c7CE7TCPdofOtfXTw== X-Received: by 2002:a05:620a:699:: with SMTP id f25mr3460618qkh.192.1589958251431; Wed, 20 May 2020 00:04:11 -0700 (PDT) Received: from localhost.localdomain ([2804:14d:72b1:8920:a2ce:f815:f14d:bfac]) by smtp.gmail.com with ESMTPSA id i23sm1598893qke.65.2020.05.20.00.04.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 May 2020 00:04: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, v6 07/10] media: vidtv: implement a PSI generator Date: Wed, 20 May 2020 04:03:31 -0300 Message-Id: <20200520070334.1778751-8-dwlsalmeida@gmail.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200520070334.1778751-1-dwlsalmeida@gmail.com> References: <20200520070334.1778751-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/vidtv_psi.c | 1037 ++++++++++++++++++ drivers/media/test-drivers/vidtv/vidtv_psi.h | 415 +++++++ 2 files changed, 1452 insertions(+) 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/vidtv_psi.c b/drivers/media/test-drivers/vidtv/vidtv_psi.c new file mode 100644 index 0000000000000..51e9114173362 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_psi.c @@ -0,0 +1,1037 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file contains the logic to work with MPEG Program-Specific Information. + * These are defined both in ISO/IEC 13818-1 (systems) and ETSI EN 300 468. + * PSI is carried in the form of table structures, and although each table might + * technically be broken into one or more sections, we do not do this here, + * hence 'table' and 'section' are interchangeable for us. + * + * This code currently supports three tables: PAT, PMT and SDT. These are the + * bare minimum to get userspace to recognize our MPEG transport stream. It can + * be extended to support more PSI tables in the future. + * + * A note on endianness: MPEG layout is big-endian, therefore: + * - All fields spanning more than a byte must undergo 'cpu_to_beXX()' + * - All bitfields must have their ordering reversed if + * __LITTLE_ENDIAN_BITFIELD is defined. + * + * Written by: Daniel W. S. Almeida + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include + +#include "vidtv_psi.h" +#include "vidtv_common.h" +#include "vidtv_ts.h" + +#define CRC_SIZE_IN_BYTES 4 + +static u32 +vidtv_psi_ts_psi_write_into(struct psi_write_args args) +{ + /* + * Packetize PSI sections into TS packets: + * push a TS header (4bytes) every 184 bytes + * manage the continuity_counter + * add stuffing after the CRC + */ + + u32 nbytes_past_boundary = (args.dest_offset % TS_PACKET_LEN); + bool aligned = nbytes_past_boundary == 0; + + /* + * whether we need to fragment the data into multiple ts packets + * if we are aligned we need to spare one byte for the pointer_field + */ + bool split = (aligned) ? + args.len > TS_PAYLOAD_LEN - 1 : + nbytes_past_boundary + args.len > TS_PACKET_LEN; + + /* how much we can write in this packet */ + u32 payload_write_len = (split) ? + (aligned) ? TS_PAYLOAD_LEN : + TS_PACKET_LEN - nbytes_past_boundary : + args.len; + + struct psi_write_args new_args = {}; + struct vidtv_mpeg_ts ts_header = {}; + + u32 nbytes = 0; /* number of bytes written by this function */ + u32 temp = 0; + + /* Just a sanity check, should not really happen because we stuff + * the packet when we finish a section, i.e. when we write the crc at + * the end. But if this happens then we have messed up the logic + * somewhere. + */ + if (args.new_psi_section && !aligned) { + pr_warn_ratelimited("Cannot write a new PSI section in a misaligned buffer\n"); + + /* forcibly align and hope for the best */ + nbytes += vidtv_memset(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + TS_FILL_BYTE, + TS_PACKET_LEN - nbytes_past_boundary); + + 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; + ts_header.continuity_counter = *args.continuity_counter; + ts_header.payload = 1; + /* no adaptation field */ + ts_header.adaptation_field = 0; + + /* copy the header */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + &ts_header, + sizeof(ts_header)); + + /* + * increment the countinuity counter since we have started a new + * packet + * + * This will trigger a discontinuity if the buffer is full, + * which is what we want: the continuity counter will be + * incremented but nothing will get copied by + * vidtv_memcpy/vidtv_memset, effectively dropping the packet. + */ + vidtv_ts_inc_cc(args.continuity_counter); + } + + if (args.new_psi_section) { + /* write the pointer_field in the first byte of the payload */ + temp = vidtv_memset(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + 0x0, + 1); + /* one byte was used by the pointer field*/ + nbytes += temp; + if (payload_write_len == TS_PAYLOAD_LEN) + payload_write_len -= temp; + } + + /* write as much of the payload as we possibly can */ + nbytes += vidtv_memcpy(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + args.from, + payload_write_len); + + if (split) { + /* 'payload_write_len' written from a total of 'len' requested*/ + args.len -= payload_write_len; + /* + * recursively write the rest of the data until we do not + * need to split it anymore + */ + memcpy(&new_args, &args, sizeof(struct psi_write_args)); + new_args.from = args.from + payload_write_len; + new_args.dest_offset = args.dest_offset + nbytes; + new_args.new_psi_section = false; + + nbytes += vidtv_psi_ts_psi_write_into(new_args); + } + + /* + * as the CRC is last in the section, stuff the rest of the + * packet if there is any remaining space in there + */ + if (args.is_crc) + nbytes += vidtv_memset(args.dest_buf, + args.dest_offset + nbytes, + args.dest_buf_sz, + TS_FILL_BYTE, + TS_PACKET_LEN - payload_write_len); + + 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; + __be32 crc; + struct psi_write_args psi_args = {}; + + crc = cpu_to_be32(crc32_be(0, args.dest_buf, args.dest_offset)); + + psi_args.dest_buf = args.dest_buf; + psi_args.from = &crc; + psi_args.len = CRC_SIZE_IN_BYTES; + psi_args.dest_offset = args.dest_offset; + psi_args.pid = args.pid; + psi_args.new_psi_section = false; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = true; + psi_args.dest_buf_sz = args.dest_buf_sz; + + 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) +{ + /* + * Caller must recompute the section length afterwards at some point + * This function transfers ownedship of desc. + * Start by cleaning the old data + */ + if (*to) + vidtv_psi_desc_destroy(*to); + + *to = desc; /* reassign pointer */ +} + +static u32 vidtv_psi_desc_write_into(struct desc_write_args args) +{ + /* the number of bytes written by this function */ + u32 nbytes = 0; + struct psi_write_args psi_args = {}; + + psi_args.dest_buf = args.dest_buf; + psi_args.from = args.desc; + + psi_args.len = sizeof_field(struct vidtv_psi_desc, type) + + sizeof_field(struct vidtv_psi_desc, length); + + psi_args.dest_offset = args.dest_offset; + psi_args.pid = args.pid; + psi_args.new_psi_section = false; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = false; + psi_args.dest_buf_sz = args.dest_buf_sz; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + /* move 'from' pointer to point to u8 data[] */ + psi_args.from = args.desc + + sizeof_field(struct vidtv_psi_desc, type) + + sizeof_field(struct vidtv_psi_desc, length) + + sizeof(struct vidtv_psi_desc *); + + psi_args.len = args.desc->length; + psi_args.dest_offset = args.dest_offset + nbytes; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + return nbytes; +} + +static u32 +vidtv_psi_table_header_write_into(struct header_write_args args) +{ + /* the number of bytes written by this function */ + u32 nbytes = 0; + struct psi_write_args psi_args = {}; + + psi_args.dest_buf = args.dest_buf; + psi_args.from = args.h; + psi_args.len = sizeof(struct vidtv_psi_table_header); + psi_args.dest_offset = args.dest_offset; + psi_args.pid = args.pid; + psi_args.new_psi_section = true; + psi_args.continuity_counter = args.continuity_counter; + psi_args.is_crc = false; + psi_args.dest_buf_sz = args.dest_buf_sz; + + nbytes += vidtv_psi_ts_psi_write_into(psi_args); + + return nbytes; +} + +void +vidtv_psi_pat_table_comp_sec_len(struct vidtv_psi_table_pat *pat) +{ + /* see ISO/IEC 13818-1 : 2000 p.43 */ + u16 length = 0; + u32 i; + + /* from immediately after 'section_length' until 'last_section_number'*/ + length += PAT_LEN_UNTIL_LAST_SECTION_NUMBER; + + /* do not count the pointer */ + for (i = 0; i < pat->programs; ++i) + length += sizeof(struct vidtv_psi_table_pat_program) - + sizeof(struct vidtv_psi_table_pat_program *); + + length += CRC_SIZE_IN_BYTES; + + if (length > MAX_SECTION_LEN) + pr_warn_ratelimited("len %d > %d\n", length, MAX_SECTION_LEN); + + pat->header.section_length = length; +} + +void vidtv_psi_pmt_table_comp_sec_len(struct vidtv_psi_table_pmt *pmt) +{ + /* see ISO/IEC 13818-1 : 2000 p.46 */ + u16 length = 0; + struct vidtv_psi_table_pmt_stream *s = pmt->stream; + + /* from immediately after 'section_length' until 'program_info_length'*/ + length += PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH; + + pmt->desc_length = vidtv_psi_desc_comp_len(pmt->descriptor); + length += pmt->desc_length; + + while (s) { + /* skip both pointers at the end */ + length += sizeof(struct vidtv_psi_table_pmt_stream) - + sizeof(struct vidtv_psi_desc *) - + sizeof(struct vidtv_psi_table_pmt_stream *); + + s->desc_length = vidtv_psi_desc_comp_len(s->descriptor); + length += s->desc_length; + + s = s->next; + } + + length += CRC_SIZE_IN_BYTES; + + if (length > MAX_SECTION_LEN) + pr_warn_ratelimited("len %d > %d\n", length, MAX_SECTION_LEN); + + pmt->header.section_length = length; +} + +void vidtv_psi_sdt_table_comp_sec_len(struct vidtv_psi_table_sdt *sdt) +{ + /* see ETSI EN 300 468 V 1.10.1 p.24 */ + u16 length = 0; + struct vidtv_psi_table_sdt_service *s = sdt->service; + + /* + * from immediately after 'section_length' until + * 'reserved_for_future_use' + */ + length += SDT_LEN_UNTIL_RESERVED_FOR_FUTURE_USE; + + while (s) { + /* skip both pointers at the end */ + length += sizeof(struct vidtv_psi_table_sdt_service) - + sizeof(struct vidtv_psi_desc *) - + sizeof(struct vidtv_psi_table_sdt_service *); + + s->desc_length = vidtv_psi_desc_comp_len(s->descriptor); + length += s->desc_length; + + s = s->next; + } + + length += CRC_SIZE_IN_BYTES; + + if (length > MAX_SECTION_LEN) + pr_warn_ratelimited("len %d > %d\n", length, MAX_SECTION_LEN); + + sdt->header.section_length = length; +} + +struct vidtv_psi_table_pat_program* +vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head, + u16 service_id, + u16 pid) +{ + /* + * if 'head' is attached to a table, caller should recompute + * the section length afterwards at some point + */ + struct vidtv_psi_table_pat_program *program; + + program = kzalloc(sizeof(*program), GFP_KERNEL); + + program->service_id = cpu_to_be16(service_id); + /* pid for the PMT section in the TS */ + program->pid = pid; + program->next = NULL; + program->reserved = 0x7; + + if (head) { + while (head->next) + head = head->next; + + head->next = program; + } + + return program; +} + +void +vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p) +{ + struct vidtv_psi_table_pat_program *curr = p; + struct vidtv_psi_table_pat_program *tmp = NULL; + + while (curr) { + tmp = curr; + curr = curr->next; + kfree(tmp); + } +} + +void +vidtv_psi_pat_program_assign(struct vidtv_psi_table_pat *pat, + struct vidtv_psi_table_pat_program *p) +{ + /* This function transfers ownership of p to the table */ + + u16 program_count = 0; + struct vidtv_psi_table_pat_program *program = p; + struct vidtv_psi_table_pat_program *temp = pat->program; + + while (program) { + ++program_count; + program = program->next; + } + + pat->programs = program_count; + pat->program = p; + + /* Recompute section length */ + vidtv_psi_pat_table_comp_sec_len(pat); + + /* reassign if the new size is too big */ + if (pat->header.section_length > MAX_SECTION_LEN) + vidtv_psi_pat_program_assign(pat, temp); + else + vidtv_psi_pat_program_destroy(temp); +} + +void vidtv_psi_pat_table_init(struct vidtv_psi_table_pat *pat, + bool update_version_num, + u16 transport_stream_id) +{ + static u8 pat_version; + + pat->header.table_id = 0x0; + pat->header.syntax = 0x1; + pat->header.zero = 0x0; + pat->header.one = 0x03; + + /* transport stream ID, at will */ + pat->header.id = cpu_to_be16(transport_stream_id); + pat->header.current_next = 0x1; + + /* ETSI 300 468: indicates changes in the TS described by this table*/ + if (update_version_num) + ++pat_version; + + pat->header.version = pat_version; + + pat->header.one2 = 0x03; + pat->header.section_id = 0x0; + pat->header.last_section = 0x0; + + pat->programs = 0; + + vidtv_psi_pat_table_comp_sec_len(pat); +} + +u32 vidtv_psi_pat_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_pat *pat, + u32 buf_sz, + u8 *continuity_counter) +{ + /* the number of bytes written by this function */ + u32 nbytes = 0; + const u16 pat_pid = VIDTV_PAT_PID; + + struct vidtv_psi_table_pat_program *p = pat->program; + struct header_write_args h_args = {}; + struct psi_write_args args = {}; + struct crc32_write_args c_args = {}; + + vidtv_psi_pat_table_comp_sec_len(pat); + + h_args.dest_buf = buf; + h_args.dest_offset = offset; + h_args.h = &pat->header; + h_args.pid = pat_pid; + h_args.continuity_counter = continuity_counter; + h_args.dest_buf_sz = buf_sz; + + nbytes += vidtv_psi_table_header_write_into(h_args); + + /* note that the field 'u16 programs' is not really part of the PAT */ + + args.dest_buf = buf; + args.pid = pat_pid; + args.new_psi_section = false; + args.continuity_counter = continuity_counter; + args.is_crc = false; + args.dest_buf_sz = buf_sz; + + while (p) { + /* copy the PAT programs */ + args.from = p; + /* skip the pointer */ + args.len = sizeof(*p) - + sizeof(struct vidtv_psi_table_pat_program *); + args.dest_offset = offset + nbytes; + + nbytes += vidtv_psi_ts_psi_write_into(args); + + p = p->next; + } + + c_args.dest_buf = buf; + c_args.dest_offset = offset + nbytes; + c_args.pid = pat_pid; + c_args.continuity_counter = continuity_counter; + c_args.dest_buf_sz = buf_sz; + + nbytes += table_section_crc32_write_into(c_args); + + return nbytes; +} + +void +vidtv_psi_pat_table_destroy(struct vidtv_psi_table_pat *p) +{ + vidtv_psi_pat_program_destroy(p->program); +} + +struct vidtv_psi_table_pmt_stream* +vidtv_psi_pmt_stream_init(struct vidtv_psi_table_pmt_stream *head, + enum vidtv_psi_stream_types stream_type, + u16 es_pid) +{ + struct vidtv_psi_table_pmt_stream *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + + stream->type = stream_type; + stream->elementary_pid = es_pid; + stream->reserved = 0x07; + + stream->desc_length = vidtv_psi_desc_comp_len(stream->descriptor); + + stream->zero = 0x0; + stream->reserved2 = 0x0f; + + if (head) { + while (head->next) + head = head->next; + + head->next = stream; + } + + return stream; +} + +void vidtv_psi_pmt_stream_destroy(struct vidtv_psi_table_pmt_stream *s) +{ + struct vidtv_psi_table_pmt_stream *curr_stream = s; + struct vidtv_psi_table_pmt_stream *tmp_stream = NULL; + + while (curr_stream) { + tmp_stream = curr_stream; + curr_stream = curr_stream->next; + kfree(tmp_stream); + } +} + +void vidtv_psi_pmt_stream_assign(struct vidtv_psi_table_pmt *pmt, + struct vidtv_psi_table_pmt_stream *s) +{ + /* This function transfers ownership of s to the table */ + struct vidtv_psi_table_pmt_stream *stream = s; + struct vidtv_psi_desc *desc = s->descriptor; + struct vidtv_psi_table_pmt_stream *temp = pmt->stream; + + while (stream) + stream = stream->next; + + while (desc) + desc = desc->next; + + pmt->stream = s; + /* Recompute section length */ + vidtv_psi_pmt_table_comp_sec_len(pmt); + + /* reassign if the new size is too big */ + if (pmt->header.section_length > MAX_SECTION_LEN) + vidtv_psi_pmt_stream_assign(pmt, temp); + else + vidtv_psi_pmt_stream_destroy(temp); +} + +u16 vidtv_psi_pmt_get_pid(struct vidtv_psi_table_pmt *section, + struct vidtv_psi_table_pat *pat) +{ + struct vidtv_psi_table_pat_program *program = pat->program; + + /* + * service_id is the same as program_number in the + * corresponding program_map_section + * see ETSI EN 300 468 v1.15.1 p. 24 + */ + while (program) { + if (program->service_id == section->header.id) + return pat->program->pid; + + program = program->next; + } + + return TS_LAST_VALID_PID + 1; /* not found */ +} + +void vidtv_psi_pmt_table_init(struct vidtv_psi_table_pmt *pmt, + bool update_version_num, + u16 program_number, + u16 pcr_pid) +{ + static u8 pmt_version; + + pmt->header.table_id = 0x2; + pmt->header.syntax = 0x1; + pmt->header.zero = 0x0; + pmt->header.one = 0x3; + + pmt->header.id = cpu_to_be16(program_number); + pmt->header.current_next = 0x1; + + /* ETSI 300 468: indicates changes in the TS described by this table*/ + if (update_version_num) + ++pmt_version; + + pmt->header.version = pmt_version; + + pmt->header.one2 = 0x3; + pmt->header.section_id = 0; + pmt->header.last_section = 0; + + pmt->pcr_pid = (pcr_pid) ? pcr_pid : 0x1fff; + pmt->reserved2 = 0x03; + + pmt->reserved3 = 0x0f; + pmt->zero3 = 0x0; + + pmt->desc_length = vidtv_psi_desc_comp_len(pmt->descriptor); + + vidtv_psi_pmt_table_comp_sec_len(pmt); +} + +u32 vidtv_psi_pmt_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_pmt *pmt, + u16 pid, + u32 buf_sz, + u8 *continuity_counter) +{ + /* the number of bytes written by this function */ + u32 nbytes = 0; + struct vidtv_psi_desc *table_descriptor = pmt->descriptor; + struct vidtv_psi_table_pmt_stream *stream = pmt->stream; + struct vidtv_psi_desc *stream_descriptor = (stream) ? + pmt->stream->descriptor : + NULL; + + struct header_write_args h_args = {}; + struct psi_write_args args = {}; + struct desc_write_args d_args = {}; + struct crc32_write_args c_args = {}; + + vidtv_psi_pmt_table_comp_sec_len(pmt); + + h_args.dest_buf = buf; + h_args.dest_offset = offset; + h_args.h = &pmt->header; + h_args.pid = pid; + h_args.continuity_counter = continuity_counter; + h_args.dest_buf_sz = buf_sz; + + nbytes += vidtv_psi_table_header_write_into(h_args); + + /* write the two bitfields */ + args.dest_buf = buf; + args.from = pmt + sizeof(struct vidtv_psi_table_header); + args.len = sizeof_field(struct vidtv_psi_table_pmt, bitfield) + + sizeof_field(struct vidtv_psi_table_pmt, bitfield2); + args.dest_offset = offset + nbytes; + args.pid = pid; + args.new_psi_section = false; + args.continuity_counter = continuity_counter; + args.is_crc = false; + args.dest_buf_sz = buf_sz; + + nbytes += vidtv_psi_ts_psi_write_into(args); + + while (table_descriptor) { + /* write the descriptors, if any */ + d_args.dest_buf = buf; + d_args.dest_offset = offset + nbytes; + d_args.desc = table_descriptor; + d_args.pid = pid; + d_args.continuity_counter = continuity_counter; + d_args.dest_buf_sz = buf_sz; + + nbytes += vidtv_psi_desc_write_into(d_args); + + table_descriptor = table_descriptor->next; + } + + while (stream) { + /* write the streams, if any */ + args.from = stream; + args.len = sizeof_field(struct vidtv_psi_table_pmt_stream, + type) + + sizeof_field(struct vidtv_psi_table_pmt_stream, + bitfield) + + sizeof_field(struct vidtv_psi_table_pmt_stream, + bitfield2); + args.dest_offset = offset + nbytes; + + nbytes += vidtv_psi_ts_psi_write_into(args); + + while (stream_descriptor) { + /* write the stream descriptors, if any */ + d_args.desc = stream_descriptor; + d_args.dest_offset = offset + nbytes; + + nbytes += vidtv_psi_desc_write_into(d_args); + + stream_descriptor = stream_descriptor->next; + } + + stream = stream->next; + } + + c_args.dest_buf = buf; + c_args.dest_offset = offset + nbytes; + c_args.pid = pid; + c_args.continuity_counter = continuity_counter; + c_args.dest_buf_sz = buf_sz; + + nbytes += table_section_crc32_write_into(c_args); + + return nbytes; +} + +void vidtv_psi_pmt_table_destroy(struct vidtv_psi_table_pmt *pmt) +{ + struct vidtv_psi_desc *curr_desc = pmt->descriptor; + struct vidtv_psi_desc *tmp_desc = NULL; + + while (curr_desc) { + tmp_desc = curr_desc; + curr_desc = curr_desc->next; + vidtv_psi_desc_destroy(tmp_desc); + kfree(tmp_desc); + } + + vidtv_psi_pmt_stream_destroy(pmt->stream); +} + +void vidtv_psi_sdt_table_init(struct vidtv_psi_table_sdt *sdt, + bool update_version_num, + u16 transport_stream_id) +{ + static u8 sdt_version; + + sdt->header.table_id = 0x42; + + sdt->header.one = 0x3; + sdt->header.zero = 0x1; + /* + * The PAT, PMT, and CAT all set this to 0. + * Other tables set this to 1. + */ + sdt->header.syntax = 0x1; + + /* + * This is a 16-bit field which serves as a label for identification + * of the TS, about which the SDT informs, from any other multiplex + * within the delivery system. + */ + sdt->header.id = cpu_to_be16(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 = cpu_to_be16(transport_stream_id); + sdt->reserved = 0xff; + + vidtv_psi_sdt_table_comp_sec_len(sdt); +} + +u32 vidtv_psi_sdt_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_sdt *sdt, + u32 buf_sz, + u8 *continuity_counter) +{ + u32 nbytes = 0; /* the number of bytes written */ + u16 sdt_pid = VIDTV_SDT_PID; /* see ETSI EN 300 468 v1.15.1 p. 11 */ + + struct vidtv_psi_table_sdt_service *service = sdt->service; + struct vidtv_psi_desc *service_desc = (sdt->service) ? + sdt->service->descriptor : + NULL; + + struct header_write_args h_args = {}; + struct psi_write_args args = {}; + struct desc_write_args d_args = {}; + struct crc32_write_args c_args = {}; + + vidtv_psi_sdt_table_comp_sec_len(sdt); + + h_args.dest_buf = buf; + h_args.dest_offset = offset; + h_args.h = &sdt->header; + h_args.pid = sdt_pid; + h_args.continuity_counter = continuity_counter; + h_args.dest_buf_sz = buf_sz; + + nbytes += vidtv_psi_table_header_write_into(h_args); + + args.dest_buf = buf; + args.from = sdt + sizeof(struct vidtv_psi_table_header); + + args.len = sizeof_field(struct vidtv_psi_table_sdt, network_id) + + sizeof_field(struct vidtv_psi_table_sdt, reserved); + + args.dest_offset = offset + nbytes; + args.pid = sdt_pid; + args.new_psi_section = false; + args.continuity_counter = continuity_counter; + args.is_crc = false; + args.dest_buf_sz = buf_sz; + + /* copy u16 network_id + u8 reserved)*/ + nbytes += vidtv_psi_ts_psi_write_into(args); + + while (service) { + /* copy the services, if any */ + args.from = service; + /* skip both pointers at the end */ + args.len = sizeof(struct vidtv_psi_table_sdt_service) - + sizeof(struct vidtv_psi_desc *) - + sizeof(struct vidtv_psi_table_sdt_service *); + args.dest_offset = offset + nbytes; + + nbytes += vidtv_psi_ts_psi_write_into(args); + + while (service_desc) { + /* copy the service descriptors, if any */ + d_args.dest_buf = buf; + d_args.dest_offset = offset + nbytes; + d_args.desc = service_desc; + d_args.pid = sdt_pid; + d_args.continuity_counter = continuity_counter; + d_args.dest_buf_sz = buf_sz; + + nbytes += vidtv_psi_desc_write_into(d_args); + + service_desc = service_desc->next; + } + + service = service->next; + } + + c_args.dest_buf = buf; + c_args.dest_offset = offset + nbytes; + c_args.pid = sdt_pid; + c_args.continuity_counter = continuity_counter; + c_args.dest_buf_sz = buf_sz; + + nbytes += table_section_crc32_write_into(c_args); + + return nbytes; +} + +void vidtv_psi_sdt_table_destroy(struct vidtv_psi_table_sdt *sdt) +{ + struct vidtv_psi_table_sdt_service *curr_service = sdt->service; + struct vidtv_psi_table_sdt_service *tmp_service = NULL; + struct vidtv_psi_desc *curr_desc = (sdt->service) ? + sdt->service->descriptor : NULL; + struct vidtv_psi_desc *tmp_desc = NULL; + + while (curr_service) { + curr_desc = curr_service->descriptor; + + while (curr_desc) { + /* clear all descriptors for the service */ + tmp_desc = curr_desc; + curr_desc = curr_desc->next; + vidtv_psi_desc_destroy(tmp_desc); + kfree(tmp_desc); + } + + /* then clear the current service */ + tmp_service = curr_service; + curr_service = curr_service->next; + kfree(tmp_service); + } +} + +struct vidtv_psi_table_sdt_service +*vidtv_psi_sdt_service_init(struct vidtv_psi_table_sdt_service *head, + u16 service_id) +{ + /* + * if 'head' is attached to a table, caller should recompute + * the section length afterwards at some point + */ + struct vidtv_psi_table_sdt_service *service; + + service = kzalloc(sizeof(*service), GFP_KERNEL); + + /* + * ETSI 300 468: this is a 16bit field which serves as a label to + * identify this service from any other service within the TS. + * The service id is the same as the program number in the + * corresponding program_map_section + */ + service->service_id = cpu_to_be16(service_id); + service->EIT_schedule = 0x0; + service->EIT_present_following = 0x0; + service->reserved = 0x3f; /* all bits on */ + service->free_CA_mode = 0x0; /* not scrambled */ + service->running_status = RUNNING; + + if (head) { + while (head->next) + head = head->next; + + head->next = service; + } + + return service; +} + +void +vidtv_psi_sdt_service_destroy(struct vidtv_psi_table_sdt_service *service) +{ + struct vidtv_psi_table_sdt_service *curr = service; + struct vidtv_psi_table_sdt_service *tmp = NULL; + + while (curr) { + tmp = curr; + curr = curr->next; + kfree(tmp); + } +} + +void +vidtv_psi_sdt_service_assign(struct vidtv_psi_table_sdt *sdt, + struct vidtv_psi_table_sdt_service *service) +{ + struct vidtv_psi_table_sdt_service *temp = sdt->service; + + sdt->service = service; + + /* recompute section length */ + vidtv_psi_sdt_table_comp_sec_len(sdt); + + /* reassign if the new size is too big */ + if (sdt->header.section_length > MAX_SECTION_LEN) + vidtv_psi_sdt_service_assign(sdt, temp); + else + vidtv_psi_sdt_service_destroy(temp); +} + +void +vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat, + struct vidtv_psi_table_pmt sec[]) + +{ + /* + * PMTs contain information about programs. For each program, + * there is one PMT section. This function will create a section + * for each program found in the PAT + */ + struct vidtv_psi_table_pat_program *program = pat->program; + u32 i = 0; + + while (program) { + vidtv_psi_pmt_table_init(&sec[i], + false, + be16_to_cpu(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..77b616a46757a --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_psi.h @@ -0,0 +1,415 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This file contains the logic to work with MPEG Program-Specific Information. + * These are defined both in ISO/IEC 13818-1 (systems) and ETSI EN 300 468. + * PSI is carried in the form of table structures, and although each table might + * technically be broken into one or more sections, we do not do this here, + * hence 'table' and 'section' are interchangeable for us. + * + * This code currently supports three tables: PAT, PMT and SDT. These are the + * bare minimum to get userspace to recognize our MPEG transport stream. It can + * be extended to support more PSI tables in the future. + * + * A note on endianness: MPEG layout is big-endian, therefore: + * - All fields spanning more than a byte must undergo 'cpu_to_beXX()' + * - All bitfields must have their ordering reversed if + * __LITTLE_ENDIAN_BITFIELD is defined. + * + * Written by: Daniel W. S. Almeida + */ + +#ifndef VIDTV_PSI_H +#define VIDTV_PSI_H + +#include +#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 PMT_LEN_UNTIL_PROGRAM_INFO_LENGTH 9 +#define SDT_LEN_UNTIL_RESERVED_FOR_FUTURE_USE 8 +#define MAX_SECTION_LEN 1021 +#define VIDTV_PAT_PID 0 +#define VIDTV_SDT_PID 0x0011 + +enum vidtv_psi_descriptors { + REGISTRATION_DESCRIPTOR = 0x05, + SERVICE_DESCRIPTOR = 0x48, +}; + +enum vidtv_psi_stream_types { + /* see ISO/IEC 13818-1 2000 p. 48 */ + STREAM_PRIVATE_DATA = 0x06, +}; + +struct vidtv_psi_desc { + u8 type; + u8 length; + struct vidtv_psi_desc *next; + u8 data[]; +} __packed; + +struct vidtv_psi_desc_service { + u8 type; + u8 length; + struct vidtv_psi_desc *next; + + u8 service_type; + char *name; + char *name_emph; + char *provider; + char *provider_emph; +} __packed; + +struct vidtv_psi_desc_registration { + u8 type; + u8 length; + struct vidtv_psi_desc *next; + + /* + * The format_identifier is a 32-bit value obtained from a Registration + * Authority as designated by ISO/IEC JTC 1/SC 29. + */ + __be32 format_id; + /* + * The meaning of additional_identification_info bytes, if any, are + * defined by the assignee of that format_identifier, and once defined + * they shall not change. + */ + u8 additional_identification_info[]; +} __packed; + +struct vidtv_psi_table_header { + u8 table_id; + union { + __be16 bitfield; + struct { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u16 section_length:12; + u8 one:2; + u8 zero:1; + u8 syntax:1; +#elif defined(__BIG_ENDIAN_BITFIELD) + u8 syntax:1; + u8 zero:1; + u8 one:2; + u16 section_length:12; +#else +#error "Unknown bitfield ordering" +#endif + } __packed; + } __packed; + + __be16 id; /* TS ID */ +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 current_next:1; + u8 version:5; + u8 one2:2; +#elif defined(__BIG_ENDIAN_BITFIELD) + u8 one2:2; + u8 version:5; + u8 current_next:1; +#else +#error "Unknown bitfield ordering" +#endif + u8 section_id; /* section_number */ + u8 last_section; /* last_section_number */ +} __packed; + +struct vidtv_psi_table_pat_program { + __be16 service_id; + union { + __be16 bitfield; + struct { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 reserved:3; + u16 pid:13; +#elif defined(__BIG_ENDIAN_BITFIELD) + u16 pid:13; + u8 reserved:3; +#else +#error "Unknown bitfield ordering" +#endif + } __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 { + __be16 service_id; +#if defined(__LITTLE_ENDIAN_BITFIELD) + u8 EIT_present_following:1; + u8 EIT_schedule:1; + u8 reserved:6; +#elif defined(__BIG_ENDIAN_BITFIELD) + u8 reserved:6; + u8 EIT_schedule:1; + u8 EIT_present_following:1; +#else +#error "Unknown bitfield ordering" +#endif + union { + __be16 bitfield; + struct { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u16 desc_length:12; + u16 free_CA_mode:1; + u16 running_status:3; +#elif defined(__BIG_ENDIAN_BITFIELD) + u16 running_status:3; + u16 free_CA_mode:1; + u16 desc_length:12; +#else +#error "Unknown bitfield ordering" +#endif + } __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; + __be16 network_id; /* original_network_id */ + u8 reserved; + struct vidtv_psi_table_sdt_service *service; +} __packed; + +enum service_running_status { + RUNNING, +}; + +enum service_type { + /* see ETSI EN 300 468 v1.15.1 p. 77 */ + DIGITAL_TELEVISION_SERVICE = 0x1, +}; + +struct vidtv_psi_table_pmt_stream { + u8 type; + union { + __be16 bitfield; + struct { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u16 elementary_pid:13; + u16 reserved:3; +#elif defined(__BIG_ENDIAN_BITFIELD) + u16 reserved:3; + u16 elementary_pid:13; +#else +#error "Unknown bitfield ordering" +#endif + } __packed; + } __packed; + union { + __be16 bitfield2; + struct { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u16 desc_length:10; + u16 zero:2; + u16 reserved2:4; +#elif defined(__BIG_ENDIAN_BITFIELD) + u16 reserved2:4; + u16 zero:2; + u16 desc_length:10; +#else +#error "Unknown bitfield ordering" +#endif + } __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 { + __be16 bitfield; + struct { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u16 pcr_pid:13; + u16 reserved2:3; +#elif defined(__BIG_ENDIAN_BITFIELD) + u16 reserved2:3; + u16 pcr_pid:13; +#else +#error "Unknown bitfield ordering" +#endif + } __packed; + } __packed; + + union { + __be16 bitfield2; + struct { +#if defined(__LITTLE_ENDIAN_BITFIELD) + u16 desc_length:10; /* program_info_length */ + u16 zero3:2; + u16 reserved3:4; +#elif defined(__BIG_ENDIAN_BITFIELD) + u16 reserved3:4; + u16 zero3:2; + u16 desc_length:10; /* program_info_length */ +#else +#error "Unknown bitfield ordering" +#endif + } __packed; + } __packed; + struct vidtv_psi_desc *descriptor; + struct vidtv_psi_table_pmt_stream *stream; +} __packed; + +/** + * struct psi_write_args - Arguments for the PSI packetizer + * @dest_buf: The buffer to write into. + * @from: PSI data to be copied. + * @len: How much to write. + * @dest_offset: where to start writing in the dest_buffer. + * @pid: TS packet ID. + * @new_psi_section: Set when starting a table section. + * @continuity_counter: Incremented on every new packet. + * @is_crc: Set when writing the CRC at the end. + * @dest_buf_sz: The size of the dest_buffer + */ +struct psi_write_args { + void *dest_buf; + void *from; + size_t len; + u32 dest_offset; + u16 pid; + bool new_psi_section; + u8 *continuity_counter; + bool is_crc; + u32 dest_buf_sz; +}; + +struct desc_write_args { + void *dest_buf; + u32 dest_offset; + struct vidtv_psi_desc *desc; + u16 pid; + u8 *continuity_counter; + u32 dest_buf_sz; +}; + +struct crc32_write_args { + void *dest_buf; + u32 dest_offset; + u16 pid; + u8 *continuity_counter; + u32 dest_buf_sz; +}; + +struct header_write_args { + void *dest_buf; + u32 dest_offset; + struct vidtv_psi_table_header *h; + u16 pid; + u8 *continuity_counter; + u32 dest_buf_sz; +}; + +struct vidtv_psi_desc *vidtv_psi_desc_init(struct vidtv_psi_desc *head, + u8 type, + u8 length); + +void vidtv_psi_pat_table_init(struct vidtv_psi_table_pat *pat, + bool update_version_num, + u16 transport_stream_id); + +struct vidtv_psi_table_pat_program* +vidtv_psi_pat_program_init(struct vidtv_psi_table_pat_program *head, + u16 service_id, + u16 pid); + +struct vidtv_psi_table_pmt_stream* +vidtv_psi_pmt_stream_init(struct vidtv_psi_table_pmt_stream *head, + enum vidtv_psi_stream_types stream_type, + u16 es_pid); + +void vidtv_psi_pmt_table_init(struct vidtv_psi_table_pmt *pmt, + bool update_version_num, + u16 program_number, + u16 pcr_pid); + +void +vidtv_psi_sdt_table_init(struct vidtv_psi_table_sdt *sdt, + bool update_version_num, + u16 transport_stream_id); + +struct vidtv_psi_table_sdt_service* +vidtv_psi_sdt_service_init(struct vidtv_psi_table_sdt_service *head, + u16 service_id); + +void +vidtv_psi_desc_destroy(struct vidtv_psi_desc *desc); + +void +vidtv_psi_pat_program_destroy(struct vidtv_psi_table_pat_program *p); + +void +vidtv_psi_pat_table_destroy(struct vidtv_psi_table_pat *p); + +void +vidtv_psi_pmt_stream_destroy(struct vidtv_psi_table_pmt_stream *s); + +void +vidtv_psi_pmt_table_destroy(struct vidtv_psi_table_pmt *pmt); + +void +vidtv_psi_sdt_table_destroy(struct vidtv_psi_table_sdt *sdt); + +void +vidtv_psi_sdt_service_destroy(struct vidtv_psi_table_sdt_service *service); + +void +vidtv_psi_sdt_service_assign(struct vidtv_psi_table_sdt *sdt, + struct vidtv_psi_table_sdt_service *service); + +void vidtv_psi_desc_assign(struct vidtv_psi_desc **to, + struct vidtv_psi_desc *desc); + +void vidtv_psi_pat_program_assign(struct vidtv_psi_table_pat *pat, + struct vidtv_psi_table_pat_program *p); + +void vidtv_psi_pmt_stream_assign(struct vidtv_psi_table_pmt *pmt, + struct vidtv_psi_table_pmt_stream *s); +void +vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat, + struct vidtv_psi_table_pmt *sec); + +u16 vidtv_psi_pmt_get_pid(struct vidtv_psi_table_pmt *section, + struct vidtv_psi_table_pat *pat); + +void vidtv_psi_pat_table_comp_sec_len(struct vidtv_psi_table_pat *pat); +void vidtv_psi_pmt_table_comp_sec_len(struct vidtv_psi_table_pmt *pmt); +void vidtv_psi_sdt_table_comp_sec_len(struct vidtv_psi_table_sdt *sdt); + +u32 vidtv_psi_pat_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_pat *pat, + u32 buf_sz, + u8 *continuity_counter); + +u32 vidtv_psi_sdt_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_sdt *sdt, + u32 buf_sz, + u8 *continuity_counter); + +u32 vidtv_psi_pmt_write_into(char *buf, + u32 offset, + struct vidtv_psi_table_pmt *pmt, + u16 pid, + u32 buf_sz, + u8 *continuity_counter); + +#endif // VIDTV_PSI_H From patchwork Wed May 20 07:03:34 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Almeida X-Patchwork-Id: 209764 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.6 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id BA6A4C433E3 for ; Wed, 20 May 2020 07:04:47 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 84558207D3 for ; Wed, 20 May 2020 07:04:47 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="nf4BRmPh" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727035AbgETHEq (ORCPT ); Wed, 20 May 2020 03:04:46 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:42986 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726463AbgETHE0 (ORCPT ); Wed, 20 May 2020 03:04:26 -0400 Received: from mail-qv1-xf43.google.com (mail-qv1-xf43.google.com [IPv6:2607:f8b0:4864:20::f43]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4D5F6C061A0E; Wed, 20 May 2020 00:04:26 -0700 (PDT) Received: by mail-qv1-xf43.google.com with SMTP id v15so851762qvr.8; Wed, 20 May 2020 00:04:26 -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=Hq3/Tdxg34paqXMw3ZQ5R+wveIJ625rgUg1aPE47IHE=; b=nf4BRmPh0fxi5JIk7e1nFgdwTYxjVawUJnNITzzumlFlEjaz8Zg2+jtqFctle0ey6k fd6Fx/mkw7hmju6cb9RgZdOma2pjYFIOIEgi5cNCS17eiRkWlUEh7ga7EZi2fB/1Cg0y C3pgHFqZ6NF83FCKL+jY6Ec3kY14kEX1pnNW5vMkCx96tkRa1KTvbGRQbE4FjlG5IuXe 5Ey42bBFatYrkas7c6akQewSOaV2jD/jYy7g3WnCD4nVeJMROPt10+99gJfEKCtD8871 tBD+jVzsO1gkj2EmzqUkVV5F3Hcu2OO2691VnX2cbFgaAcVpL6CzpqKBfk1zu1fcRX2J e4ag== 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=Hq3/Tdxg34paqXMw3ZQ5R+wveIJ625rgUg1aPE47IHE=; b=H8+f5+J99ksOymhXuapJrE5B0/QdpzfIm2iUWe944ddxKQ8vejN+oDLcpzQOcV8k8q 45ORyU5VVXkDRVwIVebSJz6y9shoVHbSt9iJ0Yw2BlnqZkr6ORe9Hgz/E40FI0Z4NaAi b0SokZxkbEw56igI3OIw/1mbZfFvpfi8Sy4PJf5x23sLuVoIb6HMkVmWcA281eA+yrHq X5ZrGDIXddnNAjUP4yQmu80o8Ao9Z+5bvt579/ddGsIXZxcGnu9dqSp0celG2e7JPTkF yhH/lsBnRo6JrOR6E86yOkyfnOWycYVXoHx4Fq/h9FnSafYIW0Kn8l2qZ+Q7lhTQrddD blCg== X-Gm-Message-State: AOAM531cvU/1+8uM0MGgGZTrNEzwsaQExPT81Oxh1esc+FZVWv6Ntrt8 l0GA0DMnRJ3zaFS/wVtQmWU= X-Google-Smtp-Source: ABdhPJxyzVDGRP7BnYWjplR9fTgQrX52oM8h+L4sHFIyTWKc2jg4Fp8L5sSbLQYmfj3SbMhm5cV3fQ== X-Received: by 2002:a0c:fe88:: with SMTP id d8mr3324874qvs.208.1589958264924; Wed, 20 May 2020 00:04:24 -0700 (PDT) Received: from localhost.localdomain ([2804:14d:72b1:8920:a2ce:f815:f14d:bfac]) by smtp.gmail.com with ESMTPSA id i23sm1598893qke.65.2020.05.20.00.04.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 May 2020 00:04:24 -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, v6 10/10] media: vidtv: Add a MPEG Transport Stream Multiplexer Date: Wed, 20 May 2020 04:03:34 -0300 Message-Id: <20200520070334.1778751-11-dwlsalmeida@gmail.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200520070334.1778751-1-dwlsalmeida@gmail.com> References: <20200520070334.1778751-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 a MPEG Transport Stream multiplexer responsible for polling encoders, interleaving packets, padding the resulting stream with NULL packets if necessary and then delivering the resulting TS packets to the bridge driver so it can feed the demux. This patch includes a "channel" abstraction, which attempts to map a MPEG service into a struct that vidtv can work with. When vidtv boots, it will create some hardcoded channels: -Their services will be concatenated to populate the SDT. -Their programs will be concatenated to populate the PAT -For each program in the PAT, a PMT section will be created -The PMT section for a channel will be assigned its streams. -Every stream will have its corresponding encoder polled to produce TS packets -These packets may be interleaved by the mux and then delivered to the bridg Signed-off-by: Daniel W. S. Almeida --- drivers/media/test-drivers/vidtv/Makefile | 3 +- .../media/test-drivers/vidtv/vidtv_bridge.c | 76 +++ .../media/test-drivers/vidtv/vidtv_bridge.h | 2 + .../media/test-drivers/vidtv/vidtv_channel.c | 339 ++++++++++++++ .../media/test-drivers/vidtv/vidtv_channel.h | 66 +++ .../media/test-drivers/vidtv/vidtv_common.h | 3 + drivers/media/test-drivers/vidtv/vidtv_mux.c | 434 ++++++++++++++++++ drivers/media/test-drivers/vidtv/vidtv_mux.h | 105 +++++ drivers/media/test-drivers/vidtv/vidtv_psi.c | 18 + drivers/media/test-drivers/vidtv/vidtv_psi.h | 5 + 10 files changed, 1050 insertions(+), 1 deletion(-) create mode 100644 drivers/media/test-drivers/vidtv/vidtv_channel.c create mode 100644 drivers/media/test-drivers/vidtv/vidtv_channel.h create mode 100644 drivers/media/test-drivers/vidtv/vidtv_mux.c create mode 100644 drivers/media/test-drivers/vidtv/vidtv_mux.h diff --git a/drivers/media/test-drivers/vidtv/Makefile b/drivers/media/test-drivers/vidtv/Makefile index c99cd13f4adaf..330089e3b70c3 100644 --- a/drivers/media/test-drivers/vidtv/Makefile +++ b/drivers/media/test-drivers/vidtv/Makefile @@ -2,7 +2,8 @@ dvb-vidtv-tuner-objs := vidtv_tuner.o dvb-vidtv-demod-objs := vidtv_demod.o -dvb-vidtv-bridge-objs := vidtv_bridge.o +dvb-vidtv-bridge-objs := vidtv_bridge.o vidtv_common.o vidtv_ts.o vidtv_psi.o \ + vidtv_pes.o vidtv_s302m.o vidtv_channel.o vidtv_mux.o obj-$(CONFIG_DVB_VIDTV) += dvb-vidtv-tuner.o dvb-vidtv-demod.o \ dvb-vidtv-bridge.o diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.c b/drivers/media/test-drivers/vidtv/vidtv_bridge.c index bc1c612c23013..6e0589258d52c 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_bridge.c +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.c @@ -20,7 +20,11 @@ #include "vidtv_bridge.h" #include "vidtv_demod.h" #include "vidtv_tuner.h" +#include "vidtv_ts.h" +#include "vidtv_mux.h" +#define TS_BUF_MAX_SZ (128 * TS_PACKET_LEN) +#define TS_BUF_MIN_SZ (20 * TS_PACKET_LEN) #define TUNER_DEFAULT_ADDR 0x68 #define DEMOD_DEFAULT_ADDR 0x60 @@ -64,6 +68,56 @@ MODULE_PARM_DESC(max_frequency_shift_hz, DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nums); +/* + * Influences the signal acquisition time. See ISO/IEC 13818-1 : 2000. p. 113. + */ +static unsigned int si_period_msec = 40; +module_param(si_period_msec, uint, 0); +MODULE_PARM_DESC(si_period_msec, "How often to send SI packets. Default: 40ms"); + +static unsigned int pcr_period_msec = 40; +module_param(pcr_period_msec, uint, 0); +MODULE_PARM_DESC(pcr_period_msec, "How often to send PCR packets. Default: 40ms"); + +static unsigned int mux_rate_kbytes_sec = 4096; +module_param(mux_rate_kbytes_sec, uint, 0); +MODULE_PARM_DESC(mux_rate_kbytes_sec, "Mux rate: will pad stream if below"); + +static unsigned int pcr_pid = 0x200; +module_param(pcr_pid, uint, 0); +MODULE_PARM_DESC(pcr_pid, "PCR PID for all channels: defaults to 0x200"); + +static unsigned int ts_buf_sz_pkts = 20; +module_param(ts_buf_sz_pkts, uint, 0); +MODULE_PARM_DESC(ts_buf_sz_pkts, "Size for the TS buffer in multiples of 188 bytes"); + +static bool vidtv_bridge_check_demod_lock(struct vidtv_dvb *dvb, u32 n) +{ + enum fe_status status; + + dvb->fe[n]->ops.read_status(dvb->fe[n], &status); + + return status == (FE_HAS_SIGNAL | + FE_HAS_CARRIER | + FE_HAS_VITERBI | + FE_HAS_SYNC | + FE_HAS_LOCK); +} + +static void +vidtv_bridge_on_new_pkts_avail(void *priv, u8 *buf, u32 npkts) +{ + /* + * called on a separate thread by the mux when new packets become + * available + */ + struct vidtv_dvb *dvb = (struct vidtv_dvb *)priv; + + /* drop packets if we lose the lock */ + if (vidtv_bridge_check_demod_lock(dvb, 0)) + dvb_dmx_swfilter_packets(&dvb->demux, buf, npkts); +} + static int vidtv_start_streaming(struct vidtv_dvb *dvb) { if (dvb->streaming) { @@ -72,12 +126,14 @@ static int vidtv_start_streaming(struct vidtv_dvb *dvb) } dvb->streaming = true; + vidtv_mux_start_thread(dvb->mux); return 0; } static int vidtv_stop_streaming(struct vidtv_dvb *dvb) { dvb->streaming = false; + vidtv_mux_stop_thread(dvb->mux); return 0; } @@ -340,6 +396,8 @@ static int vidtv_bridge_probe(struct platform_device *pdev) { int ret; struct vidtv_dvb *dvb; + struct vidtv_mux_init_args mux_args = {0}; + u32 ts_buf_sz = ts_buf_sz_pkts * TS_PACKET_LEN; dvb = kzalloc(sizeof(*dvb), GFP_KERNEL); if (!dvb) @@ -355,6 +413,22 @@ static int vidtv_bridge_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dvb); + if (ts_buf_sz < TS_BUF_MIN_SZ) + ts_buf_sz = TS_BUF_MIN_SZ; + + if (ts_buf_sz > TS_BUF_MAX_SZ) + ts_buf_sz = TS_BUF_MAX_SZ; + + mux_args.mux_rate_kbytes_sec = mux_rate_kbytes_sec; + mux_args.on_new_packets_available_cb = vidtv_bridge_on_new_pkts_avail; + mux_args.ts_buf_sz = ts_buf_sz; + mux_args.pcr_period_usecs = pcr_period_msec * 1000; + mux_args.si_period_usecs = si_period_msec * 1000; + mux_args.pcr_pid = pcr_pid; + mux_args.priv = dvb; + + dvb->mux = vidtv_mux_init(mux_args); + return ret; err_dvb: @@ -369,6 +443,8 @@ static int vidtv_bridge_remove(struct platform_device *pdev) dvb = platform_get_drvdata(pdev); + vidtv_mux_destroy(dvb->mux); + mutex_destroy(&dvb->feed_lock); for (i = 0; i < NUM_FE; ++i) diff --git a/drivers/media/test-drivers/vidtv/vidtv_bridge.h b/drivers/media/test-drivers/vidtv/vidtv_bridge.h index 4931bfb73273c..aac1da13a50aa 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_bridge.h +++ b/drivers/media/test-drivers/vidtv/vidtv_bridge.h @@ -18,6 +18,7 @@ #include #include #include +#include "vidtv_mux.h" struct vidtv_dvb { struct platform_device *pdev; @@ -34,6 +35,7 @@ struct vidtv_dvb { struct mutex feed_lock; /* start/stop feed */ bool streaming; + struct vidtv_mux *mux; }; #endif // VIDTV_BRIDG_H diff --git a/drivers/media/test-drivers/vidtv/vidtv_channel.c b/drivers/media/test-drivers/vidtv/vidtv_channel.c new file mode 100644 index 0000000000000..c49426f67a680 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_channel.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the code for a 'channel' abstraction. + * + * When vidtv boots, it will create some hardcoded channels. + * Their services will be concatenated to populate the SDT. + * Their programs will be concatenated to populate the PAT + * For each program in the PAT, a PMT section will be created + * The PMT section for a channel will be assigned its streams. + * Every stream will have its corresponding encoder polled to produce TS packets + * These packets may be interleaved by the mux and then delivered to the bridge + * + * + * Written by Daniel W. S. Almeida + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__ + +#include +#include +#include +#include + +#include "vidtv_channel.h" +#include "vidtv_psi.h" +#include "vidtv_encoder.h" +#include "vidtv_mux.h" +#include "vidtv_common.h" +#include "vidtv_s302m.h" + +static void vidtv_channel_encoder_destroy(struct vidtv_encoder *e) +{ + struct vidtv_encoder *curr = e; + + while (curr) { + /* forward the call to the derived type */ + curr->destroy(curr); + curr = curr->next; + } +} + +static struct vidtv_channel +*vidtv_channel_s302m_init(struct vidtv_channel *head) +{ + /* init an audio only channel with a s302m encoder */ + const u16 s302m_service_id = 0x880; + const u16 s302m_program_num = 0x880; + const u16 s302m_program_pid = 0x101; /* packet id for PMT*/ + const u16 s302m_es_pid = 0x111; /* packet id for the ES */ + const u16 s302m_pes_audio_stream_id = 0xbd; /* PES: private_stream_1 */ + + struct vidtv_channel *s302m = kzalloc(sizeof(*s302m), GFP_KERNEL); + struct vidtv_psi_table_sdt_service *s302m_service; + struct vidtv_psi_desc_service *s302m_s_desc; + struct vidtv_s302m_encoder_init_args encoder_args = {}; + + s302m_service = vidtv_psi_sdt_service_init(NULL, s302m_service_id); + + s302m_s_desc = (struct vidtv_psi_desc_service *) + vidtv_psi_desc_init(NULL, + SERVICE_DESCRIPTOR, + sizeof(*s302m_s_desc)); + + s302m_s_desc->name = "Sine Wave PCM Audio"; + s302m_s_desc->service_type = DIGITAL_TELEVISION_SERVICE; + + s302m_s_desc->length = sizeof(s302m_s_desc->service_type) + + strlen(s302m_s_desc->name) + + strlen(s302m_s_desc->name_emph) + + strlen(s302m_s_desc->provider) + + strlen(s302m_s_desc->provider_emph); + + vidtv_psi_desc_assign(&s302m_service->descriptor, + (struct vidtv_psi_desc *) + s302m_s_desc); + + s302m->transport_stream_id = TRANSPORT_STREAM_ID; + + s302m->program = vidtv_psi_pat_program_init(NULL, + s302m_service_id, + s302m_program_pid); + + s302m->program_num = s302m_program_num; + + s302m->streams = vidtv_psi_pmt_stream_init(NULL, + STREAM_PRIVATE_DATA, + s302m_pes_audio_stream_id); + + encoder_args.access_unit_capacity = 16; + encoder_args.es_pid = s302m_es_pid; + + s302m->encoders = vidtv_s302m_encoder_init(encoder_args); + + if (head) { + while (head->next) + head = head->next; + + head->next = s302m; + } + + return s302m; +} + +static struct vidtv_psi_table_sdt_service +*vidtv_channel_sdt_serv_cat_into_new(struct vidtv_channel *channels) +{ + struct vidtv_channel *cur_chnl = channels; + struct vidtv_psi_table_sdt_service *curr = NULL; + struct vidtv_psi_table_sdt_service *head = NULL; + struct vidtv_psi_table_sdt_service *tail = NULL; + u16 service_id; + + while (cur_chnl) { + curr = cur_chnl->service; + service_id = be16_to_cpu(curr->service_id); + + if (!curr) + continue; + + while (curr->next) { + tail = vidtv_psi_sdt_service_init(tail, service_id); + + if (!head) + head = tail; + + curr = curr->next; + } + + cur_chnl = cur_chnl->next; + } + + return head; +} + +static struct vidtv_psi_table_pat_program* +vidtv_channel_pat_prog_cat_into_new(struct vidtv_channel *channels) +{ + struct vidtv_channel *cur_chnl = channels; + struct vidtv_psi_table_pat_program *curr = NULL; + struct vidtv_psi_table_pat_program *head = NULL; + struct vidtv_psi_table_pat_program *tail = NULL; + u16 serv_id; + + while (cur_chnl) { + curr = cur_chnl->program; + + if (!curr) + continue; + + while (curr->next) { + serv_id = be16_to_cpu(curr->service_id); + tail = vidtv_psi_pat_program_init(tail, + serv_id, + curr->pid); + + if (!head) + head = tail; + + curr = curr->next; + } + + cur_chnl = cur_chnl->next; + } + + return head; +} + +static void +vidtv_channel_pmt_match_sections(struct vidtv_channel *channels, + struct vidtv_psi_table_pmt sections[], + u32 nsections) +{ + struct vidtv_psi_table_pmt *curr_section = NULL; + struct vidtv_channel *cur_chnl = channels; + u32 j; + u16 c_id; + + while (cur_chnl) { + for (j = 0; j < nsections; ++j) { + curr_section = §ions[j]; + + if (!curr_section) + continue; + + c_id = be16_to_cpu(curr_section->header.id); + + /* we got a match */ + if (c_id == cur_chnl->program_num) { + vidtv_psi_pmt_stream_assign(curr_section, + cur_chnl->streams); + break; + } + } + + cur_chnl = cur_chnl->next; + } +} + +void vidtv_channel_si_init(struct vidtv_mux *m) +{ + struct vidtv_psi_table_pat *pat = m->si.pat; + struct vidtv_psi_table_sdt *sdt = m->si.sdt; + + struct vidtv_psi_table_pmt *pmt_sections = m->si.pmt_secs; + + struct vidtv_psi_table_pat_program *programs = NULL; + struct vidtv_psi_table_sdt_service *services = NULL; + + bool update_version_num = false; + + vidtv_psi_pat_table_init(pat, + update_version_num, + TRANSPORT_STREAM_ID); + + vidtv_psi_sdt_table_init(sdt, + update_version_num, + TRANSPORT_STREAM_ID); + + programs = vidtv_channel_pat_prog_cat_into_new(m->channels); + services = vidtv_channel_sdt_serv_cat_into_new(m->channels); + + /* assemble all programs and assign to PAT */ + vidtv_psi_pat_program_assign(pat, programs); + + /* assemble all services and assign to SDT */ + vidtv_psi_sdt_service_assign(sdt, services); + + /* a section for each program_id */ + pmt_sections = kcalloc(pat->programs, + sizeof(struct vidtv_psi_table_pmt), + GFP_KERNEL); + + vidtv_psi_pmt_create_sec_for_each_pat_entry(pat, + pmt_sections); + + vidtv_channel_pmt_match_sections(m->channels, + pmt_sections, + pat->programs); +} + +void vidtv_channel_si_destroy(struct vidtv_mux *m) +{ + u32 i; + + vidtv_psi_pat_table_destroy(m->si.pat); + + for (i = 0; i < m->si.num_pmt_sections; ++i) + vidtv_psi_pmt_table_destroy(&m->si.pmt_secs[i]); + + kfree(m->si.pmt_secs); + vidtv_psi_sdt_table_destroy(m->si.sdt); +} + +void vidtv_channels_init(struct vidtv_mux *m) +{ + /* we only have a single channel for now */ + m->channels = vidtv_channel_s302m_init(NULL); +} + +void vidtv_channels_destroy(struct vidtv_mux *m) +{ + struct vidtv_channel *curr = m->channels; + + while (curr) { + vidtv_psi_sdt_service_destroy(curr->service); + vidtv_psi_pat_program_destroy(curr->program); + vidtv_psi_pmt_stream_destroy(curr->streams); + vidtv_channel_encoder_destroy(curr->encoders); + curr = curr->next; + } +} + +static void +vidtv_channels_add_registration_s302m(struct vidtv_psi_table_pmt *sec) +{ + struct vidtv_psi_desc_registration *s302m_r_desc; + + /* there might be some descriptors there already */ + struct vidtv_psi_desc *parent = sec->descriptor; + + s302m_r_desc = (struct vidtv_psi_desc_registration *) + vidtv_psi_desc_init(parent, + REGISTRATION_DESCRIPTOR, + sizeof(*s302m_r_desc)); + + s302m_r_desc->format_id = cpu_to_be32(VIDTV_S302M_FORMAT_IDENTIFIER); + + if (!parent) + vidtv_psi_desc_assign(&sec->descriptor, + (struct vidtv_psi_desc *)s302m_r_desc); + + /* we are adding to the table, so recompute the length */ + vidtv_psi_pmt_table_comp_sec_len(sec); +} + +void vidtv_channels_add_registration_descs(struct vidtv_mux *m) +{ + /* + * Some formats might need a registration descriptor to be recognized. + * S302M needs it, and ffmpeg actually checks for it, so add such + * descriptor at the PMT section that contains the stream + */ + struct vidtv_channel *cur_chnl = m->channels; + struct vidtv_encoder *e = NULL; + struct vidtv_psi_table_pmt *sec = NULL; + + while (cur_chnl) { + e = cur_chnl->encoders; + sec = vidtv_psi_find_pmt_sec(m->si.pmt_secs, + m->si.pat->programs, + cur_chnl->program_num); + + /* bug somewhere */ + if (!sec) { + pr_warn_ratelimited("No section for prog_num %d\n", + cur_chnl->program_num); + + continue; + } + + while (e) { + switch (e->id) { + case S302M: + vidtv_channels_add_registration_s302m(sec); + break; + default: + break; + } + + e = e->next; + } + + cur_chnl = cur_chnl->next; + } +} diff --git a/drivers/media/test-drivers/vidtv/vidtv_channel.h b/drivers/media/test-drivers/vidtv/vidtv_channel.h new file mode 100644 index 0000000000000..02141c4c732f0 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_channel.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the code for a 'channel' abstraction. + * + * When vidtv boots, it will create some hardcoded channels. + * Their services will be concatenated to populate the SDT. + * Their programs will be concatenated to populate the PAT + * For each program in the PAT, a PMT section will be created + * The PMT section for a channel will be assigned its streams. + * Every stream will have its corresponding encoder polled to produce TS packets + * These packets may be interleaved by the mux and then delivered to the bridge + * + * + * Written by Daniel W. S. Almeida + */ + +#ifndef VIDTV_CHANNEL_H +#define VIDTV_CHANNEL_H + +#include +#include "vidtv_psi.h" +#include "vidtv_encoder.h" +#include "vidtv_mux.h" + +struct vidtv_channel { + /* a number to identify the TS, chosen at will */ + u16 transport_stream_id; + + /* will be concatenated into the SDT */ + struct vidtv_psi_table_sdt_service *service; + + /* the link between SDT, PAT and PMT */ + u16 program_num; + + /* + * a single program with one or more streams associated with it. + * Will be concatenated into the PAT + */ + struct vidtv_psi_table_pat_program *program; + + /* + * one or more streams associated with the program + * Will populate the PMT section for this program + */ + struct vidtv_psi_table_pmt_stream *streams; + + /* a list of encoders, one for each stream */ + struct vidtv_encoder *encoders; + + struct vidtv_channel *next; +}; + +/* init SI data from the channels */ +void vidtv_channel_si_init(struct vidtv_mux *m); +void vidtv_channel_si_destroy(struct vidtv_mux *m); + +void vidtv_channels_init(struct vidtv_mux *m); +void vidtv_channels_destroy(struct vidtv_mux *m); + +void vidtv_channels_add_registration_descs(struct vidtv_mux *m); + +#endif //VIDTV_CHANNEL_H diff --git a/drivers/media/test-drivers/vidtv/vidtv_common.h b/drivers/media/test-drivers/vidtv/vidtv_common.h index a0480825b23a9..a13e9331955a7 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_common.h +++ b/drivers/media/test-drivers/vidtv/vidtv_common.h @@ -13,6 +13,9 @@ #include #define CLOCK_UNIT_90KHZ 90000 +#define CLOCK_UNIT_27MHZ 27000000 +#define SLEEP_USECS 10000 +#define TRANSPORT_STREAM_ID 0x744 u32 vidtv_memcpy(void *to, size_t to_offset, diff --git a/drivers/media/test-drivers/vidtv/vidtv_mux.c b/drivers/media/test-drivers/vidtv/vidtv_mux.c new file mode 100644 index 0000000000000..c9484c491a977 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_mux.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the multiplexer logic for TS packets from different + * elementary streams + * + * Written by Daniel W. S. Almeida + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s, %d: " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "vidtv_mux.h" +#include "vidtv_ts.h" +#include "vidtv_pes.h" +#include "vidtv_encoder.h" +#include "vidtv_channel.h" +#include "vidtv_common.h" +#include "vidtv_psi.h" + +static struct vidtv_mux_pid_ctx +*vidtv_mux_get_pid_ctx(struct vidtv_mux *m, u16 pid) +{ + struct vidtv_mux_pid_ctx *ctx; + + hash_for_each_possible(m->pid_ctx, ctx, h, pid) + if (ctx->pid == pid) + return ctx; + + return NULL; +} + +static struct vidtv_mux_pid_ctx +*vidtv_mux_create_pid_ctx_once(struct vidtv_mux *m, u16 pid) +{ + struct vidtv_mux_pid_ctx *ctx; + + ctx = vidtv_mux_get_pid_ctx(m, pid); + + if (ctx) + goto end; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + ctx->pid = pid; + ctx->cc = 0; + hash_add(m->pid_ctx, &ctx->h, pid); + +end: + return ctx; +} + +static void vidtv_mux_pid_ctx_init(struct vidtv_mux *m) +{ + struct vidtv_psi_table_pat_program *p = m->si.pat->program; + + hash_init(m->pid_ctx); + /* push the pcr pid ctx */ + vidtv_mux_create_pid_ctx_once(m, m->pcr_pid); + /* push the null packet pid ctx */ + vidtv_mux_create_pid_ctx_once(m, TS_NULL_PACKET_PID); + /* push the PAT pid ctx */ + vidtv_mux_create_pid_ctx_once(m, VIDTV_PAT_PID); + /* push the SDT pid ctx */ + vidtv_mux_create_pid_ctx_once(m, VIDTV_SDT_PID); + + /* add a ctx for all PMT sections */ + while (p) { + vidtv_mux_create_pid_ctx_once(m, p->pid); + p = p->next; + } +} + +static void vidtv_mux_pid_ctx_destroy(struct vidtv_mux *m) +{ + int bkt; + struct vidtv_mux_pid_ctx *ctx; + + hash_for_each(m->pid_ctx, bkt, ctx, h) { + kfree(ctx); + } +} + +static void vidtv_mux_update_clk(struct vidtv_mux *m) +{ + /* call this at every thread iteration */ + u64 elapsed_time; + + /* this will not hold a value yet if we have just started */ + m->timing.past_jiffies = m->timing.current_jiffies ? + m->timing.current_jiffies : + get_jiffies_64(); + + m->timing.current_jiffies = get_jiffies_64(); + + elapsed_time = jiffies_to_usecs(m->timing.current_jiffies - + m->timing.past_jiffies); + + /* update the 27Mhz clock proportionally to the elapsed time */ + m->timing.clk += (CLOCK_UNIT_27MHZ / USEC_PER_SEC) * elapsed_time; +} + +static u32 vidtv_mux_push_si(struct vidtv_mux *m) +{ + u32 initial_offset = m->ts_buf_offset; + struct vidtv_mux_pid_ctx *pat_ctx; + struct vidtv_mux_pid_ctx *pmt_ctx; + struct vidtv_mux_pid_ctx *sdt_ctx; + u32 nbytes; /* the number of bytes written by this function */ + u16 pmt_pid; + u32 i; + + pat_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_PAT_PID); + sdt_ctx = vidtv_mux_get_pid_ctx(m, VIDTV_SDT_PID); + + m->ts_buf_offset += vidtv_psi_pat_write_into(m->ts_buf, + m->ts_buf_offset, + m->si.pat, + m->ts_buf_sz, + &pat_ctx->cc); + + for (i = 0; i < m->si.num_pmt_sections; ++i) { + pmt_pid = vidtv_psi_pmt_get_pid(&m->si.pmt_secs[i], + m->si.pat); + + if (pmt_pid > TS_LAST_VALID_PID) { + pr_warn_ratelimited("PID: %d not found\n", pmt_pid); + continue; + } + + pmt_ctx = vidtv_mux_get_pid_ctx(m, pmt_pid); + + /* write each section into buffer */ + m->ts_buf_offset += vidtv_psi_pmt_write_into(m->ts_buf, + m->ts_buf_offset, + &m->si.pmt_secs[i], + pmt_pid, + m->ts_buf_sz, + &pmt_ctx->cc); + } + + m->ts_buf_offset += vidtv_psi_sdt_write_into(m->ts_buf, + m->ts_buf_offset, + m->si.sdt, + m->ts_buf_sz, + &sdt_ctx->cc); + + nbytes = m->ts_buf_offset - initial_offset; + return nbytes; +} + +static u32 vidtv_mux_push_pcr(struct vidtv_mux *m) +{ + struct pcr_write_args args = {}; + struct vidtv_mux_pid_ctx *ctx; + + ctx = vidtv_mux_get_pid_ctx(m, m->pcr_pid); + args.dest_buf = m->ts_buf; + args.pid = m->pcr_pid; + args.buf_sz = m->ts_buf_sz; + args.continuity_counter = &ctx->cc; + + /* the 27Mhz clock will feed both parts of the PCR bitfield */ + args.pcr = m->timing.clk; + + return vidtv_ts_pcr_write_into(args); +} + +static bool vidtv_mux_should_push_pcr(struct vidtv_mux *m) +{ + u64 next_pcr_at; + + next_pcr_at = m->timing.start_jiffies + + usecs_to_jiffies(m->num_streamed_pcr * + m->timing.pcr_period_usecs); + + return time_after64(m->timing.current_jiffies, next_pcr_at); +} + +static bool vidtv_mux_should_push_si(struct vidtv_mux *m) +{ + u64 next_si_at; + + next_si_at = m->timing.start_jiffies + + usecs_to_jiffies(m->num_streamed_si * + m->timing.si_period_usecs); + + return time_after64(m->timing.current_jiffies, next_si_at); +} + +static u32 vidtv_mux_packetize_access_units(struct vidtv_mux *m, + struct vidtv_encoder *e) +{ + /* the number of bytes written by this function */ + u32 nbytes = 0; + struct pes_write_args args = {}; + u32 initial_offset = m->ts_buf_offset; + + u32 i; + u8 *buf; + struct vidtv_mux_pid_ctx *pid_ctx; + + pid_ctx = vidtv_mux_create_pid_ctx_once(m, e->es_pid); + + args.dest_buf = m->ts_buf; + args.dest_buf_sz = m->ts_buf_sz; + args.pid = e->es_pid; + args.is_s302m_pkt = (e->id == S302M); + args.continuity_counter = &pid_ctx->cc; + args.send_pts = true; + + for (i = 0; i < e->nunits; ++i) { + buf = e->encoder_buf + e->offsets[i]; + args.from = buf; + args.access_unit_len = e->nbytes[i]; + args.dest_offset = m->ts_buf_offset; + args.pts = e->pts[i]; + + m->ts_buf_offset += vidtv_pes_write_into(args); + } + + /* clear the encoder state once we have written the current ES data */ + e->clear(e); + + nbytes = m->ts_buf_offset - initial_offset; + return nbytes; +} + +static u32 vidtv_mux_poll_encoders(struct vidtv_mux *m) +{ + u32 nbytes = 0; + struct vidtv_channel *cur_chnl = m->channels; + struct vidtv_encoder *e = NULL; + + u64 elapsed_time_usecs = jiffies_to_usecs(m->timing.current_jiffies - + m->timing.past_jiffies); + while (cur_chnl) { + e = cur_chnl->encoders; + + while (e) { + /* encode for 'elapsed_time_usecs' */ + e->encode(e, elapsed_time_usecs); + /* get the TS packets into the mux buffer */ + nbytes += vidtv_mux_packetize_access_units(m, e); + /* grab next encoder */ + e = e->next; + } + + /* grab the next channel */ + cur_chnl = cur_chnl->next; + } + + return nbytes; +} + +static u32 vidtv_mux_pad_with_nulls(struct vidtv_mux *m, u32 npkts) +{ + struct null_packet_write_args args = {}; + u32 initial_offset = m->ts_buf_offset; + u32 nbytes; /* the number of bytes written by this function */ + u32 i; + struct vidtv_mux_pid_ctx *ctx; + + ctx = vidtv_mux_get_pid_ctx(m, TS_NULL_PACKET_PID); + + args.dest_buf = m->ts_buf; + args.buf_sz = m->ts_buf_sz; + args.continuity_counter = &ctx->cc; + args.dest_offset = m->ts_buf_offset; + + for (i = 0; i < npkts; ++i) { + m->ts_buf_offset += vidtv_ts_null_write_into(args); + args.dest_offset = m->ts_buf_offset; + } + + nbytes = m->ts_buf_offset - initial_offset; + + /* sanity check */ + if (nbytes != npkts * TS_PACKET_LEN) + pr_err_ratelimited("%d != %d\n", nbytes, npkts * TS_PACKET_LEN); + + return nbytes; +} + +static u32 vidtv_mux_check_mux_rate(struct vidtv_mux *m) +{ + /* + * attempt to maintain a constant mux rate, padding with null packets + * if needed + */ + + u32 nbytes = 0; /* the number of bytes written by this function */ + + u64 nbytes_expected; /* the number of bytes we should have written */ + u64 nbytes_streamed; /* the number of bytes we actually wrote */ + u32 num_null_pkts; /* number of null packets to bridge the gap */ + + u64 elapsed_time_usecs = jiffies_to_usecs(m->timing.current_jiffies - + m->timing.past_jiffies); + + nbytes_expected = (m->mux_rate_kbytes_sec / 1000) / USEC_PER_SEC; + nbytes_expected *= elapsed_time_usecs; + + nbytes_streamed = m->num_streamed_pkts * TS_PACKET_LEN; + + if (nbytes_streamed < nbytes_expected) { + /* can't write half a packet: roundup to a 188 multiple */ + nbytes_expected = roundup(nbytes_expected, TS_PACKET_LEN); + num_null_pkts = nbytes_expected / TS_PACKET_LEN; + nbytes += vidtv_mux_pad_with_nulls(m, num_null_pkts); + } + + return nbytes; +} + +static void vidtv_mux_clear(struct vidtv_mux *m) +{ + /* clear the packets currently in the mux */ + memset(m->ts_buf, 0, m->ts_buf_sz); + /* point to the beginning of the buffer again */ + m->ts_buf_offset = 0; +} + +static void vidtv_mux_tick(struct work_struct *work) +{ + struct vidtv_mux *m = container_of(work, + struct vidtv_mux, + mpeg_thread); + u32 nbytes; + u32 npkts; + + while (m->streaming) { + nbytes = 0; + + vidtv_mux_update_clk(m); + + if (vidtv_mux_should_push_pcr(m)) + nbytes += vidtv_mux_push_pcr(m); + + if (vidtv_mux_should_push_si(m)) + nbytes += vidtv_mux_push_si(m); + + nbytes += vidtv_mux_poll_encoders(m); + + nbytes += vidtv_mux_check_mux_rate(m); + + npkts = nbytes / TS_PACKET_LEN; + + /* if the buffer is not aligned there is a bug somewhere */ + if (nbytes % TS_PACKET_LEN) + pr_err_ratelimited("Misaligned buffer\n"); + + if (m->on_new_packets_available_cb) + m->on_new_packets_available_cb(m->priv, + m->ts_buf, + npkts); + + m->num_streamed_pkts += npkts; + vidtv_mux_clear(m); + + usleep_range(SLEEP_USECS, 2 * SLEEP_USECS); + } +} + +void vidtv_mux_start_thread(struct vidtv_mux *m) +{ + if (m->streaming) { + pr_warn_ratelimited("Already streaming. Skipping.\n"); + return; + } + + m->streaming = true; + m->timing.start_jiffies = get_jiffies_64(); + schedule_work(&m->mpeg_thread); +} + +void vidtv_mux_stop_thread(struct vidtv_mux *m) +{ + /* thread will quit */ + m->streaming = false; +} + +struct vidtv_mux *vidtv_mux_init(struct vidtv_mux_init_args args) +{ + struct vidtv_mux *m = kzalloc(sizeof(*m), GFP_KERNEL); + + m->timing.pcr_period_usecs = args.pcr_period_usecs; + m->timing.si_period_usecs = args.si_period_usecs; + + m->mux_rate_kbytes_sec = args.mux_rate_kbytes_sec; + + m->on_new_packets_available_cb = args.on_new_packets_available_cb; + + m->ts_buf = vzalloc(args.ts_buf_sz); + m->ts_buf_sz = args.ts_buf_sz; + + m->si.pat = kzalloc(sizeof(*m->si.pat), GFP_KERNEL); + m->si.sdt = kzalloc(sizeof(*m->si.sdt), GFP_KERNEL); + + vidtv_channels_init(m); + + /* will alloc data for pmt_sections after initializing pat */ + vidtv_channel_si_init(m); + + INIT_WORK(&m->mpeg_thread, vidtv_mux_tick); + + m->pcr_pid = args.pcr_pid; + m->priv = args.priv; + + vidtv_mux_pid_ctx_init(m); + vidtv_channels_add_registration_descs(m); + + return m; +} + +void vidtv_mux_destroy(struct vidtv_mux *m) +{ + vidtv_mux_pid_ctx_destroy(m); + vidtv_channel_si_destroy(m); + vidtv_channels_destroy(m); + kfree(m->si.sdt); + kfree(m->si.pat); + kfree(m->ts_buf); + kfree(m); +} diff --git a/drivers/media/test-drivers/vidtv/vidtv_mux.h b/drivers/media/test-drivers/vidtv/vidtv_mux.h new file mode 100644 index 0000000000000..1dffa2010d518 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_mux.h @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Vidtv serves as a reference DVB driver and helps validate the existing APIs + * in the media subsystem. It can also aid developers working on userspace + * applications. + * + * This file contains the multiplexer logic for TS packets from different + * elementary streams + * + * Written by Daniel W. S. Almeida + */ + +#ifndef VIDTV_MUX_H +#define VIDTV_MUX_H + +#include +#include +#include +#include "vidtv_psi.h" +struct vidtv_mux_timing { + u64 start_jiffies; + u64 current_jiffies; + u64 past_jiffies; + + /* a 27Mhz clock from which we will drive the PCR */ + u64 clk; + + u64 pcr_period_usecs; + u64 si_period_usecs; +}; + +struct vidtv_mux_si { + /* the SI tables */ + struct vidtv_psi_table_pat *pat; + struct vidtv_psi_table_pmt *pmt_secs; /* the PMT sections */ + /* as many sections as programs in the PAT */ + u16 num_pmt_sections; + struct vidtv_psi_table_sdt *sdt; +}; + +struct vidtv_mux_pid_ctx { + u16 pid; + u8 cc; /* continuity counter */ + struct hlist_node h; +}; + +struct vidtv_mux { + struct vidtv_mux_timing timing; + + /* the bit rate for the TS, in kbytes */ + u32 mux_rate_kbytes_sec; + + /* a hash table to keep track of per-PID metadata */ + DECLARE_HASHTABLE(pid_ctx, 3); + + /* a callback to inform of new TS packets ready */ + void (*on_new_packets_available_cb)(void *priv, u8 *buf, u32 npackets); + + /* the TS buffer */ + u8 *ts_buf; + + /* the TS buffer size */ + u32 ts_buf_sz; + + /* where we are in the TS buffer now */ + u32 ts_buf_offset; + + /* a list of channels */ + struct vidtv_channel *channels; + + struct vidtv_mux_si si; + u64 num_streamed_pcr; + u64 num_streamed_si; + + /* total number of packets streamed */ + u64 num_streamed_pkts; + + struct work_struct mpeg_thread; + + /* whether to keep running the main loop */ + bool streaming; + + /* the pcr PID for _all_ channels */ + u16 pcr_pid; + + void *priv; +}; + +struct vidtv_mux_init_args { + u32 mux_rate_kbytes_sec; + void (*on_new_packets_available_cb)(void *priv, u8 *buf, u32 npackets); + u32 ts_buf_sz; + u64 pcr_period_usecs; + u64 si_period_usecs; + u16 pcr_pid; + void *priv; +}; + +struct vidtv_mux *vidtv_mux_init(struct vidtv_mux_init_args args); +void vidtv_mux_destroy(struct vidtv_mux *m); + +void vidtv_mux_start_thread(struct vidtv_mux *m); +void vidtv_mux_stop_thread(struct vidtv_mux *m); + +#endif //VIDTV_MUX_H diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.c b/drivers/media/test-drivers/vidtv/vidtv_psi.c index 51e9114173362..d9547efe4626b 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_psi.c +++ b/drivers/media/test-drivers/vidtv/vidtv_psi.c @@ -1035,3 +1035,21 @@ vidtv_psi_pmt_create_sec_for_each_pat_entry(struct vidtv_psi_table_pat *pat, program = program->next; } } + +struct vidtv_psi_table_pmt +*vidtv_psi_find_pmt_sec(struct vidtv_psi_table_pmt pmt_sections[], + u16 nsections, + u16 program_num) +{ + /* find the PMT section associated with 'program_num' */ + struct vidtv_psi_table_pmt *sec = NULL; + u32 i; + + for (i = 0; i < nsections; ++i) { + sec = &pmt_sections[i]; + if (be16_to_cpu(sec->header.id) == program_num) + return sec; + } + + return NULL; +} diff --git a/drivers/media/test-drivers/vidtv/vidtv_psi.h b/drivers/media/test-drivers/vidtv/vidtv_psi.h index 77b616a46757a..f45540d53b351 100644 --- a/drivers/media/test-drivers/vidtv/vidtv_psi.h +++ b/drivers/media/test-drivers/vidtv/vidtv_psi.h @@ -412,4 +412,9 @@ u32 vidtv_psi_pmt_write_into(char *buf, u32 buf_sz, u8 *continuity_counter); +struct vidtv_psi_table_pmt +*vidtv_psi_find_pmt_sec(struct vidtv_psi_table_pmt *pmt_sections, + u16 nsections, + u16 program_num); + #endif // VIDTV_PSI_H