/* * 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 . */ #include "tvheadend.h" #include "input.h" #include "linuxdvb_private.h" #include "queue.h" #include "settings.h" #include #include #include #include #include #include /* *************************************************************************** * 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)); 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) { 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 */ 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); } } /* 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 }