// 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 #include #include #include #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); } }