Commit d6c80f22 authored by Christophe Massiot's avatar Christophe Massiot
Browse files

* modules/access/dvb: Partial EN 50 221 implementation. This activates

   native support for CAM modules (without using an external program).
   When used in conjunction with --programs, it also allows to descramble
   several services with one professional CAM.
parent b2147ab9
SOURCES_dvb = \
access.c \
linux_dvb.c \
en50221.c \
dvb.h \
$(NULL)
......@@ -58,9 +58,6 @@ static void Close( vlc_object_t *p_this );
#define DEVICE_TEXT N_("Device number to use on adapter")
#define DEVICE_LONGTEXT ""
#define CAM_TEXT N_("Use CAM")
#define CAM_LONGTEXT ""
#define FREQ_TEXT N_("Transponder/multiplex frequency")
#define FREQ_LONGTEXT N_("In kHz for DVB-S or Hz for DVB-C/T")
......@@ -131,7 +128,6 @@ vlc_module_begin();
VLC_FALSE );
add_integer( "dvb-device", 0, NULL, DEVICE_TEXT, DEVICE_LONGTEXT,
VLC_TRUE );
add_bool( "dvb-cam", 0, NULL, CAM_TEXT, CAM_LONGTEXT, VLC_FALSE );
add_integer( "dvb-frequency", 11954000, NULL, FREQ_TEXT, FREQ_LONGTEXT,
VLC_FALSE );
add_integer( "dvb-inversion", 2, NULL, INVERSION_TEXT, INVERSION_LONGTEXT,
......@@ -190,7 +186,7 @@ vlc_module_end();
static block_t *Block( access_t * );
static int Control( access_t *, int, va_list );
#define SATELLITE_READ_ONCE 3
#define DVB_READ_ONCE 3
#define TS_PACKET_SIZE 188
static void FilterUnset( access_t *, int i_max );
......@@ -274,13 +270,7 @@ static int Open( vlc_object_t *p_this )
FilterSet( p_access, 0x0, OTHER_TYPE );
}
p_sys->b_cam = var_GetBool( p_access, "dvb-cam" );
if ( p_sys->b_cam )
{
msg_Dbg( p_access, "initing CAM..." );
if ( E_(CAMOpen)( p_access ) < 0 )
p_sys->b_cam = VLC_FALSE;
}
E_(CAMOpen)( p_access );
return VLC_SUCCESS;
}
......@@ -297,9 +287,7 @@ static void Close( vlc_object_t *p_this )
E_(DVRClose)( p_access );
E_(FrontendClose)( p_access );
if ( p_sys->b_cam )
E_(CAMClose)( p_access );
E_(CAMClose)( p_access );
free( p_sys );
}
......@@ -310,40 +298,52 @@ static void Close( vlc_object_t *p_this )
static block_t *Block( access_t *p_access )
{
access_sys_t *p_sys = p_access->p_sys;
struct timeval timeout;
fd_set fds;
int i_ret;
block_t *p_block;
/* Initialize file descriptor set */
FD_ZERO( &fds );
FD_SET( p_sys->i_handle, &fds );
/* We'll wait 0.5 second if nothing happens */
timeout.tv_sec = 0;
timeout.tv_usec = 500000;
/* Find if some data is available */
while( (i_ret = select( p_sys->i_handle + 1, &fds, NULL, NULL, &timeout )) == 0 ||
(i_ret < 0 && errno == EINTR) )
for ( ; ; )
{
struct timeval timeout;
fd_set fds;
int i_ret;
/* Initialize file descriptor set */
FD_ZERO( &fds );
FD_SET( p_sys->i_handle, &fds );
/* We'll wait 0.5 second if nothing happens */
timeout.tv_sec = 0;
timeout.tv_usec = 500000;
if( p_access->b_die )
/* Find if some data is available */
i_ret = select( p_sys->i_handle + 1, &fds, NULL, NULL, &timeout );
if ( p_access->b_die )
return NULL;
}
if ( i_ret < 0 )
{
msg_Err( p_access, "select error (%s)", strerror(errno) );
return NULL;
if ( i_ret < 0 && errno == EINTR )
continue;
if ( i_ret < 0 )
{
msg_Err( p_access, "select error (%s)", strerror(errno) );
return NULL;
}
if ( p_sys->i_ca_handle && mdate() > p_sys->i_ca_next_event )
{
E_(CAMPoll)( p_access );
p_sys->i_ca_next_event = mdate() + p_sys->i_ca_timeout;
}
if ( FD_ISSET( p_sys->i_handle, &fds ) )
{
break;
}
}
p_block = block_New( p_access, SATELLITE_READ_ONCE * TS_PACKET_SIZE );
if( ( p_block->i_buffer = read( p_sys->i_handle, p_block->p_buffer, SATELLITE_READ_ONCE * TS_PACKET_SIZE ) ) <= 0 )
p_block = block_New( p_access, DVB_READ_ONCE * TS_PACKET_SIZE );
if( ( p_block->i_buffer = read( p_sys->i_handle, p_block->p_buffer,
DVB_READ_ONCE * TS_PACKET_SIZE ) ) <= 0 )
{
msg_Err( p_access, "read failed (%s)", strerror(errno) );
block_Release( p_block );
......@@ -376,7 +376,7 @@ static int Control( access_t *p_access, int i_query, va_list args )
/* */
case ACCESS_GET_MTU:
pi_int = (int*)va_arg( args, int * );
*pi_int = SATELLITE_READ_ONCE * TS_PACKET_SIZE;
*pi_int = DVB_READ_ONCE * TS_PACKET_SIZE;
break;
case ACCESS_GET_PTS_DELAY:
......@@ -405,26 +405,16 @@ static int Control( access_t *p_access, int i_query, va_list args )
break;
case ACCESS_SET_PRIVATE_ID_CA:
if ( p_sys->b_cam )
{
int i_program;
uint16_t i_vpid, i_apid1, i_apid2, i_apid3;
uint8_t i_cad_length;
uint8_t *p_cad;
i_program = (int)va_arg( args, int );
i_vpid = (int16_t)va_arg( args, int );
i_apid1 = (uint16_t)va_arg( args, int );
i_apid2 = (uint16_t)va_arg( args, int );
i_apid3 = (uint16_t)va_arg( args, int );
i_cad_length = (uint8_t)va_arg( args, int );
p_cad = (uint8_t *)va_arg( args, uint8_t * );
E_(CAMSet)( p_access, i_program, i_vpid, i_apid1, i_apid2,
i_apid3, i_cad_length, p_cad );
}
break;
{
uint8_t **pp_capmts;
int i_nb_capmts;
pp_capmts = (uint8_t **)va_arg( args, uint8_t ** );
i_nb_capmts = (int)va_arg( args, int );
E_(CAMSet)( p_access, pp_capmts, i_nb_capmts );
break;
}
default:
msg_Warn( p_access, "unimplemented query in control" );
return VLC_EGENERIC;
......@@ -509,7 +499,6 @@ static void VarInit( access_t *p_access )
/* */
var_Create( p_access, "dvb-adapter", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
var_Create( p_access, "dvb-device", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
var_Create( p_access, "dvb-cam", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
var_Create( p_access, "dvb-frequency", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
var_Create( p_access, "dvb-inversion", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
var_Create( p_access, "dvb-probe", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
......@@ -575,7 +564,6 @@ static int ParseMRL( access_t *p_access )
{
GET_OPTION_INT("adapter")
else GET_OPTION_INT("device")
else GET_OPTION_BOOL("cam")
else GET_OPTION_INT("frequency")
else GET_OPTION_INT("inversion")
else GET_OPTION_BOOL("probe")
......
......@@ -30,6 +30,7 @@
#define DMX "/dev/dvb/adapter%d/demux%d"
#define FRONTEND "/dev/dvb/adapter%d/frontend%d"
#define DVR "/dev/dvb/adapter%d/dvr%d"
#define CA "/dev/dvb/adapter%d/ca%d"
/*****************************************************************************
* Local structures
......@@ -43,7 +44,19 @@ typedef struct
typedef struct frontend_t frontend_t;
#define MAX_DEMUX 24
typedef struct
{
int i_slot;
int i_resource_id;
void (* pf_handle)( access_t *, int, uint8_t *, int );
void (* pf_close)( access_t *, int );
void (* pf_manage)( access_t *, int );
void *p_sys;
} en50221_session_t;
#define MAX_DEMUX 48
#define MAX_CI_SLOTS 16
#define MAX_SESSIONS 32
struct access_sys_t
{
......@@ -51,8 +64,16 @@ struct access_sys_t
demux_handle_t p_demux_handles[MAX_DEMUX];
frontend_t *p_frontend;
vlc_bool_t b_budget_mode;
vlc_bool_t b_cam;
int i_cam_handle;
/* CA management */
int i_ca_handle;
int i_nb_slots;
vlc_bool_t pb_active_slot[MAX_CI_SLOTS];
vlc_bool_t pb_tc_has_data[MAX_CI_SLOTS];
en50221_session_t p_sessions[MAX_SESSIONS];
mtime_t i_ca_timeout, i_ca_next_event;
uint8_t **pp_capmts;
int i_nb_capmts;
};
#define VIDEO0_TYPE 1
......@@ -77,6 +98,12 @@ int E_(DVROpen)( access_t * );
void E_(DVRClose)( access_t * );
int E_(CAMOpen)( access_t * );
int E_(CAMSet)( access_t *, uint16_t, uint16_t, uint16_t, uint16_t, uint16_t,
uint16_t, uint8_t * );
int E_(CAMPoll)( access_t * );
int E_(CAMSet)( access_t *, uint8_t **, int );
void E_(CAMClose)( access_t * );
int E_(en50221_Init)( access_t * );
int E_(en50221_Poll)( access_t * );
int E_(en50221_SetCAPMT)( access_t *, uint8_t **, int );
void E_(en50221_End)( access_t * );
/*****************************************************************************
* en50221.c : implementation of the transport, session and applications
* layers of EN 50 221
*****************************************************************************
* Copyright (C) 2004 VideoLAN
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
* Based on code from libdvbci Copyright (C) 2000 Klaus Schmidinger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/input.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include "dvb.h"
#undef DEBUG_TPDU
static void ResourceManagerOpen( access_t * p_access, int i_session_id );
static void ApplicationInformationOpen( access_t * p_access, int i_session_id );
static void ConditionalAccessOpen( access_t * p_access, int i_session_id );
static void DateTimeOpen( access_t * p_access, int i_session_id );
static void MMIOpen( access_t * p_access, int i_session_id );
/*****************************************************************************
* Utility functions
*****************************************************************************/
#define SIZE_INDICATOR 0x80
static uint8_t *GetLength( uint8_t *p_data, int *pi_length )
{
*pi_length = *p_data++;
if ( (*pi_length & SIZE_INDICATOR) != 0 )
{
int l = *pi_length & ~SIZE_INDICATOR;
int i;
*pi_length = 0;
for ( i = 0; i < l; i++ )
*pi_length = (*pi_length << 8) | *p_data++;
}
return p_data;
}
static uint8_t *SetLength( uint8_t *p_data, int i_length )
{
uint8_t *p = p_data;
if ( i_length < 128 )
{
*p++ = i_length;
}
else if ( i_length < 256 )
{
*p++ = SIZE_INDICATOR | 0x1;
*p++ = i_length;
}
else if ( i_length < 65536 )
{
*p++ = SIZE_INDICATOR | 0x2;
*p++ = i_length >> 8;
*p++ = i_length & 0xff;
}
else if ( i_length < 16777216 )
{
*p++ = SIZE_INDICATOR | 0x3;
*p++ = i_length >> 16;
*p++ = (i_length >> 8) & 0xff;
*p++ = i_length & 0xff;
}
else
{
*p++ = SIZE_INDICATOR | 0x4;
*p++ = i_length >> 24;
*p++ = (i_length >> 16) & 0xff;
*p++ = (i_length >> 8) & 0xff;
*p++ = i_length & 0xff;
}
return p;
}
/*
* Transport layer
*/
#define MAX_TPDU_SIZE 2048
#define MAX_TPDU_DATA (MAX_TPDU_SIZE - 4)
#define DATA_INDICATOR 0x80
#define T_SB 0x80
#define T_RCV 0x81
#define T_CREATE_TC 0x82
#define T_CTC_REPLY 0x83
#define T_DELETE_TC 0x84
#define T_DTC_REPLY 0x85
#define T_REQUEST_TC 0x86
#define T_NEW_TC 0x87
#define T_TC_ERROR 0x88
#define T_DATA_LAST 0xA0
#define T_DATA_MORE 0xA1
static void Dump( vlc_bool_t b_outgoing, uint8_t *p_data, int i_size )
{
#ifdef DEBUG_TPDU
int i;
#define MAX_DUMP 256
fprintf(stderr, "%s ", b_outgoing ? "-->" : "<--");
for ( i = 0; i < i_size && i < MAX_DUMP; i++)
fprintf(stderr, "%02X ", p_data[i]);
fprintf(stderr, "%s\n", i_size >= MAX_DUMP ? "..." : "");
#endif
}
/*****************************************************************************
* TPDUSend
*****************************************************************************/
static int TPDUSend( access_t * p_access, uint8_t i_slot, uint8_t i_tag,
const uint8_t *p_content, int i_length )
{
access_sys_t *p_sys = p_access->p_sys;
uint8_t i_tcid = i_slot + 1;
uint8_t p_data[MAX_TPDU_SIZE];
int i_size;
i_size = 0;
p_data[0] = i_slot;
p_data[1] = i_tcid;
p_data[2] = i_tag;
switch ( i_tag )
{
case T_RCV:
case T_CREATE_TC:
case T_CTC_REPLY:
case T_DELETE_TC:
case T_DTC_REPLY:
case T_REQUEST_TC:
p_data[3] = 1; /* length */
p_data[4] = i_tcid;
i_size = 5;
break;
case T_NEW_TC:
case T_TC_ERROR:
p_data[3] = 2; /* length */
p_data[4] = i_tcid;
p_data[5] = p_content[0];
i_size = 6;
break;
case T_DATA_LAST:
case T_DATA_MORE:
{
/* i_length <= MAX_TPDU_DATA */
uint8_t *p = p_data + 3;
p = SetLength( p, i_length + 1 );
*p++ = i_tcid;
if ( i_length )
memcpy( p, p_content, i_length );
i_size = i_length + (p - p_data);
}
break;
default:
break;
}
Dump( VLC_TRUE, p_data, i_size );
if ( write( p_sys->i_ca_handle, p_data, i_size ) != i_size )
{
msg_Err( p_access, "cannot write to CAM device (%s)",
strerror(errno) );
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
/*****************************************************************************
* TPDURecv
*****************************************************************************/
#define CAM_READ_TIMEOUT 3500 // ms
static int TPDURecv( access_t * p_access, uint8_t i_slot, uint8_t *pi_tag,
uint8_t *p_data, int *pi_size )
{
access_sys_t *p_sys = p_access->p_sys;
uint8_t i_tcid = i_slot + 1;
int i_size;
struct pollfd pfd[1];
pfd[0].fd = p_sys->i_ca_handle;
pfd[0].events = POLLIN;
if ( !(poll(pfd, 1, CAM_READ_TIMEOUT) > 0 && (pfd[0].revents & POLLIN)) )
{
msg_Err( p_access, "cannot poll from CAM device" );
return VLC_EGENERIC;
}
if ( pi_size == NULL )
{
p_data = malloc( MAX_TPDU_SIZE );
}
for ( ; ; )
{
i_size = read( p_sys->i_ca_handle, p_data, MAX_TPDU_SIZE );
if ( i_size >= 0 || errno != EINTR )
break;
}
if ( i_size < 5 )
{
msg_Err( p_access, "cannot read from CAM device (%d:%s)", i_size,
strerror(errno) );
return VLC_EGENERIC;
}
if ( p_data[1] != i_tcid )
{
msg_Err( p_access, "invalid read from CAM device (%d instead of %d)",
p_data[1], i_tcid );
return VLC_EGENERIC;
}
*pi_tag = p_data[2];
p_sys->pb_tc_has_data[i_slot] = (i_size >= 4
&& p_data[i_size - 4] == T_SB
&& p_data[i_size - 3] == 2
&& (p_data[i_size - 1] & DATA_INDICATOR))
? VLC_TRUE : VLC_FALSE;
Dump( VLC_FALSE, p_data, i_size );
if ( pi_size == NULL )
free( p_data );
else
*pi_size = i_size;
return VLC_SUCCESS;
}
/*
* Session layer
*/
#define ST_SESSION_NUMBER 0x90
#define ST_OPEN_SESSION_REQUEST 0x91
#define ST_OPEN_SESSION_RESPONSE 0x92
#define ST_CREATE_SESSION 0x93
#define ST_CREATE_SESSION_RESPONSE 0x94
#define ST_CLOSE_SESSION_REQUEST 0x95
#define ST_CLOSE_SESSION_RESPONSE 0x96
#define SS_OK 0x00
#define SS_NOT_ALLOCATED 0xF0
#define RI_RESOURCE_MANAGER 0x00010041
#define RI_APPLICATION_INFORMATION 0x00020041
#define RI_CONDITIONAL_ACCESS_SUPPORT 0x00030041
#define RI_HOST_CONTROL 0x00200041
#define RI_DATE_TIME 0x00240041
#define RI_MMI 0x00400041
static int ResourceIdToInt( uint8_t *p_data )
{
return ((int)p_data[0] << 24) | ((int)p_data[1] << 16)
| ((int)p_data[2] << 8) | p_data[3];
}
/*****************************************************************************
* SPDUSend
*****************************************************************************/
static int SPDUSend( access_t * p_access, int i_session_id,
uint8_t *p_data, int i_size )
{
access_sys_t *p_sys = p_access->p_sys;
uint8_t *p_spdu = malloc( i_size + 4 );
uint8_t *p = p_spdu;
uint8_t i_tag;
uint8_t i_slot = p_sys->p_sessions[i_session_id - 1].i_slot;
*p++ = ST_SESSION_NUMBER;
*p++ = 0x02;
*p++ = (i_session_id >> 8);
*p++ = i_session_id & 0xff;
memcpy( p, p_data, i_size );
i_size += 4;
p = p_spdu;
while ( i_size > 0 )
{
if ( i_size > MAX_TPDU_DATA )
{
if ( TPDUSend( p_access, i_slot, T_DATA_MORE, p,
MAX_TPDU_DATA ) != VLC_SUCCESS )
{
msg_Err( p_access, "couldn't send TPDU on session %d",
i_session_id );
free( p_spdu );
return VLC_EGENERIC;
}
p += MAX_TPDU_DATA;
i_size -= MAX_TPDU_DATA;
}
else
{
if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p, i_size )
!= VLC_SUCCESS )
{
msg_Err( p_access, "couldn't send TPDU on session %d",
i_session_id );
free( p_spdu );
return VLC_EGENERIC;
}
i_size = 0;
}
if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS
|| i_tag != T_SB )
{
msg_Err( p_access, "couldn't recv TPDU on session %d",
i_session_id );