diff options
Diffstat (limited to 'drivers/media/test-drivers/vidtv/vidtv_channel.c')
-rw-r--r-- | drivers/media/test-drivers/vidtv/vidtv_channel.c | 546 |
1 files changed, 546 insertions, 0 deletions
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 000000000..7838e6272 --- /dev/null +++ b/drivers/media/test-drivers/vidtv/vidtv_channel.c @@ -0,0 +1,546 @@ +// 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(sizeof(*s302m), GFP_KERNEL); + 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(sizeof(*curr_e), GFP_KERNEL); + 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); + + /* assemble all services and assign to SDT */ + vidtv_psi_sdt_service_assign(m->si.sdt, services); + + /* assemble all events and assign to EIT */ + vidtv_psi_eit_event_assign(m->si.eit, events); + + 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 0; +} + +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); + } +} |