root/drivers/media/test-drivers/vidtv/vidtv_channel.c
// 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
 * Their events will be concatenated to populate the EIT
 * 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
 *
 *
 * Copyright (C) 2020 Daniel W. S. Almeida
 */

#include <linux/dev_printk.h>
#include <linux/ratelimit.h>
#include <linux/slab.h>
#include <linux/types.h>

#include "vidtv_channel.h"
#include "vidtv_common.h"
#include "vidtv_encoder.h"
#include "vidtv_mux.h"
#include "vidtv_psi.h"
#include "vidtv_s302m.h"

static void vidtv_channel_encoder_destroy(struct vidtv_encoder *e)
{
        struct vidtv_encoder *tmp = NULL;
        struct vidtv_encoder *curr = e;

        while (curr) {
                /* forward the call to the derived type */
                tmp = curr;
                curr = curr->next;
                tmp->destroy(tmp);
        }
}

#define ENCODING_ISO8859_15 "\x0b"
#define TS_NIT_PID      0x10

/*
 * init an audio only channel with a s302m encoder
 */
struct vidtv_channel
*vidtv_channel_s302m_init(struct vidtv_channel *head, u16 transport_stream_id)
{
        const __be32 s302m_fid              = cpu_to_be32(VIDTV_S302M_FORMAT_IDENTIFIER);
        char *event_text = ENCODING_ISO8859_15 "Bagatelle No. 25 in A minor for solo piano, also known as F\xfcr Elise, composed by Ludwig van Beethoven";
        char *event_name = ENCODING_ISO8859_15 "Ludwig van Beethoven: F\xfcr Elise";
        struct vidtv_s302m_encoder_init_args encoder_args = {};
        char *iso_language_code = ENCODING_ISO8859_15 "eng";
        char *provider = ENCODING_ISO8859_15 "LinuxTV.org";
        char *name = ENCODING_ISO8859_15 "Beethoven";
        const u16 s302m_es_pid              = 0x111; /* packet id for the ES */
        const u16 s302m_program_pid         = 0x101; /* packet id for PMT*/
        const u16 s302m_service_id          = 0x880;
        const u16 s302m_program_num         = 0x880;
        const u16 s302m_beethoven_event_id  = 1;
        struct vidtv_channel *s302m;

        s302m = kzalloc_obj(*s302m);
        if (!s302m)
                return NULL;

        s302m->name = kstrdup(name, GFP_KERNEL);
        if (!s302m->name)
                goto free_s302m;

        s302m->service = vidtv_psi_sdt_service_init(NULL, s302m_service_id, false, true);
        if (!s302m->service)
                goto free_name;

        s302m->service->descriptor = (struct vidtv_psi_desc *)
                                     vidtv_psi_service_desc_init(NULL,
                                                                 DIGITAL_RADIO_SOUND_SERVICE,
                                                                 name,
                                                                 provider);
        if (!s302m->service->descriptor)
                goto free_service;

        s302m->transport_stream_id = transport_stream_id;

        s302m->program = vidtv_psi_pat_program_init(NULL,
                                                    s302m_service_id,
                                                    s302m_program_pid);
        if (!s302m->program)
                goto free_service;

        s302m->program_num = s302m_program_num;

        s302m->streams = vidtv_psi_pmt_stream_init(NULL,
                                                   STREAM_PRIVATE_DATA,
                                                   s302m_es_pid);
        if (!s302m->streams)
                goto free_program;

        s302m->streams->descriptor = (struct vidtv_psi_desc *)
                                     vidtv_psi_registration_desc_init(NULL,
                                                                      s302m_fid,
                                                                      NULL,
                                                                      0);
        if (!s302m->streams->descriptor)
                goto free_streams;

        encoder_args.es_pid = s302m_es_pid;

        s302m->encoders = vidtv_s302m_encoder_init(encoder_args);
        if (!s302m->encoders)
                goto free_streams;

        s302m->events = vidtv_psi_eit_event_init(NULL, s302m_beethoven_event_id);
        if (!s302m->events)
                goto free_encoders;
        s302m->events->descriptor = (struct vidtv_psi_desc *)
                                    vidtv_psi_short_event_desc_init(NULL,
                                                                    iso_language_code,
                                                                    event_name,
                                                                    event_text);
        if (!s302m->events->descriptor)
                goto free_events;

        if (head) {
                while (head->next)
                        head = head->next;

                head->next = s302m;
        }

        return s302m;

free_events:
        vidtv_psi_eit_event_destroy(s302m->events);
free_encoders:
        vidtv_s302m_encoder_destroy(s302m->encoders);
free_streams:
        vidtv_psi_pmt_stream_destroy(s302m->streams);
free_program:
        vidtv_psi_pat_program_destroy(s302m->program);
free_service:
        vidtv_psi_sdt_service_destroy(s302m->service);
free_name:
        kfree(s302m->name);
free_s302m:
        kfree(s302m);

        return NULL;
}

static struct vidtv_psi_table_eit_event
*vidtv_channel_eit_event_cat_into_new(struct vidtv_mux *m)
{
        /* Concatenate the events */
        const struct vidtv_channel *cur_chnl = m->channels;
        struct vidtv_psi_table_eit_event *curr = NULL;
        struct vidtv_psi_table_eit_event *head = NULL;
        struct vidtv_psi_table_eit_event *tail = NULL;
        struct vidtv_psi_desc *desc = NULL;
        u16 event_id;

        if (!cur_chnl)
                return NULL;

        while (cur_chnl) {
                curr = cur_chnl->events;

                if (!curr)
                        dev_warn_ratelimited(m->dev,
                                             "No events found for channel %s\n",
                                             cur_chnl->name);

                while (curr) {
                        event_id = be16_to_cpu(curr->event_id);
                        tail = vidtv_psi_eit_event_init(tail, event_id);
                        if (!tail) {
                                vidtv_psi_eit_event_destroy(head);
                                return NULL;
                        }

                        desc = vidtv_psi_desc_clone(curr->descriptor);
                        vidtv_psi_desc_assign(&tail->descriptor, desc);

                        if (!head)
                                head = tail;

                        curr = curr->next;
                }

                cur_chnl = cur_chnl->next;
        }

        return head;
}

static struct vidtv_psi_table_sdt_service
*vidtv_channel_sdt_serv_cat_into_new(struct vidtv_mux *m)
{
        /* Concatenate the services */
        const struct vidtv_channel *cur_chnl = m->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;

        struct vidtv_psi_desc *desc = NULL;
        u16 service_id;

        if (!cur_chnl)
                return NULL;

        while (cur_chnl) {
                curr = cur_chnl->service;

                if (!curr)
                        dev_warn_ratelimited(m->dev,
                                             "No services found for channel %s\n",
                                             cur_chnl->name);

                while (curr) {
                        service_id = be16_to_cpu(curr->service_id);
                        tail = vidtv_psi_sdt_service_init(tail,
                                                          service_id,
                                                          curr->EIT_schedule,
                                                          curr->EIT_present_following);
                        if (!tail)
                                goto free;

                        desc = vidtv_psi_desc_clone(curr->descriptor);
                        if (!desc)
                                goto free_tail;
                        vidtv_psi_desc_assign(&tail->descriptor, desc);

                        if (!head)
                                head = tail;

                        curr = curr->next;
                }

                cur_chnl = cur_chnl->next;
        }

        return head;

free_tail:
        vidtv_psi_sdt_service_destroy(tail);
free:
        vidtv_psi_sdt_service_destroy(head);
        return NULL;
}

static struct vidtv_psi_table_pat_program*
vidtv_channel_pat_prog_cat_into_new(struct vidtv_mux *m)
{
        /* Concatenate the programs */
        const struct vidtv_channel *cur_chnl = m->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;
        u16 pid;

        if (!cur_chnl)
                return NULL;

        while (cur_chnl) {
                curr = cur_chnl->program;

                if (!curr)
                        dev_warn_ratelimited(m->dev,
                                             "No programs found for channel %s\n",
                                             cur_chnl->name);

                while (curr) {
                        serv_id = be16_to_cpu(curr->service_id);
                        pid = vidtv_psi_get_pat_program_pid(curr);
                        tail = vidtv_psi_pat_program_init(tail,
                                                          serv_id,
                                                          pid);
                        if (!tail) {
                                vidtv_psi_pat_program_destroy(head);
                                return NULL;
                        }

                        if (!head)
                                head = tail;

                        curr = curr->next;
                }

                cur_chnl = cur_chnl->next;
        }
        /* Add the NIT table */
        vidtv_psi_pat_program_init(tail, 0, TS_NIT_PID);

        return head;
}

/*
 * Match channels to their respective PMT sections, then assign the
 * streams
 */
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_psi_table_pmt_stream *head = NULL;
        struct vidtv_psi_table_pmt_stream *tail = NULL;
        struct vidtv_psi_table_pmt_stream *s = NULL;
        struct vidtv_channel *cur_chnl = channels;
        struct vidtv_psi_desc *desc = NULL;
        u16 e_pid; /* elementary stream pid */
        u16 curr_id;
        u32 j;

        while (cur_chnl) {
                for (j = 0; j < nsections; ++j) {
                        curr_section = sections[j];

                        if (!curr_section)
                                continue;

                        curr_id = be16_to_cpu(curr_section->header.id);

                        /* we got a match */
                        if (curr_id == cur_chnl->program_num) {
                                s = cur_chnl->streams;

                                /* clone the streams for the PMT */
                                while (s) {
                                        e_pid = vidtv_psi_pmt_stream_get_elem_pid(s);
                                        tail = vidtv_psi_pmt_stream_init(tail,
                                                                         s->type,
                                                                         e_pid);

                                        if (!head)
                                                head = tail;

                                        desc = vidtv_psi_desc_clone(s->descriptor);
                                        vidtv_psi_desc_assign(&tail->descriptor,
                                                              desc);

                                        s = s->next;
                                }

                                vidtv_psi_pmt_stream_assign(curr_section, head);
                                break;
                        }
                }

                cur_chnl = cur_chnl->next;
        }
}

static void
vidtv_channel_destroy_service_list(struct vidtv_psi_desc_service_list_entry *e)
{
        struct vidtv_psi_desc_service_list_entry *tmp;

        while (e) {
                tmp = e;
                e = e->next;
                kfree(tmp);
        }
}

static struct vidtv_psi_desc_service_list_entry
*vidtv_channel_build_service_list(struct vidtv_psi_table_sdt_service *s)
{
        struct vidtv_psi_desc_service_list_entry *curr_e = NULL;
        struct vidtv_psi_desc_service_list_entry *head_e = NULL;
        struct vidtv_psi_desc_service_list_entry *prev_e = NULL;
        struct vidtv_psi_desc *desc = s->descriptor;
        struct vidtv_psi_desc_service *s_desc;

        while (s) {
                while (desc) {
                        if (s->descriptor->type != SERVICE_DESCRIPTOR)
                                goto next_desc;

                        s_desc = (struct vidtv_psi_desc_service *)desc;

                        curr_e = kzalloc_obj(*curr_e);
                        if (!curr_e) {
                                vidtv_channel_destroy_service_list(head_e);
                                return NULL;
                        }

                        curr_e->service_id = s->service_id;
                        curr_e->service_type = s_desc->service_type;

                        if (!head_e)
                                head_e = curr_e;
                        if (prev_e)
                                prev_e->next = curr_e;

                        prev_e = curr_e;

next_desc:
                        desc = desc->next;
                }
                s = s->next;
        }
        return head_e;
}

int vidtv_channel_si_init(struct vidtv_mux *m)
{
        struct vidtv_psi_desc_service_list_entry *service_list = NULL;
        struct vidtv_psi_table_pat_program *programs = NULL;
        struct vidtv_psi_table_sdt_service *services = NULL;
        struct vidtv_psi_table_eit_event *events = NULL;

        m->si.pat = vidtv_psi_pat_table_init(m->transport_stream_id);
        if (!m->si.pat)
                return -ENOMEM;

        m->si.sdt = vidtv_psi_sdt_table_init(m->network_id,
                                             m->transport_stream_id);
        if (!m->si.sdt)
                goto free_pat;

        programs = vidtv_channel_pat_prog_cat_into_new(m);
        if (!programs)
                goto free_sdt;
        services = vidtv_channel_sdt_serv_cat_into_new(m);
        if (!services)
                goto free_programs;

        events = vidtv_channel_eit_event_cat_into_new(m);
        if (!events)
                goto free_services;

        /* look for a service descriptor for every service */
        service_list = vidtv_channel_build_service_list(services);
        if (!service_list)
                goto free_events;

        /* use these descriptors to build the NIT */
        m->si.nit = vidtv_psi_nit_table_init(m->network_id,
                                             m->transport_stream_id,
                                             m->network_name,
                                             service_list);
        if (!m->si.nit)
                goto free_service_list;

        m->si.eit = vidtv_psi_eit_table_init(m->network_id,
                                             m->transport_stream_id,
                                             programs->service_id);
        if (!m->si.eit)
                goto free_nit;

        /* assemble all programs and assign to PAT */
        vidtv_psi_pat_program_assign(m->si.pat, programs);
        programs = NULL;

        /* assemble all services and assign to SDT */
        vidtv_psi_sdt_service_assign(m->si.sdt, services);
        services = NULL;

        /* assemble all events and assign to EIT */
        vidtv_psi_eit_event_assign(m->si.eit, events);
        events = NULL;

        m->si.pmt_secs = vidtv_psi_pmt_create_sec_for_each_pat_entry(m->si.pat,
                                                                     m->pcr_pid);
        if (!m->si.pmt_secs)
                goto free_eit;

        vidtv_channel_pmt_match_sections(m->channels,
                                         m->si.pmt_secs,
                                         m->si.pat->num_pmt);

        vidtv_channel_destroy_service_list(service_list);

        return 0;

free_eit:
        vidtv_psi_eit_table_destroy(m->si.eit);
free_nit:
        vidtv_psi_nit_table_destroy(m->si.nit);
free_service_list:
        vidtv_channel_destroy_service_list(service_list);
free_events:
        vidtv_psi_eit_event_destroy(events);
free_services:
        vidtv_psi_sdt_service_destroy(services);
free_programs:
        vidtv_psi_pat_program_destroy(programs);
free_sdt:
        vidtv_psi_sdt_table_destroy(m->si.sdt);
free_pat:
        vidtv_psi_pat_table_destroy(m->si.pat);
        return -EINVAL;
}

void vidtv_channel_si_destroy(struct vidtv_mux *m)
{
        u32 i;

        for (i = 0; i < m->si.pat->num_pmt; ++i)
                vidtv_psi_pmt_table_destroy(m->si.pmt_secs[i]);

        vidtv_psi_pat_table_destroy(m->si.pat);

        kfree(m->si.pmt_secs);
        vidtv_psi_sdt_table_destroy(m->si.sdt);
        vidtv_psi_nit_table_destroy(m->si.nit);
        vidtv_psi_eit_table_destroy(m->si.eit);
}

int vidtv_channels_init(struct vidtv_mux *m)
{
        /* this is the place to add new 'channels' for vidtv */
        m->channels = vidtv_channel_s302m_init(NULL, m->transport_stream_id);

        if (!m->channels)
                return -ENOMEM;

        return 0;
}

void vidtv_channels_destroy(struct vidtv_mux *m)
{
        struct vidtv_channel *curr = m->channels;
        struct vidtv_channel *tmp = NULL;

        while (curr) {
                kfree(curr->name);
                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);
                vidtv_psi_eit_event_destroy(curr->events);

                tmp = curr;
                curr = curr->next;
                kfree(tmp);
        }
}