343 lines
9 KiB
C
343 lines
9 KiB
C
/*
|
|
* Tvheadend - Linux DVB device management
|
|
*
|
|
* Copyright (C) 2013 Adam Sutton
|
|
*
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "tvheadend.h"
|
|
#include "input.h"
|
|
#include "linuxdvb_private.h"
|
|
#include "queue.h"
|
|
#include "settings.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
|
|
/* ***************************************************************************
|
|
* DVB Device
|
|
* **************************************************************************/
|
|
|
|
/*
|
|
* BUS str table
|
|
*/
|
|
static struct strtab bustab[] = {
|
|
{ "PCI", BUS_PCI },
|
|
{ "USB1", BUS_USB1 },
|
|
{ "USB2", BUS_USB2 },
|
|
{ "USB3", BUS_USB3 }
|
|
};
|
|
static const char*
|
|
devinfo_bus2str ( int p )
|
|
{
|
|
return val2str(p, bustab);
|
|
}
|
|
static int
|
|
devinfo_str2bus ( const char *str )
|
|
{
|
|
return str2val(str, bustab);
|
|
}
|
|
|
|
/*
|
|
* Get bus information
|
|
*/
|
|
static void
|
|
get_device_info ( device_info_t *di, int a )
|
|
{
|
|
FILE *fp;
|
|
DIR *dp;
|
|
struct dirent *de;
|
|
uint16_t u16;
|
|
int speed;
|
|
char path[512], buf[512];
|
|
ssize_t c;
|
|
int mina = a;
|
|
|
|
/* Check for subsystem */
|
|
#define DVB_DEV_PATH "/sys/class/dvb/dvb%d.frontend0/device"
|
|
snprintf(path, sizeof(path), DVB_DEV_PATH "/subsystem", a);
|
|
if ((c = readlink(path, buf, sizeof(buf))) != -1) {
|
|
buf[c] = '\0';
|
|
char *bus = basename(buf);
|
|
if (!strcmp(bus, "pci")) {
|
|
di->di_bus = BUS_PCI;
|
|
snprintf(path, sizeof(path), DVB_DEV_PATH "/subsystem_vendor", a);
|
|
if ((fp = fopen(path, "r"))) {
|
|
if (fscanf(fp, "0x%hx", &u16) == 1)
|
|
di->di_dev = u16;
|
|
}
|
|
di->di_dev <<= 16;
|
|
snprintf(path, sizeof(path), DVB_DEV_PATH "/subsystem_device", a);
|
|
if ((fp = fopen(path, "r"))) {
|
|
if (fscanf(fp, "0x%hx", &u16) == 1)
|
|
di->di_dev |= u16;
|
|
}
|
|
|
|
} else if (!strcmp(bus, "usb")) {
|
|
di->di_bus = BUS_USB1;
|
|
snprintf(path, sizeof(path), DVB_DEV_PATH "/idVendor", a);
|
|
if ((fp = fopen(path, "r"))) {
|
|
if (fscanf(fp, "%hx", &u16) == 1)
|
|
di->di_dev = u16;
|
|
}
|
|
di->di_dev <<= 16;
|
|
snprintf(path, sizeof(path), DVB_DEV_PATH "/idProduct", a);
|
|
if ((fp = fopen(path, "r"))) {
|
|
if (fscanf(fp, "%hx", &u16) == 1)
|
|
di->di_dev |= u16;
|
|
}
|
|
snprintf(path, sizeof(path), DVB_DEV_PATH "/speed", a);
|
|
if ((fp = fopen(path, "r"))) {
|
|
if (fscanf(fp, "%d", &speed) == 1) {
|
|
if (speed > 480) {
|
|
di->di_bus = BUS_USB3;
|
|
} else if (speed == 480) {
|
|
di->di_bus = BUS_USB2;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
} else {
|
|
tvhlog(LOG_WARNING, "linuxdvb",
|
|
"could not determine host connection for adapter%d", a);
|
|
}
|
|
}
|
|
|
|
/* Get Path */
|
|
snprintf(path, sizeof(path), DVB_DEV_PATH, a);
|
|
if ((c = readlink(path, buf, sizeof(buf))) != -1) {
|
|
buf[c] = '\0';
|
|
strcpy(di->di_path, basename(buf));
|
|
}
|
|
|
|
/* Find minimum adapter number */
|
|
snprintf(path, sizeof(path), DVB_DEV_PATH "/dvb", a);
|
|
if ((dp = opendir(path))) {
|
|
while ((de = readdir(dp))) {
|
|
int t;
|
|
if ((sscanf(de->d_name, "dvb%d.frontend0", &t)))
|
|
if (t < mina) mina = t;
|
|
}
|
|
closedir(dp);
|
|
}
|
|
di->di_min_adapter = mina;
|
|
|
|
/* Create ID */
|
|
if (*di->di_path && di->di_dev) {
|
|
snprintf(buf, sizeof(buf), "%s/%s/%04x:%04x",
|
|
devinfo_bus2str(di->di_bus), di->di_path,
|
|
di->di_dev >> 16, di->di_dev & 0xFFFF);
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "/dev/dvb/adapter%d", a);
|
|
}
|
|
di->di_id = strdup(buf);
|
|
}
|
|
static void
|
|
get_min_dvb_adapter ( device_info_t *di )
|
|
{
|
|
int mina = -1;
|
|
char path[512];
|
|
DIR *dp;
|
|
struct dirent *de;
|
|
|
|
snprintf(path, sizeof(path), "/sys/bus/%s/devices/%s/dvb",
|
|
di->di_bus == BUS_PCI ? "pci" : "usb", di->di_path);
|
|
|
|
/* Find minimum adapter number */
|
|
if ((dp = opendir(path))) {
|
|
while ((de = readdir(dp))) {
|
|
int t;
|
|
if ((sscanf(de->d_name, "dvb%d.frontend0", &t)))
|
|
if (mina == -1 || t < mina) mina = t;
|
|
}
|
|
}
|
|
di->di_min_adapter = mina;
|
|
}
|
|
|
|
|
|
static void
|
|
linuxdvb_device_class_save ( idnode_t *in )
|
|
{
|
|
linuxdvb_device_save((linuxdvb_device_t*)in);
|
|
}
|
|
void linuxdvb_device_save ( linuxdvb_device_t *ld )
|
|
{
|
|
htsmsg_t *m, *e, *l;
|
|
linuxdvb_hardware_t *lh;
|
|
|
|
m = htsmsg_create_map();
|
|
|
|
linuxdvb_hardware_save((linuxdvb_hardware_t*)ld, m);
|
|
if (ld->ld_devid.di_id) {
|
|
htsmsg_add_str(m, "devid", ld->ld_devid.di_id);
|
|
htsmsg_add_str(m, "devbus", devinfo_bus2str(ld->ld_devid.di_bus));
|
|
htsmsg_add_str(m, "devpath", ld->ld_devid.di_path);
|
|
}
|
|
|
|
/* Adapters */
|
|
l = htsmsg_create_map();
|
|
LIST_FOREACH(lh, &ld->lh_children, lh_parent_link) {
|
|
e = htsmsg_create_map();
|
|
linuxdvb_adapter_save((linuxdvb_adapter_t*)lh, e);
|
|
htsmsg_add_msg(l, idnode_uuid_as_str(&lh->mi_id), e);
|
|
}
|
|
htsmsg_add_msg(m, "adapters", l);
|
|
|
|
/* Save */
|
|
hts_settings_save(m, "input/linuxdvb/devices/%s",
|
|
idnode_uuid_as_str(&ld->mi_id));
|
|
}
|
|
|
|
const idclass_t linuxdvb_device_class =
|
|
{
|
|
.ic_super = &linuxdvb_hardware_class,
|
|
.ic_class = "linuxdvb_device",
|
|
.ic_caption = "LinuxDVB Device",
|
|
.ic_save = linuxdvb_device_class_save,
|
|
.ic_properties = (const property_t[]){
|
|
{ PROPDEF2("devid", "Device ID",
|
|
PT_STR, linuxdvb_device_t, ld_devid.di_id, 1) },
|
|
{}
|
|
}
|
|
};
|
|
|
|
static linuxdvb_hardware_list_t linuxdvb_device_all;
|
|
|
|
idnode_t **
|
|
linuxdvb_root ( void )
|
|
{
|
|
return linuxdvb_hardware_enumerate(&linuxdvb_device_all);
|
|
}
|
|
|
|
linuxdvb_device_t *
|
|
linuxdvb_device_create0 ( const char *uuid, htsmsg_t *conf )
|
|
{
|
|
uint32_t u32;
|
|
const char *str;
|
|
linuxdvb_device_t *ld;
|
|
htsmsg_t *e;
|
|
htsmsg_field_t *f;
|
|
|
|
/* Create */
|
|
ld = calloc(1, sizeof(linuxdvb_device_t));
|
|
printf("create device %p\n", ld);
|
|
if (idnode_insert(&ld->mi_id, uuid, &linuxdvb_device_class)) {
|
|
free(ld);
|
|
return NULL;
|
|
}
|
|
LIST_INSERT_HEAD(&linuxdvb_device_all, (linuxdvb_hardware_t*)ld, lh_parent_link);
|
|
|
|
/* No config */
|
|
if (!conf)
|
|
return ld;
|
|
|
|
/* Load config */
|
|
linuxdvb_hardware_load((linuxdvb_hardware_t*)ld, conf);
|
|
if (!htsmsg_get_u32(conf, "enabled", &u32) && u32)
|
|
ld->lh_enabled = 1;
|
|
if ((str = htsmsg_get_str(conf, "displayname")))
|
|
ld->lh_displayname = strdup(str);
|
|
if ((str = htsmsg_get_str(conf, "devid")))
|
|
ld->ld_devid.di_id = strdup(str);
|
|
if ((str = htsmsg_get_str(conf, "devbus")))
|
|
ld->ld_devid.di_bus = devinfo_str2bus(str);
|
|
if ((str = htsmsg_get_str(conf, "devpath")))
|
|
strncpy(ld->ld_devid.di_path, str, sizeof(ld->ld_devid.di_path));
|
|
get_min_dvb_adapter(&ld->ld_devid);
|
|
|
|
/* Adapters */
|
|
if ((conf = htsmsg_get_map(conf, "adapters"))) {
|
|
HTSMSG_FOREACH(f, conf) {
|
|
if (!(e = htsmsg_get_map_by_field(f))) continue;
|
|
(void)linuxdvb_adapter_create0(ld, f->hmf_name, e);
|
|
}
|
|
}
|
|
|
|
return ld;
|
|
}
|
|
|
|
static linuxdvb_device_t *
|
|
linuxdvb_device_find_by_hwid ( const char *hwid )
|
|
{
|
|
linuxdvb_hardware_t *lh;
|
|
LIST_FOREACH(lh, &linuxdvb_device_all, lh_parent_link) {
|
|
printf("hwid = %s\n", hwid);
|
|
printf("lh = %p\n", lh);
|
|
printf("devid = %s\n", ((linuxdvb_device_t*)lh)->ld_devid.di_id);
|
|
if (!strcmp(hwid, ((linuxdvb_device_t*)lh)->ld_devid.di_id))
|
|
return (linuxdvb_device_t*)lh;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
linuxdvb_device_t *
|
|
linuxdvb_device_find_by_adapter ( int a )
|
|
{
|
|
linuxdvb_device_t *ld;
|
|
device_info_t dev;
|
|
|
|
/* Get device info */
|
|
get_device_info(&dev, a);
|
|
|
|
/* Find existing */
|
|
if ((ld = linuxdvb_device_find_by_hwid(dev.di_id)))
|
|
return ld;
|
|
|
|
/* Create new */
|
|
if (!(ld = linuxdvb_device_create0(NULL, NULL))) {
|
|
tvhlog(LOG_ERR, "linuxdvb", "failed to create device for adapter%d", a);
|
|
return NULL;
|
|
}
|
|
|
|
/* Copy device info */
|
|
printf("added dev info to %p\n", ld);
|
|
memcpy(&ld->ld_devid, &dev, sizeof(dev));
|
|
ld->lh_displayname = strdup(dev.di_id);
|
|
return ld;
|
|
}
|
|
|
|
void linuxdvb_device_init ( int adapter_mask )
|
|
{
|
|
int a;
|
|
DIR *dp;
|
|
htsmsg_t *s, *e;
|
|
htsmsg_field_t *f;
|
|
|
|
/* Load configuration */
|
|
if ((s = hts_settings_load_r(1, "input/linuxdvb/devices"))) {
|
|
HTSMSG_FOREACH(f, s) {
|
|
if (!(e = htsmsg_get_map_by_field(f))) continue;
|
|
if (!(e = htsmsg_get_map(e, "config"))) continue;
|
|
(void)linuxdvb_device_create0(f->hmf_name, e);
|
|
printf("created from config\n");
|
|
}
|
|
}
|
|
|
|
/* Scan for hardware */
|
|
if ((dp = opendir("/dev/dvb"))) {
|
|
struct dirent *de;
|
|
while ((de = readdir(dp))) {
|
|
if (sscanf(de->d_name, "adapter%d", &a) != 1) continue;
|
|
if ((0x1 << a) & adapter_mask)
|
|
linuxdvb_adapter_added(a);
|
|
}
|
|
}
|
|
|
|
// TODO: add udev support for hotplug
|
|
}
|