/* * Media controller interface library * * Copyright (C) 2010-2014 Ideas on board SPRL * * Contact: Laurent Pinchart * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ //#include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mediactl-priv.h" #include "mediactl.h" #include "tools.h" #ifdef LOG_TAG #undef LOG_TAG #endif #define LOG_TAG "aiqtool" /* ----------------------------------------------------------------------------- * Graph access */ struct media_pad* media_entity_remote_source(struct media_pad* pad) { unsigned int i; if (!(pad->flags & MEDIA_PAD_FL_SINK)) { return NULL; } for (i = 0; i < pad->entity->num_links; ++i) { struct media_link* link = &pad->entity->links[i]; if (!(link->flags & MEDIA_LNK_FL_ENABLED)) { continue; } if (link->sink == pad) { return link->source; } } return NULL; } struct media_entity* media_get_entity_by_name(struct media_device* media, const char* name) { unsigned int i; for (i = 0; i < media->entities_count; ++i) { struct media_entity* entity = &media->entities[i]; if (strcmp(entity->info.name, name) == 0) { return entity; } } return NULL; } struct media_entity* media_get_entity_by_id(struct media_device* media, __u32 id) { bool next = id & MEDIA_ENT_ID_FLAG_NEXT; unsigned int i; id &= ~MEDIA_ENT_ID_FLAG_NEXT; for (i = 0; i < media->entities_count; ++i) { struct media_entity* entity = &media->entities[i]; if ((entity->info.id == id && !next) || (entity->info.id > id && next)) { return entity; } } return NULL; } unsigned int media_get_entities_count(struct media_device* media) { return media->entities_count; } struct media_entity* media_get_entity(struct media_device* media, unsigned int index) { if (index >= media->entities_count) { return NULL; } return &media->entities[index]; } const struct media_pad* media_entity_get_pad(struct media_entity* entity, unsigned int index) { if (index >= entity->info.pads) { return NULL; } return &entity->pads[index]; } unsigned int media_entity_get_links_count(struct media_entity* entity) { return entity->num_links; } const struct media_link* media_entity_get_link(struct media_entity* entity, unsigned int index) { if (index >= entity->num_links) { return NULL; } return &entity->links[index]; } const char* media_entity_get_devname(struct media_entity* entity) { return entity->devname[0] ? entity->devname : NULL; } struct media_entity* media_get_default_entity(struct media_device* media, unsigned int type) { switch (type) { case MEDIA_ENT_T_DEVNODE_V4L: return media->def.v4l; case MEDIA_ENT_T_DEVNODE_FB: return media->def.fb; case MEDIA_ENT_T_DEVNODE_ALSA: return media->def.alsa; case MEDIA_ENT_T_DEVNODE_DVB: return media->def.dvb; } return NULL; } const struct media_device_info* media_get_info(struct media_device* media) { return &media->info; } const char* media_get_devnode(struct media_device* media) { return media->devnode; } const struct media_entity_desc* media_entity_get_info(struct media_entity* entity) { return &entity->info; } /* ----------------------------------------------------------------------------- * Open/close */ static int media_device_open(struct media_device* media) { int ret; if (media->fd != -1) { return 0; } media_dbg(media, "Opening media device %s\n", media->devnode); media->fd = open(media->devnode, O_RDWR); if (media->fd < 0) { ret = -errno; media_dbg(media, "%s: Can't open media device %s\n", __func__, media->devnode); return ret; } return 0; } static void media_device_close(struct media_device* media) { if (media->fd != -1) { close(media->fd); media->fd = -1; } } /* ----------------------------------------------------------------------------- * Link setup */ int media_setup_link(struct media_device* media, struct media_pad* source, struct media_pad* sink, __u32 flags) { struct media_link_desc ulink = {{0}}; struct media_link* link; unsigned int i; int ret; ret = media_device_open(media); if (ret < 0) { goto done; } for (i = 0; i < source->entity->num_links; i++) { link = &source->entity->links[i]; if (link->source->entity == source->entity && link->source->index == source->index && link->sink->entity == sink->entity && link->sink->index == sink->index) { media_dbg(media, "#### %s:%d -> %s:%d", link->source->entity->info.name, link->source->index, link->sink->entity->info.name, link->sink->index); break; } } if (i == source->entity->num_links) { media_dbg(media, "%s: Link not found\n", __func__); ret = -ENOENT; goto done; } /* source pad */ ulink.source.entity = source->entity->info.id; ulink.source.index = source->index; ulink.source.flags = MEDIA_PAD_FL_SOURCE; /* sink pad */ ulink.sink.entity = sink->entity->info.id; ulink.sink.index = sink->index; ulink.sink.flags = MEDIA_PAD_FL_SINK; ulink.flags = flags | (link->flags & MEDIA_LNK_FL_IMMUTABLE); ret = ioctl(media->fd, MEDIA_IOC_SETUP_LINK, &ulink); if (ret == -1) { ret = -errno; media_dbg(media, "%s: Unable to setup link (%s)\n", __func__, strerror(errno)); goto done; } link->flags = ulink.flags; link->twin->flags = ulink.flags; ret = 0; done: media_device_close(media); return ret; } int media_reset_links(struct media_device* media) { unsigned int i, j; int ret; for (i = 0; i < media->entities_count; ++i) { struct media_entity* entity = &media->entities[i]; for (j = 0; j < entity->num_links; j++) { struct media_link* link = &entity->links[j]; if (link->flags & MEDIA_LNK_FL_IMMUTABLE || link->source->entity != entity) { continue; } ret = media_setup_link(media, link->source, link->sink, link->flags & ~MEDIA_LNK_FL_ENABLED); if (ret < 0) { return ret; } } } return 0; } /* ----------------------------------------------------------------------------- * Entities, pads and links enumeration */ static struct media_link* media_entity_add_link(struct media_entity* entity) { if (entity->num_links >= entity->max_links) { struct media_link* links = entity->links; unsigned int max_links = entity->max_links * 2; unsigned int i; links = realloc(links, max_links * sizeof *links); if (links == NULL) { return NULL; } for (i = 0; i < entity->num_links; ++i) { links[i].twin->twin = &links[i]; } entity->max_links = max_links; entity->links = links; } return &entity->links[entity->num_links++]; } static int media_enum_links(struct media_device* media) { __u32 id; int ret = 0; for (id = 1; id <= media->entities_count; id++) { struct media_entity* entity = &media->entities[id - 1]; struct media_links_enum links = {0}; unsigned int i; links.entity = entity->info.id; links.pads = calloc(entity->info.pads, sizeof(struct media_pad_desc)); links.links = calloc(entity->info.links, sizeof(struct media_link_desc)); if (ioctl(media->fd, MEDIA_IOC_ENUM_LINKS, &links) < 0) { ret = -errno; media_dbg(media, "%s: Unable to enumerate pads and links (%s).\n", __func__, strerror(errno)); free(links.pads); free(links.links); return ret; } for (i = 0; i < entity->info.pads; ++i) { entity->pads[i].entity = entity; entity->pads[i].index = links.pads[i].index; entity->pads[i].flags = links.pads[i].flags; } for (i = 0; i < entity->info.links; ++i) { struct media_link_desc* link = &links.links[i]; struct media_link* fwdlink; struct media_link* backlink; struct media_entity* source; struct media_entity* sink; source = media_get_entity_by_id(media, link->source.entity); sink = media_get_entity_by_id(media, link->sink.entity); if (source == NULL || sink == NULL) { media_dbg(media, "WARNING entity %u link %u from %u/%u to %u/%u is invalid!\n", id, i, link->source.entity, link->source.index, link->sink.entity, link->sink.index); ret = -EINVAL; } else { fwdlink = media_entity_add_link(source); fwdlink->source = &source->pads[link->source.index]; fwdlink->sink = &sink->pads[link->sink.index]; fwdlink->flags = link->flags; backlink = media_entity_add_link(sink); backlink->source = &source->pads[link->source.index]; backlink->sink = &sink->pads[link->sink.index]; backlink->flags = link->flags; fwdlink->twin = backlink; backlink->twin = fwdlink; } } free(links.pads); free(links.links); } return ret; } #ifdef HAVE_LIBUDEV #include static inline int media_udev_open(struct udev** udev) { *udev = udev_new(); if (*udev == NULL) { return -ENOMEM; } return 0; } static inline void media_udev_close(struct udev* udev) { if (udev != NULL) { udev_unref(udev); } } static int media_get_devname_udev(struct udev* udev, struct media_entity* entity) { struct udev_device* device; dev_t devnum; const char* p; int ret = -ENODEV; if (udev == NULL) { return -EINVAL; } devnum = makedev(entity->info.v4l.major, entity->info.v4l.minor); media_dbg(entity->media, "looking up device: %u:%u\n", major(devnum), minor(devnum)); device = udev_device_new_from_devnum(udev, 'c', devnum); if (device) { p = udev_device_get_devnode(device); if (p) { strncpy(entity->devname, p, sizeof(entity->devname)); entity->devname[sizeof(entity->devname) - 1] = '\0'; } ret = 0; } udev_device_unref(device); return ret; } #else /* HAVE_LIBUDEV */ struct udev; static inline int media_udev_open(struct udev** udev) { return 0; } static inline void media_udev_close(struct udev* udev) { } static inline int media_get_devname_udev(struct udev* udev, struct media_entity* entity) { return -ENOTSUP; } #endif /* HAVE_LIBUDEV */ static int media_get_devname_sysfs(struct media_entity* entity) { struct stat devstat; char devname[32]; char sysname[32]; char target[1024]; char* p; int ret; sprintf(sysname, "/sys/dev/char/%u:%u", entity->info.v4l.major, entity->info.v4l.minor); ret = readlink(sysname, target, sizeof(target) - 1); if (ret < 0) { return -errno; } target[ret] = '\0'; p = strrchr(target, '/'); if (p == NULL) { return -EINVAL; } sprintf(devname, "/dev/%s", p + 1); if (strstr(p + 1, "dvb")) { char* s = p + 1; if (strncmp(s, "dvb", 3)) { return -EINVAL; } s += 3; p = strchr(s, '.'); if (!p) { return -EINVAL; } *p = '/'; sprintf(devname, "/dev/dvb/adapter%s", s); } else { sprintf(devname, "/dev/%s", p + 1); } ret = stat(devname, &devstat); if (ret < 0) { return -errno; } /* Sanity check: udev might have reordered the device nodes. * Make sure the major/minor match. We should really use * libudev. */ if (major(devstat.st_rdev) == entity->info.v4l.major && minor(devstat.st_rdev) == entity->info.v4l.minor) { strcpy(entity->devname, devname); } return 0; } static int media_enum_entities(struct media_device* media) { struct media_entity* entity; struct udev* udev; unsigned int size; __u32 id; int ret; ret = media_udev_open(&udev); if (ret < 0) { media_dbg(media, "Can't get udev context\n"); } for (id = 0, ret = 0;; id = entity->info.id) { size = (media->entities_count + 1) * sizeof(*media->entities); media->entities = realloc(media->entities, size); entity = &media->entities[media->entities_count]; memset(entity, 0, sizeof(*entity)); entity->fd = -1; entity->info.id = id | MEDIA_ENT_ID_FLAG_NEXT; entity->media = media; ret = ioctl(media->fd, MEDIA_IOC_ENUM_ENTITIES, &entity->info); if (ret < 0) { ret = errno != EINVAL ? -errno : 0; break; } /* Number of links (for outbound links) plus number of pads (for * inbound links) is a good safe initial estimate of the total * number of links. */ entity->max_links = entity->info.pads + entity->info.links; entity->pads = malloc(entity->info.pads * sizeof(*entity->pads)); entity->links = malloc(entity->max_links * sizeof(*entity->links)); if (entity->pads == NULL || entity->links == NULL) { ret = -ENOMEM; break; } media->entities_count++; if (entity->info.flags & MEDIA_ENT_FL_DEFAULT) { switch (entity->info.type) { case MEDIA_ENT_T_DEVNODE_V4L: media->def.v4l = entity; break; case MEDIA_ENT_T_DEVNODE_FB: media->def.fb = entity; break; case MEDIA_ENT_T_DEVNODE_ALSA: media->def.alsa = entity; break; case MEDIA_ENT_T_DEVNODE_DVB: media->def.dvb = entity; break; } } /* Find the corresponding device name. */ if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE && media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV) { continue; } /* Don't try to parse empty major,minor */ if (!entity->info.dev.major && !entity->info.dev.minor) { continue; } /* Try to get the device name via udev */ if (!media_get_devname_udev(udev, entity)) { continue; } /* Fall back to get the device name via sysfs */ media_get_devname_sysfs(entity); } media_udev_close(udev); return ret; } int media_device_enumerate(struct media_device* media) { int ret; if (media->entities) { return 0; } ret = media_device_open(media); if (ret < 0) { return ret; } memset(&media->info, 0, sizeof(media->info)); ret = ioctl(media->fd, MEDIA_IOC_DEVICE_INFO, &media->info); if (ret < 0) { ret = -errno; media_dbg(media, "%s: Unable to retrieve media device " "information for device %s (%s)\n", __func__, media->devnode, strerror(errno)); goto done; } media_dbg(media, "Enumerating entities\n"); ret = media_enum_entities(media); if (ret < 0) { media_dbg(media, "%s: Unable to enumerate entities for device %s (%s)\n", __func__, media->devnode, strerror(-ret)); goto done; } media_dbg(media, "Found %u entities\n", media->entities_count); media_dbg(media, "Enumerating pads and links\n"); ret = media_enum_links(media); if (ret < 0) { media_dbg(media, "%s: Unable to enumerate pads and linksfor device %s\n", __func__, media->devnode); goto done; } ret = 0; done: media_device_close(media); return ret; } /* ----------------------------------------------------------------------------- * Create/destroy */ static void media_debug_default(void* ptr, ...) { } void media_debug_set_handler(struct media_device* media, void (*debug_handler)(void*, ...), void* debug_priv) { if (debug_handler) { media->debug_handler = debug_handler; media->debug_priv = debug_priv; } else { media->debug_handler = media_debug_default; media->debug_priv = NULL; } } static struct media_device* __media_device_new(void) { struct media_device* media; media = calloc(1, sizeof(*media)); if (media == NULL) { return NULL; } media->fd = -1; media->refcount = 1; media_debug_set_handler(media, NULL, NULL); return media; } struct media_device* media_device_new(const char* devnode) { struct media_device* media; media = __media_device_new(); if (media == NULL) { return NULL; } media->devnode = strdup(devnode); if (media->devnode == NULL) { media_device_unref(media); return NULL; } return media; } struct media_device* media_device_new_emulated(struct media_device_info* info) { struct media_device* media; media = __media_device_new(); if (media == NULL) { return NULL; } media->info = *info; return media; } struct media_device* media_device_ref(struct media_device* media) { media->refcount++; return media; } void media_device_unref(struct media_device* media) { unsigned int i; media->refcount--; if (media->refcount > 0) { return; } for (i = 0; i < media->entities_count; ++i) { struct media_entity* entity = &media->entities[i]; free(entity->pads); free(entity->links); if (entity->fd != -1) { close(entity->fd); } } free(media->entities); free(media->devnode); free(media); } int media_device_add_entity(struct media_device* media, const struct media_entity_desc* desc, const char* devnode) { struct media_entity** defent = NULL; struct media_entity* entity; unsigned int size; size = (media->entities_count + 1) * sizeof(*media->entities); entity = realloc(media->entities, size); if (entity == NULL) { return -ENOMEM; } media->entities = entity; media->entities_count++; entity = &media->entities[media->entities_count - 1]; memset(entity, 0, sizeof *entity); entity->fd = -1; entity->media = media; strncpy(entity->devname, devnode, sizeof entity->devname); entity->devname[sizeof entity->devname - 1] = '\0'; entity->info.id = 0; entity->info.type = desc->type; entity->info.flags = 0; memcpy(entity->info.name, desc->name, sizeof entity->info.name); switch (entity->info.type) { case MEDIA_ENT_T_DEVNODE_V4L: defent = &media->def.v4l; entity->info.v4l = desc->v4l; break; case MEDIA_ENT_T_DEVNODE_FB: defent = &media->def.fb; entity->info.fb = desc->fb; break; case MEDIA_ENT_T_DEVNODE_ALSA: defent = &media->def.alsa; entity->info.alsa = desc->alsa; break; case MEDIA_ENT_T_DEVNODE_DVB: defent = &media->def.dvb; entity->info.dvb = desc->dvb; break; } if (desc->flags & MEDIA_ENT_FL_DEFAULT) { entity->info.flags |= MEDIA_ENT_FL_DEFAULT; if (defent) { *defent = entity; } } return 0; } struct media_entity* media_parse_entity(struct media_device* media, const char* p, char** endp) { unsigned int entity_id; struct media_entity* entity; char* end; /* endp can be NULL. To avoid spreading NULL checks across the function, * set endp to &end in that case. */ if (endp == NULL) { endp = &end; } for (; isspace(*p); ++p) ; if (*p == '"' || *p == '\'') { char* name; for (end = (char*)p + 1; *end && *end != '"' && *end != '\''; ++end) ; if (*end != '"' && *end != '\'') { media_dbg(media, "missing matching '\"'\n"); *endp = end; return NULL; } name = strndup(p + 1, end - p - 1); if (!name) { return NULL; } entity = media_get_entity_by_name(media, name); free(name); if (entity == NULL) { media_dbg(media, "no such entity \"%.*s\"\n", end - p - 1, p + 1); *endp = (char*)p + 1; return NULL; } ++end; } else { entity_id = strtoul(p, &end, 10); entity = media_get_entity_by_id(media, entity_id); if (entity == NULL) { media_dbg(media, "no such entity %d\n", entity_id); *endp = (char*)p; return NULL; } } for (p = end; isspace(*p); ++p) ; *endp = (char*)p; return entity; } struct media_pad* media_parse_pad(struct media_device* media, const char* p, char** endp) { unsigned int pad; struct media_entity* entity; char* end; if (endp == NULL) { endp = &end; } entity = media_parse_entity(media, p, &end); if (!entity) { *endp = end; return NULL; } if (*end != ':') { media_dbg(media, "Expected ':'\n", *end); *endp = end; return NULL; } for (p = end + 1; isspace(*p); ++p) ; pad = strtoul(p, &end, 10); if (pad >= entity->info.pads) { media_dbg(media, "No pad '%d' on entity \"%s\". Maximum pad number is %d\n", pad, entity->info.name, entity->info.pads - 1); *endp = (char*)p; return NULL; } for (p = end; isspace(*p); ++p) ; *endp = (char*)p; return &entity->pads[pad]; } struct media_link* media_parse_link(struct media_device* media, const char* p, char** endp) { struct media_link* link; struct media_pad* source; struct media_pad* sink; unsigned int i; char* end; source = media_parse_pad(media, p, &end); if (source == NULL) { *endp = end; return NULL; } if (end[0] != '-' || end[1] != '>') { *endp = end; media_dbg(media, "Expected '->'\n"); return NULL; } p = end + 2; sink = media_parse_pad(media, p, &end); if (sink == NULL) { *endp = end; return NULL; } *endp = end; for (i = 0; i < source->entity->num_links; i++) { link = &source->entity->links[i]; if (link->source == source && link->sink == sink) { return link; } } media_dbg(media, "No link between \"%s\":%d and \"%s\":%d\n", source->entity->info.name, source->index, sink->entity->info.name, sink->index); return NULL; } int media_parse_setup_link(struct media_device* media, const char* p, char** endp) { struct media_link* link; __u32 flags; char* end; link = media_parse_link(media, p, &end); if (link == NULL) { media_dbg(media, "%s: Unable to parse link\n", __func__); *endp = end; return -EINVAL; } p = end; if (*p++ != '[') { media_dbg(media, "Unable to parse link flags: expected '['.\n"); *endp = (char*)p - 1; return -EINVAL; } flags = strtoul(p, &end, 10); for (p = end; isspace(*p); p++) ; if (*p++ != ']') { media_dbg(media, "Unable to parse link flags: expected ']'.\n"); *endp = (char*)p - 1; return -EINVAL; } for (; isspace(*p); p++) ; *endp = (char*)p; media_dbg(media, "Setting up link %u:%u -> %u:%u [%u]\n", link->source->entity->info.id, link->source->index, link->sink->entity->info.id, link->sink->index, flags); return media_setup_link(media, link->source, link->sink, flags); } void media_print_streampos(struct media_device* media, const char* p, const char* end) { int pos; pos = end - p + 1; if (pos < 0) { pos = 0; } if (pos > strlen(p)) { pos = strlen(p); } media_dbg(media, "\n"); media_dbg(media, " %s\n", p); media_dbg(media, " %*s\n", pos, "^"); } int media_parse_setup_links(struct media_device* media, const char* p) { char* end; int ret; do { ret = media_parse_setup_link(media, p, &end); if (ret < 0) { media_print_streampos(media, p, end); return ret; } p = end + 1; } while (*end == ','); return *end ? -EINVAL : 0; }