/* * This library 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 library 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. */ #include "usbg/usbg.h" #include "usbg/usbg_internal.h" #include "usbg/function/uvc.h" #include #include #include #include #include #ifdef HAS_GADGET_SCHEMES #include #endif #define UVC_PATH_CONTROL "control" #define UVC_PATH_HEADER "header/h" #define UVC_PATH_CLASS_FS "class/fs/h" #define UVC_PATH_CLASS_HS "class/hs/h" #define UVC_PATH_CLASS_SS "class/ss/h" #define UVC_PATH_STREAMING "streaming" #define UVC_PATH_STREAMING_UNCOMPRESSED "uncompressed/u" #define UVC_PATH_STREAMING_MJPEG "mjpeg/m" #define MAX_FRAMES 16 #define MAX_FORMATS 2 const char * format_names[MAX_FORMATS] = { UVC_PATH_STREAMING_MJPEG, UVC_PATH_STREAMING_UNCOMPRESSED, }; struct formats { bool frames[MAX_FRAMES]; bool frames_initiated; }; struct usbg_f_uvc { struct usbg_function func; struct formats formats[MAX_FORMATS]; bool formats_initiated; }; #define UVC_DEC_ATTR(_name) \ { \ .name = #_name, \ .offset = offsetof(struct usbg_f_uvc_config_attrs, _name), \ .get = usbg_get_dec, \ .set = usbg_set_dec, \ .import = usbg_get_config_node_int, \ .export = usbg_set_config_node_int, \ } #define UVC_STRING_ATTR(_name) \ { \ .name = #_name, \ .offset = offsetof(struct usbg_f_uvc_config_attrs, _name), \ .get = usbg_get_string, \ .set = usbg_set_string, \ .export = usbg_set_config_node_string, \ .import = usbg_get_config_node_string, \ } struct { const char *name; size_t offset; usbg_attr_get_func get; usbg_attr_set_func set; usbg_import_node_func import; usbg_export_node_func export; } uvc_config_attr[USBG_F_UVC_CONFIG_ATTR_MAX] = { [USBG_F_UVC_CONFIG_MAXBURST] = UVC_DEC_ATTR(streaming_maxburst), [USBG_F_UVC_CONFIG_MAXPACKET] = UVC_DEC_ATTR(streaming_maxpacket), [USBG_F_UVC_CONFIG_INTERVAL] = UVC_DEC_ATTR(streaming_interval), [USBG_F_UVC_CONFIG_FUNCTION_NAME] = UVC_STRING_ATTR(function_name), }; #undef UVC_DEC_ATTR #undef UVC_STRING_ATTR #define UVC_DEC_ATTR(_name) \ { \ .name = #_name, \ .offset = offsetof(struct usbg_f_uvc_frame_attrs, _name), \ .get = usbg_get_dec, \ .set = usbg_set_dec, \ .import = usbg_get_config_node_int, \ .export = usbg_set_config_node_int, \ } struct { const char *name; size_t offset; usbg_attr_get_func get; usbg_attr_set_func set; usbg_import_node_func import; usbg_export_node_func export; } uvc_frame_attr[USBG_F_UVC_FRAME_ATTR_MAX] = { [USBG_F_UVC_FRAME_INDEX] = UVC_DEC_ATTR(bFrameIndex), [USBG_F_UVC_FRAME_CAPABILITIES] = UVC_DEC_ATTR(bmCapabilities), [USBG_F_UVC_FRAME_MIN_BITRATE] = UVC_DEC_ATTR(dwMinBitRate), [USBG_F_UVC_FRAME_MAX_BITRATE] = UVC_DEC_ATTR(dwMaxBitRate), [USBG_F_UVC_FRAME_MAX_VIDEO_BUFFERSIZE] = UVC_DEC_ATTR(dwMaxVideoFrameBufferSize), [USBG_F_UVC_FRAME_DEFAULT_INTERVAL] = UVC_DEC_ATTR(dwDefaultFrameInterval), [USBG_F_UVC_FRAME_INTERVAL] = UVC_DEC_ATTR(dwFrameInterval), [USBG_F_UVC_FRAME_HEIGHT] = UVC_DEC_ATTR(wHeight), [USBG_F_UVC_FRAME_WIDTH] = UVC_DEC_ATTR(wWidth), }; #undef UVC_DEC_ATTR #define UVC_DEC_ATTR(_name) \ { \ .name = #_name, \ .offset = offsetof(struct usbg_f_uvc_format_attrs, _name), \ .get = usbg_get_dec, \ .set = usbg_set_dec, \ .import = usbg_get_config_node_int, \ .export = usbg_set_config_node_int, \ } #define UVC_DEC_ATTR_RO(_name) \ { \ .name = #_name, \ .ro = true, \ .offset = offsetof(struct usbg_f_uvc_format_attrs, _name), \ .get = usbg_get_dec, \ .export = usbg_set_config_node_int, \ } static inline int usbg_get_guid(const char *path, const char *name, const char *attr, void *val) { return usbg_read_buf_alloc(path, name, attr, (char **)val, GUID_BIN_LENGTH); } static inline int usbg_set_guid(const char *path, const char *name, const char *attr, void *val) { return usbg_write_guid(path, name, attr, *(char **)val); } #define UVC_GUID_ATTR(_name) \ { \ .name = #_name, \ .offset = offsetof(struct usbg_f_uvc_format_attrs, _name), \ .get = usbg_get_guid, \ .set = usbg_set_guid, \ .export = usbg_set_config_node_guid, \ .import = usbg_get_config_node_string, \ } struct { const char *name; size_t offset; bool ro; usbg_attr_get_func get; usbg_attr_set_func set; usbg_import_node_func import; usbg_export_node_func export; } uvc_format_attr[USBG_F_UVC_FORMAT_ATTR_MAX] = { [USBG_F_UVC_FORMAT_CONTROLS] = UVC_DEC_ATTR_RO(bmaControls), [USBG_F_UVC_FORMAT_INTERLACE_FLAGS] = UVC_DEC_ATTR_RO(bmInterlaceFlags), [USBG_F_UVC_FORMAT_ASPECTRATIO_X] = UVC_DEC_ATTR_RO(bAspectRatioX), [USBG_F_UVC_FORMAT_ASPECTRATIO_Y] = UVC_DEC_ATTR_RO(bAspectRatioY), [USBG_F_UVC_FORMAT_DEFAULT_FRAME_INDEX] = UVC_DEC_ATTR(bDefaultFrameIndex), [USBG_F_UVC_FORMAT_GUID_FORMAT] = UVC_GUID_ATTR(guidFormat), [USBG_F_UVC_FORMAT_BITS_PER_PIXEL] = UVC_DEC_ATTR(bBitsPerPixel), [USBG_F_UVC_FORMAT_FORMAT_INDEX] = UVC_DEC_ATTR_RO(bFormatIndex), }; #undef UVC_DEC_ATTR #undef UVC_DEC_ATTR_RO static inline int frame_select(const struct dirent *dent) { int ret; int id; ret = file_select(dent); if (!ret) goto out; ret = sscanf(dent->d_name, "frame.%d", &id); out: return ret; } static inline int frame_sort(const struct dirent **d1, const struct dirent **d2) { int ret; int id1, id2; ret = sscanf((*d1)->d_name, "frame.%d", &id1); if (ret != 1) goto err; ret = sscanf((*d2)->d_name, "frame.%d", &id2); if (ret != 1) goto err; return id1 < id2 ? -1 : id1 > id2; err: /* * This should not happened because dentries has been * already checked by frame_select function. This * error procedure is just in case. */ return -1; } int init_frames(struct usbg_f_uvc *uvc, int j) { struct dirent **dent; char fpath[USBG_MAX_PATH_LENGTH]; int nmb, i, id, ret = 0; nmb = snprintf(fpath, sizeof(fpath), "%s/%s/" UVC_PATH_STREAMING "/%s/", uvc->func.path, uvc->func.name, format_names[j]); if (nmb >= sizeof(fpath)) { ret = USBG_ERROR_PATH_TOO_LONG; return ret; } nmb = scandir(fpath, &dent, frame_select, frame_sort); if (nmb < 0) { ret = usbg_translate_error(errno); return ret; } for (i = 0; i < nmb; ++i) { /* don't check the error as we know that name is valid */ sscanf(dent[i]->d_name, "frame.%d", &id); uvc->formats[j].frames[id] = true; free(dent[i]); } free(dent); uvc->formats[j].frames_initiated = true; return 0; } static inline struct formats *get_formats_mask(struct usbg_f_uvc *uvc) { int i, ret; if (!uvc->formats_initiated) { uvc->formats_initiated = true; for (i = 0; i < MAX_FORMATS; ++i) { ret = init_frames(uvc, i); if (ret) uvc->formats_initiated = false; } } return uvc->formats; } GENERIC_ALLOC_INST(uvc_internal, struct usbg_f_uvc, func) static int uvc_alloc_inst(struct usbg_function_type *type, usbg_function_type type_code, const char *instance, const char *path, struct usbg_gadget *parent, struct usbg_function **f) { struct usbg_f_uvc *uvcf; int ret; ret = uvc_internal_alloc_inst(type, type_code, instance, path, parent, f); if (ret) goto out; uvcf = usbg_to_uvc_function(*f); if (!uvcf) return -ENOMEM; memset(uvcf->formats, 0, sizeof(uvcf->formats)); uvcf->formats_initiated = false; out: return ret; } GENERIC_FREE_INST(uvc, struct usbg_f_uvc, func) static int uvc_set_attrs(struct usbg_function *f, void *f_attrs) { return usbg_f_uvc_set_attrs(usbg_to_uvc_function(f), f_attrs); } static int uvc_get_attrs(struct usbg_function *f, void *f_attrs) { return usbg_f_uvc_get_attrs(usbg_to_uvc_function(f), f_attrs); } static void uvc_cleanup_attrs(struct usbg_function *f, void *f_attrs) { usbg_f_uvc_cleanup_attrs(f_attrs); } int usbg_f_uvc_get_config_attr_val(usbg_f_uvc *uvcf, enum usbg_f_uvc_config_attr iattr, union usbg_f_uvc_config_attr_val *val) { char ipath[USBG_MAX_PATH_LENGTH]; int nmb; nmb = snprintf(ipath, sizeof(ipath), "%s/%s/", uvcf->func.path, uvcf->func.name); if (nmb >= sizeof(ipath)) return USBG_ERROR_PATH_TOO_LONG; return uvc_config_attr[iattr].get(ipath, "", uvc_config_attr[iattr].name, val); } int usbg_f_uvc_set_config_attr_val(usbg_f_uvc *uvcf, enum usbg_f_uvc_config_attr iattr, union usbg_f_uvc_config_attr_val val) { char ipath[USBG_MAX_PATH_LENGTH]; int nmb; nmb = snprintf(ipath, sizeof(ipath), "%s/%s/", uvcf->func.path, uvcf->func.name); if (nmb >= sizeof(ipath)) return USBG_ERROR_PATH_TOO_LONG; return uvc_config_attr[iattr].set(ipath, "", uvc_config_attr[iattr].name, &val); } int usbg_f_uvc_get_config_attrs(usbg_f_uvc *uvcf, struct usbg_f_uvc_config_attrs *iattrs) { int i; int ret = 0; for (i = USBG_F_UVC_CONFIG_ATTR_MIN; i < USBG_F_UVC_CONFIG_ATTR_MAX; ++i) { ret = usbg_f_uvc_get_config_attr_val(uvcf, i, (union usbg_f_uvc_config_attr_val *) ((char *)iattrs + uvc_config_attr[i].offset)); if (ret) break; } return ret; } int usbg_f_uvc_set_config_attrs(usbg_f_uvc *uvcf, const struct usbg_f_uvc_config_attrs *iattrs) { int i; int ret = 0; for (i = USBG_F_UVC_FRAME_ATTR_MIN; i < USBG_F_UVC_FRAME_ATTR_MAX; ++i) { ret = usbg_f_uvc_set_config_attr_val(uvcf, i, *(union usbg_f_uvc_config_attr_val *) ((char *)iattrs + uvc_config_attr[i].offset)); if (ret) break; } return ret; } int usbg_f_uvc_get_frame_attr_val(usbg_f_uvc *uvcf, const char* format, int frame_id, enum usbg_f_uvc_frame_attr fattr, union usbg_f_uvc_frame_attr_val *val) { char fpath[USBG_MAX_PATH_LENGTH]; int nmb; nmb = snprintf(fpath, sizeof(fpath), "%s/%s/" UVC_PATH_STREAMING "/%s/frame.%d/", uvcf->func.path, uvcf->func.name, format, frame_id); if (nmb >= sizeof(fpath)) return USBG_ERROR_PATH_TOO_LONG; return uvc_frame_attr[fattr].get(fpath, "", uvc_frame_attr[fattr].name, val); } int usbg_f_uvc_set_frame_attr_val(usbg_f_uvc *uvcf, const char* format, int frame_id, enum usbg_f_uvc_frame_attr fattr, union usbg_f_uvc_frame_attr_val val) { char fpath[USBG_MAX_PATH_LENGTH]; int nmb; nmb = snprintf(fpath, sizeof(fpath), "%s/%s/" UVC_PATH_STREAMING "/%s/frame.%d/", uvcf->func.path, uvcf->func.name, format, frame_id); if (nmb >= sizeof(fpath)) return USBG_ERROR_PATH_TOO_LONG; return uvc_frame_attr[fattr].set(fpath, "", uvc_frame_attr[fattr].name, &val); } int usbg_f_uvc_get_frame_attrs(usbg_f_uvc *uvcf, const char* format, int frame_id, struct usbg_f_uvc_frame_attrs *fattrs) { int i; int ret = 0; for (i = USBG_F_UVC_FRAME_ATTR_MIN; i < USBG_F_UVC_FRAME_ATTR_MAX; ++i) { ret = usbg_f_uvc_get_frame_attr_val(uvcf, format, frame_id, i, (union usbg_f_uvc_frame_attr_val *) ((char *)fattrs + uvc_frame_attr[i].offset)); if (ret) break; } fattrs->bFrameIndex = frame_id; return ret; } int usbg_f_uvc_set_frame_attrs(usbg_f_uvc *uvcf, const char* format, int frame_id, const struct usbg_f_uvc_frame_attrs *fattrs) { int i; int ret = 0; for (i = USBG_F_UVC_FRAME_ATTR_MIN; i < USBG_F_UVC_FRAME_ATTR_MAX; ++i) { ret = usbg_f_uvc_set_frame_attr_val(uvcf, format, frame_id, i, *(union usbg_f_uvc_frame_attr_val *) ((char *)fattrs + uvc_frame_attr[i].offset)); if (ret) break; } return ret; } int usbg_f_uvc_get_format_attr_val(usbg_f_uvc *uvcf, const char* format, enum usbg_f_uvc_format_attr fattr, union usbg_f_uvc_format_attr_val *val) { char fpath[USBG_MAX_PATH_LENGTH]; int nmb; nmb = snprintf(fpath, sizeof(fpath), "%s/%s/" UVC_PATH_STREAMING "/%s/", uvcf->func.path, uvcf->func.name, format); if (nmb >= sizeof(fpath)) return USBG_ERROR_PATH_TOO_LONG; return uvc_format_attr[fattr].get(fpath, "", uvc_format_attr[fattr].name, val); } int usbg_f_uvc_set_format_attr_val(usbg_f_uvc *uvcf, const char* format, enum usbg_f_uvc_format_attr fattr, union usbg_f_uvc_format_attr_val val) { char fpath[USBG_MAX_PATH_LENGTH]; int nmb; nmb = snprintf(fpath, sizeof(fpath), "%s/%s/" UVC_PATH_STREAMING "/%s/", uvcf->func.path, uvcf->func.name, format); if (nmb >= sizeof(fpath)) return USBG_ERROR_PATH_TOO_LONG; return uvc_format_attr[fattr].set(fpath, "", uvc_format_attr[fattr].name, &val); } int usbg_f_uvc_get_format_attrs(usbg_f_uvc *uvcf, const char* format, struct usbg_f_uvc_format_attrs *fattrs) { int i; int ret = 0; for (i = USBG_F_UVC_FORMAT_ATTR_MIN; i < USBG_F_UVC_FORMAT_ATTR_MAX; ++i) { ret = usbg_f_uvc_get_format_attr_val(uvcf, format, i, (union usbg_f_uvc_format_attr_val *) ((char *)fattrs + uvc_format_attr[i].offset)); if (ret) break; } return ret; } int usbg_f_uvc_set_format_attrs(usbg_f_uvc *uvcf, const char* format, const struct usbg_f_uvc_format_attrs *fattrs) { int i; int ret = 0; for (i = USBG_F_UVC_FORMAT_ATTR_MIN; i < USBG_F_UVC_FORMAT_ATTR_MAX; ++i) { ret = usbg_f_uvc_set_format_attr_val(uvcf, format, i, *(union usbg_f_uvc_format_attr_val *) ((char *)fattrs + uvc_format_attr[i].offset)); if (ret) break; } return ret; } static int uvc_create_dir(const char *path) { char tmp[USBG_MAX_PATH_LENGTH]; int nmb, ret = USBG_SUCCESS; char *p = NULL; size_t len; nmb = snprintf(tmp, sizeof(tmp), "%s", path); if(nmb >= sizeof(tmp)) return USBG_ERROR_PATH_TOO_LONG; len = strlen(tmp); if(tmp[len - 1] == '/') tmp[len - 1] = 0; for (p = tmp + 1; *p; p++) { if(*p == '/') { *p = 0; if((mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && errno != EEXIST) { ret = usbg_translate_error(errno); break; } *p = '/'; } } if(ret != USBG_SUCCESS) return ret; if((mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) != 0) && errno != EEXIST) return usbg_translate_error(errno); return ret; } static int uvc_link(char *path, char *to, char *from) { char oldname[USBG_MAX_PATH_LENGTH]; char newname[USBG_MAX_PATH_LENGTH]; int ret = USBG_SUCCESS; int nmb; nmb = snprintf(oldname, sizeof(oldname), "%s/%s", path, to); if (nmb >= sizeof(oldname)) return USBG_ERROR_PATH_TOO_LONG; nmb = snprintf(newname, sizeof(newname), "%s/%s", path, from); if (nmb >= sizeof(newname)) return USBG_ERROR_PATH_TOO_LONG; if(symlink(oldname, newname)) return usbg_translate_error(errno); return ret; } static int uvc_set_class(usbg_f_uvc *uvcf, char *cs) { char path[USBG_MAX_PATH_LENGTH]; char header_path[USBG_MAX_PATH_LENGTH]; int nmb; int ret; nmb = snprintf(path, sizeof(path), "%s/%s/%s", uvcf->func.path, uvcf->func.name, cs); if (nmb >= sizeof(path)) return USBG_ERROR_PATH_TOO_LONG; nmb = snprintf(header_path, sizeof(header_path), "%s/" UVC_PATH_HEADER, path); if (nmb >= sizeof(header_path)) return USBG_ERROR_PATH_TOO_LONG; ret = uvc_create_dir(header_path); if (ret != USBG_SUCCESS) return ret; if (!strncmp(cs, UVC_PATH_STREAMING, strlen(UVC_PATH_STREAMING))) { char check_path[USBG_MAX_PATH_LENGTH]; struct stat buffer; nmb = snprintf(check_path, sizeof(check_path), "%s/" UVC_PATH_STREAMING_UNCOMPRESSED, path); if (nmb >= sizeof(check_path)) return USBG_ERROR_PATH_TOO_LONG; ret = stat(check_path, &buffer); if (!ret) { ret = uvc_link(path, UVC_PATH_STREAMING_UNCOMPRESSED, "header/h/u"); if (ret != USBG_SUCCESS) return ret; } nmb = snprintf(check_path, sizeof(check_path), "%s/" UVC_PATH_STREAMING_MJPEG, path); if (nmb >= sizeof(check_path)) return USBG_ERROR_PATH_TOO_LONG; ret = stat(check_path, &buffer); if (!ret) { ret = uvc_link(path, UVC_PATH_STREAMING_MJPEG, "header/h/m"); if (ret != USBG_SUCCESS) return ret; } ret = uvc_link(path, UVC_PATH_HEADER, UVC_PATH_CLASS_HS); if (ret) return ret; } ret = uvc_link(path, UVC_PATH_HEADER, UVC_PATH_CLASS_FS); if (ret) return ret; return uvc_link(path, UVC_PATH_HEADER, UVC_PATH_CLASS_SS); } #ifdef HAS_GADGET_SCHEMES int usbg_f_uvc_get_nframes(const bool *frames) { int nframes = 0; int i; if (!frames) return USBG_ERROR_INVALID_PARAM; for (i = 0; i < MAX_FRAMES; ++i) nframes += frames[i] ? 1 : 0; return nframes; } int usbg_f_uvc_create_frame(usbg_f_uvc *uvcf, const char* format, bool *frames, int frame_id, struct usbg_f_uvc_frame_attrs *fattrs) { char frame_path[USBG_MAX_PATH_LENGTH]; int nmb; int ret; if (frame_id >= MAX_FRAMES) return USBG_ERROR_INVALID_PARAM; if (frames[frame_id]) return USBG_ERROR_EXIST; nmb = snprintf(frame_path, sizeof(frame_path), "%s/%s/" UVC_PATH_STREAMING "/%s/frame.%d/", uvcf->func.path, uvcf->func.name, format, frame_id); if (nmb >= sizeof(frame_path)) return USBG_ERROR_PATH_TOO_LONG; ret = uvc_create_dir(frame_path); if (ret) return usbg_translate_error(errno); if (fattrs) { ret = usbg_f_uvc_set_frame_attrs(uvcf, format, frame_id, fattrs); if (ret) goto remove_frame; } frames[frame_id] = true; return 0; remove_frame: rmdir(frame_path); return ret; } static int uvc_import_frame_attrs(struct usbg_f_uvc *uvcf, const char* format, int frame_id, config_setting_t *root) { union usbg_f_uvc_frame_attr_val val; int ret = 0; int i; for (i = USBG_F_UVC_FRAME_ATTR_MIN; i < USBG_F_UVC_FRAME_ATTR_MAX; ++i) { ret = uvc_frame_attr[i].import(root, uvc_frame_attr[i].name, &val); /* node not found */ if (ret == 0) continue; /* error */ if (ret < 0) break; ret = usbg_f_uvc_set_frame_attr_val(uvcf, format, frame_id, i, val); if (ret) break; } return ret; } static int uvc_import_format_attrs(struct usbg_f_uvc *uvcf, const char* format, config_setting_t *root) { union usbg_f_uvc_format_attr_val val; int ret = 0; int i; for (i = USBG_F_UVC_FORMAT_ATTR_MIN; i < USBG_F_UVC_FORMAT_ATTR_MAX; ++i) { if (uvc_format_attr[i].ro) continue; ret = uvc_format_attr[i].import(root, uvc_format_attr[i].name, &val); /* node not found */ if (ret == 0) continue; /* error */ if (ret < 0) break; ret = usbg_f_uvc_set_format_attr_val(uvcf, format, i, val); if (ret) break; } return ret; } static int uvc_import_format(struct usbg_f_uvc *uvcf, const char *format, bool *frames, config_setting_t *root) { config_setting_t *frames_node, *node; int i, nframes; int ret = 0; frames_node = config_setting_get_member(root, "frames"); if (!frames_node) { ret = USBG_ERROR_INVALID_PARAM; goto out; } if (!config_setting_is_list(frames_node)) { ret = USBG_ERROR_INVALID_TYPE; goto out; } nframes = config_setting_length(frames_node); for (i = 0; i < nframes; ++i) { node = config_setting_get_elem(frames_node, i); if (!node) { ret = USBG_ERROR_INVALID_FORMAT; goto out; } if (!config_setting_is_group(node)) { ret = USBG_ERROR_INVALID_TYPE; goto out; } if (!frames[i]) { ret = usbg_f_uvc_create_frame(uvcf, format, frames, i, NULL); if (ret) goto out; } ret = uvc_import_frame_attrs(uvcf, format, i, node); if (ret) goto out; } out: return ret; } static int uvc_import_config(struct usbg_f_uvc *uvcf, config_setting_t *root) { union usbg_f_uvc_config_attr_val val; config_setting_t *config_node; int ret = 0; int i; config_node = config_setting_get_member(root, "config"); if (!config_node) { ret = USBG_ERROR_INVALID_PARAM; return ret; } if (!config_setting_is_group(config_node)) { ret = USBG_ERROR_INVALID_TYPE; return ret; } for (i = USBG_F_UVC_CONFIG_ATTR_MIN; i < USBG_F_UVC_CONFIG_ATTR_MAX; ++i) { ret = uvc_config_attr[i].import(config_node, uvc_config_attr[i].name, &val); /* node not found */ if (ret == 0) continue; /* error */ if (ret < 0) break; ret = usbg_f_uvc_set_config_attr_val(uvcf, i, val); if (ret) break; } return ret; } static int uvc_libconfig_import(struct usbg_function *f, config_setting_t *root) { struct usbg_f_uvc *uvcf = usbg_to_uvc_function(f); config_setting_t *formats_node, *node; int nmb, ret, i, nformats; char fp[USBG_MAX_PATH_LENGTH]; const char *format; struct formats *formats; formats_node = config_setting_get_member(root, "formats"); if (!formats_node) { ret = USBG_ERROR_INVALID_PARAM; goto out; } if (!config_setting_is_group(formats_node)) { ret = USBG_ERROR_INVALID_TYPE; goto out; } nformats = config_setting_length(formats_node); formats = get_formats_mask(uvcf); for (i = 0; i < nformats; ++i) { node = config_setting_get_elem(formats_node, i); if (!node) { ret = USBG_ERROR_INVALID_FORMAT; goto out; } if (!config_setting_is_group(node)) { ret = USBG_ERROR_INVALID_TYPE; goto out; } format = config_setting_name(node); nmb = snprintf(fp, sizeof(fp), "%s/%c", format, format[0]); if (nmb >= sizeof(fp)) return USBG_ERROR_PATH_TOO_LONG; ret = uvc_import_format(uvcf, fp, formats[i].frames, node); if (ret) goto out; ret = uvc_import_format_attrs(uvcf, fp, node); if (ret) goto out; } ret = uvc_import_config(uvcf, root); if (ret != USBG_SUCCESS) return ret; ret = uvc_set_class(uvcf, UVC_PATH_CONTROL); if (ret != USBG_SUCCESS) return ret; ret = uvc_set_class(uvcf, UVC_PATH_STREAMING); if (ret != USBG_SUCCESS) return ret; out: return ret; } static int uvc_export_config(struct usbg_f_uvc *uvcf, config_setting_t *root) { config_setting_t *config_node; union usbg_f_uvc_config_attr_val val; int i; int ret = 0; config_node = config_setting_add(root, "config", CONFIG_TYPE_GROUP); if (!config_node) goto out; for (i = USBG_F_UVC_CONFIG_ATTR_MIN; i < USBG_F_UVC_CONFIG_ATTR_MAX; ++i) { ret = usbg_f_uvc_get_config_attr_val(uvcf, i, &val); if (ret) break; ret = uvc_config_attr[i].export(config_node, uvc_config_attr[i].name, &val); if (ret) break; } out: return ret; } static int uvc_export_format_attrs(struct usbg_f_uvc *uvcf, const char *format, config_setting_t *root) { union usbg_f_uvc_format_attr_val val; int ret = 0; int i; for (i = USBG_F_UVC_FORMAT_ATTR_MIN; i < USBG_F_UVC_FORMAT_ATTR_MAX; ++i) { ret = usbg_f_uvc_get_format_attr_val(uvcf, format, i, &val); if (ret) break; ret = uvc_format_attr[i].export(root, uvc_format_attr[i].name, &val); if (ret) break; } if (ret == USBG_ERROR_NOT_FOUND) ret = 0; return ret; } static int uvc_export_frame_attrs(struct usbg_f_uvc *uvcf, const char *format, int frame_id, config_setting_t *root) { union usbg_f_uvc_frame_attr_val val; int ret = 0; int i; for (i = USBG_F_UVC_FRAME_ATTR_MIN; i < USBG_F_UVC_FRAME_ATTR_MAX; ++i) { ret = usbg_f_uvc_get_frame_attr_val(uvcf, format, frame_id, i, &val); if (ret) break; ret = uvc_frame_attr[i].export(root, uvc_frame_attr[i].name, &val); if (ret) break; } if (ret == USBG_ERROR_NOT_FOUND) ret = 0; return ret; } static int uvc_export_frames(struct usbg_f_uvc *uvcf, const char *format, bool *frames, config_setting_t *root) { config_setting_t *frames_node, *node; int i, nframes; int ret = 0; frames_node = config_setting_add(root, "frames", CONFIG_TYPE_LIST); if (!frames_node) { ret = USBG_ERROR_INVALID_FORMAT; goto out; } nframes = usbg_f_uvc_get_nframes(frames); if (nframes < 0) return nframes; for (i = 0; i < nframes; ++i) { node = config_setting_add(frames_node, "", CONFIG_TYPE_GROUP); if (!node) goto out; ret = uvc_export_frame_attrs(uvcf, format, i, node); if (ret) goto out; } out: return ret; } static int uvc_libconfig_export(struct usbg_function *f, config_setting_t *root) { struct usbg_f_uvc *uvcf = usbg_to_uvc_function(f); config_setting_t *formats_node, *node; char fp[USBG_MAX_PATH_LENGTH]; struct formats *formats; int i, ret = 0; char *p; formats_node = config_setting_add(root, "formats", CONFIG_TYPE_GROUP); if (!formats_node) { ret = USBG_ERROR_INVALID_PARAM; goto out; } formats = get_formats_mask(uvcf); for (i = 0; i < MAX_FORMATS; ++i) { /* remove trailing /m and /u from format */ strcpy(fp, format_names[i]); p = fp + strlen(fp) - 2; *p = '\0'; node = config_setting_add(formats_node, fp, CONFIG_TYPE_GROUP); if (!node) goto out; ret = uvc_export_frames(uvcf, format_names[i], formats[i].frames, node); if (ret) goto out; ret = uvc_export_format_attrs(uvcf, format_names[i], node); if (ret) goto out; } ret = uvc_export_config(uvcf, root); out: return ret; } #endif static int uvc_set_format(char *format_path, const char *format, const struct usbg_f_uvc_format_attrs *attrs) { return usbg_write_dec(format_path, format, "bDefaultFrameIndex", attrs->bDefaultFrameIndex); } static int uvc_set_frame(char *format_path, const char *format, const struct usbg_f_uvc_frame_attrs *attrs) { char frame_path[USBG_MAX_PATH_LENGTH]; char full_frame_path[USBG_MAX_PATH_LENGTH]; char frame_name[32]; int nmb, ret; nmb = snprintf(frame_name, sizeof(frame_name), "frame.%d", attrs->bFrameIndex); if (nmb >= sizeof(frame_name)) return USBG_ERROR_PATH_TOO_LONG; nmb = snprintf(frame_path, sizeof(frame_path), "%s/%s", format_path, format); if (nmb >= sizeof(frame_path)) return USBG_ERROR_PATH_TOO_LONG; nmb = snprintf(full_frame_path, sizeof(frame_path), "%s/%s", frame_path, frame_name); if (nmb >= sizeof(full_frame_path)) return USBG_ERROR_PATH_TOO_LONG; ret = uvc_create_dir(full_frame_path); if (ret != USBG_SUCCESS) return ret; ret = usbg_write_dec(frame_path, frame_name, "dwFrameInterval", attrs->dwFrameInterval); if (ret != USBG_SUCCESS) return ret; ret = usbg_write_dec(frame_path, frame_name, "dwDefaultFrameInterval", attrs->dwDefaultFrameInterval); if (ret != USBG_SUCCESS) return ret; ret = usbg_write_dec(frame_path, frame_name, "dwMaxVideoFrameBufferSize", attrs->dwMaxVideoFrameBufferSize); if (ret != USBG_SUCCESS) return ret; ret = usbg_write_dec(frame_path, frame_name, "dwMinBitRate", attrs->dwMinBitRate); if (ret != USBG_SUCCESS) return ret; ret = usbg_write_dec(frame_path, frame_name, "dwMaxBitRate", attrs->dwMaxBitRate); if (ret != USBG_SUCCESS) return ret; ret = usbg_write_dec(frame_path, frame_name, "wHeight", attrs->wHeight); if (ret != USBG_SUCCESS) return ret; return usbg_write_dec(frame_path, frame_name, "wWidth", attrs->wWidth); } static int uvc_set_streaming(char *func_path, const char *format, const struct usbg_f_uvc_format_attrs *attrs) { struct usbg_f_uvc_frame_attrs **frame_attrs; char streaming_path[USBG_MAX_PATH_LENGTH]; int nmb, ret, i; nmb = snprintf(streaming_path, sizeof(streaming_path), "%s/" UVC_PATH_STREAMING, func_path); if (nmb >= sizeof(streaming_path)) return USBG_ERROR_PATH_TOO_LONG; for(frame_attrs = attrs->frames, i = 0; frame_attrs[i]; ++i) { if (frame_attrs[i]) { ret = uvc_set_frame(streaming_path, format, frame_attrs[i]); if(ret != USBG_SUCCESS) ERROR("Error: %d", ret); } } ret = uvc_set_format(streaming_path, format, attrs); if(ret != USBG_SUCCESS) ERROR("Error: %d", ret); return ret; } static int dir_nftw_cb(const char *pathname, const struct stat *sbuf, int type, struct FTW *ftwb) { (void) sbuf; (void) type; (void) ftwb; int ret; ret = remove(pathname); if (ret < -1) ERROR("failed to remove %s - %s", pathname, strerror(ret)); return 0; } int remove_dir(const char *dirpath) { const int max_open_descs = 8; int ret; ret = nftw(dirpath, dir_nftw_cb, max_open_descs, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); if (ret < 0) { ERROR("nftw failed"); return ret; } return 0; } static int content_nftw_cb(const char *pathname, const struct stat *sbuf, int type, struct FTW *ftwb) { (void) sbuf; (void) type; (void) ftwb; int ret; if(ftwb->level == 0) return 0; ret = remove(pathname); if(ret < -1) ERROR("failed to remove %s - %s", pathname, strerror(ret)); return 0; } int remove_dir_content(const char *dirpath) { const int max_open_descs = 8; int ret; /* traverse in reverse order (handle directory after it's content), stay within the same file system and do not follow symbolic links */ ret = nftw(dirpath, content_nftw_cb, max_open_descs, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); if (ret < 0) { ERROR("nftw failed"); return ret; } return 0; } static int uvc_remove(struct usbg_function *f, int opts) { usbg_f_uvc *uvcf = usbg_to_uvc_function(f); char streaming_path[USBG_MAX_PATH_LENGTH]; char control_path[USBG_MAX_PATH_LENGTH]; char path[USBG_MAX_PATH_LENGTH]; int nmb, ret = USBG_SUCCESS; nmb = snprintf(path, sizeof(path), "%s/%s", uvcf->func.path, uvcf->func.name); if (nmb >= sizeof(path)) return USBG_ERROR_PATH_TOO_LONG; nmb = snprintf(streaming_path, sizeof(streaming_path), "%s/streaming", path); if (nmb >= sizeof(streaming_path)) return USBG_ERROR_PATH_TOO_LONG; nmb = snprintf(control_path, sizeof(control_path), "%s/control", path); if (nmb >= sizeof(control_path)) return USBG_ERROR_PATH_TOO_LONG; if(remove_dir_content(streaming_path) < 0) return USBG_ERROR_PATH_TOO_LONG; if(remove_dir_content(control_path) < 0) return USBG_ERROR_PATH_TOO_LONG; if(remove_dir(streaming_path) < 0) return USBG_ERROR_PATH_TOO_LONG; if(remove_dir(control_path) < 0) return USBG_ERROR_PATH_TOO_LONG; return ret; } struct usbg_function_type usbg_f_type_uvc = { .name = "uvc", .alloc_inst = uvc_alloc_inst, .free_inst = uvc_free_inst, .set_attrs = uvc_set_attrs, .get_attrs = uvc_get_attrs, .cleanup_attrs = uvc_cleanup_attrs, #ifdef HAS_GADGET_SCHEMES .import = uvc_libconfig_import, .export = uvc_libconfig_export, #endif .remove = uvc_remove, }; /* API implementation */ usbg_f_uvc *usbg_to_uvc_function(usbg_function *f) { return f->ops == &usbg_f_type_uvc ? container_of(f, struct usbg_f_uvc, func) : NULL; } usbg_function *usbg_from_uvc_function(usbg_f_uvc *ff) { return &ff->func; } int usbg_f_uvc_get_attrs(usbg_f_uvc *uvcf, struct usbg_f_uvc_attrs *attrs) { return USBG_SUCCESS; } int usbg_f_uvc_set_attrs(usbg_f_uvc *uvcf, const struct usbg_f_uvc_attrs *attrs) { char path[USBG_MAX_PATH_LENGTH]; struct usbg_f_uvc_format_attrs **format_attrs; int ret = USBG_SUCCESS; int nmb, i; if (!attrs) return USBG_ERROR_INVALID_PARAM; nmb = snprintf(path, sizeof(path), "%s/%s", uvcf->func.path, uvcf->func.name); if (nmb >= sizeof(path)) return USBG_ERROR_PATH_TOO_LONG; for(format_attrs = attrs->formats, i = 0; format_attrs[i]; ++i) { ret = uvc_set_streaming(path, format_attrs[i]->format, format_attrs[i]); if(ret != USBG_SUCCESS) ERROR("Error: %d", ret); } ret = uvc_set_class(uvcf, UVC_PATH_CONTROL); if (ret != USBG_SUCCESS) return ret; ret = uvc_set_class(uvcf, UVC_PATH_STREAMING); if (ret != USBG_SUCCESS) return ret; return ret; }