547 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			547 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			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(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);
 | 
						|
	}
 | 
						|
}
 |