Add common, media_ctrl and v4l2_ctrl modules with CLI drivers and docs
Modules (src/modules/): - common/error: App_Error struct with structured detail union, error codes, app_error_print(); designed for future upgrade to preprocessor-generated location codes - media_ctrl: media device enumeration, topology query (entities/pads/links), link enable/disable via Media Controller API (/dev/media*) - v4l2_ctrl: control enumeration (with menu item fetching), get/set via V4L2 ext controls API, device discovery (/dev/video*) All modules use -std=c11 -D_GNU_SOURCE, build artifacts go to build/ only. Kernel-version-dependent constants guarded with #ifdef + #warning. CLI drivers (dev/cli/): - media_ctrl_cli: list, info, topology, set-link subcommands - v4l2_ctrl_cli: list, controls, get, set subcommands Docs (docs/cli/): - media_ctrl_cli.md and v4l2_ctrl_cli.md with usage, examples, and context within the video routing system Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
248
dev/cli/media_ctrl_cli.c
Normal file
248
dev/cli/media_ctrl_cli.c
Normal file
@@ -0,0 +1,248 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "media_ctrl.h"
|
||||
|
||||
static void usage(const char *prog) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s <command> [args]\n"
|
||||
"\n"
|
||||
"Commands:\n"
|
||||
" list List all /dev/media* devices\n"
|
||||
" info <device> Show device information\n"
|
||||
" topology <device> Show full pipeline topology\n"
|
||||
" set-link <device> <src_entity>:<src_pad> <sink_entity>:<sink_pad> <0|1>\n"
|
||||
" Enable (1) or disable (0) a link\n"
|
||||
"\n"
|
||||
"Examples:\n"
|
||||
" %s list\n"
|
||||
" %s info /dev/media0\n"
|
||||
" %s topology /dev/media0\n"
|
||||
" %s set-link /dev/media0 1:0 2:0 1\n",
|
||||
prog, prog, prog, prog, prog);
|
||||
}
|
||||
|
||||
/* --- list --- */
|
||||
|
||||
static void on_device_found(const char *path, void *userdata) {
|
||||
(void)userdata;
|
||||
printf(" %s\n", path);
|
||||
}
|
||||
|
||||
static int cmd_list(void) {
|
||||
printf("Media devices:\n");
|
||||
struct App_Error err = media_ctrl_find_devices(on_device_found, NULL);
|
||||
if (!APP_IS_OK(err)) {
|
||||
app_error_print(&err);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --- info --- */
|
||||
|
||||
static int cmd_info(const char *device) {
|
||||
struct Media_Ctrl *ctrl = NULL;
|
||||
struct App_Error err = media_ctrl_open(device, &ctrl);
|
||||
if (!APP_IS_OK(err)) {
|
||||
app_error_print(&err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct Media_Device_Info info;
|
||||
err = media_ctrl_get_info(ctrl, &info);
|
||||
if (!APP_IS_OK(err)) {
|
||||
app_error_print(&err);
|
||||
media_ctrl_close(ctrl);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Device: %s\n", device);
|
||||
printf("Driver: %s\n", info.driver);
|
||||
printf("Model: %s\n", info.model);
|
||||
printf("Serial: %s\n", info.serial);
|
||||
printf("Bus info: %s\n", info.bus_info);
|
||||
printf("Media version: %u.%u.%u\n",
|
||||
(info.media_version >> 16) & 0xff,
|
||||
(info.media_version >> 8) & 0xff,
|
||||
info.media_version & 0xff);
|
||||
printf("Hardware rev: 0x%08x\n", info.hw_revision);
|
||||
printf("Driver version: %u.%u.%u\n",
|
||||
(info.driver_version >> 16) & 0xff,
|
||||
(info.driver_version >> 8) & 0xff,
|
||||
info.driver_version & 0xff);
|
||||
|
||||
media_ctrl_close(ctrl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --- topology --- */
|
||||
|
||||
struct Topology_State {
|
||||
struct Media_Ctrl *ctrl;
|
||||
};
|
||||
|
||||
static void on_pad(const struct Media_Pad *pad, void *userdata) {
|
||||
(void)userdata;
|
||||
printf(" pad %u: %s\n", pad->index, media_pad_flag_name(pad->flags));
|
||||
}
|
||||
|
||||
static void on_link(const struct Media_Link *link, void *userdata) {
|
||||
(void)userdata;
|
||||
const char *enabled = (link->flags & MEDIA_LNK_FL_ENABLED) ? "enabled" : "disabled";
|
||||
const char *mutable = (link->flags & MEDIA_LNK_FL_IMMUTABLE) ? ", immutable" : "";
|
||||
printf(" link: entity %u pad %u -> entity %u pad %u [%s%s]\n",
|
||||
link->source.entity_id, link->source.index,
|
||||
link->sink.entity_id, link->sink.index,
|
||||
enabled, mutable);
|
||||
}
|
||||
|
||||
static void on_entity(const struct Media_Entity *entity, void *userdata) {
|
||||
struct Topology_State *state = userdata;
|
||||
|
||||
printf(" Entity %u: %s\n", entity->id, entity->name);
|
||||
printf(" type: %s (0x%08x)\n", media_entity_type_name(entity->type), entity->type);
|
||||
printf(" flags: 0x%08x\n", entity->flags);
|
||||
printf(" pads: %u links: %u\n", entity->pad_count, entity->link_count);
|
||||
|
||||
if (entity->dev_major != 0 || entity->dev_minor != 0) {
|
||||
printf(" device: %u:%u\n", entity->dev_major, entity->dev_minor);
|
||||
}
|
||||
|
||||
struct App_Error err = media_ctrl_enum_entity_pads_and_links(
|
||||
state->ctrl, entity, on_pad, on_link, NULL);
|
||||
if (!APP_IS_OK(err)) {
|
||||
fprintf(stderr, " (error enumerating pads/links: ");
|
||||
app_error_print(&err);
|
||||
fprintf(stderr, ")\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int cmd_topology(const char *device) {
|
||||
struct Media_Ctrl *ctrl = NULL;
|
||||
struct App_Error err = media_ctrl_open(device, &ctrl);
|
||||
if (!APP_IS_OK(err)) {
|
||||
app_error_print(&err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct Media_Device_Info info;
|
||||
err = media_ctrl_get_info(ctrl, &info);
|
||||
if (!APP_IS_OK(err)) {
|
||||
app_error_print(&err);
|
||||
media_ctrl_close(ctrl);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Device: %s (%s)\n\n", info.model, device);
|
||||
|
||||
struct Topology_State state = { .ctrl = ctrl };
|
||||
err = media_ctrl_enum_entities(ctrl, on_entity, &state);
|
||||
if (!APP_IS_OK(err)) {
|
||||
app_error_print(&err);
|
||||
media_ctrl_close(ctrl);
|
||||
return 1;
|
||||
}
|
||||
|
||||
media_ctrl_close(ctrl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --- set-link --- */
|
||||
|
||||
static int parse_entity_pad(const char *s, uint32_t *entity_out, uint16_t *pad_out) {
|
||||
unsigned int entity, pad;
|
||||
if (sscanf(s, "%u:%u", &entity, &pad) != 2) {
|
||||
return -1;
|
||||
}
|
||||
*entity_out = (uint32_t)entity;
|
||||
*pad_out = (uint16_t)pad;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_set_link(
|
||||
const char *device,
|
||||
const char *src_arg,
|
||||
const char *sink_arg,
|
||||
const char *enabled_arg)
|
||||
{
|
||||
uint32_t src_entity, sink_entity;
|
||||
uint16_t src_pad, sink_pad;
|
||||
|
||||
if (parse_entity_pad(src_arg, &src_entity, &src_pad) < 0) {
|
||||
fprintf(stderr, "Invalid source: '%s' (expected entity:pad)\n", src_arg);
|
||||
return 1;
|
||||
}
|
||||
if (parse_entity_pad(sink_arg, &sink_entity, &sink_pad) < 0) {
|
||||
fprintf(stderr, "Invalid sink: '%s' (expected entity:pad)\n", sink_arg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int enabled = atoi(enabled_arg);
|
||||
|
||||
struct Media_Ctrl *ctrl = NULL;
|
||||
struct App_Error err = media_ctrl_open(device, &ctrl);
|
||||
if (!APP_IS_OK(err)) {
|
||||
app_error_print(&err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
err = media_ctrl_set_link(ctrl, src_entity, src_pad, sink_entity, sink_pad, enabled);
|
||||
if (!APP_IS_OK(err)) {
|
||||
app_error_print(&err);
|
||||
media_ctrl_close(ctrl);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Link %u:%u -> %u:%u %s\n",
|
||||
src_entity, src_pad, sink_entity, sink_pad,
|
||||
enabled ? "enabled" : "disabled");
|
||||
|
||||
media_ctrl_close(ctrl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --- main --- */
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *cmd = argv[1];
|
||||
|
||||
if (strcmp(cmd, "list") == 0) {
|
||||
return cmd_list();
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "info") == 0) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "info requires a device argument\n");
|
||||
return 1;
|
||||
}
|
||||
return cmd_info(argv[2]);
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "topology") == 0) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "topology requires a device argument\n");
|
||||
return 1;
|
||||
}
|
||||
return cmd_topology(argv[2]);
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "set-link") == 0) {
|
||||
if (argc < 6) {
|
||||
fprintf(stderr, "set-link requires: <device> <src_entity>:<src_pad> <sink_entity>:<sink_pad> <0|1>\n");
|
||||
return 1;
|
||||
}
|
||||
return cmd_set_link(argv[2], argv[3], argv[4], argv[5]);
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unknown command: %s\n\n", cmd);
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user