diff options
-rw-r--r-- | CMakeLists.txt | 76 | ||||
-rw-r--r-- | hal-backend-device-common.pc.in | 10 | ||||
-rw-r--r-- | include/hal-common-interface.h | 39 | ||||
-rw-r--r-- | packaging/hal-backend-device-common.spec | 30 | ||||
-rw-r--r-- | src/usb_gadget/usb_cfs_client_common.c | 642 | ||||
-rw-r--r-- | src/usb_gadget/usb_client_common.c | 481 | ||||
-rw-r--r-- | src/usb_gadget/usb_gadget.h | 26 | ||||
-rw-r--r-- | src/usb_gadget/usb_gadget_common.c | 396 |
8 files changed, 1610 insertions, 90 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e46af6..8fbc524 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,77 +1,29 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.6) -PROJECT(device-node C) +PROJECT(hal-backend-device-common C) -SET(PREFIX ${CMAKE_INSTALL_PREFIX}) -SET(EXEC_PREFIX "${PREFIX}/bin") -SET(LIBDIR ${LIB_INSTALL_DIR}) -SET(INCLUDEDIR "${PREFIX}/include/${PROJECT_NAME}") -SET(VERSION 0.1) - -SET(INC_DIR include) -INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}) -INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/${INC_DIR}) +SET(SRCS + src/usb_gadget/usb_gadget_common.c + src/usb_gadget/usb_client_common.c + src/usb_gadget/usb_cfs_client_common.c +) INCLUDE(FindPkgConfig) -pkg_check_modules(rpkgs REQUIRED dlog vconf glib-2.0 libsystemd libusbgx libsyscommon) +pkg_check_modules(hal-backend-device-common_pkgs REQUIRED libusbgx libsyscommon) -FOREACH(flag ${rpkgs_CFLAGS}) +FOREACH(flag ${hal-backend-device-common_pkgs_CFLAGS}) SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") ENDFOREACH(flag) -SET(SRCS - src/device-node.c - src/device-plugin.c) - -SET(HEADERS - include/device-node.h - include/devman_plugin_intf.h) - -INCLUDE(devices/CMakeLists.txt) - SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden") -SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -g") -SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -Werror -Wl,-zdefs") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") -ADD_DEFINITIONS("-DLIBPATH=\"${LIB_INSTALL_DIR}\"") - -ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRCS} ${TARGET_SRCS}) -TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${rpkgs_LDFLAGS} "-ldl") -SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES SOVERSION ${VERSION}) -INSTALL(TARGETS ${PROJECT_NAME} DESTINATION ${LIB_INSTALL_DIR} COMPONENT RuntimeLibraries) - -ADD_LIBRARY(hwcommon SHARED hw/common.c hw/usb_client_common.c hw/usb_cfs_client_common.c hw/usb_gadget_common.c) -TARGET_LINK_LIBRARIES(hwcommon ${rpkgs_LDFLAGS} "-ldl") -SET_TARGET_PROPERTIES(hwcommon PROPERTIES SOVERSION ${VERSION}) -INSTALL(TARGETS hwcommon DESTINATION ${LIB_INSTALL_DIR} COMPONENT RuntimeLibraries) - -CONFIGURE_FILE(${PROJECT_NAME}.pc.in ${PROJECT_NAME}.pc @ONLY) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) - -# will be deprecated -CONFIGURE_FILE(devman_plugin.pc.in devman_plugin.pc @ONLY) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/devman_plugin.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) - -FOREACH(hfile ${HEADERS}) - INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${hfile} DESTINATION include/${PROJECT_NAME}) -ENDFOREACH(hfile) - -INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hw/ DESTINATION include/hw - FILES_MATCHING - PATTERN "*.h") +ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRCS}) -CONFIGURE_FILE(hwcommon.pc.in hwcommon.pc @ONLY) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hwcommon.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${hal-backend-device-common_pkgs_LDFLAGS}) -# dummy mtp responder -INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mtp-responder-dummy/strs DESTINATION /etc/mtp-responder-dummy) -INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mtp-responder-dummy/descs DESTINATION /etc/mtp-responder-dummy) -INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mtp-responder-dummy/mtp-responder-dummy.socket DESTINATION /usr/lib/systemd/system) -INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mtp-responder-dummy/mtp-responder-dummy.service DESTINATION /usr/lib/systemd/system) +INSTALL(TARGETS ${PROJECT_NAME} DESTINATION /hal/lib/ COMPONENT RuntimeLibraries) -ADD_SUBDIRECTORY(battery-plugin) -ADD_DEPENDENCIES(battery-plugin hwcommon) +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/hal-common-interface.h DESTINATION include/hal/device) -IF(BUILD_GTESTS STREQUAL on) - ADD_SUBDIRECTORY(unittest) -ENDIF() +CONFIGURE_FILE(hal-backend-device-common.pc.in hal-backend-device-common.pc @ONLY) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hal-backend-device-common.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) diff --git a/hal-backend-device-common.pc.in b/hal-backend-device-common.pc.in index a718e24..08ee2fb 100644 --- a/hal-backend-device-common.pc.in +++ b/hal-backend-device-common.pc.in @@ -1,12 +1,6 @@ # Package Information for pkg-config -prefix=@PREFIX@ -exec_prefix=@EXEC_PREFIX@ -libdir=@LIBDIR@ -includedir=@INCLUDEDIR@ Name: HAL backend device common Description: Library for HAL backend device modules -Version: @VERSION@ -Requires.private: glib-2.0 -Libs: -L${libdir} -lhal-backend-device-common -Cflags: -I${includedir} +Version: 1.1 +Libs: -L/hal/lib -Wl,-rpath,/hal/lib -lhal-backend-device-common diff --git a/include/hal-common-interface.h b/include/hal-common-interface.h new file mode 100644 index 0000000..662efb4 --- /dev/null +++ b/include/hal-common-interface.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef __HAL_COMMON_H_ +#define __HAL_COMMON_H_ + +#include <hal/device/hal-usb_gadget-interface.h> + +#ifdef __cplusplus +extern "C" { +#endif + +int simple_translator_open(hal_backend_usb_gadget_funcs *usb_gadget_funcs); +int simple_translator_close(hal_backend_usb_gadget_funcs *usb_gadget_funcs); + +int hw_legacy_gadget_open(hal_backend_usb_gadget_funcs *usb_gadget_funcs); +int hw_legacy_gadget_close(hal_backend_usb_gadget_funcs *usb_gadget_funcs); + +int hw_cfs_gadget_open(hal_backend_usb_gadget_funcs *usb_gadget_funcs); +int hw_cfs_gadget_close(hal_backend_usb_gadget_funcs *usb_gadget_funcs); + +#ifdef __cplusplus +} +#endif +#endif /* __HAL_BOARD_INTERFACE_H__ */ diff --git a/packaging/hal-backend-device-common.spec b/packaging/hal-backend-device-common.spec index 6bcf9c8..6d6ce69 100644 --- a/packaging/hal-backend-device-common.spec +++ b/packaging/hal-backend-device-common.spec @@ -1,5 +1,5 @@ Name: hal-backend-device-common -Summary: HAL backend device common +Summary: HAL backend device common Version: 1 Release: 1 Group: System/API @@ -7,16 +7,14 @@ License: Apache-2.0 Source0: %{name}-%{version}.tar.gz Source1: %{name}.manifest BuildRequires: cmake -BuildRequires: pkgconfig(vconf) -BuildRequires: pkgconfig(dlog) -BuildRequires: pkgconfig(glib-2.0) -BuildRequires: pkgconfig(gio-2.0) -BuildRequires: pkgconfig(gmock) -BuildRequires: pkgconfig(capi-system-info) -BuildRequires: pkgconfig(libsystemd) BuildRequires: pkgconfig(libusbgx) +BuildRequires: pkgconfig(hal-api-device) + BuildRequires: pkgconfig(libsyscommon) +# TODO remove later. It's temporary patch +BuildRequires: pkgconfig(dlog) + %description Library for HAL backend device modules @@ -30,29 +28,21 @@ Library for HAL backend device modules (devel) %prep %setup -q cp %{SOURCE1} . + %build +%cmake . make %{?jobs:-j%jobs} %install %make_install -%post -p /sbin/ldconfig -%postun -p /sbin/ldconfig - %files %manifest %{name}.manifest %license LICENSE.Apache-2.0 -%{_libdir}/*.so.* -%{_libdir}/libbattery-plugin.so -%{_unitdir}/mtp-responder-dummy.socket -%{_unitdir}/mtp-responder-dummy.service -/etc/mtp-responder-dummy/strs -/etc/mtp-responder-dummy/descs +/hal/lib/libhal-backend-device-common.so %files devel %manifest %{name}.manifest %license LICENSE.Apache-2.0 -%{_includedir}/device-node/*.h -%{_includedir}/hw/*.h -%{_libdir}/*.so +%{_includedir}/hal/device/hal-common-interface.h %{_libdir}/pkgconfig/*.pc diff --git a/src/usb_gadget/usb_cfs_client_common.c b/src/usb_gadget/usb_cfs_client_common.c new file mode 100644 index 0000000..89ccd32 --- /dev/null +++ b/src/usb_gadget/usb_cfs_client_common.c @@ -0,0 +1,642 @@ +/* + * libdevice-node + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/mount.h> +#include <linux/limits.h> + +#include <usbg/usbg.h> +#include <usbg/function/net.h> + +#include <libsyscommon/dbus-systemd.h> +#include <hal/device/hal-usb_gadget-interface.h> + +#include "usb_gadget.h" + +#define CONFIGFS_PATH "/sys/kernel/config" + +#define CONFIGFS_GADGET_NAME "hal-gadget" +#define CONFIGFS_CONFIG_LABEL "hal-config" + +#define NAME_INSTANCE_SEP '.' +#define MAX_INSTANCE_LEN 512 + +#define USB_FUNCS_PATH "/dev/usb-funcs" + +#ifndef EXPORT +#define EXPORT __attribute__ ((visibility("default"))) +#endif + +enum cfs_function_service_operation { + CFS_FUNCTION_SERVICE_START, + CFS_FUNCTION_SERVICE_STOP, + CFS_FUNCTION_SERVICE_POST_STOP, +}; + +struct cfs_client { + usbg_state *ctx; + usbg_gadget *gadget; + usbg_udc *udc; +}; + +/* Based on values in slp-gadget kernel module */ +struct usbg_gadget_attrs default_g_attrs = { + .idVendor = DEFAULT_VID, + .idProduct = DEFAULT_PID, + .bcdDevice = DEFAULT_BCD_DEVICE, +}; + +struct usbg_gadget_strs default_g_strs = { + .manufacturer = DEFAULT_MANUFACTURER, + .product = DEFAULT_PRODUCT, + .serial = DEFAULT_SERIAL, +}; + +struct cfs_client *g_cfs_client; + +static struct usb_function *cfs_find_usb_function(usbg_function *function) +{ + char *sep; + char buf[MAX_INSTANCE_LEN]; + const char *instance = usbg_get_function_instance(function); + const char *name = usbg_get_function_type_str(usbg_get_function_type(function)); + + /* Ex. name:"ffs", instance: "sdb.default" */ + if (strcmp(name, usbg_get_function_type_str(USBG_F_FFS)) == 0) { + strncpy(buf, instance, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + + /* Ex. "sdb.default" ==> "sdb" + "default" */ + sep = strchr(buf, NAME_INSTANCE_SEP); + if (!sep || !sep[1]) + return NULL; + *sep = '\0'; + + name = buf; + instance = sep + 1; + } + + return find_usb_function_by_name_instance(name, instance); +} + +static bool cfs_is_function_supported(struct usb_function *func) +{ + char buf[PATH_MAX]; + + if (func->is_functionfs) { + /* functionfs must have a service */ + if (!func->service) + return false; + + snprintf(buf, sizeof(buf), "/usr/lib/systemd/system/%s.socket", func->service); + if (access(buf, F_OK)) + return false; + } else { + if (usbg_lookup_function_type(func->name) < 0) + return false; + } + + return true; +} + +static bool cfs_is_gadget_supported(struct usb_gadget *gadget) +{ + int i, j; + struct usb_configuration *config; + + if (!gadget || !gadget->configs || !gadget->configs[0] || !gadget->configs[0]->funcs[0]) + return false; + + if (!gadget->attrs.idVendor || !gadget->attrs.idProduct || !gadget->attrs.bcdDevice) + return false; + + /* only strings in US_en are allowed */ + if (gadget->strs.lang_code != DEFAULT_LANG) + return false; + + if (!gadget->strs.manufacturer || !gadget->strs.product || !gadget->strs.serial) + return false; + + for (j = 0; gadget->configs[j]; ++j) { + config = gadget->configs[j]; + + if (!config->funcs) + return false; + + for (i = 0; config->funcs[i]; ++i) + if (!cfs_is_function_supported(config->funcs[i])) + return false; + } + + return true; +} + +static int cfs_set_gadget_attrs(struct cfs_client *cfs_client, + struct usb_gadget_attrs *attrs) +{ + int ret; + struct usbg_gadget_attrs gadget_attrs; + + ret = usbg_get_gadget_attrs(cfs_client->gadget, &gadget_attrs); + if (ret) + return ret; + + gadget_attrs.bDeviceClass = attrs->bDeviceClass; + gadget_attrs.bDeviceSubClass = attrs->bDeviceSubClass; + gadget_attrs.bDeviceProtocol = attrs->bDeviceProtocol; + gadget_attrs.idVendor = attrs->idVendor; + gadget_attrs.idProduct = attrs->idProduct; + gadget_attrs.bcdDevice = attrs->bcdDevice; + + ret = usbg_set_gadget_attrs(cfs_client->gadget, &gadget_attrs); + + return ret; +} + +static int cfs_set_gadget_strs(struct cfs_client *cfs_client, struct usb_gadget_strings *strs) +{ + int ret; + + if (!strs->manufacturer || !strs->product || !strs->serial) + return -EINVAL; + + ret = usbg_set_gadget_str(cfs_client->gadget, USBG_STR_MANUFACTURER, strs->lang_code, strs->manufacturer); + if (ret) + return ret; + + ret = usbg_set_gadget_str(cfs_client->gadget, USBG_STR_PRODUCT, strs->lang_code, strs->product); + if (ret) + return ret; + + ret = usbg_set_gadget_str(cfs_client->gadget, USBG_STR_SERIAL_NUMBER, strs->lang_code, strs->serial); + if (ret) + return ret; + + return 0; +} + +static int cfs_ensure_dir(char *path) +{ + if (mkdir(path, 0770) < 0) + return (errno == EEXIST) ? 0 : -errno; + + return 0; +} + + +static int cfs_prep_ffs_service(struct usb_function *usb_func, usbg_function *function) +{ + int ret; + const char *name; + const char *service; + const char *instance; + const char *dev_name; + char buf[MAX_INSTANCE_LEN]; + + if (!usb_func || !function) + return -EINVAL; + + if (usbg_get_function_type(function) != USBG_F_FFS) + return -EINVAL; + + name = usb_func->name; + service = usb_func->service; + instance = usb_func->instance; + dev_name = usbg_get_function_instance(function); + + /* "/dev/usb-funcs" + "/" + "sdb" + "/" + "default" + '0' */ + if (strlen(USB_FUNCS_PATH) + strlen(name) + strlen(instance) + 3 > sizeof(buf)) + return -ENAMETOOLONG; + + /* mkdir /dev/usb-funcs */ + ret = cfs_ensure_dir(USB_FUNCS_PATH); + if (ret < 0) + return ret; + + /* mkdir /dev/usb-funcs/sdb */ + snprintf(buf, sizeof(buf), "%s/%s", USB_FUNCS_PATH, name); + ret = cfs_ensure_dir(buf); + if (ret < 0) + goto out_rmdir; + + /* mkdir /dev/usb-funcs/sdb/default */ + snprintf(buf, sizeof(buf), "%s/%s/%s", USB_FUNCS_PATH, name, instance); + ret = cfs_ensure_dir(buf); + if (ret < 0) + goto out_rmdir; + + /* mount -t functionfs sdb.default /dev/usb-funcs/sdb/default */ + ret = mount(dev_name, buf, "functionfs", 0, NULL); + if (ret < 0) + goto out_rmdir; + + /* start sdbd.socket */ + ret = systemd_start_unit_wait_started(service, ".socket", -1); + if (ret < 0) + goto out_unmount; + + return 0; + +out_unmount: + umount(buf); + +out_rmdir: + snprintf(buf, sizeof(buf), "%s/%s/%s", USB_FUNCS_PATH, name, instance); + rmdir(buf); + + snprintf(buf, sizeof(buf), "%s/%s", USB_FUNCS_PATH, name); + rmdir(buf); + + rmdir(USB_FUNCS_PATH); + + return ret; +} + +static int cfs_cleanup_ffs_service(usbg_function *function) +{ + int ret; + char buf[MAX_INSTANCE_LEN]; + struct usb_function *usb_function; + + if (!function) + return -EINVAL; + + usb_function = cfs_find_usb_function(function); + if (!usb_function) + return -ENOENT; + + /* stop .socket first and stop .service later becuase of socket activation */ + if (usb_function->service) { + (void)systemd_stop_unit_wait_stopped(usb_function->service, ".socket", -1); + (void)systemd_stop_unit_wait_stopped(usb_function->service, ".service", -1); + } + + /* umount /dev/usb-funcs/[sdb|mtp]/default and remove it's directory */ + ret = snprintf(buf, sizeof(buf), "%s/%s/%s", USB_FUNCS_PATH, usb_function->name, usb_function->instance); + if (ret < 0) + return ret; + + ret = umount(buf); + if (ret < 0) + return ret; + + ret = rmdir(buf); + if (ret < 0) + return ret; + + /* remove /dev/usb-funcs/[sdb|mtp] directory */ + ret = snprintf(buf, sizeof(buf), "%s/%s", USB_FUNCS_PATH, usb_function->name); + if (ret < 0) + return ret; + + ret = rmdir(buf); + if (ret < 0 && errno != ENOTEMPTY) + return ret; + + /* remove /dev/usb-funcs/ directory */ + ret = rmdir(USB_FUNCS_PATH); + if (ret < 0 && errno != ENOTEMPTY) + return ret; + + return 0; +} + + +static int cfs_set_rndis_mac_addr(usbg_gadget *gadget, usbg_function *func) +{ + int i, ret; + struct ether_addr ethaddr; + struct usbg_gadget_strs strs; + struct usbg_f_net *nf = usbg_to_net_function(func); + + if (!nf) + return -EINVAL; + + ret = usbg_get_gadget_strs(gadget, LANG_US_ENG, &strs); + if (ret != USBG_SUCCESS) + return ret; + + for (i = 0; i < ETHER_ADDR_LEN; i++) + ethaddr.ether_addr_octet[i] = 0; + + for (i = 0; (i < 256) && strs.serial[i]; i++) { + ethaddr.ether_addr_octet[i % (ETHER_ADDR_LEN - 1) + 1] ^= strs.serial[i]; + } + ethaddr.ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ + ethaddr.ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ + + usbg_free_gadget_strs(&strs); + + /* host_addr changes mac address */ + ret = usbg_f_net_set_host_addr(nf, ðaddr); + + return ret; +} + +static int cfs_cleanup_all_config_and_function(struct cfs_client *cfs_client) +{ + int ret; + usbg_config *config; + usbg_function *function; + + /* delete all configs */ +restart_rm_config: + usbg_for_each_config(config, cfs_client->gadget) { + ret = usbg_rm_config(config, USBG_RM_RECURSE); + if (ret) + return ret; + + goto restart_rm_config; /* You cannot delete a config directly in an iterator. */ + } + + /* delete all functions */ +restart_rm_function: + usbg_for_each_function(function, cfs_client->gadget) { + if (usbg_get_function_type(function) == USBG_F_FFS) { + ret = cfs_cleanup_ffs_service(function); + if (ret) + return ret; + } + + ret = usbg_rm_function(function, USBG_RM_RECURSE); + if (ret) + return ret; + + goto restart_rm_function; /* You cannot delete a function directly in an iterator. */ + } + + return 0; +} + +static int cfs_set_gadget_config(struct cfs_client *cfs_client, int config_id, struct usb_configuration *usb_config) +{ + int i; + int ret; + int function_type; + usbg_config *config; + usbg_function *function; + struct usb_function *usb_func; + char instance[MAX_INSTANCE_LEN]; + struct usbg_config_attrs cattrs = { + .bmAttributes = usb_config->attrs.bmAttributs, + .bMaxPower = usb_config->attrs.MaxPower/2, + }; + + if (!usb_config->funcs || !usb_config->funcs[0]) + return -EINVAL; + + ret = usbg_create_config(cfs_client->gadget, config_id, CONFIGFS_CONFIG_LABEL, &cattrs, NULL, &config); + if (ret) + return ret; + + if (usb_config->strs.config_str) { + ret = usbg_set_config_string(config, usb_config->strs.lang_code, usb_config->strs.config_str); + if (ret) + return ret; + } + + for (i = 0; usb_config->funcs[i]; ++i) { + usb_func = usb_config->funcs[i]; + + /* name("sdb") + NAME_INSTANCE_SEP(".") + instance("default") + '\0' */ + if (strlen(usb_func->name) + strlen(usb_func->instance) + 2 > sizeof(instance)) + return -ENAMETOOLONG; + + /* In functionfs, the instance is used in the format "[sdb|mtp].default" instead of "default" */ + if (usb_func->is_functionfs) { + function_type = USBG_F_FFS; + snprintf(instance, sizeof(instance), "%s%c%s", usb_func->name, NAME_INSTANCE_SEP, usb_func->instance); + } else { + function_type = usbg_lookup_function_type(usb_func->name); + strncpy(instance, usb_func->instance, sizeof(instance) - 1); + instance[sizeof(instance) - 1] = '\0'; + } + + function = usbg_get_function(cfs_client->gadget, function_type, instance); + if (!function) { + ret = usbg_create_function(cfs_client->gadget, function_type, instance, NULL, &function); + if (ret) + return ret; + + /* Setting rndis mac address. This should be done at this point, + * since the node host_addr changes to read only after the function + * is added to config. */ + if (usbg_get_function_type(function) == USBG_F_RNDIS) + (void)cfs_set_rndis_mac_addr(cfs_client->gadget, function); /* A random value is used if fails */ + + if (usbg_get_function_type(function) == USBG_F_FFS) { + ret = cfs_prep_ffs_service(usb_func, function); + if (ret) + return ret; + } + } + + ret = usbg_add_config_function(config, NULL, function); + if (ret) + return ret; + } + + return 0; +} + +static int cfs_reconfigure_gadget(struct usb_gadget *gadget) +{ + int i; + int ret; + + if (!g_cfs_client || !gadget) + return -EINVAL; + + /* Verify the gadget and check if function is supported */ + if (!cfs_is_gadget_supported(gadget)) + return -ENOTSUP; + + ret = cfs_set_gadget_attrs(g_cfs_client, &gadget->attrs); + if (ret) + return ret; + + ret = cfs_set_gadget_strs(g_cfs_client, &gadget->strs); + if (ret) + return ret; + + ret = cfs_cleanup_all_config_and_function(g_cfs_client); + if (ret) + return ret; + + for (i = 0; gadget->configs[i]; ++i) { + ret = cfs_set_gadget_config(g_cfs_client, i + 1, gadget->configs[i]); + if (ret) + return ret; + } + + return 0; +} + +static void cfs_start_stop_service_and_handler(usbg_gadget *gadget, enum cfs_function_service_operation operation) +{ + usbg_function *function; + struct usb_function *usb_function; + + usbg_for_each_function(function, gadget) { + usb_function = cfs_find_usb_function(function); + if (!usb_function) + continue; + + switch(operation) { + case CFS_FUNCTION_SERVICE_START: + if (usb_function->handler) + usb_function->handler(1); + + /* functionfs service is automatically started by socket activation */ + if (!usb_function->is_functionfs && usb_function->service) + (void)systemd_start_unit_wait_started(usb_function->service, ".service", -1); + break; + + case CFS_FUNCTION_SERVICE_STOP: + if (!usb_function->is_functionfs && usb_function->service) + (void)systemd_stop_unit_wait_stopped(usb_function->service, ".service", -1); + + if (usb_function->handler) + usb_function->handler(0); + break; + + case CFS_FUNCTION_SERVICE_POST_STOP: + if (usb_function->is_functionfs && usb_function->service) + (void)systemd_stop_unit_wait_stopped(usb_function->service, ".service", -1); + break; + + default: + break; + } + } +} + +static int cfs_enable(void) +{ + int ret; + + if (!g_cfs_client) + return -EINVAL; + + ret = usbg_enable_gadget(g_cfs_client->gadget, g_cfs_client->udc); + if (ret) + return ret; + + cfs_start_stop_service_and_handler(g_cfs_client->gadget, CFS_FUNCTION_SERVICE_START); + + return 0; +} + +static int cfs_disable(void) +{ + int ret; + + if (!g_cfs_client) + return -EINVAL; + + cfs_start_stop_service_and_handler(g_cfs_client->gadget, CFS_FUNCTION_SERVICE_STOP); + + ret = usbg_disable_gadget(g_cfs_client->gadget); /* ignore error checking */ + + /* + * Since functionfs service works with socket activation, you must stop it after disabling gadget. + * If usb data may come in after stopping functionfs service and before disabling gadget, + * functionfs service wakes up again by socket activation. + */ + cfs_start_stop_service_and_handler(g_cfs_client->gadget, CFS_FUNCTION_SERVICE_POST_STOP); + + return ret; +} + +EXPORT +int hw_cfs_gadget_open(hal_backend_usb_gadget_funcs *usb_gadget_funcs) +{ + int ret; + + if (!usb_gadget_funcs) + return -EINVAL; + + g_cfs_client = calloc(1, sizeof(*g_cfs_client)); + if (!g_cfs_client) + return -ENOMEM; + + ret = usbg_init(CONFIGFS_PATH, &g_cfs_client->ctx); + if (ret) + goto err_usbg_init; + + g_cfs_client->udc = usbg_get_first_udc(g_cfs_client->ctx); + if (!g_cfs_client->udc) { + ret = -ENODEV; + goto err_no_udc; + } + + ret = usbg_create_gadget(g_cfs_client->ctx, CONFIGFS_GADGET_NAME, + &default_g_attrs, &default_g_strs, &g_cfs_client->gadget); + if (ret) + goto err_create_gadget; + + usb_gadget_funcs->enable = cfs_enable; + usb_gadget_funcs->disable = cfs_disable; + usb_gadget_funcs->reconfigure_gadget = cfs_reconfigure_gadget; + + return 0; + +err_create_gadget: +err_no_udc: + usbg_cleanup(g_cfs_client->ctx); +err_usbg_init: + free(g_cfs_client); + + return ret; +} + +EXPORT +int hw_cfs_gadget_close(hal_backend_usb_gadget_funcs *usb_gadget_funcs) +{ + usbg_function *function; + struct usb_function *usb_func; + + if (!usb_gadget_funcs || !g_cfs_client) + return -EINVAL; + + usbg_for_each_function(function, g_cfs_client->gadget) { + usb_func = cfs_find_usb_function(function); + if (!usb_func) + continue; + + if (usb_func->is_functionfs && usb_func->service) { + (void)systemd_stop_unit_wait_stopped(usb_func->service, ".socket", -1); + (void)systemd_stop_unit_wait_stopped(usb_func->service, ".service", -1); + } + } + + /* + * For now we don't check for errors + * but we should somehow handle them + */ + usbg_rm_gadget(g_cfs_client->gadget, USBG_RM_RECURSE); + usbg_cleanup(g_cfs_client->ctx); + free(g_cfs_client); + + return 0; +} + diff --git a/src/usb_gadget/usb_client_common.c b/src/usb_gadget/usb_client_common.c new file mode 100644 index 0000000..f3f49f1 --- /dev/null +++ b/src/usb_gadget/usb_client_common.c @@ -0,0 +1,481 @@ +/* + * libdevice-node + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdbool.h> +#include <linux/limits.h> + +#include <libsyscommon/dbus-systemd.h> +#include <hal/device/hal-usb_gadget-interface.h> + +#include "usb_gadget.h" + +#define MAX_GADGET_STR_LEN 256 + +#define LEGACY_ROOTPATH "/sys/class/usb_mode/usb0" + +/* Device descriptor values */ +#define LEGACY_ID_VENDOR_PATH LEGACY_ROOTPATH"/idVendor" +#define LEGACY_ID_PRODUCT_PATH LEGACY_ROOTPATH"/idProduct" +#define LEGACY_BCD_DEVICE_PATH LEGACY_ROOTPATH"/bcdDevice" +#define LEGACY_CLASS_PATH LEGACY_ROOTPATH"/bDeviceClass" +#define LEGACY_SUBCLASS_PATH LEGACY_ROOTPATH"/bDeviceSubClass" +#define LEGACY_PROTOCOL_PATH LEGACY_ROOTPATH"/bDeviceProtocol" + +/* Strings */ +#define LEGACY_IMANUFACTURER_PATH LEGACY_ROOTPATH"/iManufacturer" +#define LEGACY_IPRODUCT_PATH LEGACY_ROOTPATH"/iProduct" + +/* Functions in each config */ +#define LEGACY_CONFIG_1_PATH LEGACY_ROOTPATH"/funcs_fconf" +#define LEGACY_CONFIG_2_PATH LEGACY_ROOTPATH"/funcs_sconf" + +/* should be single char */ +#define LEGACY_FUNC_SEP "," + +/* ON/OFF switch */ +#define LEGACY_ENABLE_PATH LEGACY_ROOTPATH"/enable" +#define LEGACY_ENABLE "1" +#define LEGACY_DISABLE "0" + +#ifndef EXPORT +#define EXPORT __attribute__ ((visibility("default"))) +#endif + +// TODO move to common code +#if 1 +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#define SHARED_H_BUF_MAX 255 + +static inline int sys_read_buf(char *file, char *buf, int len) +{ + int fd, r; + + if (!file || !buf || len < 0) + return -EINVAL; + + fd = open(file, O_RDONLY); + if (fd == -1) + return -ENOENT; + + r = read(fd, buf, len); + close(fd); + if ((r >= 0) && (r < len)) + buf[r] = '\0'; + else + return -EIO; + + return 0; +} + +static inline int sys_write_buf(char *file, char *buf) +{ + int fd, r; + + if (!file || !buf) + return -EINVAL; + + fd = open(file, O_WRONLY); + if (fd == -1) + return -EPERM; + + r = write(fd, buf, strlen(buf)); + close(fd); + if (r < 0) + return -EIO; + + return 0; +} + +static inline int sys_get_str(char *fname, char *str, int len) +{ + int r; + + if (!fname || !str || len < 0) + return -EINVAL; + + r = sys_read_buf(fname, str, len); + if (r < 0) + return r; + + return 0; +} + +static inline int sys_set_str(char *fname, char *val) +{ + int r; + + if (!fname || !val) + return -EINVAL; + + r = sys_write_buf(fname, val); + if (r < 0) + return r; + + return 0; +} + +static inline int sys_get_int(char *fname, int *val) +{ + char buf[SHARED_H_BUF_MAX]; + int r; + + if (!fname || !val) + return -EINVAL; + + r = sys_read_buf(fname, buf, sizeof(buf)); + if (r < 0) + return r; + + *val = atoi(buf); + return 0; +} + +static inline int sys_set_int(char *fname, int val) +{ + char buf[SHARED_H_BUF_MAX]; + int r; + + if (!fname) + return -EINVAL; + + snprintf(buf, sizeof(buf), "%d", val); + r = sys_write_buf(fname, buf); + if (r < 0) + return r; + + return 0; +} +#endif + +static bool legacy_is_function_supported(struct usb_function *func) +{ + char buf[PATH_MAX]; + + snprintf (buf, sizeof(buf), "%s/f_%s", LEGACY_ROOTPATH, func->name); + if (access(buf, F_OK)) + return false; + + return true; +} + +static bool legacy_is_gadget_supported(struct usb_gadget *gadget) +{ + int i, j; + struct usb_configuration *config; + + if (!gadget || !gadget->configs || !gadget->configs[0] || !gadget->configs[0]->funcs[0]) + return false; + + if (!gadget->attrs.idVendor || !gadget->attrs.idProduct || !gadget->attrs.bcdDevice) + return false; + + /* only strings in US_en are allowed */ + if (gadget->strs.lang_code != DEFAULT_LANG) + return false; + + if (!gadget->strs.manufacturer || !gadget->strs.product) + return false; + + for (j = 0; gadget->configs[j]; ++j) { + config = gadget->configs[j]; + + if (!config->funcs) + return false; + + for (i = 0; config->funcs[i]; ++i) + if (!legacy_is_function_supported(config->funcs[i])) + return false; + } + + return true; +} + +/* TODO. Maybe move this to sys ? */ +static int legacy_set_int_hex(char *path, int val) +{ + int r; + char buf[MAX_GADGET_STR_LEN]; + + if (!path) + return -EINVAL; + + snprintf(buf, sizeof(buf), "%x", val); + r = sys_set_str(path, buf); + if (r < 0) + return r; + + return 0; +} + +static int legacy_set_gadget_attrs(struct usb_gadget_attrs *attrs) +{ + int ret; + + ret = sys_set_int(LEGACY_CLASS_PATH, attrs->bDeviceClass); + if (ret) + return ret; + + ret = sys_set_int(LEGACY_SUBCLASS_PATH, attrs->bDeviceSubClass); + if (ret) + return ret; + + ret = sys_set_int(LEGACY_PROTOCOL_PATH, attrs->bDeviceProtocol); + if (ret) + return ret; + + ret = legacy_set_int_hex(LEGACY_ID_VENDOR_PATH, attrs->idVendor); + if (ret) + return ret; + + ret = legacy_set_int_hex(LEGACY_ID_PRODUCT_PATH, attrs->idProduct); + if (ret) + return ret; + + ret = legacy_set_int_hex(LEGACY_BCD_DEVICE_PATH, attrs->bcdDevice); + if (ret) + return ret; + + return 0; +} + +static int legacy_set_gadget_strs(struct usb_gadget_strings *strs) +{ + int ret; + + if (!strs->manufacturer || !strs->product) + return -EINVAL; + + ret = sys_set_str(LEGACY_IMANUFACTURER_PATH, strs->manufacturer); + if (ret) + return ret; + + ret = sys_set_str(LEGACY_IPRODUCT_PATH, strs->product); + if (ret) + return ret; + + /* The serial is written by the slp gadget kernel driver */ + + return 0; +} + +static int legacy_set_gadget_config(char *cpath, + struct usb_configuration *config) +{ + char buf[MAX_GADGET_STR_LEN]; + int left = sizeof(buf); + char *pos = buf; + int ret; + int i; + + if (!config) { + buf[0] = '\n'; + buf[1] = '\0'; + goto empty_config; + } + + for (i = 0; config->funcs[i]; ++i) { + ret = snprintf(pos, left, "%s" LEGACY_FUNC_SEP, + config->funcs[i]->name); + if (ret >= left) + return -EOVERFLOW; + + pos += ret; + left -= ret; + } + + /* eliminate last separator */ + *(pos - 1) = '\0'; + +empty_config: + return sys_set_str(cpath, buf); +} + +static int legacy_reconfigure_gadget(struct usb_gadget *gadget) +{ + int ret; + + if (!gadget) + return -EINVAL; + + /* Verify the gadget and check if function is supported */ + if (!legacy_is_gadget_supported(gadget)) + return -ENOTSUP; + + ret = legacy_set_gadget_attrs(&gadget->attrs); + if (ret) + return ret; + + ret = legacy_set_gadget_strs(&gadget->strs); + if (ret) + return ret; + + ret = legacy_set_gadget_config(LEGACY_CONFIG_1_PATH, gadget->configs[0]); + if (ret) + return ret; + + ret = legacy_set_gadget_config(LEGACY_CONFIG_2_PATH, gadget->configs[1]); + if (ret) + return ret; + + return 0; +} + +static void legacy_start_stop_service_and_handler(bool start) +{ + int i; + int ret; + int n_func = 0; + char *ptr; + char *begin; + char *fname; + char *sep = LEGACY_FUNC_SEP; + char buf[MAX_GADGET_STR_LEN]; + struct usb_function *func; + struct usb_function *funcs[USB_FUNCTION_IDX_MAX]; + + /* SLP gadget uses two USB configuration. + * (/sys/class/usb_mode/usb0/funcs_fconf and /sys/class/usb_mode/usb0/funcs_sconf) + * + * One usb function can be included in two configurations simultaneously. + * In this situation, a handler associated with function can be called twice for a usb function. + * To prevent duplicate calls, + * Collect all functions and remove duplicates and process them. + */ + + /* First configuration */ + ret = sys_get_str(LEGACY_CONFIG_1_PATH, buf, sizeof(buf)); + if (ret) + goto second_configuration; + + /* Caution: buf ends with '\n' */ + ptr = strchr(buf, '\n'); + if (ptr) + *ptr = 0; + + begin = buf; + for (fname = strsep(&begin, sep); fname; fname = strsep(&begin, sep)) { + func = find_usb_function_by_name(fname); + if (!func) + continue; + + for (i = 0; i < n_func; i++) + if (funcs[i] == func) + continue; + + if(n_func >= USB_FUNCTION_IDX_MAX) /* What happen */ + break; + + funcs[n_func] = func; + n_func++; + } + + /* Second configuration */ +second_configuration: + ret = sys_get_str(LEGACY_CONFIG_2_PATH, buf, sizeof(buf)); + if (ret) + return; + + /* Caution: buf ends with '\n' */ + ptr = strchr(buf, '\n'); + if (ptr) + *ptr = 0; + + begin = buf; + for (fname = strsep(&begin, sep); fname; fname = strsep(&begin, sep)) { + func = find_usb_function_by_name(fname); + if (!func) + continue; + + for (i = 0; i < n_func; i++) + if (funcs[i] == func) + continue; + + if(n_func >= USB_FUNCTION_IDX_MAX) /* What happen */ + break; + + funcs[n_func] = func; + n_func++; + } + + for (i = 0; i < n_func; i++) { + if (start) { + if (funcs[i]->handler) + funcs[i]->handler(1); + + if (funcs[i]->service) + (void)systemd_start_unit_wait_started(funcs[i]->service, ".service", -1); + } else { + if (funcs[i]->service) + (void)systemd_stop_unit_wait_stopped(funcs[i]->service, ".service", -1); + + if (funcs[i]->handler) + funcs[i]->handler(0); + } + } +} + +static int legacy_enable(void) +{ + int ret; + + ret = sys_set_str(LEGACY_ENABLE_PATH, LEGACY_ENABLE); + if (ret < 0) + return ret; + + legacy_start_stop_service_and_handler(true); + + return 0; +} + +static int legacy_disable(void) +{ + legacy_start_stop_service_and_handler(false); + + return sys_set_str(LEGACY_ENABLE_PATH, LEGACY_DISABLE); +} + +EXPORT +int hw_legacy_gadget_open(hal_backend_usb_gadget_funcs *usb_gadget_funcs) +{ + if (!usb_gadget_funcs) + return -EINVAL; + + /* check if slp usb gadget exists */ + if (access("/sys/class/usb_mode/usb0/enable", F_OK)) + return -ENOENT; + + usb_gadget_funcs->enable = legacy_enable; + usb_gadget_funcs->enable = legacy_disable; + usb_gadget_funcs->reconfigure_gadget = legacy_reconfigure_gadget; + + return 0; +} + +EXPORT +int hw_legacy_gadget_close(hal_backend_usb_gadget_funcs *usb_gadget_funcs) +{ + return 0; +} diff --git a/src/usb_gadget/usb_gadget.h b/src/usb_gadget/usb_gadget.h new file mode 100644 index 0000000..ac14373 --- /dev/null +++ b/src/usb_gadget/usb_gadget.h @@ -0,0 +1,26 @@ +/* + * libdevice-node + * + * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __HW_USB_GADGET_H__ +#define __HW_USB_GADGET_H__ + +struct usb_function *find_usb_function_by_id(int id); +struct usb_function *find_usb_function_by_name(const char *name); +struct usb_function *find_usb_function_by_name_instance(const char *name, const char *instance); + +#endif diff --git a/src/usb_gadget/usb_gadget_common.c b/src/usb_gadget/usb_gadget_common.c new file mode 100644 index 0000000..734f933 --- /dev/null +++ b/src/usb_gadget/usb_gadget_common.c @@ -0,0 +1,396 @@ +/* + * libdevice-node + * + * Copyright (c) 2018 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +//include <hw/board.h> TODO move to common +#include <libsyscommon/dbus-systemd.h> +#include <hal/device/hal-usb_gadget-interface.h> + +#include "usb_gadget.h" + +#ifndef EXPORT +#define EXPORT __attribute__ ((visibility("default"))) +#endif + +static struct usb_function *_available_funcs[]; + +struct usb_function *find_usb_function_by_id(int id); + +static void simple_cleanup_config(struct usb_configuration *config) +{ + if (!config) + return; + + free(config->strs.config_str); + + if (config->funcs) + free(config->funcs); + + free(config); +} + +static void cleanup_gadget(struct usb_gadget *gadget) +{ + int i; + + if (!gadget) + return; + + free(gadget->strs.manufacturer); + free(gadget->strs.product); + free(gadget->strs.serial); + + if (gadget->configs) { + for (i = 0; gadget->configs[i]; ++i) + simple_cleanup_config(gadget->configs[i]); + + free(gadget->configs); + } + + free(gadget); +} + +static int alloc_default_config(struct usb_configuration **_config) +{ + struct usb_configuration *config; + + config = calloc(1, sizeof(*config)); + if (!config) + return -ENOMEM; + + config->attrs.bmAttributs = DEFAULT_BMATTRIBUTES; + config->attrs.MaxPower = DEFAULT_MAX_POWER; + + /* TODO. Here is where to set the string used in config of configfs */ + + *_config = config; + + return 0; +} + +static int get_device_serial(char **out) +{ +#if 0 // TODO fix build error + struct hw_info *info; + struct hw_common *common; + struct hw_board *board; + int ret; + + if (hw_get_info(BOARD_HARDWARE_DEVICE_ID, (const struct hw_info **)&info)) + return -ENOENT; + + ret = info->open(info, NULL, &common); + if (ret < 0) + return ret; + + board = container_of(common, struct hw_board, common); + return board->get_device_serial(out); +#else + return -1; +#endif +} + +static int alloc_default_gadget(struct usb_gadget **_gadget) +{ + struct usb_gadget *gadget; + struct usb_configuration **configs; + + gadget = calloc(1, sizeof(*gadget)); + if (!gadget) + goto out; + + gadget->attrs.idVendor = DEFAULT_VID; + gadget->attrs.idProduct = DEFAULT_PID; + gadget->attrs.bcdDevice = DEFAULT_BCD_DEVICE; + + gadget->strs.lang_code = DEFAULT_LANG; + gadget->strs.manufacturer = strdup(DEFAULT_MANUFACTURER); + gadget->strs.product = strdup(DEFAULT_PRODUCT); + if (get_device_serial(&gadget->strs.serial) < 0) + gadget->strs.serial = strdup(DEFAULT_SERIAL); + + if (!gadget->strs.manufacturer || !gadget->strs.product || !gadget->strs.serial) + goto free_strs; + + /* slp-gadget use max 2 confiuration and NULL termination */ + configs = calloc(3, sizeof(*configs)); + if (!configs) + goto free_strs; + + gadget->configs = configs; + *_gadget = gadget; + + return 0; + +free_strs: + free(gadget->strs.manufacturer); + free(gadget->strs.product); + free(gadget->strs.serial); + free(gadget); +out: + return -ENOMEM; +} + +static int id_to_gadget(struct usb_gadget_id *gadget_id, struct usb_gadget **_gadget) +{ + int ret; + int i, j; + int n_configs; + struct usb_gadget *gadget; + int functions[2][sizeof(gadget_id->function_mask)*8]; /* zero terminates */ + + if (!gadget_id || !_gadget) + return -EINVAL; + + ret = alloc_default_gadget(&gadget); + if (ret) + goto out; + + /* + * Currently all gadgets use inly single configuration but + * slp-gadget is capable to handle two of them + * + * Order of interfaces in configuration is significant + * so in this switch we sort our functions in a correct order + */ + switch (gadget_id->function_mask) { + /* MTP, ACM, SDB */ + case USB_FUNCTION_MTP | USB_FUNCTION_ACM: + n_configs = 1; + functions[0][0] = USB_FUNCTION_MTP; + functions[0][1] = USB_FUNCTION_ACM; + functions[0][2] = 0; + gadget->attrs.idProduct = 0x6860; + break; + + case USB_FUNCTION_MTP | USB_FUNCTION_ACM | USB_FUNCTION_SDB: + n_configs = 1; + functions[0][0] = USB_FUNCTION_MTP; + functions[0][1] = USB_FUNCTION_ACM; + functions[0][2] = USB_FUNCTION_SDB; + functions[0][3] = 0; + gadget->attrs.idProduct = 0x6860; + break; + + /* DIAG */ + case USB_FUNCTION_MTP | USB_FUNCTION_ACM | USB_FUNCTION_SDB | USB_FUNCTION_DIAG: + n_configs = 1; + functions[0][0] = USB_FUNCTION_MTP; + functions[0][1] = USB_FUNCTION_ACM; + functions[0][2] = USB_FUNCTION_SDB; + functions[0][3] = USB_FUNCTION_DIAG; + functions[0][4] = 0; + gadget->attrs.idProduct = 0x6860; + break; + + /* RNDIS */ + case USB_FUNCTION_RNDIS: + n_configs = 1; + functions[0][0] = USB_FUNCTION_RNDIS; + functions[0][1] = 0; + gadget->attrs.idProduct = 0x6863; + break; + + case USB_FUNCTION_RNDIS | USB_FUNCTION_DIAG: + n_configs = 1; + functions[0][0] = USB_FUNCTION_RNDIS; + functions[0][1] = USB_FUNCTION_DIAG; + functions[0][2] = 0; + gadget->attrs.idProduct = 0x6864; + break; + + case USB_FUNCTION_ACM | USB_FUNCTION_SDB | USB_FUNCTION_RNDIS: + n_configs = 1; + functions[0][0] = USB_FUNCTION_RNDIS; + functions[0][1] = USB_FUNCTION_ACM; + functions[0][2] = USB_FUNCTION_SDB; + functions[0][3] = 0; + gadget->attrs.idProduct = 0x6864; + break; + + /* RMNET */ + case USB_FUNCTION_DIAG | USB_FUNCTION_RMNET: + n_configs = 1; + functions[0][0] = USB_FUNCTION_DIAG; + functions[0][1] = USB_FUNCTION_RMNET; + functions[0][2] = 0; + gadget->attrs.idProduct = 0x685d; + break; + + /* DM */ + case USB_FUNCTION_ACM | USB_FUNCTION_SDB | USB_FUNCTION_DM: + n_configs = 1; + functions[0][0] = USB_FUNCTION_ACM; + functions[0][1] = USB_FUNCTION_SDB; + functions[0][2] = USB_FUNCTION_DM; + functions[0][3] = 0; + gadget->attrs.idProduct = 0x6860; + break; + + default: + ret = -EINVAL; + goto free_gadget; + }; + + for (j = 0; j < n_configs; ++j) { + int n_funcs_in_config; + struct usb_configuration *config; + + for (i = 0; functions[j][i]; ++i); + n_funcs_in_config = i; + + ret = alloc_default_config(&config); + if (ret) + goto free_configs; + + gadget->configs[j] = config; + config->funcs = calloc(n_funcs_in_config + 1, sizeof(void *)); + if (!config->funcs) + goto free_configs; + + for (i = 0; functions[j][i]; ++i) { + config->funcs[i] = find_usb_function_by_id(functions[j][i]); + if (!config->funcs[i]) + goto free_configs; + } + } + + *_gadget = gadget; + + return 0; + +free_configs: +free_gadget: + cleanup_gadget(gadget); +out: + return ret; +} + +void rndis_handler(int enable) +{ + if (enable) + (void)systemd_start_unit_wait_started("rndis.service", NULL, -1); + else + (void)systemd_stop_unit_wait_stopped("rndis.service", NULL, -1); +} + +#define DEFINE_USB_FUNCTION(_id, _name, _is_functionfs, _service, _handler) \ + static struct usb_function _##_name##_function = { \ + .id = _id, \ + .name = #_name, \ + .instance = "default", \ + .is_functionfs = _is_functionfs, \ + .service = _service, \ + .handler = _handler, \ + } + +DEFINE_USB_FUNCTION(USB_FUNCTION_MTP, mtp, 1, "mtp-responder", NULL); +DEFINE_USB_FUNCTION(USB_FUNCTION_ACM, acm, 0, "data-router", NULL); +DEFINE_USB_FUNCTION(USB_FUNCTION_SDB, sdb, 1, "sdbd", NULL); +DEFINE_USB_FUNCTION(USB_FUNCTION_RNDIS, rndis, 0, "sshd", rndis_handler); +DEFINE_USB_FUNCTION(USB_FUNCTION_DIAG, diag, 0, NULL, NULL); +DEFINE_USB_FUNCTION(USB_FUNCTION_CONN_GADGET, conn_gadget, 0, NULL, NULL); +DEFINE_USB_FUNCTION(USB_FUNCTION_DM, dm, 0, NULL, NULL); +DEFINE_USB_FUNCTION(USB_FUNCTION_RMNET, rmnet, 0, NULL, NULL); + +#undef DEFINE_USB_FUNCTION + +/* Caution: index order of arrary is important, because simple_translator_open() uses it. */ +static struct usb_function *_available_funcs[] = { + [USB_FUNCTION_IDX_MTP] = &_mtp_function, + [USB_FUNCTION_IDX_ACM] = &_acm_function, + [USB_FUNCTION_IDX_SDB] = &_sdb_function, + [USB_FUNCTION_IDX_RNDIS] = &_rndis_function, + [USB_FUNCTION_IDX_DIAG] = &_diag_function, + [USB_FUNCTION_IDX_CONN_GADGET] = &_conn_gadget_function, + [USB_FUNCTION_IDX_DM] = &_dm_function, + [USB_FUNCTION_IDX_RMNET] = &_rmnet_function, + [USB_FUNCTION_IDX_MAX] = NULL /* An indicator to end the array */ +}; + +struct usb_function *find_usb_function_by_id(int id) +{ + int i; + + for (i = 0; _available_funcs[i]; i++) + if (_available_funcs[i]->id == id) + return _available_funcs[i]; + + return NULL; +} + +struct usb_function *find_usb_function_by_name(const char *name) +{ + int i; + + if(!name || !name[0]) + return NULL; + + for (i = 0; _available_funcs[i]; i++) + if (!strcmp(name, _available_funcs[i]->name)) + return _available_funcs[i]; + + return NULL; +} + +struct usb_function *find_usb_function_by_name_instance(const char *name, const char *instance) +{ + int i; + + if(!name || !name[0] || !instance || !instance[0]) + return NULL; + + for (i = 0; _available_funcs[i]; ++i) + if (!strcmp(name, _available_funcs[i]->name) && !strcmp(instance, _available_funcs[i]->instance)) + return _available_funcs[i]; + + return NULL; +} + +EXPORT +int simple_translator_open(hal_backend_usb_gadget_funcs *usb_gadget_funcs) +{ + if (!usb_gadget_funcs) + return -EINVAL; + + usb_gadget_funcs->id_to_gadget = id_to_gadget; + usb_gadget_funcs->cleanup_gadget = cleanup_gadget; + + /* Use mtp-responder-dummy.socket when there is no mtp-responser.socket. + * + * The mtp-responder.socket is special in the configfs environment. + * If mtp-responder.socket is missing, gadget configuration will fail. + * As a result, all usb operations do not work properly. + * So in environments that mtp doesn't support, use dummy mtp. + */ + if (access("/usr/lib/systemd/system/mtp-responder.socket", F_OK)) { + _available_funcs[USB_FUNCTION_IDX_MTP]->service = "mtp-responder-dummy"; + } + + return 0; +} + +EXPORT +int simple_translator_close(hal_backend_usb_gadget_funcs *usb_gadget_funcs) +{ + return 0; +} |