|
@@ -0,0 +1,334 @@
|
|
|
+#include <stdio.h>
|
|
|
+#include <libconfig.h>
|
|
|
+#include <malloc.h>
|
|
|
+#include <string.h>
|
|
|
+#include <dirent.h>
|
|
|
+#include <fcntl.h>
|
|
|
+#include <unistd.h>
|
|
|
+#include <sys/ioctl.h>
|
|
|
+#include <errno.h>
|
|
|
+#include <linux/media.h>
|
|
|
+#include <stdint.h>
|
|
|
+#include <limits.h>
|
|
|
+#include "libmegapixels.h"
|
|
|
+#include "mode.h"
|
|
|
+#include "util.h"
|
|
|
+
|
|
|
+char *
|
|
|
+find_path_for_devnode(struct media_v2_intf_devnode devnode)
|
|
|
+{
|
|
|
+ char uevent_path[PATH_MAX];
|
|
|
+ snprintf(uevent_path, PATH_MAX, "/sys/dev/char/%d:%d/uevent", devnode.major, devnode.minor);
|
|
|
+
|
|
|
+ FILE *fp = fopen(uevent_path, "r");
|
|
|
+ if (!fp) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ char line[PATH_MAX];
|
|
|
+ char path[PATH_MAX];
|
|
|
+ while (fgets(line, PATH_MAX, fp)) {
|
|
|
+ if (strncmp(line, "DEVNAME=", 8) == 0) {
|
|
|
+ // Drop newline
|
|
|
+ unsigned long length = strlen(line);
|
|
|
+ if (line[length - 1] == '\n')
|
|
|
+ line[length - 1] = '\0';
|
|
|
+
|
|
|
+ snprintf(path, length, "/dev/%s", line + 8);
|
|
|
+ return strdup(path);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+find_media_node(libmegapixels_camera *camera, const char *media_name, const char *sensor_name)
|
|
|
+{
|
|
|
+ struct dirent *dir;
|
|
|
+ DIR *d = opendir("/dev");
|
|
|
+ while ((dir = readdir(d)) != NULL) {
|
|
|
+ if (strncmp(dir->d_name, "media", 5) == 0) {
|
|
|
+ char path[PATH_MAX];
|
|
|
+ snprintf(path, PATH_MAX, "/dev/%s", dir->d_name);
|
|
|
+
|
|
|
+ int media_fd = open(path, O_RDWR);
|
|
|
+ if (media_fd == -1) {
|
|
|
+ fprintf(stderr, "Could not open %s\n", path);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ struct media_device_info mdi;
|
|
|
+ if (xioctl(media_fd, MEDIA_IOC_DEVICE_INFO, &mdi) == -1) {
|
|
|
+ fprintf(stderr, "Could not MDI\n");
|
|
|
+ close(media_fd);
|
|
|
+ }
|
|
|
+ if (strcmp(mdi.driver, media_name) != 0 && strcmp(mdi.model, media_name) != 0) {
|
|
|
+ close(media_fd);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // This media device matches on model or driver, scan the entities for the sensor
|
|
|
+ struct media_v2_topology topology = {0};
|
|
|
+ if (xioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) == -1 ||
|
|
|
+ topology.num_entities == 0) {
|
|
|
+ close(media_fd);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ struct media_v2_entity *entities = calloc(topology.num_entities, sizeof(struct media_v2_entity));
|
|
|
+ struct media_v2_interface *interfaces = calloc(topology.num_interfaces, sizeof(struct media_v2_interface));
|
|
|
+ struct media_v2_pad *pads = calloc(topology.num_pads, sizeof(struct media_v2_pad));
|
|
|
+ struct media_v2_link *links = calloc(topology.num_links, sizeof(struct media_v2_link));
|
|
|
+
|
|
|
+ topology.ptr_entities = (uint64_t) entities;
|
|
|
+ topology.ptr_interfaces = (uint64_t) interfaces;
|
|
|
+ topology.ptr_pads = (uint64_t) pads;
|
|
|
+ topology.ptr_links = (uint64_t) links;
|
|
|
+
|
|
|
+ if (xioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology) == -1) {
|
|
|
+ close(media_fd);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Find the sensor
|
|
|
+ unsigned long len = strlen(sensor_name);
|
|
|
+ int found = 0;
|
|
|
+ for (int i = 0; i < topology.num_entities; i++) {
|
|
|
+ if (strncmp(entities[i].name, sensor_name, len) == 0) {
|
|
|
+ found++;
|
|
|
+ for (int j = 0; j < topology.num_links; j++) {
|
|
|
+ if (links[j].sink_id != entities[i].id) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (int k = 0; k < topology.num_interfaces; k++) {
|
|
|
+ if (interfaces[k].id != links[j].source_id) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ camera->sensor_path = find_path_for_devnode(interfaces[k].devnode);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Find the bridge
|
|
|
+ for (int i = 0; i < topology.num_entities; i++) {
|
|
|
+ if (entities[i].function == MEDIA_ENT_F_IO_V4L) {
|
|
|
+ found++;
|
|
|
+ for (int j = 0; j < topology.num_links; j++) {
|
|
|
+ if (links[j].sink_id != entities[i].id) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (int k = 0; k < topology.num_interfaces; k++) {
|
|
|
+ if (interfaces[k].id != links[j].source_id) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ camera->video_path = find_path_for_devnode(interfaces[k].devnode);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // TODO: find /dev path for this entity
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ close(media_fd);
|
|
|
+ if (found == 2) {
|
|
|
+ camera->media_path = strdup(path);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ closedir(d);
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+load_camera(libmegapixels_devconfig *config, config_t *cfg, const char *name)
|
|
|
+{
|
|
|
+ const char *sensor_driver, *bridge_driver;
|
|
|
+ config_setting_t *root = config_lookup(cfg, name);
|
|
|
+ if (!config_setting_lookup_string(root, "SensorDriver", &sensor_driver)) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (!config_setting_lookup_string(root, "BridgeDriver", &bridge_driver)) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ libmegapixels_camera *camera;
|
|
|
+ camera = malloc(sizeof(libmegapixels_camera));
|
|
|
+ int res = find_media_node(camera, bridge_driver, sensor_driver);
|
|
|
+ if (res < 0) {
|
|
|
+ free(camera);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ camera->name = strdup(name);
|
|
|
+ camera->sensor_name = strdup(sensor_driver);
|
|
|
+ camera->bridge_name = strdup(bridge_driver);
|
|
|
+ config->cameras = realloc(config->cameras, (config->count + 1) * sizeof(config->cameras));
|
|
|
+ if (config->cameras == NULL) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ config->cameras[config->count++] = camera;
|
|
|
+
|
|
|
+ config_setting_t *modes = config_setting_lookup(root, "Modes");
|
|
|
+ config_setting_t *mode;
|
|
|
+
|
|
|
+ int num_modes = config_setting_length(modes);
|
|
|
+ camera->modes = malloc(num_modes * sizeof(libmegapixels_mode));
|
|
|
+ camera->num_modes = num_modes;
|
|
|
+ int n = 0;
|
|
|
+ while (1) {
|
|
|
+ mode = config_setting_get_elem(modes, n);
|
|
|
+ if (mode == NULL) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!config_setting_lookup_int(mode, "Width", &(camera->modes[n].width))) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (!config_setting_lookup_int(mode, "Height", &(camera->modes[n].height))) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (!config_setting_lookup_int(mode, "Rate", &(camera->modes[n].rate))) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ const char *fmt;
|
|
|
+ config_setting_lookup_string(mode, "Format", &fmt);
|
|
|
+ camera->modes[n].v4l_pixfmt = format_name_to_v4l_pixfmt(fmt);
|
|
|
+ camera->modes[n].media_busfmt = format_name_to_media_busfmt(fmt);
|
|
|
+
|
|
|
+
|
|
|
+ if (!config_setting_lookup_int(mode, "Rotate", &(camera->modes[n].rotation))) {
|
|
|
+ camera->modes[n].rotation = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!config_setting_lookup_float(mode, "FocalLength", &(camera->modes[n].focal_length))) {
|
|
|
+ camera->modes[n].focal_length = 0.0f;
|
|
|
+ }
|
|
|
+ if (!config_setting_lookup_float(mode, "FNumber", &(camera->modes[n].f_number))) {
|
|
|
+ camera->modes[n].f_number = 0.0f;
|
|
|
+ }
|
|
|
+
|
|
|
+ config_setting_t *cmds = config_setting_lookup(mode, "Pipeline");
|
|
|
+ config_setting_t *cmd;
|
|
|
+
|
|
|
+ if (cmds == NULL) {
|
|
|
+ fprintf(stderr, "Missing pipeline\n");
|
|
|
+ n++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ int num_cmds = config_setting_length(cmds);
|
|
|
+ camera->modes[n].cmds = malloc(num_cmds * sizeof(libmegapixels_cmd));
|
|
|
+ camera->modes[n].num_cmds = num_cmds;
|
|
|
+ int m = 0;
|
|
|
+ while (1) {
|
|
|
+ cmd = config_setting_get_elem(cmds, m);
|
|
|
+ if (cmd == NULL) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ libmegapixels_cmd *cur = &(camera->modes[n].cmds[m]);
|
|
|
+ const char *type;
|
|
|
+ if (!config_setting_lookup_string(cmd, "Type", &type)) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (strcmp(type, "Link") == 0) {
|
|
|
+ camera->modes[n].cmds[m].type = LIBMEGAPIXELS_CMD_LINK;
|
|
|
+ if (!config_setting_lookup_string(cmd, "From", &cur->entity_from)) {
|
|
|
+ fprintf(stderr, "Missing From\n");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!config_setting_lookup_string(cmd, "To", &cur->entity_to)) {
|
|
|
+ fprintf(stderr, "Missing To\n");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!config_setting_lookup_int(cmd, "FromPad", &cur->pad_from)) {
|
|
|
+ cur->pad_from = 0;
|
|
|
+ }
|
|
|
+ if (!config_setting_lookup_int(cmd, "ToPad", &cur->pad_to)) {
|
|
|
+ cur->pad_to = 0;
|
|
|
+ }
|
|
|
+ } else if (strcmp(type, "Mode") == 0) {
|
|
|
+ camera->modes[n].cmds[m].type = LIBMEGAPIXELS_CMD_MODE;
|
|
|
+ if (!config_setting_lookup_string(cmd, "Entity", &cur->entity_from)) {
|
|
|
+ fprintf(stderr, "Missing entity\n");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!config_setting_lookup_int(cmd, "Width", &cur->width)) {
|
|
|
+ cur->width = camera->modes[n].width;
|
|
|
+ }
|
|
|
+ if (!config_setting_lookup_int(cmd, "Height", &cur->height)) {
|
|
|
+ cur->height = camera->modes[n].height;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ m++;
|
|
|
+ }
|
|
|
+ n++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int
|
|
|
+libmegapixels_load_file(libmegapixels_devconfig **config, const char *file)
|
|
|
+{
|
|
|
+ config_t cfg;
|
|
|
+ config_setting_t *setting, *member;
|
|
|
+
|
|
|
+ *config = malloc(sizeof(libmegapixels_devconfig));
|
|
|
+ (*config)->path = strdup(file);
|
|
|
+ (*config)->count = 0;
|
|
|
+ (*config)->cameras = malloc(sizeof((*config)->cameras));
|
|
|
+
|
|
|
+ config_init(&cfg);
|
|
|
+ if (!config_read_file(&cfg, file)) {
|
|
|
+ fprintf(stderr, "Could not read %s\n", file);
|
|
|
+ config_destroy(&cfg);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ int version = 0;
|
|
|
+ if (config_lookup_int(&cfg, "Version", &version)) {
|
|
|
+ if (version != 1) {
|
|
|
+ fprintf(stderr, "Invalid version %d\n", version);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ fprintf(stderr, "Missing version\n");
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!config_lookup_string(&cfg, "Make", (const char **) &(*config)->make)) {
|
|
|
+ (*config)->make = strdup("Megapixels");
|
|
|
+ }
|
|
|
+ if (!config_lookup_string(&cfg, "Model", (const char **) &(*config)->model)) {
|
|
|
+ (*config)->model = strdup("Camera");
|
|
|
+ }
|
|
|
+
|
|
|
+ setting = config_root_setting(&cfg);
|
|
|
+ int n = 0;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ member = config_setting_get_elem(setting, n++);
|
|
|
+ if (member == NULL) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (strcmp(member->name, "Version") == 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (strcmp(member->name, "Make") == 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (strcmp(member->name, "Model") == 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ load_camera(*config, &cfg, member->name);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 1;
|
|
|
+}
|