diff options
33 files changed, 11053 insertions, 457 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7dfeaed --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +/examples/* +!/examples/*.* +/examples/.deps +/examples/.libs +/src/.deps +/src/.libs +/tests/* +!/tests/*.* +/tests/.deps +*.o +*.lo +*.la +*.so +*.so.* +Makefile +Makefile.in +/configure +/config.* +/aclocal.m4 +/autom4te.cache +/compile +/depcomp +/libtool +/ltmain.sh +/ar-lib +/missing +/install-sh +/doxygen.cfg +/libusbg.pc @@ -1 +1,3 @@ Matt Porter <mporter@linaro.org> +Krzysztof Opasiak <k.opasiak@samsung.com> + @@ -1,5 +1,2 @@ -Mon, 20 Jan 2014 Matt Porter <mporter@linaro.org> 0.1.0 -- Rename from libgadget->libusbg -- Update examples to use standard configfs mount point -Wed, 04 Sep 2013 Matt Porter <mporter@linaro.org> 0.0.1 -- Initial release +Tue, 22 Dec 2015 Krzysztof Opasiak <k.opasiak@samsung.com> 0.0.1 + - Initial fork from libusbg @@ -1,4 +1,4 @@ -Installing libusbg: +Installing libusbgx: $ autoreconf -i $ ./configure diff --git a/Makefile.am b/Makefile.am index 5fc3cd6..05e1379 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,8 +1,13 @@ include $(top_srcdir)/aminclude.am SUBDIRS = src examples + +if BUILD_TESTS +SUBDIRS += tests +endif + ACLOCAL_AMFLAGS = -I m4 EXTRA_DIST = doxygen.cfg library_includedir=$(includedir)/usbg library_include_HEADERS = include/usbg/usbg.h pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = libusbg.pc +pkgconfig_DATA = libusbgx.pc @@ -1,7 +1,7 @@ -libusbg +libusbg-neXt (libusbgx) ------- -libusbg is a C library encapsulating the kernel USB gadget-configfs +libusbgx is a C library encapsulating the kernel USB gadget-configfs userspace API functionality. It provides routines for creating and parsing USB gadget devices using diff --git a/configure.ac b/configure.ac index 878c2ab..a456ba7 100644 --- a/configure.ac +++ b/configure.ac @@ -1,10 +1,51 @@ -AC_INIT([libusbg], [0.1.0], [mporter@linaro.org]) -AM_INIT_AUTOMAKE([-Wall -Werror foreign]) +AC_INIT([libusbgx], [0.0.1], [k.opasiak@samsung.com]) +AM_INIT_AUTOMAKE([1.12 -Wall -Werror foreign serial-tests]) AC_PROG_CC AM_PROG_AR +AM_PROG_CC_C_O AC_CONFIG_MACRO_DIR([m4]) AC_DEFINE([_GNU_SOURCE], [], [Use GNU extensions]) + +AC_ARG_WITH([libconfig], + AS_HELP_STRING([--without-libconfig], [build without using libconfig]), + [with_libconfig=$withval], [with_libconfig=yes]) + +AC_ARG_ENABLE([gadget-schemes], + AS_HELP_STRING([--disable-gadget-schemes], [build without gadget-schemes support]), + [enable_gadget_schemes=$enableval], [enable_gadget_schemes=yes]) + +AC_ARG_ENABLE([tests], + AS_HELP_STRING([--enable-tests], [build with tests]), + [enable_tests=$enableval], [enable_tests=no]) + +# if both tests and schemes are disabled, we do not need libconfig +AS_IF([test "x$enable_gadget_schemes" = xno && test "x$enable_tests" = xno], [with_libconfig=no]) + +AS_IF([test "x$with_libconfig" = xyes], [ + PKG_CHECK_MODULES([LIBCONFIG], [libconfig >= 1.4], + [ AC_DEFINE(HAS_LIBCONFIG, 1, [detected libconfig]) + PKG_CHECK_MODULES([NEW_LIBCONFIG], [libconfig >= 1.5], + AC_DEFINE(HAVE_LIBCONFIG_15, 1, [detected libconfig equal to or greater than 1.5]), + AC_DEFINE(HAVE_LIBCONFIG_15, 0, [])) + ]) + CFLAGS="$CFLAGS $LIBCONFIG_CFLAGS" + LIBS="$LIBS $LIBCONFIG_LIBS" +], [ + enable_gadget_schemes=no +]) + +AS_IF([test "x$enable_tests" = xyes], [ + PKG_CHECK_MODULES([CMOCKA], [cmocka >= 1.0.0], + AC_DEFINE(HAS_CMOCKA, 1, [detected cmocka])) + AC_CONFIG_FILES([tests/Makefile]) +]) +AM_CONDITIONAL(BUILD_TESTS, [test "x$enable_tests" = xyes]) + +AS_IF([test "x$enable_gadget_schemes" = xyes], + [AC_DEFINE(HAS_GADGET_SCHEMES, 1, [gadget schemes are enables])]) +AM_CONDITIONAL(TEST_GADGET_SCHEMES, [test "x$enable_gadget_schemes" != xno]) + LT_INIT -AC_CONFIG_FILES([Makefile src/Makefile examples/Makefile libusbg.pc]) +AC_CONFIG_FILES([Makefile src/Makefile examples/Makefile libusbgx.pc doxygen.cfg]) DX_INIT_DOXYGEN([$PACKAGE_NAME],[doxygen.cfg]) AC_OUTPUT diff --git a/doc/gadget_schemes.txt b/doc/gadget_schemes.txt new file mode 100644 index 0000000..d1d44a5 --- /dev/null +++ b/doc/gadget_schemes.txt @@ -0,0 +1,301 @@ + + Gadget schemes + + +Index: +1. What are gadget schemes? +2. Why gadget schemes? +3. Gadget scheme syntax + 3.1 Function scheme + 3.2 Configuration scheme + 3.3 Gadget scheme +4. Conclusion + + + 1. What are gadget schemes? + +Gadget schemes are files which contains configuration data of +gadget/function/configuration. Those files can be generated using +usbg_export_*() functions for whole gadget, configuration or single +function. Library provides also set of usbg_import_*() functions which +allows to load configuration data back to configfs. + + + 2. Why gadget schemes? + +New kernel interface - ConfigFS which along with libcomposite allows +to set up custom gadget. This can be achieved using simple, command +line file system operation like mkdir, rmdir, ln -s, read and +write. Yes, it is possible to configure usb gadget using only command +line but each time after reboot user needs to recreate all gadgets +once again. This means that after each reboot user needs to use about +15 commands (depends on number and types of function). This is +definitely not acceptable for those who used legacy gadgets and write +only modprobe g_ether. + +One of first idea to solve this is to create a script and run it +after each reboot. This approach is feasible but has many +disadvantages. First of them is security. ConfigFS is modifiable by +default only by root, so scripts has to be executed with root +rights. Secondly it's really hard to modify such a script because many +calls has hard-coded path where for example echo should be +done. There is a lot of simple, but low level operations which can +cause a lot of confusion for beginner. + +Second approach is to create executable which will create our gadget +using base libusbg API. It is also possible but let's think for a +moment why configfs has been introduced. It has been announced to +separate code from configuration. Code is a piece of C code in kernel +module which realizes usb function and configuration is understood as +composition of those functions into a gadget as a whole. If we would +like to create binary file for each gadget we would waste a lot of +work which kernel contributors put to remove hard-coded gadgets from +linux kernel. This all leads us to solution described in this document +- gadget schemes. Light weight configuration files which describes +composition of functions into gadget. They can be simply loaded using +usbg_import_*() and exported using usbg_export_*(). This makes them +easy to use equivalent of modprobe gadget_module. + + + 3. Gadget scheme syntax + +Gadget schemes implementation uses libconfig for reading and writing +scheme files. This means that all limitations of libconfig are also +present in gadget schemes. More over there are additional constrains +for scheme files. Gadget scheme is only a password and import and +export is not limited to whole gadgets. It is possible to export all 3 +types of gadget entity: function, configuration and gadget. Please +refer to libconfig documentation for details about syntax and rules. + + 3.1 Function scheme + +Function scheme is a file or part of file which represents single +function. + +Example: + +instance = "my_func_instance" +type = "ecm" + +attrs = { + dev_addr = "ef:33:be:9a:90:36" + dev_addr = "ab:63:6e:8b:10:16" + qmult = 5 +} + +For functions, type is the only attribute which is always +mandatory. Instance is mandatory only if function is part of bigger +scheme (gadget for example). By default usbg_export_function() does +not export instance name, because usbg_import_function() takes +instance as one of parameters. This convention allows for simple +function movement between gadgets without names conflict. + +Attrs section is optional. It may not be included, present but empty +or present and filled with function attributes. Attribute names are +similar as those from configfs. Each type of function has own set of +possible attributes. It is worth to mention that some attributes are +read only and they cannot be imported from file. To make it more +user-friendly read-only parameters are just ignored. This allows for +direct use of previously exported function in import. If some +attribute has not been provided default value provided by kernel will +be used. + + 3.2 Configuration scheme + +Configuration scheme is a file or part of file which represents single +configuration with its attributes, strings and bindings. + +Simple example: + +id = 1 +name = "My favorite config" + +attrs = { + bMaxPower = 0x40 + bmAttributes = 0x00 +} + +strings = ( + { + lang = 0x409 + configuration = "My favorite string" + } +) + +functions = ( + "function_label" +) + +This is example of simple configuration with some attributes values, +strings in US English and only one function. For configurations name +is the only field which is always mandatory. Id is mandatory only if +this is a part of bigger structure (gadget scheme). + +Attrs section is optional. It may not be included, present but empty +or present and filled with function attributes. Attribute names are +similar as those from configfs. Currently usb configuration has only +two attributes: bMaxPower and bmAttributes. Their meaning and set of +allowed values can be found in usb standard. + +Strings section presence policy is the same as attrs section. This +section contains a list (that's the meaning of parentheses) of strings +with their language codes. Each group of strings has to declare their +language using lang field. Configuration string is optional. If this +field is not set, empty string provided by kernel will be used for +this language. Max number of languages is defined during kernel +compilation using MAX_USB_STRING_LANGS define. + +Functions section is also optional. This allows for exporting not +fully configured configurations. This section defines bindings between +functions and configurations. The easiest and shortest way to define a +connection between functions and configuration is to provide list of +comma separated functions labels. For details about function labels +please refer to gadget schemes subsection. Bindings of function to +given configuration could be defined in different ways which has been +presented in featured example. + +Featured example: + +id = 1 +name = "My favorite config" + +attrs = { + bMaxPower = 0x40 + bmAttributes = 0x00 +} + +strings = ( + { + lang = 0x409 + configuration = "My favorite string" + } , { + lang = 0x415 + configuration = "Moj ulubiony napis" + } +) + +functions = ( + "function_label" + , { + name = "my_binding_name" + function = "other_function_label" + } , { + name = "my_binding_name" + function = { + type = "ecm" + instance = "my_inline_func_definition" + attrs = { + dev_addr = "ef:33:be:9a:90:36" + } + } + } +) + +First way to add function to configuration has been described along +with simple example. Second way is to provide a group with two +fields. First one is name and it should contain a string with binding +name. This field is optional and can be omitted what makes this more +verbose equivalent of previous method. Second field, named function +is mandatory. This field should contain function label. Third way to +add function to config is to define it inline. This method allows to +define a brand new function instead of providing function label of +existing one. + + 3.3 Gadget scheme + +Gadget scheme is a file which represents whole gadget with +configurations, attributes, strings and functions. + +Example: + +attrs = { + bcdUSB = 0x200 + bDeviceClass = 0x0 + bDeviceSubClass = 0x0 + bDeviceProtocol = 0x0 + bMaxPacketSize0 = 0x40 + idVendor = 0x1D6B + idProduct = 0x104 + bcdDevice = 0x1 +} + +strings = ( + { + lang = 0x409 + manufacturer = "Foo Inc." + product = "Bar Gadget" + serialnumber = "0123456789" + } +) + +functions = { + acm_usb0 = { + instance = "usb0" + type = "acm" + } + + my_awesome_label = { + instance = "inst_name" + @include "my_func_scheme.scheme" + } +} + +configs = ( + { + id = 1 + name = "The only one" + attrs = { + bmAttributes = 0x80 + bMaxPower = 0x2 + } + strings = ( + { + lang = 0x409 + configuration = "Config id 1" + } ) + functions = ( + { + name = "acm.GS0" + function = "acm_usb0" + } + ) + } , { + id = 2 + @include "some_config.scheme" + } +) + +All sections in gadget scheme are optional. If attrs section has not +been defined defaults provided by kernel are used for each attribute. +All possible gadget attributes has been listed in above example. Their +names are similar to those provided by usb standard and configfs. + +Strings section is similar to strings section from configuration +scheme. Allowed strings are listed in example. + +Functions section is used to define functions which are aggregated by +this gadget. Definition of each function begins with unique label. Any +string which fulfills libconfig naming rules can be used as label, +but there is one important thing - function labels are not stored in +configfs. They are transient and are lost while executing +usbg_cleanup(). To allow using this label after next usbg_init() there +is a naming rule: type + "_" + instance. If label follows this +convention it could be regenerated each time when it is +needed. Definition of each function contains a function scheme which +has been described in one of previous sections. It is also possible +to use include directive of libconfig and provide only instance name +in gadget shceme and include previously exported function scheme from +other gadget. + +Configfs section contains list of configurations definitions. Each +configuration is defined using configuration scheme described in +previous section. Each configuration can be fully defined in gadget +scheme file or simply included from other file just like function. + + 4. Conclusion + +Syntax of gadget scheme is based on libconfig and if any doubts appear +don't hesitate to look into documentation of this library. There are +also sample applications which shows how to use usbg_import_*() and +usbg_export_*() functions in examples directory. + diff --git a/doc/tests_guideline.txt b/doc/tests_guideline.txt new file mode 100644 index 0000000..2f5a2d8 --- /dev/null +++ b/doc/tests_guideline.txt @@ -0,0 +1,166 @@ +Libusbg testing guideline +------------------------- + +Libusbg tests use cmocka library to simulate fake configfs filesystem, +by wrapping input/output functions. + +## Building and running tests + +# Requirements +Building libusbg tests requires: +-CMocka unit testing framework in version >= 0.3 +-libconfig in version >= 1.4 + +# Building and running +Before testing make sure that you have successfully built libusbg (see INSTALL for +more details). Tests must be enabled in configuration, do it by adding proper flag +when configuring: + + $ ./configure --enable-tests + +Then, to build and run all provided tests, run following command: + + $ make check + +This should execute testing script and produce report on standart output. +It contains list of all test cases and its status - OK, FAILED and SKIPPED. At the +end of report number of passed/failed tests is written and then all failed test +cases are listed. This report is also avaible in tests/test-suite.log file. + +It's also possible to pass custom configuration file to testing environment. +Currently it's used only for skipping tests. Use --generate-config and +--use-config options of test.sh to generate default config and read config +from file. Run ./test.sh --help for more help with testing environment. + +# Tests skipping +When you want to skip some test cases, use configuration files for test suite. +To generate default config run: + + $ make check GENERATE_CONFIG=[file_name] + +It will generate tests/[file_name] file with configuration for testing. You can +remove test cases from 'tests' list to disable them. With custom configuration file +run: + + $make check USE_CONFIG=[file_name] + +Where file_name is name of previously generated configuration file. Test suite +will skip tests not listed in config. + + +## Writting tests + +Before starting your own tests implementation, become familiar with cmocka +library (cmocka.org). + +test.c file contains tests implementation. Test cases are stored in +UnitTest structures (from cmocka) and run by run_tests macro. + +In cmocka each test case can be composed of three parts: setup, test and +teardown. + +# Setup functions +In setup input data must be initialized and assigned to pointer given as +argument. + +Libusbg requires initialized usbg_state structure for most of it's api +functions. In most cases we define initial state in test_* strutures and +pass it to test function, in order to run usbg_init. Each test_state +can be defined quite simply by listing gadgets, its configs and functions +(using gcc extenstion), e.g.: + + static struct test_state simple_state = { + .path = "config", + .gadgets = simple_gadgets, + .udcs = simple_udcs + }; + +test_state structure (or other structure, if neccessary for test case) +is casted to void * in setup function. Note, that when using test_state +you must sort its content and fill additional fields (i.e. pathes strings). +It can be done by calling prepare_state before test. + +# Test functions +Test functions contain libusbg functions calls, preparations for them and +checking results. + +When calling usbg function which operates on filesystem proper preparation +is needed. Usbg-test framework provides functions which tell cmocka what +operations are expected from corresponding usbg function (push_* and pull_* +functions). + +E.g., in most cases you want correctly initialized usbg_state. It can be done +by preparing filesystem by push_init and running usbg_init after that: + + push_init(in); + usbg_ret = usbg_init(in->path, out); + +init_with_state function does that and checks results. + +When tested usbg function was run, you can check results by cmocka assert +macros. Usbg-test also provides set of assert functions for usbg structures. + +# Teardown functions +When test was run, you can define teardown function, which can do the cleanup. +Argument is passed same way as before, by assigning it to cmocka state pointer +in test function and receiving it in teardown function. + +In most cases you will just want to cleanup after initializing state and running +some usbg functions. To do that teardown_state can be used as teardown function. +You can write custom teardown for other cases. + +Note that in preparation for test some memory is allocated. All allocated +pointers are stored on global stack and should be freed by calling cleanup_stack +after test is finished (in teardown function). + +Remember that teardown is called also when test failed. You should assign +something correct (NULL for example) for your teardown function to test +argument before running functions which may fail. + +# Composing test cases +All test cases are defined in list of UnitTest structures. You can define test +case by macros provided by cmocka or by USBG_TEST or USBG_TEST_TS macro. + +USBG_TEST is similar to unit_test_setup_teardown from cmocka, but always uses +the same teardown (teardown_state) and names test case with custom string. +It combines setup function with test function, so one test function can be +run with many different states. + +# Documenting test cases +For tests documentation few doxygen macros are created. +\usbg_test indicates that current comment block contains test case documentation. +\test_desc{name, description, function} describe test with its name, function +which is tested and short descripttion of what this test does. + +# Adding tests +Simplest way to add more tests is defining test states and new setup functions, +combining them with existing testing functions using USBG_TEST. You can also +write own test function. When you have test and setup prepared, add + + USBG_TEST_TS("test_name", test_function, setup_function), +or + USBG_TEST("test_name", test_function, setup_function, teardown_function), + +at the end of tests[] array. Remember to add documentation to the case +(see Documenting test cases). + +# Removing tests +In order to remove test case just delete corresponding element on tests[] list +(including documentation above it). + +# Modyfing tests +When you want to change data using in test case, change corresponding test_* +structures. Note, that single test state can be used in many test cases and +modyfing it can effect them as well. +You can also change test logic (by modifying test function used in case), as +long as you know what you're doing. + + +## Final notes + +Remember, that in test environment functions operating on files are replaced +and operations on files cannot be performed. However, using standard input/output +is possible. + +All test cases are run in single thread, so some failures on one test case +(e.g. SIGSEGV) can cause crash on whole tests set. diff --git a/doxygen.cfg b/doxygen.cfg.in index 4d7a5d8..d42ff83 100644 --- a/doxygen.cfg +++ b/doxygen.cfg.in @@ -650,6 +650,8 @@ WARN_LOGFILE = INPUT = $(SRCDIR)/include/usbg/ $(SRCDIR)/src $(SRCDIR)/examples/ +@BUILD_TESTS_TRUE@INPUT += $(SRCDIR)/tests/ + # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built @@ -1503,7 +1505,7 @@ INCLUDE_FILE_PATTERNS = # undefined via #undef or recursively expanded use the := operator # instead of the = operator. -PREDEFINED = +PREDEFINED = DOXYGEN # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. @@ -1779,3 +1781,8 @@ GENERATE_LEGEND = YES # the various graphs. DOT_CLEANUP = YES + +ALIASES += tests_start="<table> <tr> <th>Name</th> <th>Description</th> <th>Tested function</th> </tr> \n" +ALIASES += usbg_test="\page usbg_tests" +ALIASES += test_desc{3}="<tr> <td>\1</td> <td>\2</td> <td>\ref \3</td> </tr> \n" +ALIASES += tests_end="</table> " diff --git a/examples/Makefile.am b/examples/Makefile.am index f9f9407..8c2acb2 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -1,5 +1,10 @@ -bin_PROGRAMS = show-gadgets gadget-acm-ecm +bin_PROGRAMS = show-gadgets gadget-acm-ecm gadget-vid-pid-remove gadget-ffs gadget-export gadget-import show-udcs gadget-ms gadget-midi gadget_acm_ecm_SOURCES = gadget-acm-ecm.c show_gadgets_SOURCES = show-gadgets.c -AM_CPPFLAGS=-I../include/ -AM_LDFLAGS=-L../src/ -lusbg +gadget_vid_pid_remove_SOURCES = gadget-vid-pid-remove.c +gadget_ffs_SOURCES = gadget-ffs.c +gadget_export_SOURCE = gadget-export.c +gadget_import_SOURCE = gadget-import.c +show_udcs_SOURCE = show-udcs.c +AM_CPPFLAGS=-I$(top_srcdir)/include/ +AM_LDFLAGS=-L../src/ -lusbgx diff --git a/examples/gadget-acm-ecm.c b/examples/gadget-acm-ecm.c index 4e8010f..65407ff 100644 --- a/examples/gadget-acm-ecm.c +++ b/examples/gadget-acm-ecm.c @@ -16,6 +16,7 @@ #include <errno.h> #include <stdio.h> +#include <linux/usb/ch9.h> #include <usbg/usbg.h> /** @@ -37,24 +38,24 @@ int main(void) int usbg_ret; usbg_gadget_attrs g_attrs = { - 0x0200, /* bcdUSB */ - 0x00, /* Defined at interface level */ - 0x00, /* subclass */ - 0x00, /* device protocol */ - 0x0040, /* Max allowed packet size */ - VENDOR, - PRODUCT, - 0x0001, /* Verson of device */ + .bcdUSB = 0x0200, + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 64, /* Max allowed ep0 packet size */ + .idVendor = VENDOR, + .idProduct = PRODUCT, + .bcdDevice = 0x0001, /* Verson of device */ }; usbg_gadget_strs g_strs = { - "0123456789", /* Serial number */ - "Foo Inc.", /* Manufacturer */ - "Bar Gadget" /* Product string */ + .str_ser = "0123456789", /* Serial number */ + .str_mnf = "Foo Inc.", /* Manufacturer */ + .str_prd = "Bar Gadget" /* Product string */ }; usbg_config_strs c_strs = { - "CDC 2xACM+ECM" + .configuration = "CDC 2xACM+ECM" }; usbg_ret = usbg_init("/sys/kernel/config", &s); diff --git a/examples/gadget-export.c b/examples/gadget-export.c new file mode 100644 index 0000000..9d51e9e --- /dev/null +++ b/examples/gadget-export.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 Samsung Electronics + * + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * @file gadget-export.c + * @example gadget-export.c + * This is an example of how to export a gadget to file. + * Common reason of doing this is to share schema of gadget + * between different devices or preserve gadget between reboots. + */ + +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <usbg/usbg.h> + +int main(int argc, char **argv) +{ + usbg_state *s; + usbg_gadget *g; + int ret = -EINVAL; + int usbg_ret; + FILE *output; + + if (argc != 3) { + fprintf(stderr, "Usage: gadget-export gadget_name file_name\n"); + return ret; + } + + /* Prepare output file */ + output = fopen(argv[2], "w"); + if (!output) { + fprintf(stderr, "Error on fopen. Error: %s\n", strerror(errno)); + goto out1; + } + + /* Do gadget exporting */ + usbg_ret = usbg_init("/sys/kernel/config", &s); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on USB gadget init\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + g = usbg_get_gadget(s, argv[1]); + if (!g) { + fprintf(stderr, "Error on get gadget\n"); + goto out3; + } + + usbg_ret = usbg_export_gadget(g, output); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on export gadget\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out3; + } + + ret = 0; + +out3: + usbg_cleanup(s); +out2: + fclose(output); +out1: + return ret; +} diff --git a/examples/gadget-ffs.c b/examples/gadget-ffs.c new file mode 100644 index 0000000..6274c15 --- /dev/null +++ b/examples/gadget-ffs.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2014 Samsung Electronics + * + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * @file gadget-ffs.c + * @example gadget-ffs.c + * This is an example of how to create gadget with FunctionFS based functions + * in two ways. After executing this program gadget will not be enabled + * because ffs instances should be mounted and both descriptors and strings + * should be written to ep0. + * For more details about FunctionFS please refer to FunctionFS documentation + * in linux kernel repository. + */ + +#include <errno.h> +#include <stdio.h> +#include <linux/usb/ch9.h> +#include <usbg/usbg.h> + +#define VENDOR 0x1d6b +#define PRODUCT 0x0104 + +int main(void) +{ + usbg_state *s; + usbg_gadget *g; + usbg_config *c; + usbg_function *f_ffs1, *f_ffs2; + int ret = -EINVAL; + int usbg_ret; + + usbg_gadget_attrs g_attrs = { + .bcdUSB = 0x0200, + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 64, /* Max allowed ep0 packet size */ + .idVendor = VENDOR, + .idProduct = PRODUCT, + .bcdDevice = 0x0001, /* Verson of device */ + }; + + usbg_gadget_strs g_strs = { + .str_ser = "0123456789", /* Serial number */ + .str_mnf = "Foo Inc.", /* Manufacturer */ + .str_prd = "Bar Gadget" /* Product string */ + }; + + usbg_config_strs c_strs = { + .configuration = "2xFFS" + }; + + usbg_function_attrs f_attrs = { + .attrs.ffs = { + .dev_name = "my_awesome_dev_name", + }, + }; + + usbg_ret = usbg_init("/sys/kernel/config", &s); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on USB gadget init\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out1; + } + + usbg_ret = usbg_create_gadget(s, "g1", &g_attrs, &g_strs, &g); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on create gadget\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + usbg_ret = usbg_create_function(g, F_FFS, "my_dev_name", NULL, &f_ffs1); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error creating ffs1 function\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + /* When NULL is passed as instance name, dev_name take from f_attrs + is used as instance name for this function */ + usbg_ret = usbg_create_function(g, F_FFS, NULL, &f_attrs, &f_ffs2); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error creating ffs2 function\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + /* NULL can be passed to use kernel defaults */ + usbg_ret = usbg_create_config(g, 1, "The only one", NULL, &c_strs, &c); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error creating config\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + usbg_ret = usbg_add_config_function(c, "some_name_here", f_ffs1); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error adding ffs1\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + usbg_ret = usbg_add_config_function(c, "some_name_here_too", f_ffs2); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error adding ffs2\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + fprintf(stdout, "2xFFS gadget has been created.\n" + "Enable it after preparing your functions.\n"); + + /* + * Here we end up with two created ffs instances but they are not + * fully operational. Now we have to do step by step: + * 1) Mount both instances: + * $ mount my_dev_name -t functionfs /path/to/mount/dir1 + * $ mount my_awesome_dev_name -t functionfs /path/to/mount/dir2 + * + * 2) Run ffs daemons for both instances: + * $ my-ffs-daemon /path/to/mount/dir1 + * $ my-ffs-daemon /path/to/mount/dir2 + * + * 3) Enable your gadget: + * $ echo "my_udc_name" > /sys/kernel/config/usb_gadget/g1/UDC + */ + + ret = 0; + +out2: + usbg_cleanup(s); + +out1: + return ret; +} diff --git a/examples/gadget-import.c b/examples/gadget-import.c new file mode 100644 index 0000000..e684fdb --- /dev/null +++ b/examples/gadget-import.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 Samsung Electronics + * + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * @file gadget-import.c + * @example gadget-import.c + * This is an example of how to import a gadget from file. + * Common reason of doing this is to create gadget base on schema + * from other devices or resurect gadget after reboot. + */ + +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <usbg/usbg.h> + +int main(int argc, char **argv) +{ + usbg_state *s; + int ret = -EINVAL; + int usbg_ret; + FILE *input; + + if (argc != 3) { + fprintf(stderr, "Usage: gadget-import gadget_name file_name\n"); + return ret; + } + + /* Prepare input file */ + input = fopen(argv[2], "r"); + if (!input) { + fprintf(stderr, "Error on fopen. Error: %s\n", strerror(errno)); + goto out1; + } + + /* Do gadget exporting */ + usbg_ret = usbg_init("/sys/kernel/config", &s); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on USB gadget init\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + usbg_ret = usbg_import_gadget(s, input, argv[1], NULL); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on import gadget\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + if (usbg_ret == USBG_ERROR_INVALID_FORMAT) + fprintf(stderr, "Line: %d. Error: %s\n", + usbg_get_gadget_import_error_line(s), + usbg_get_gadget_import_error_text(s)); + goto out3; + } + + ret = 0; + +out3: + usbg_cleanup(s); +out2: + fclose(input); +out1: + return ret; +} diff --git a/examples/gadget-midi.c b/examples/gadget-midi.c new file mode 100644 index 0000000..f795de1 --- /dev/null +++ b/examples/gadget-midi.c @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2014 Samsung Electronics + * + * Pawel Szewczyk <p.szewczyk@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <errno.h> +#include <stdio.h> +#include <linux/usb/ch9.h> +#include <usbg/usbg.h> + +#define VENDOR 0x1d6b +#define PRODUCT 0x0104 + +int main() { + usbg_state *s; + usbg_gadget *g; + usbg_config *c; + usbg_function *f_midi; + int ret = -EINVAL; + int usbg_ret; + + usbg_gadget_attrs g_attrs = { + .bcdUSB = 0x0200, + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 64, /* Max allowed ep0 packet size */ + .idVendor = VENDOR, + .idProduct = PRODUCT, + .bcdDevice = 0x0001, /* Verson of device */ + }; + + usbg_gadget_strs g_strs = { + .str_ser = "0123456789", /* Serial number */ + .str_mnf = "Foo Inc.", /* Manufacturer */ + .str_prd = "Bar Gadget" /* Product string */ + }; + + usbg_config_strs c_strs = { + .configuration = "1xMIDI" + }; + + usbg_function_attrs f_attrs = { + .header.attrs_type = USBG_F_ATTRS_MIDI, + .attrs.midi = { + .index = 0, + .id = "usb0", + .buflen = 128, + .qlen = 16, + .in_ports = 2, + .out_ports = 3, + }, + }; + + usbg_ret = usbg_init("/sys/kernel/config", &s); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on usbg init\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out1; + } + + usbg_ret = usbg_create_gadget(s, "g1", &g_attrs, &g_strs, &g); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error creating gadget\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + usbg_ret = usbg_create_function(g, F_MIDI, "usb0", &f_attrs, &f_midi); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error creating function\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + usbg_ret = usbg_create_config(g, 1, "The only one", NULL, &c_strs, &c); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error creating config\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + usbg_ret = usbg_add_config_function(c, "some_name", f_midi); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error adding function\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + usbg_ret = usbg_enable_gadget(g, DEFAULT_UDC); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error enabling gadget\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + ret = 0; +out2: + usbg_cleanup(s); + +out1: + return ret; +} diff --git a/examples/gadget-ms.c b/examples/gadget-ms.c new file mode 100644 index 0000000..d6c4aaf --- /dev/null +++ b/examples/gadget-ms.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2014 Samsung Electronics + * + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * @file gadget-ms.c + * @example gadget-ms.c + * This is an example of how to create gadget with mass storage function + * with two luns. + */ + +#include <errno.h> +#include <stdio.h> +#include <linux/usb/ch9.h> +#include <usbg/usbg.h> + +#define VENDOR 0x1d6b +#define PRODUCT 0x0104 + +int main(int argc, char **argv) +{ + usbg_state *s; + usbg_gadget *g; + usbg_config *c; + usbg_function *f_ms; + int ret = -EINVAL; + int usbg_ret; + + usbg_gadget_attrs g_attrs = { + .bcdUSB = 0x0200, + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 64, /* Max allowed ep0 packet size */ + .idVendor = VENDOR, + .idProduct = PRODUCT, + .bcdDevice = 0x0001, /* Verson of device */ + }; + + usbg_gadget_strs g_strs = { + .str_ser = "0123456789", /* Serial number */ + .str_mnf = "Foo Inc.", /* Manufacturer */ + .str_prd = "Bar Gadget" /* Product string */ + }; + + usbg_f_ms_lun_attrs f_ms_luns_array[] = { + { + .id = -1, /* allows to place in any position */ + .cdrom = 1, + .ro = 0, + .nofua = 0, + .removable = 1, + .filename = "", + }, { + .id = -1, /* allows to place in any position */ + .cdrom = 0, + .ro = 0, + .nofua = 0, + .removable = 1, + .filename = argv[1], + } + }; + + usbg_f_ms_lun_attrs *f_ms_luns[] = { + /* + * When id in lun structure is below 0 we can place it in any + * arbitrary position + */ + &f_ms_luns_array[1], + &f_ms_luns_array[0], + NULL, + }; + + usbg_function_attrs f_attrs = { + .header.attrs_type = USBG_F_ATTRS_MS, + .attrs.ms = { + .stall = 0, + .nluns = 2, + .luns = f_ms_luns, + }, + }; + + usbg_config_strs c_strs = { + "1xMass Storage" + }; + + if (argc < 2) { + fprintf(stderr, "Usage: gadget-ms file\n"); + goto out1; + } + + usbg_ret = usbg_init("/sys/kernel/config", &s); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on USB gadget init\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out1; + } + + usbg_ret = usbg_create_gadget(s, "g1", &g_attrs, &g_strs, &g); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on create gadget\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + usbg_ret = usbg_create_function(g, F_MASS_STORAGE, "my_reader", + &f_attrs, &f_ms); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error creating mass storage function\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + /* NULL can be passed to use kernel defaults */ + usbg_ret = usbg_create_config(g, 1, "The only one", NULL, &c_strs, &c); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error creating config\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + usbg_ret = usbg_add_config_function(c, "some_name_here", f_ms); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error adding ms function\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + usbg_ret = usbg_enable_gadget(g, DEFAULT_UDC); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error enabling gadget\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + ret = 0; + +out2: + usbg_cleanup(s); + +out1: + return ret; +} diff --git a/examples/gadget-vid-pid-remove.c b/examples/gadget-vid-pid-remove.c new file mode 100644 index 0000000..c3f9c9b --- /dev/null +++ b/examples/gadget-vid-pid-remove.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 Samsung Electronics + * + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * @file gadget-vid-pid-remove.c + * @example gadget-vid-pid-remove.c + * This is an example of how to find and remove an gadget device with given + * Vendor ID and product ID. + */ + +#include <errno.h> +#include <stdio.h> +#include <usbg/usbg.h> + +#define VENDOR 0x1d6b +#define PRODUCT 0x0104 + +int remove_gadget(usbg_gadget *g) +{ + int usbg_ret; + usbg_udc *u; + + /* Check if gadget is enabled */ + u = usbg_get_gadget_udc(g); + + /* If gadget is enable we have to disable it first */ + if (u) { + usbg_ret = usbg_disable_gadget(g); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on USB disable gadget udc\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out; + } + } + + /* Remove gadget with USBG_RM_RECURSE flag to remove + * also its configurations, functions and strings */ + usbg_ret = usbg_rm_gadget(g, USBG_RM_RECURSE); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on USB gadget remove\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + } + +out: + return usbg_ret; +} + +int main(void) +{ + int usbg_ret; + int ret = -EINVAL; + usbg_state *s; + usbg_gadget *g; + usbg_gadget_attrs g_attrs; + + usbg_ret = usbg_init("/sys/kernel/config", &s); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on USB state init\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out1; + } + + g = usbg_get_first_gadget(s); + while (g != NULL) { + /* Get current gadget attrs to be compared */ + usbg_ret = usbg_get_gadget_attrs(g, &g_attrs); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on USB get gadget attrs\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out2; + } + + /* Compare attrs with given values and remove if suitable */ + if (g_attrs.idVendor == VENDOR && g_attrs.idProduct == PRODUCT) { + usbg_gadget *g_next = usbg_get_next_gadget(g); + + usbg_ret = remove_gadget(g); + if (usbg_ret != USBG_SUCCESS) + goto out2; + + g = g_next; + } else { + g = usbg_get_next_gadget(g); + } + } + +out2: + usbg_cleanup(s); +out1: + return ret; +} diff --git a/examples/show-gadgets.c b/examples/show-gadgets.c index 1ae3860..d27cde5 100644 --- a/examples/show-gadgets.c +++ b/examples/show-gadgets.c @@ -29,33 +29,51 @@ void show_gadget(usbg_gadget *g) { - char buf[USBG_MAX_STR_LENGTH]; + const char *name, *udc; + usbg_udc *u; int usbg_ret; usbg_gadget_attrs g_attrs; usbg_gadget_strs g_strs; - usbg_get_gadget_name(g, buf, USBG_MAX_STR_LENGTH); + name = usbg_get_gadget_name(g); + if (!name) { + fprintf(stderr, "Unable to get gadget name\n"); + return; + } + usbg_ret = usbg_get_gadget_attrs(g, &g_attrs); if (usbg_ret != USBG_SUCCESS) { fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), - usbg_strerror(usbg_ret)); + usbg_strerror(usbg_ret)); return; } fprintf(stdout, "ID %04x:%04x '%s'\n", - g_attrs.idVendor, g_attrs.idProduct, buf); + g_attrs.idVendor, g_attrs.idProduct, name); - usbg_get_gadget_udc(g, buf, USBG_MAX_STR_LENGTH); - fprintf(stdout, " UDC\t\t\t%s\n", buf); + u = usbg_get_gadget_udc(g); + if (u) + /* gadget is enabled */ + udc = usbg_get_udc_name(u); + else + /* gadget is disabled */ + udc = "\0"; + + fprintf(stdout, " UDC\t\t\t%s\n", udc); + + fprintf(stdout, " bcdUSB\t\t%x.%02x\n", + g_attrs.bcdUSB >> 8, + g_attrs.bcdUSB & 0x00ff); fprintf(stdout, " bDeviceClass\t\t0x%02x\n", g_attrs.bDeviceClass); fprintf(stdout, " bDeviceSubClass\t0x%02x\n", g_attrs.bDeviceSubClass); fprintf(stdout, " bDeviceProtocol\t0x%02x\n", g_attrs.bDeviceProtocol); - fprintf(stdout, " bMaxPacketSize0\t0x%02x\n", g_attrs.bMaxPacketSize0); - fprintf(stdout, " bcdDevice\t\t0x%04x\n", g_attrs.bcdDevice); - fprintf(stdout, " bcdUSB\t\t0x%04x\n", g_attrs.bcdUSB); + fprintf(stdout, " bMaxPacketSize0\t%d\n", g_attrs.bMaxPacketSize0); fprintf(stdout, " idVendor\t\t0x%04x\n", g_attrs.idVendor); fprintf(stdout, " idProduct\t\t0x%04x\n", g_attrs.idProduct); + fprintf(stdout, " bcdDevice\t\t%x.%02x\n", + g_attrs.bcdDevice >> 8, + g_attrs.bcdDevice & 0x00ff); usbg_ret = usbg_get_gadget_strs(g, LANG_US_ENG, &g_strs); if (usbg_ret != USBG_SUCCESS) { @@ -63,19 +81,24 @@ void show_gadget(usbg_gadget *g) usbg_strerror(usbg_ret)); return; } - fprintf(stdout, " Serial Number\t\t%s\n", g_strs.str_ser); fprintf(stdout, " Manufacturer\t\t%s\n", g_strs.str_mnf); fprintf(stdout, " Product\t\t%s\n", g_strs.str_prd); + fprintf(stdout, " Serial Number\t\t%s\n", g_strs.str_ser); } void show_function(usbg_function *f) { - char instance[USBG_MAX_STR_LENGTH]; + const char *instance; usbg_function_type type; int usbg_ret; usbg_function_attrs f_attrs; - usbg_get_function_instance(f, instance, USBG_MAX_STR_LENGTH); + instance = usbg_get_function_instance(f); + if (!instance) { + fprintf(stderr, "Unable to get function instance name\n"); + return; + } + type = usbg_get_function_type(f); usbg_ret = usbg_get_function_attrs(f, &f_attrs); if (usbg_ret != USBG_SUCCESS) { @@ -86,46 +109,90 @@ void show_function(usbg_function *f) fprintf(stdout, " Function, type: %s instance: %s\n", usbg_get_function_type_str(type), instance); - switch (type) { - case F_SERIAL: - case F_ACM: - case F_OBEX: + + switch (f_attrs.header.attrs_type) { + case USBG_F_ATTRS_SERIAL: fprintf(stdout, " port_num\t\t%d\n", - f_attrs.serial.port_num); + f_attrs.attrs.serial.port_num); break; - case F_ECM: - case F_SUBSET: - case F_NCM: - case F_EEM: - case F_RNDIS: + + case USBG_F_ATTRS_NET: + { + usbg_f_net_attrs *f_net_attrs = &f_attrs.attrs.net; + fprintf(stdout, " dev_addr\t\t%s\n", - ether_ntoa(&f_attrs.net.dev_addr)); + ether_ntoa(&f_net_attrs->dev_addr)); fprintf(stdout, " host_addr\t\t%s\n", - ether_ntoa(&f_attrs.net.host_addr)); - fprintf(stdout, " ifname\t\t%s\n", f_attrs.net.ifname); - fprintf(stdout, " qmult\t\t%d\n", f_attrs.net.qmult); + ether_ntoa(&f_net_attrs->host_addr)); + fprintf(stdout, " ifname\t\t%s\n", f_net_attrs->ifname); + fprintf(stdout, " qmult\t\t%d\n", f_net_attrs->qmult); break; - case F_PHONET: - fprintf(stdout, " ifname\t\t%s\n", f_attrs.phonet.ifname); + } + + case USBG_F_ATTRS_PHONET: + fprintf(stdout, " ifname\t\t%s\n", f_attrs.attrs.phonet.ifname); break; + + case USBG_F_ATTRS_FFS: + fprintf(stdout, " dev_name\t\t%s\n", f_attrs.attrs.ffs.dev_name); + break; + + case USBG_F_ATTRS_MS: + { + usbg_f_ms_attrs *attrs = &f_attrs.attrs.ms; + int i; + + fprintf(stdout, " stall\t\t%d\n", attrs->stall); + fprintf(stdout, " nluns\t\t%d\n", attrs->nluns); + for (i = 0; i < attrs->nluns; ++i) { + fprintf(stdout, " lun %d:\n", attrs->luns[i]->id); + fprintf(stdout, " cdrom\t\t%d\n", attrs->luns[i]->cdrom); + fprintf(stdout, " ro\t\t%d\n", attrs->luns[i]->ro); + fprintf(stdout, " nofua\t\t%d\n", attrs->luns[i]->nofua); + fprintf(stdout, " removable\t\t%d\n", attrs->luns[i]->removable); + fprintf(stdout, " file\t\t%s\n", attrs->luns[i]->filename); + } + break; + } + + case USBG_F_ATTRS_MIDI: + { + usbg_f_midi_attrs *attrs = &f_attrs.attrs.midi; + + fprintf(stdout, " index\t\t%d\n", attrs->index); + fprintf(stdout, " id\t\t\t%s\n", attrs->id); + fprintf(stdout, " in_ports\t\t%d\n", attrs->in_ports); + fprintf(stdout, " out_ports\t\t%d\n", attrs->out_ports); + fprintf(stdout, " buflen\t\t%d\n", attrs->buflen); + fprintf(stdout, " qlen\t\t%d\n", attrs->qlen); + break; + } + default: fprintf(stdout, " UNKNOWN\n"); } + + usbg_cleanup_function_attrs(&f_attrs); } void show_config(usbg_config *c) { usbg_binding *b; usbg_function *f; - char buf[USBG_MAX_STR_LENGTH], instance[USBG_MAX_STR_LENGTH]; + const char *label, *instance, *bname; usbg_function_type type; usbg_config_attrs c_attrs; usbg_config_strs c_strs; int usbg_ret, id; - usbg_get_config_label(c, buf, USBG_MAX_STR_LENGTH); + label = usbg_get_config_label(c); + if (!label) { + fprintf(stderr, "Unable to get config label\n"); + return; + } + id = usbg_get_config_id(c); - fprintf(stdout, " Configuration: '%s' ID: %d\n", buf, id); + fprintf(stdout, " Configuration: '%s' ID: %d\n", label, id); usbg_ret = usbg_get_config_attrs(c, &c_attrs); if (usbg_ret != USBG_SUCCESS) { @@ -147,11 +214,15 @@ void show_config(usbg_config *c) fprintf(stdout, " configuration\t%s\n", c_strs.configuration); usbg_for_each_binding(b, c) { - usbg_get_binding_name(b, buf, USBG_MAX_STR_LENGTH); + bname = usbg_get_binding_name(b); f = usbg_get_binding_target(b); - usbg_get_function_instance(f, instance, USBG_MAX_STR_LENGTH); + instance = usbg_get_function_instance(f); type = usbg_get_function_type(f); - fprintf(stdout, " %s -> %s %s\n", buf, + if (!bname || !instance) { + fprintf(stderr, "Unable to get binding details\n"); + return; + } + fprintf(stdout, " %s -> %s %s\n", bname, usbg_get_function_type_str(type), instance); } } diff --git a/examples/show-udcs.c b/examples/show-udcs.c new file mode 100644 index 0000000..66e950f --- /dev/null +++ b/examples/show-udcs.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2014 Samsung Electronics + * + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * @file show-udcs.c + * @example show-udcs.c + * This is an example of how to learn about UDCs available in system + * and find out what gadget are enabled on them. + */ + +#include <errno.h> +#include <stdio.h> +#include <usbg/usbg.h> + +int main(void) +{ + int usbg_ret; + int ret = -EINVAL; + usbg_state *s; + usbg_gadget *g; + usbg_udc *u; + const char *udc_name, *gadget_name; + + usbg_ret = usbg_init("/sys/kernel/config", &s); + if (usbg_ret != USBG_SUCCESS) { + fprintf(stderr, "Error on USB state init\n"); + fprintf(stderr, "Error: %s : %s\n", usbg_error_name(usbg_ret), + usbg_strerror(usbg_ret)); + goto out; + } + + usbg_for_each_udc(u, s) { + udc_name = usbg_get_udc_name(u); + g = usbg_get_udc_gadget(u); + if (g) + /* some gadget is enabled */ + gadget_name = usbg_get_gadget_name(g); + else + gadget_name = ""; + + fprintf(stdout, "%s <-> %s\n", udc_name, gadget_name); + } + + ret = 0; + usbg_cleanup(s); +out: + return ret; +} diff --git a/include/usbg/usbg.h b/include/usbg/usbg.h index 1407f10..bcf221d 100644 --- a/include/usbg/usbg.h +++ b/include/usbg/usbg.h @@ -22,15 +22,20 @@ #include <netinet/ether.h> #include <stdint.h> #include <limits.h> +#include <stdbool.h> +#include <stdio.h> /* For FILE * */ + +#ifdef __cplusplus +extern "C" { +#endif /** * @file include/usbg/usbg.h - * @todo Add usbg_remove_[gadget|config|function|binding] APIs * @todo Clean up static buffers in structures */ /** - * @addtogroup libusbg + * @addtogroup libusbgx * Public API for USB gadget-configfs library * @{ */ @@ -39,9 +44,21 @@ #define LANG_US_ENG 0x0409 #define DEFAULT_CONFIG_LABEL "config" +/* This one has to be at least 18 bytes to hold network address */ #define USBG_MAX_STR_LENGTH 256 #define USBG_MAX_PATH_LENGTH PATH_MAX #define USBG_MAX_NAME_LENGTH 40 +/* Dev name for ffs is a part of function name, we subtract 4 char for "ffs." */ +#define USBG_MAX_DEV_LENGTH (USBG_MAX_NAME_LENGTH - 4) +/* ConfigFS just like SysFS uses page size as max size of file content */ +#define USBG_MAX_FILE_SIZE 4096 + +/** + * @brief Additional option for usbg_rm_* functions. + * @details This option allows to remove all content + * of gadget/config/function recursively. + */ +#define USBG_RM_RECURSE 1 /* * Internal structures @@ -51,6 +68,7 @@ struct usbg_gadget; struct usbg_config; struct usbg_function; struct usbg_binding; +struct usbg_udc; /** * @brief State of the gadget devices in the system @@ -78,6 +96,29 @@ typedef struct usbg_function usbg_function; typedef struct usbg_binding usbg_binding; /** + * @brief USB device controller + */ +typedef struct usbg_udc usbg_udc; + +/** + * @typedef usbg_gadget_attr + * @brief Gadget attributes which can be set using + * usbg_set_gadget_attr() function. + */ +typedef enum { + USBG_GADGET_ATTR_MIN = 0, + BCD_USB = USBG_GADGET_ATTR_MIN, + B_DEVICE_CLASS, + B_DEVICE_SUB_CLASS, + B_DEVICE_PROTOCOL, + B_MAX_PACKET_SIZE_0, + ID_VENDOR, + ID_PRODUCT, + BCD_DEVICE, + USBG_GADGET_ATTR_MAX, +} usbg_gadget_attr; + +/** * @typedef usbg_gadget_attrs * @brief USB gadget device attributes */ @@ -93,6 +134,14 @@ typedef struct uint16_t bcdDevice; } usbg_gadget_attrs; +typedef enum { + USBG_GADGET_STR_MIN = 0, + STR_PRODUCT = USBG_GADGET_STR_MIN, + STR_MANUFACTURER, + STR_SERIAL_NUMBER, + USBG_GADGET_STR_MAX, +} usbg_gadget_str; + /** * @typedef usbg_gadget_strs * @brief USB gadget device strings @@ -129,7 +178,8 @@ typedef struct */ typedef enum { - F_SERIAL, + USBG_FUNCTION_TYPE_MIN = 0, + F_SERIAL = USBG_FUNCTION_TYPE_MIN, F_ACM, F_OBEX, F_ECM, @@ -138,6 +188,11 @@ typedef enum F_EEM, F_RNDIS, F_PHONET, + F_FFS, + F_MASS_STORAGE, + F_MIDI, + F_LOOPBACK, + USBG_FUNCTION_TYPE_MAX, } usbg_function_type; /** @@ -155,7 +210,7 @@ typedef struct { typedef struct { struct ether_addr dev_addr; struct ether_addr host_addr; - char ifname[USBG_MAX_STR_LENGTH]; + const char *ifname; int qmult; } usbg_f_net_attrs; @@ -164,10 +219,66 @@ typedef struct { * @brief Attributes for the phonet USB function */ typedef struct { - char ifname[USBG_MAX_STR_LENGTH]; + const char *ifname; } usbg_f_phonet_attrs; /** + * @typedef usbg_f_ffs_attrs + * @brief Attributes for function fs based functions + * @details This is read only and a virtual attribute, it is non present + * on config fs. + */ +typedef struct { + const char *dev_name; +} usbg_f_ffs_attrs; + +/** + * @typedef usbg_f_ms_attrs + * @brief Attributes for mass storage functions + */ +typedef struct usbg_f_ms_lun_attrs { + int id; + bool cdrom; + bool ro; + bool nofua; + bool removable; + const char *filename; +} usbg_f_ms_lun_attrs; + +/** + * @typedef usbg_f_ms_attrs + * @brief Attributes for mass storage functions + */ +typedef struct { + bool stall; + int nluns; + usbg_f_ms_lun_attrs **luns; +} usbg_f_ms_attrs; + +/** + * @typedef usbg_f_midi_attrs + * @brief Attributes for the MIDI function + */ +typedef struct { + int index; + const char *id; + unsigned int in_ports; + unsigned int out_ports; + unsigned int buflen; + unsigned int qlen; +} usbg_f_midi_attrs; + + +/** + * @typedef usbg_f_loopback_attrs + * @brief Attributes for Loopback function + */ +typedef struct { + unsigned int buflen; + unsigned int qlen; +} usbg_f_loopback_attrs; + +/** * @typedef attrs * @brief Attributes for a given function type */ @@ -175,6 +286,29 @@ typedef union { usbg_f_serial_attrs serial; usbg_f_net_attrs net; usbg_f_phonet_attrs phonet; + usbg_f_ffs_attrs ffs; + usbg_f_ms_attrs ms; + usbg_f_midi_attrs midi; + usbg_f_loopback_attrs loopback; +} usbg_f_attrs; + +typedef enum { + USBG_F_ATTRS_SERIAL = 1, + USBG_F_ATTRS_NET, + USBG_F_ATTRS_PHONET, + USBG_F_ATTRS_FFS, + USBG_F_ATTRS_MS, + USBG_F_ATTRS_MIDI, + USBG_F_ATTRS_LOOPBACK, +} usbg_f_attrs_type; + +typedef struct { + int attrs_type; +} usbg_f_attrs_header; + +typedef struct { + usbg_f_attrs_header header; + usbg_f_attrs attrs; } usbg_function_attrs; /* Error codes */ @@ -195,17 +329,22 @@ typedef enum { USBG_ERROR_BUSY = -8, USBG_ERROR_NOT_SUPPORTED = -9, USBG_ERROR_PATH_TOO_LONG = -10, + USBG_ERROR_INVALID_FORMAT = -11, + USBG_ERROR_MISSING_TAG = -12, + USBG_ERROR_INVALID_TYPE = -13, + USBG_ERROR_INVALID_VALUE = -14, + USBG_ERROR_NOT_EMPTY = -15, USBG_ERROR_OTHER_ERROR = -99 } usbg_error; -/* +/** * @brief Get the error name as a constant string * @param e error code * @return Constant string with error name */ extern const char *usbg_error_name(usbg_error e); -/* +/** * @brief Get the short description of error * @param e error code * @return Constant string with error description @@ -215,20 +354,30 @@ extern const char *usbg_strerror(usbg_error e); /* Library init and cleanup */ /** - * @brief Initialize the libusbg library state + * @brief Initialize the libusbgx library state * @param configfs_path Path to the mounted configfs filesystem - * @param Pointer to be filled with pointer to usbg_state + * @param state Pointer to be filled with pointer to usbg_state * @return 0 on success, usbg_error on error */ -extern int usbg_init(char *configfs_path, usbg_state **state); +extern int usbg_init(const char *configfs_path, usbg_state **state); /** - * @brief Clean up the libusbg library state + * @brief Clean up the libusbgx library state * @param s Pointer to state */ extern void usbg_cleanup(usbg_state *s); /** + * @brief Get ConfigFS path + * @param s Pointer to state + * @return Path to configfs or NULL if error occurred + * @warning Returned buffer should not be edited! + * Returned string is valid as long as passed usbg_state is valid. + * For example path is valid until usbg_cleanup() call. + */ +extern const char *usbg_get_configfs_path(usbg_state *s); + +/** * @brief Get ConfigFS path length * @param s Pointer to state * @return Length of path or usbg_error if error occurred. @@ -236,13 +385,13 @@ extern void usbg_cleanup(usbg_state *s); extern size_t usbg_get_configfs_path_len(usbg_state *s); /** - * @brieg Get ConfigFS path + * @brief Copy ConfigFS path to buffer * @param s Pointer to state * @param buf Buffer where path should be copied * @param len Length of given buffer * @return 0 on success or usbg_error if error occurred. */ -extern int usbg_get_configfs_path(usbg_state *s, char *buf, size_t len); +extern int usbg_cpy_configfs_path(usbg_state *s, char *buf, size_t len); /* USB gadget queries */ @@ -276,6 +425,67 @@ extern usbg_function *usbg_get_function(usbg_gadget *g, */ extern usbg_config *usbg_get_config(usbg_gadget *g, int id, const char *label); +/** + * @brief Get a udc by name + * @param s Pointer to state + * @param name Name of the udc + * @return Pointer to udc or NULL if a matching udc isn't found + */ +extern usbg_udc *usbg_get_udc(usbg_state *s, const char *name); + +/* USB gadget/config/function/binding removal */ + +/** + * @brief Remove binding between configuration and function + * @details This function frees also the memory allocated for binding + * @param b Binding to be removed + * @return 0 on success, usbg_error if error occurred + */ +extern int usbg_rm_binding(usbg_binding *b); + +/** + * @brief Remove configuration + * @details This function frees also the memory allocated for configuration + * @param c Configuration to be removed + * @param opts Additional options for configuration removal. + * @return 0 on success, usbg_error if error occurred + */ +extern int usbg_rm_config(usbg_config *c, int opts); + +/** + * @brief Remove existing USB function + * @details This function frees also the memory allocated for function + * @param f Function to be removed + * @param opts Additional options for configuration removal. + * @return 0 on success, usbg_error if error occurred + */ +extern int usbg_rm_function(usbg_function *f, int opts); + +/** + * @brief Remove existing USB gadget + * @details This function frees also the memory allocated for gadget + * @param g Gadget to be removed + * @param opts Additional options for configuration removal. + * @return 0 on success, usbg_error if error occurred + */ +extern int usbg_rm_gadget(usbg_gadget *g, int opts); + +/** + * @brief Remove configuration strings for given language + * @param c Pointer to configuration + * @param lang Language of strings which should be deleted + * @return 0 on success, usbg_error if error occurred + */ +extern int usbg_rm_config_strs(usbg_config *c, int lang); + +/** + * @brief Remove gadget strings for given language + * @param g Pointer to gadget + * @param lang Language of strings which should be deleted + * @return 0 on success, usbg_error if error occurred + */ +extern int usbg_rm_gadget_strs(usbg_gadget *g, int lang); + /* USB gadget allocation and configuration */ /** @@ -287,7 +497,7 @@ extern usbg_config *usbg_get_config(usbg_gadget *g, int id, const char *label); * @param g Pointer to be filled with pointer to gadget * @return 0 on success usbg_error if error occurred */ -extern int usbg_create_gadget_vid_pid(usbg_state *s, char *name, +extern int usbg_create_gadget_vid_pid(usbg_state *s, const char *name, uint16_t idVendor, uint16_t idProduct, usbg_gadget **g); /** @@ -301,8 +511,60 @@ extern int usbg_create_gadget_vid_pid(usbg_state *s, char *name, * @note Given strings are assumed to be in US English * @return 0 on success usbg_error if error occurred */ -extern int usbg_create_gadget(usbg_state *s, char *name, - usbg_gadget_attrs *g_attrs, usbg_gadget_strs *g_strs, usbg_gadget **g); +extern int usbg_create_gadget(usbg_state *s, const char *name, + const usbg_gadget_attrs *g_attrs, const usbg_gadget_strs *g_strs, + usbg_gadget **g); + +/** + * @brief Get string representing selected gadget attribute + * @param attr code of selected attribute + * @return String suitable for given attribute or NULL if such + * string has not been found + */ +extern const char *usbg_get_gadget_attr_str(usbg_gadget_attr attr); + +/** + * @brief Lookup attr code based on its name + * @param name of attribute + * @return code of suitable attribute or usbg_error + */ +extern int usbg_lookup_gadget_attr(const char *name); + +/** + * @brief Lookup str code based on its name + * @param name of string + * @return code of suitable string or usbg_error + */ +extern int usbg_lookup_gadget_str(const char *name); + +/** + * @brief Get name of selected gadget string + * @param str Gadget string code + * @return Name of string associated with this code + */ +extern const char *usbg_get_gadget_str_name(usbg_gadget_str str); + +/** + * @brief Set selected attribute to value + * @param g Pointer to gadget + * @param attr Code of selected attribute + * @param val value to be set + * @return 0 on success, usbg_error otherwise + * @note val is of type int but value provided to this function should + * be suitable to place it in type dedicated for selected attr (uint16 or uint8) + */ +extern int usbg_set_gadget_attr(usbg_gadget *g, usbg_gadget_attr attr, int val); + +/** + * @brief Get value of selected attribute + * @param g Pointer to gadget + * @param attr Code of selected attribute + * @return Value of selected attribute (always above zero) or + * usbg_error if error occurred. + * @note User should use only lowest one or two bytes as attribute value + * depending on attribute size (see usbg_gadget_attrs for details). + */ +extern int usbg_get_gadget_attr(usbg_gadget *g, usbg_gadget_attr attr); /** * @brief Set the USB gadget attributes @@ -311,7 +573,7 @@ extern int usbg_create_gadget(usbg_state *s, char *name, * @return 0 on success usbg_error if error occurred */ extern int usbg_set_gadget_attrs(usbg_gadget *g, - usbg_gadget_attrs *g_attrs); + const usbg_gadget_attrs *g_attrs); /** * @brief Get the USB gadget strings @@ -322,6 +584,16 @@ extern int usbg_set_gadget_attrs(usbg_gadget *g, extern int usbg_get_gadget_attrs(usbg_gadget *g, usbg_gadget_attrs *g_attrs); /** + * @brief Get gadget name + * @param g Pointer to gadget + * @return Gadget name or NULL if error occurred. + * @warning Returned buffer should not be edited! + * Returned string is valid as long as passed usbg_gadget is valid. + * For example gadget name is valid until someone remove gadget. + */ +extern const char *usbg_get_gadget_name(usbg_gadget *g); + +/** * @brief Get gadget name length * @param g Gadget which name length should be returned * @return Length of name string or usbg_error if error occurred. @@ -329,13 +601,13 @@ extern int usbg_get_gadget_attrs(usbg_gadget *g, usbg_gadget_attrs *g_attrs); extern size_t usbg_get_gadget_name_len(usbg_gadget *g); /** - * @brieg Get gadget name - * @param b Pointer to gadget + * @brief Copy gadget name + * @param g Pointer to gadget * @param buf Buffer where name should be copied * @param len Length of given buffer * @return 0 on success or usbg_error if error occurred. */ -extern int usbg_get_gadget_name(usbg_gadget *g, char *buf, size_t len); +extern int usbg_cpy_gadget_name(usbg_gadget *g, char *buf, size_t len); /** * @brief Set the USB gadget vendor id @@ -410,21 +682,31 @@ extern int usbg_set_gadget_device_bcd_usb(usbg_gadget *g, uint16_t bcdUSB); * @brief Get the USB gadget strings * @param g Pointer to gadget * @param lang Language of strings - * @param g_sttrs Structure to be filled + * @param g_strs Structure to be filled * @return 0 on success usbg_error if error occurred */ extern int usbg_get_gadget_strs(usbg_gadget *g, int lang, usbg_gadget_strs *g_strs); /** + * @brief Set selected string + * @param g Pointer to gadget + * @param str Code of selected string + * @param val value to be set + * @return 0 on success, usbg_error otherwise + */ +extern int usbg_set_gadget_str(usbg_gadget *g, usbg_gadget_str str, int lang, + const char *val); + +/** * @brief Set the USB gadget strings * @param g Pointer to gadget * @param lang USB language ID - * @param g_sttrs Gadget attributes + * @param g_strs Gadget attributes * @return 0 on success usbg_error if error occurred */ extern int usbg_set_gadget_strs(usbg_gadget *g, int lang, - usbg_gadget_strs *g_strs); + const usbg_gadget_strs *g_strs); /** * @brief Set the serial number for a gadget @@ -433,7 +715,8 @@ extern int usbg_set_gadget_strs(usbg_gadget *g, int lang, * @param ser Serial number * @return 0 on success usbg_error if error occurred */ -extern int usbg_set_gadget_serial_number(usbg_gadget *g, int lang, char *ser); +extern int usbg_set_gadget_serial_number(usbg_gadget *g, int lang, + const char *ser); /** * @brief Set the manufacturer name for a gadget @@ -442,7 +725,8 @@ extern int usbg_set_gadget_serial_number(usbg_gadget *g, int lang, char *ser); * @param mnf Manufacturer * @return 0 on success usbg_error if error occurred */ -extern int usbg_set_gadget_manufacturer(usbg_gadget *g, int lang, char *mnf); +extern int usbg_set_gadget_manufacturer(usbg_gadget *g, int lang, + const char *mnf); /** * @brief Set the product name for a gadget @@ -451,7 +735,8 @@ extern int usbg_set_gadget_manufacturer(usbg_gadget *g, int lang, char *mnf); * @param prd Product * @return 0 on success usbg_error if error occurred */ -extern int usbg_set_gadget_product(usbg_gadget *g, int lang, char *prd); +extern int usbg_set_gadget_product(usbg_gadget *g, int lang, + const char *prd); /* USB function allocation and configuration */ @@ -466,7 +751,18 @@ extern int usbg_set_gadget_product(usbg_gadget *g, int lang, char *prd); * @return 0 on success usbg_error if error occurred */ extern int usbg_create_function(usbg_gadget *g, usbg_function_type type, - char *instance, usbg_function_attrs *f_attrs, usbg_function **f); + const char *instance, const usbg_function_attrs *f_attrs, + usbg_function **f); + +/** + * @brief Get function instance name + * @param f Pointer to function + * @return instance name or NULL if error occurred. + * @warning Returned buffer should not be edited! + * Returned string is valid as long as passed usbg_function is valid. + * For example instance name is valid until someone remove this function. + */ +extern const char *usbg_get_function_instance(usbg_function *f); /** * @brief Get function instance name length @@ -476,13 +772,13 @@ extern int usbg_create_function(usbg_gadget *g, usbg_function_type type, extern size_t usbg_get_function_instance_len(usbg_function *f); /** - * @brief Get function instance name + * @brief Copy function instance name * @param f Pointer to function * @param buf Buffer where instance name should be copied * @param len Length of given buffer * @return 0 on success or usbg_error if error occurred. */ -extern int usbg_get_function_instance(usbg_function *f, char *buf, size_t len); +extern int usbg_cpy_function_instance(usbg_function *f, char *buf, size_t len); /** * @brief Get function type as a string @@ -491,6 +787,30 @@ extern int usbg_get_function_instance(usbg_function *f, char *buf, size_t len); */ extern const char *usbg_get_function_type_str(usbg_function_type type); +/** + * @brief Lookup function type suitable for given name + * @param name Name of function + * @return Function type enum or negative error code + */ +extern int usbg_lookup_function_type(const char *name); + +/** + * @brief Lookup attrs type for given type of function + * @param f_type type of functions + * @return Attributes type for this type of function + */ +extern int usbg_lookup_function_attrs_type(int f_type); + +/** + * @brief Cleanup content of function attributes + * @param f_attrs function attributes which should be cleaned up. + * @note This function should be called to free + * additional memory allocated by usbg_get_function_attrs(). + * @warning None of attributes in passed structure should be + * accessed after returning from this function. + */ +extern void usbg_cleanup_function_attrs(usbg_function_attrs *f_attrs); + /* USB configurations allocation and configuration */ /** @@ -505,7 +825,18 @@ extern const char *usbg_get_function_type_str(usbg_function_type type); * @return 0 on success usbg_error if error occurred */ extern int usbg_create_config(usbg_gadget *g, int id, const char *label, - usbg_config_attrs *c_attrs, usbg_config_strs *c_strs, usbg_config **c); + const usbg_config_attrs *c_attrs, const usbg_config_strs *c_strs, + usbg_config **c); + +/** + * @brief Get config label + * @param c Pointer to config + * @return config label or NULL if error occurred. + * @warning Returned buffer should not be edited! + * Returned string is valid as long as passed usbg_config is valid. + * For example config label is valid until someone remove this function. + */ +extern const char *usbg_get_config_label(usbg_config *c); /** * @brief Get config label length @@ -515,16 +846,16 @@ extern int usbg_create_config(usbg_gadget *g, int id, const char *label, extern size_t usbg_get_config_label_len(usbg_config *c); /** - * @brieg Get config label + * @brief Copy config label * @param c Pointer to config * @param buf Buffer where label should be copied * @param len Length of given buffer * @return 0 on success or usbg_error if error occurred. */ -extern int usbg_get_config_label(usbg_config *c, char *buf, size_t len); +extern int usbg_cpy_config_label(usbg_config *c, char *buf, size_t len); /** - * @brieg Get config id + * @brief Get config id * @param c Pointer to config * @return Configuration id or usbg_error if error occurred. */ @@ -537,7 +868,7 @@ extern int usbg_get_config_id(usbg_config *c); * @return 0 on success or usbg_error if error occurred. */ extern int usbg_set_config_attrs(usbg_config *c, - usbg_config_attrs *c_attrs); + const usbg_config_attrs *c_attrs); /** * @brief Get the USB configuration strings @@ -567,7 +898,7 @@ extern int usbg_set_config_bm_attrs(usbg_config *c, int bmAttributes); * @brief Get the USB configuration strings * @param c Pointer to configuration * @param lang Language of strings - * @param c_sttrs Structure to be filled + * @param c_strs Structure to be filled * @return 0 on success or usbg_error if error occurred. */ extern int usbg_get_config_strs(usbg_config *c, int lang, @@ -577,11 +908,11 @@ extern int usbg_get_config_strs(usbg_config *c, int lang, * @brief Set the USB configuration strings * @param c Pointer to configuration * @param lang USB language ID - * @param c_sttrs Configuration strings + * @param c_strs Configuration strings * @return 0 on success, usbg_error on failure. */ extern int usbg_set_config_strs(usbg_config *c, int lang, - usbg_config_strs *c_strs); + const usbg_config_strs *c_strs); /** * @brief Set the configuration string @@ -590,7 +921,7 @@ extern int usbg_set_config_strs(usbg_config *c, int lang, * @param string Configuration description * @return 0 on success, usbg_error on failure. */ -extern int usbg_set_config_string(usbg_config *c, int lang, char *string); +extern int usbg_set_config_string(usbg_config *c, int lang, const char *string); /** * @brief Add a function to a configuration @@ -599,7 +930,8 @@ extern int usbg_set_config_string(usbg_config *c, int lang, char *string); * @param f Pointer to function * @return 0 on success, usbg_error on failure. */ -extern int usbg_add_config_function(usbg_config *c, char *name, usbg_function *f); +extern int usbg_add_config_function(usbg_config *c, const char *name, + usbg_function *f); /** * @brief Get target function of given binding @@ -609,6 +941,16 @@ extern int usbg_add_config_function(usbg_config *c, char *name, usbg_function *f extern usbg_function *usbg_get_binding_target(usbg_binding *b); /** + * @brief Get binding name + * @param b Pointer to binding + * @return Binding name or NULL if error occurred. + * @warning Returned buffer should not be edited! + * Returned string is valid as long as passed usbg_binding is valid. + * For example binding name is valid until someone remove this binding. + */ +extern const char *usbg_get_binding_name(usbg_binding *b); + +/** * @brief Get binding name length * @param b Binding which name length should be returned * @return Length of name string or usbg_error if error occurred. @@ -616,30 +958,24 @@ extern usbg_function *usbg_get_binding_target(usbg_binding *b); extern size_t usbg_get_binding_name_len(usbg_binding *b); /** - * @brief Get binding name + * @brief Copy binding name * @param b Pointer to binding * @param buf Buffer where name should be copied * @param len Length of given buffer * @return 0 on success or usbg_error if error occurred. */ -extern int usbg_get_binding_name(usbg_binding *b, char *buf, size_t len); +extern int usbg_cpy_binding_name(usbg_binding *b, char *buf, size_t len); /* USB gadget setup and teardown */ /** - * @brief Get a list of UDC devices on the system - * @param udc_list Pointer to pointer to dirent pointer - * @return Number of UDC devices on success, usbg_error on failure - */ -extern int usbg_get_udcs(struct dirent ***udc_list); - -/** * @brief Enable a USB gadget device * @param g Pointer to gadget - * @param udc Name of UDC to enable gadget + * @param udc where gadget should be assigned. + * If NULL, default one (first) is used. * @return 0 on success or usbg_error if error occurred. */ -extern int usbg_enable_gadget(usbg_gadget *g, char *udc); +extern int usbg_enable_gadget(usbg_gadget *g, usbg_udc *udc); /** * @brief Disable a USB gadget device @@ -649,6 +985,16 @@ extern int usbg_enable_gadget(usbg_gadget *g, char *udc); extern int usbg_disable_gadget(usbg_gadget *g); /** + * @brief Get name of udc + * @param u Pointer to udc + * @return UDC name or NULL if error occurred. + * @warning Returned buffer should not be edited! + * Returned string is valid as long as passed usbg_state is valid. + * For example UDC name is valid until usbg_cleanup(). + */ +extern const char *usbg_get_udc_name(usbg_udc *u); + +/** * @brief Get gadget name length * @param g Gadget which name length should be returned * @return Length of name string or usbg_error if error occurred. @@ -657,14 +1003,27 @@ extern int usbg_disable_gadget(usbg_gadget *g); extern size_t usbg_get_gadget_udc_len(usbg_gadget *g); /** - * @brieg Get name of udc to which gadget is binded - * @param b Pointer to gadget + * @brief Copy name of udc + * @param u Pointer to udc * @param buf Buffer where udc name should be copied * @param len Length of given buffer * @return 0 on success or usbg_error if error occurred. - * @note If gadget isn't enabled on any udc returned string is empty. */ -extern int usbg_get_gadget_udc(usbg_gadget *g, char *buf, size_t len); +extern int usbg_cpy_udc_name(usbg_udc *u, char *buf, size_t len); + +/** + * @brief Get udc to which gadget is bound + * @param g Pointer to gadget + * @return Pointer to UDC or NULL if gadget is not enabled + */ +extern usbg_udc *usbg_get_gadget_udc(usbg_gadget *g); + +/** + * @brief Get gadget which is attached to this UDC + * @param u Pointer to udc + * @return Pointer to gadget or NULL if UDC is free + */ +extern usbg_gadget *usbg_get_udc_gadget(usbg_udc *u); /* * USB function-specific attribute configuration @@ -693,7 +1052,8 @@ extern int usbg_get_function_attrs(usbg_function *f, * @param f_attrs Attributes to be set * @return 0 on success, usbg_error if error occurred */ -extern int usbg_set_function_attrs(usbg_function *f, usbg_function_attrs *f_attrs); +extern int usbg_set_function_attrs(usbg_function *f, + const usbg_function_attrs *f_attrs); /** * @brief Set USB function network device address @@ -756,6 +1116,15 @@ extern int usbg_set_net_qmult(usbg_function *f, int qmult); b = usbg_get_next_binding(b)) /** + * @def usbg_for_each_udc(b, c) + * Iterates over each udc + */ +#define usbg_for_each_udc(u, s) \ + for (u = usbg_get_first_udc(s); \ + u != NULL; \ + u = usbg_get_next_udc(u)) + +/** * @brief Get first gadget in gadget list * @param s State of library * @return Pointer to gadget or NULL if list is empty. @@ -781,41 +1150,164 @@ extern usbg_config *usbg_get_first_config(usbg_gadget *g); /** * @brief Get first binding in binding list - * @param C Pointer to configuration + * @param c Pointer to configuration * @return Pointer to binding or NULL if list is empty. * @note Bindings are sorted in strings (name) order */ extern usbg_binding *usbg_get_first_binding(usbg_config *c); /** + * @brief Get first udc in udc list + * @param s State of library + * @return Pointer to udc or NULL if list is empty. + * @note UDCs are sorted in strings (name) order + */ +extern usbg_udc *usbg_get_first_udc(usbg_state *s); + +/** * @brief Get the next gadget on a list. - * @pram g Pointer to current gadget + * @param g Pointer to current gadget * @return Next gadget or NULL if end of list. */ extern usbg_gadget *usbg_get_next_gadget(usbg_gadget *g); /** * @brief Get the next function on a list. - * @pram g Pointer to current function + * @param f Pointer to current function * @return Next function or NULL if end of list. */ extern usbg_function *usbg_get_next_function(usbg_function *f); /** * @brief Get the next config on a list. - * @pram g Pointer to current config + * @param c Pointer to current config * @return Next config or NULL if end of list. */ extern usbg_config *usbg_get_next_config(usbg_config *c); /** * @brief Get the next binding on a list. - * @pram g Pointer to current binding + * @param b Pointer to current binding * @return Next binding or NULL if end of list. */ extern usbg_binding *usbg_get_next_binding(usbg_binding *b); /** + * @brief Get the next udc on a list. + * @param u Pointer to current udc + * @return Next udc or NULL if end of list. + */ +extern usbg_udc *usbg_get_next_udc(usbg_udc *u); + +/* Import / Export API */ + +/** + * @brief Exports usb function to file + * @param f Pointer to function to be exported + * @param stream where function should be saved + * @return 0 on success, usbg_error otherwise + */ +extern int usbg_export_function(usbg_function *f, FILE *stream); + +/** + * @brief Exports configuration to file + * @param c Pointer to configuration to be exported + * @param stream where configuration should be saved + * @return 0 on success, usbg_error otherwise + */ +extern int usbg_export_config(usbg_config *c, FILE *stream); + +/** + * @brief Exports whole gadget to file + * @param g Pointer to gadget to be exported + * @param stream where gadget should be saved + * @return 0 on success, usbg_error otherwise + */ +extern int usbg_export_gadget(usbg_gadget *g, FILE *stream); + +/** + * @brief Imports usb function from file and adds it to given gadget + * @param g Gadget where function should be placed + * @param stream from which function should be imported + * @param instance name which should be used for new function + * @param f place for pointer to imported function + * if NULL this param will be ignored. + * @return 0 on success, usbg_error otherwise + */ +extern int usbg_import_function(usbg_gadget *g, FILE *stream, + const char *instance, usbg_function **f); + +/** + * @brief Imports usb configuration from file and adds it to given gadget + * @param g Gadget where configuration should be placed + * @param stream from which configuration should be imported + * @param id which should be used for new configuration + * @param c place for pointer to imported configuration + * if NULL this param will be ignored. + * @return 0 on success, usbg_error otherwise + */ +extern int usbg_import_config(usbg_gadget *g, FILE *stream, int id, + usbg_config **c); +/** + * @brief Imports usb gadget from file + * @param s current state of library + * @param stream from which gadget should be imported + * @param name which should be used for new gadget + * @param g place for pointer to imported gadget + * if NULL this param will be ignored. + * @return 0 on success, usbg_error otherwise + */ +extern int usbg_import_gadget(usbg_state *s, FILE *stream, + const char *name, usbg_gadget **g); + +/** + * @brief Get text of error which occurred during last function import + * @param g gadget where function import error occurred + * @return Text of error or NULL if no error data + */ +extern const char *usbg_get_func_import_error_text(usbg_gadget *g); + +/** + * @brief Get line number where function import error occurred + * @param g gadget where function import error occurred + * @return line number or value below 0 if no error data + */ +extern int usbg_get_func_import_error_line(usbg_gadget *g); + +/** + * @brief Get text of error which occurred during last config import + * @param g gadget where config import error occurred + * @return Text of error or NULL if no error data + */ +extern const char *usbg_get_config_import_error_text(usbg_gadget *g); + +/** + * @brief Get line number where config import error occurred + * @param g gadget where config import error occurred + * @return line number or value below 0 if no error data + */ +extern int usbg_get_config_import_error_line(usbg_gadget *g); + +/** + * @brief Get text of error which occurred during last gadget import + * @param s where gadget import error occurred + * @return Text of error or NULL if no error data + */ +extern const char *usbg_get_gadget_import_error_text(usbg_state *s); + +/** + * @brief Get line number where gadget import error occurred + * @param s where gadget import error occurred + * @return line number or value below 0 if no error data + */ +extern int usbg_get_gadget_import_error_line(usbg_state *s); + +/** * @} */ + +#ifdef __cplusplus +} +#endif + #endif /* __USBG_H__ */ diff --git a/include/usbg/usbg_internal.h b/include/usbg/usbg_internal.h new file mode 100644 index 0000000..30d3fcf --- /dev/null +++ b/include/usbg/usbg_internal.h @@ -0,0 +1,174 @@ +/* + * 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. + */ + +#ifndef USBG_INTERNAL_H +#define USBG_INTERNAL_H + +#include <sys/queue.h> +#include <string.h> +#include <usbg/usbg.h> + +#ifdef HAS_LIBCONFIG +#include <libconfig.h> +#else + typedef struct _should_not_be_used config_t; + void config_destroy(config_t *config); +#endif + +/** + * @file include/usbg/usbg_internal.h + */ + +#ifndef offsetof +#define offsetof(type, member) __builtin_offsetof (type, member) +#endif /* offsetof */ + +#ifndef container_of +#define container_of(ptr, type, field) ({ \ + const typeof(((type *)0)->field) *member = (ptr); \ + (type *)( (char *)member - offsetof(type, field) ); \ + }) +#endif /* container_of */ + +struct usbg_state +{ + char *path; + char *configfs_path; + + TAILQ_HEAD(ghead, usbg_gadget) gadgets; + TAILQ_HEAD(uhead, usbg_udc) udcs; + config_t *last_failed_import; +}; + +struct usbg_gadget +{ + char *name; + char *path; + + TAILQ_ENTRY(usbg_gadget) gnode; + TAILQ_HEAD(chead, usbg_config) configs; + TAILQ_HEAD(fhead, usbg_function) functions; + usbg_state *parent; + config_t *last_failed_import; + usbg_udc *udc; +}; + +struct usbg_config +{ + TAILQ_ENTRY(usbg_config) cnode; + TAILQ_HEAD(bhead, usbg_binding) bindings; + usbg_gadget *parent; + + char *name; + char *path; + char *label; + int id; +}; + +typedef int (*usbg_rm_function_callback)(usbg_function *, int); + +struct usbg_function +{ + TAILQ_ENTRY(usbg_function) fnode; + usbg_gadget *parent; + + char *name; + char *path; + char *instance; + /* Only for internal library usage */ + char *label; + usbg_function_type type; + usbg_rm_function_callback rm_callback; +}; + +struct usbg_binding +{ + TAILQ_ENTRY(usbg_binding) bnode; + usbg_config *parent; + usbg_function *target; + + char *name; + char *path; +}; + +struct usbg_udc +{ + TAILQ_ENTRY(usbg_udc) unode; + usbg_state *parent; + usbg_gadget *gadget; + + char *name; +}; + +#define ARRAY_SIZE(array) (sizeof(array)/sizeof(*array)) + +#define ARRAY_SIZE_SENTINEL(array, size) \ + static void __attribute__ ((unused)) array##_size_sentinel() \ + { \ + char array##_smaller_than_expected[ \ + (int)(ARRAY_SIZE(array) - size)] \ + __attribute__ ((unused)); \ + \ + char array##_larger_than_expected[ \ + (int)(size - ARRAY_SIZE(array))] \ + __attribute__ ((unused)); \ + } + +#define ERROR(msg, ...) do {\ + fprintf(stderr, "%s() "msg" \n", \ + __func__, ##__VA_ARGS__);\ + fflush(stderr);\ + } while (0) + +#define ERRORNO(msg, ...) do {\ + fprintf(stderr, "%s() %s: "msg" \n", \ + __func__, strerror(errno), ##__VA_ARGS__);\ + fflush(stderr);\ + } while (0) + +/* Insert in string order */ +#define INSERT_TAILQ_STRING_ORDER(HeadPtr, HeadType, NameField, ToInsert, NodeField) \ + do { \ + if (TAILQ_EMPTY((HeadPtr)) || \ + (strcmp((ToInsert)->NameField, TAILQ_FIRST((HeadPtr))->NameField) < 0)) \ + TAILQ_INSERT_HEAD((HeadPtr), (ToInsert), NodeField); \ + else if (strcmp((ToInsert)->NameField, TAILQ_LAST((HeadPtr), HeadType)->NameField) > 0) \ + TAILQ_INSERT_TAIL((HeadPtr), (ToInsert), NodeField); \ + else { \ + typeof(ToInsert) _cur; \ + TAILQ_FOREACH(_cur, (HeadPtr), NodeField) { \ + if (strcmp((ToInsert)->NameField, _cur->NameField) > 0) \ + continue; \ + TAILQ_INSERT_BEFORE(_cur, (ToInsert), NodeField); \ + } \ + } \ + } while (0) + +#define STRINGS_DIR "strings" +#define CONFIGS_DIR "configs" +#define FUNCTIONS_DIR "functions" +#define GADGETS_DIR "usb_gadget" + +static inline int file_select(const struct dirent *dent) +{ + if ((strcmp(dent->d_name, ".") == 0) || (strcmp(dent->d_name, "..") == 0)) + return 0; + else + return 1; +} + +int usbg_translate_error(int error); + +char *usbg_ether_ntoa_r(const struct ether_addr *addr, char *buf); + +#endif /* USBG_INTERNAL_H */ + diff --git a/libusbg.pc.in b/libusbgx.pc.in index 46eb245..8ea8fb5 100644 --- a/libusbg.pc.in +++ b/libusbgx.pc.in @@ -3,9 +3,9 @@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ -Name: libusbg +Name: libusbgx Description: USB gadget-configfs library -Requires: +Requires: libconfig Version: @PACKAGE_VERSION@ Libs: -L${libdir} -lusbg Cflags: -I${includedir} diff --git a/src/Makefile.am b/src/Makefile.am index d955a4c..b1eb951 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,11 @@ -lib_LTLIBRARIES = libusbg.la -libusbg_la_SOURCES = usbg.c -libusbg_la_LDFLAGS = -version-info 0:1:0 -AM_CPPFLAGS=-I../include/ +lib_LTLIBRARIES = libusbgx.la +libusbgx_la_SOURCES = usbg.c +if TEST_GADGET_SCHEMES +libusbgx_la_SOURCES += usbg_schemes_libconfig.c +else +libusbgx_la_SOURCES += usbg_schemes_none.c +endif +libusbgx_la_LDFLAGS = $(LIBCONFIG_LIBS) +libusbgx_la_LDFLAGS += -version-info 0:0:0 +libusbgx_la_CFLAGS = $(LIBCONFIG_CFLAGS) +AM_CPPFLAGS=-I$(top_srcdir)/include/ @@ -16,7 +16,7 @@ #include <dirent.h> #include <errno.h> -#include <usbg/usbg.h> + #include <netinet/ether.h> #include <stdio.h> #include <stdlib.h> @@ -26,68 +26,13 @@ #include <sys/stat.h> #include <unistd.h> #include <ctype.h> - -#define STRINGS_DIR "strings" -#define CONFIGS_DIR "configs" -#define FUNCTIONS_DIR "functions" +#include <stdbool.h> +#include "usbg/usbg_internal.h" /** * @file usbg.c - * @todo Handle buffer overflows */ -struct usbg_state -{ - char *path; - - TAILQ_HEAD(ghead, usbg_gadget) gadgets; -}; - -struct usbg_gadget -{ - char *name; - char *path; - char udc[USBG_MAX_STR_LENGTH]; - - TAILQ_ENTRY(usbg_gadget) gnode; - TAILQ_HEAD(chead, usbg_config) configs; - TAILQ_HEAD(fhead, usbg_function) functions; - usbg_state *parent; -}; - -struct usbg_config -{ - TAILQ_ENTRY(usbg_config) cnode; - TAILQ_HEAD(bhead, usbg_binding) bindings; - usbg_gadget *parent; - - char *name; - char *path; - char *label; - int id; -}; - -struct usbg_function -{ - TAILQ_ENTRY(usbg_function) fnode; - usbg_gadget *parent; - - char *name; - char *path; - char *instance; - - usbg_function_type type; -}; - -struct usbg_binding -{ - TAILQ_ENTRY(usbg_binding) bnode; - usbg_config *parent; - usbg_function *target; - - char *name; - char *path; -}; /** * @var function_names @@ -104,39 +49,38 @@ const char *function_names[] = "eem", "rndis", "phonet", + "ffs", + "mass_storage", + "midi", + "Loopback", +}; + +ARRAY_SIZE_SENTINEL(function_names, USBG_FUNCTION_TYPE_MAX); + +const char *gadget_attr_names[] = +{ + "bcdUSB", + "bDeviceClass", + "bDeviceSubClass", + "bDeviceProtocol", + "bMaxPacketSize0", + "idVendor", + "idProduct", + "bcdDevice" +}; + +ARRAY_SIZE_SENTINEL(gadget_attr_names, USBG_GADGET_ATTR_MAX); + +const char *gadget_str_names[] = +{ + "product", + "manufacturer", + "serialnumber", }; -#define ERROR(msg, ...) do {\ - fprintf(stderr, "%s() "msg" \n", \ - __func__, ##__VA_ARGS__);\ - fflush(stderr);\ - } while (0) - -#define ERRORNO(msg, ...) do {\ - fprintf(stderr, "%s() %s: "msg" \n", \ - __func__, strerror(errno), ##__VA_ARGS__);\ - fflush(stderr);\ - } while (0) - -/* Insert in string order */ -#define INSERT_TAILQ_STRING_ORDER(HeadPtr, HeadType, NameField, ToInsert, NodeField) \ - do { \ - if (TAILQ_EMPTY((HeadPtr)) || \ - (strcmp((ToInsert)->NameField, TAILQ_FIRST((HeadPtr))->NameField) < 0)) \ - TAILQ_INSERT_HEAD((HeadPtr), (ToInsert), NodeField); \ - else if (strcmp((ToInsert)->NameField, TAILQ_LAST((HeadPtr), HeadType)->NameField) > 0) \ - TAILQ_INSERT_TAIL((HeadPtr), (ToInsert), NodeField); \ - else { \ - typeof(ToInsert) _cur; \ - TAILQ_FOREACH(_cur, (HeadPtr), NodeField) { \ - if (strcmp((ToInsert)->NameField, _cur->NameField) > 0) \ - continue; \ - TAILQ_INSERT_BEFORE(_cur, (ToInsert), NodeField); \ - } \ - } \ - } while (0) - -static int usbg_translate_error(int error) +ARRAY_SIZE_SENTINEL(gadget_str_names, USBG_GADGET_STR_MAX); + +int usbg_translate_error(int error) { int ret; @@ -146,12 +90,14 @@ static int usbg_translate_error(int error) break; case EACCES: case EROFS: + case EPERM: ret = USBG_ERROR_NO_ACCESS; break; case ENOENT: case ENOTDIR: ret = USBG_ERROR_NOT_FOUND; break; + case ERANGE: case EINVAL: case USBG_ERROR_INVALID_PARAM: ret = USBG_ERROR_INVALID_PARAM; @@ -168,6 +114,9 @@ static int usbg_translate_error(int error) case EBUSY: ret = USBG_ERROR_BUSY; break; + case ENOTEMPTY: + ret = USBG_ERROR_NOT_EMPTY; + break; default: ret = USBG_ERROR_OTHER_ERROR; } @@ -213,6 +162,21 @@ const char *usbg_error_name(usbg_error e) case USBG_ERROR_PATH_TOO_LONG: ret = "USBG_ERROR_PATH_TOO_LONG"; break; + case USBG_ERROR_INVALID_FORMAT: + ret = "USBG_ERROR_INVALID_FORMAT"; + break; + case USBG_ERROR_MISSING_TAG: + ret = "USBG_ERROR_MISSING_TAG"; + break; + case USBG_ERROR_INVALID_TYPE: + ret = "USBG_ERROR_INVALID_TYPE"; + break; + case USBG_ERROR_INVALID_VALUE: + ret = "USBG_ERROR_INVALID_VALUE"; + break; + case USBG_ERROR_NOT_EMPTY: + ret = "USBG_ERROR_NOT_EMPTY"; + break; case USBG_ERROR_OTHER_ERROR: ret = "USBG_ERROR_OTHER_ERROR"; break; @@ -259,6 +223,21 @@ const char *usbg_strerror(usbg_error e) case USBG_ERROR_PATH_TOO_LONG: ret = "Created path was too long to process it."; break; + case USBG_ERROR_INVALID_FORMAT: + ret = "Given file has incompatible format."; + break; + case USBG_ERROR_MISSING_TAG: + ret = "One of mandatory tags is missing."; + break; + case USBG_ERROR_INVALID_TYPE: + ret = "One of attributes has incompatible type."; + break; + case USBG_ERROR_INVALID_VALUE: + ret = "Incorrect value provided as attribute."; + break; + case USBG_ERROR_NOT_EMPTY: + ret = "Entity is not empty."; + break; case USBG_ERROR_OTHER_ERROR: ret = "Other error"; break; @@ -267,30 +246,112 @@ const char *usbg_strerror(usbg_error e) return ret; } -static int usbg_lookup_function_type(char *name) +int usbg_lookup_function_attrs_type(int f_type) { - int i = 0; - int max = sizeof(function_names)/sizeof(char *); + int ret; + + switch (f_type) { + case F_SERIAL: + case F_ACM: + case F_OBEX: + ret = USBG_F_ATTRS_SERIAL; + break; + case F_ECM: + case F_SUBSET: + case F_NCM: + case F_EEM: + case F_RNDIS: + ret = USBG_F_ATTRS_NET; + break; + case F_PHONET: + ret = USBG_F_ATTRS_PHONET; + break; + case F_FFS: + ret = USBG_F_ATTRS_FFS; + break; + case F_MASS_STORAGE: + ret = USBG_F_ATTRS_MS; + break; + case F_MIDI: + ret = USBG_F_ATTRS_MIDI; + break; + case F_LOOPBACK: + ret = USBG_F_ATTRS_LOOPBACK; + break; + default: + ret = USBG_ERROR_NOT_SUPPORTED; + } + + return ret; +} + +int usbg_lookup_function_type(const char *name) +{ + int i = USBG_FUNCTION_TYPE_MIN; if (!name) - return -1; + return USBG_ERROR_INVALID_PARAM; do { if (!strcmp(name, function_names[i])) - break; + return i; + i++; + } while (i != USBG_FUNCTION_TYPE_MAX); + + return USBG_ERROR_NOT_FOUND; +} + +const char *usbg_get_function_type_str(usbg_function_type type) +{ + return type >= USBG_FUNCTION_TYPE_MIN && + type < USBG_FUNCTION_TYPE_MAX ? + function_names[type] : NULL; +} + +int usbg_lookup_gadget_attr(const char *name) +{ + int i = USBG_GADGET_ATTR_MIN; + + if (!name) + return USBG_ERROR_INVALID_PARAM; + + do { + if (!strcmp(name, gadget_attr_names[i])) + return i; i++; - } while (i != max); + } while (i != USBG_GADGET_ATTR_MAX); + + return USBG_ERROR_NOT_FOUND; +} + +int usbg_lookup_gadget_str(const char *name) +{ + int i = USBG_GADGET_STR_MIN; - if (i == max) - i = -1; + if (!name) + return USBG_ERROR_INVALID_PARAM; - return i; + do { + if (!strcmp(name, gadget_str_names[i])) + return i; + i++; + } while (i != USBG_GADGET_STR_MAX); + + return USBG_ERROR_NOT_FOUND; } -const const char *usbg_get_function_type_str(usbg_function_type type) +const char *usbg_get_gadget_attr_str(usbg_gadget_attr attr) { - return type > 0 && type < sizeof(function_names)/sizeof(char *) ? - function_names[type] : NULL; + return attr >= USBG_GADGET_ATTR_MIN && + attr < USBG_GADGET_ATTR_MAX ? + gadget_attr_names[attr] : NULL; +} + +const char *usbg_get_gadget_str_name(usbg_gadget_str str) +{ + return str >= USBG_GADGET_STR_MIN && + str < USBG_GADGET_STR_MAX ? + gadget_str_names[str] : NULL; } static usbg_error usbg_split_function_instance_type(const char *full_name, @@ -368,45 +429,46 @@ static int bindings_select(const struct dirent *dent) return 0; } -static int file_select(const struct dirent *dent) -{ - if ((strcmp(dent->d_name, ".") == 0) || (strcmp(dent->d_name, "..") == 0)) - return 0; - else - return 1; -} - -static int usbg_read_buf(char *path, char *name, char *file, char *buf) +static int usbg_read_buf(const char *path, const char *name, const char *file, + char *buf) { char p[USBG_MAX_PATH_LENGTH]; FILE *fp; + char *ret_ptr; int nmb; int ret = USBG_SUCCESS; nmb = snprintf(p, sizeof(p), "%s/%s/%s", path, name, file); - if (nmb < sizeof(p)) { - fp = fopen(p, "r"); - if (fp) { - /* Successfully opened */ - if (!fgets(buf, USBG_MAX_STR_LENGTH, fp)) { - ERROR("read error"); - ret = USBG_ERROR_IO; - } - - fclose(fp); - } else { - /* Set error correctly */ - ret = usbg_translate_error(errno); - } - } else { + if (nmb >= sizeof(p)) { ret = USBG_ERROR_PATH_TOO_LONG; + goto out; } + fp = fopen(p, "r"); + if (!fp) { + /* Set error correctly */ + ret = usbg_translate_error(errno); + goto out; + } + + ret_ptr = fgets(buf, USBG_MAX_STR_LENGTH, fp); + if (!ret_ptr) { + /* File is empty */ + if (feof(fp)) + buf[0] = '\0'; + /* Error occurred */ + else + ret = USBG_ERROR_IO; + } + + fclose(fp); + +out: return ret; } -static int usbg_read_int(char *path, char *name, char *file, int base, - int *dest) +static int usbg_read_int(const char *path, const char *name, const char *file, + int base, int *dest) { char buf[USBG_MAX_STR_LENGTH]; char *pos; @@ -425,7 +487,23 @@ static int usbg_read_int(char *path, char *name, char *file, int base, #define usbg_read_dec(p, n, f, d) usbg_read_int(p, n, f, 10, d) #define usbg_read_hex(p, n, f, d) usbg_read_int(p, n, f, 16, d) -static int usbg_read_string(char *path, char *name, char *file, char *buf) +static int usbg_read_bool(const char *path, const char *name, const char *file, + bool *dest) +{ + int buf; + int ret; + + ret = usbg_read_dec(path, name, file, &buf); + if (ret != USBG_SUCCESS) + goto out; + + *dest = !!buf; +out: + return ret; +} + +static int usbg_read_string(const char *path, const char *name, + const char *file, char *buf) { char *p = NULL; int ret; @@ -443,7 +521,30 @@ static int usbg_read_string(char *path, char *name, char *file, char *buf) return ret; } -static int usbg_write_buf(char *path, char *name, char *file, char *buf) +static int usbg_read_string_alloc(const char *path, const char *name, + const char *file, const char **dest) +{ + char buf[USBG_MAX_FILE_SIZE]; + char *new_buf = NULL; + int ret = USBG_SUCCESS; + + ret = usbg_read_string(path, name, file, buf); + if (ret != USBG_SUCCESS) + goto out; + + new_buf = strdup(buf); + if (!new_buf) { + ret = USBG_ERROR_NO_MEM; + goto out; + } + + *dest = new_buf; +out: + return ret; +} + +static int usbg_write_buf(const char *path, const char *name, const char *file, + const char *buf) { char p[USBG_MAX_PATH_LENGTH]; FILE *fp; @@ -473,8 +574,8 @@ static int usbg_write_buf(char *path, char *name, char *file, char *buf) return ret; } -static int usbg_write_int(char *path, char *name, char *file, int value, - char *str) +static int usbg_write_int(const char *path, const char *name, const char *file, + int value, const char *str) { char buf[USBG_MAX_STR_LENGTH]; int nmb; @@ -486,11 +587,13 @@ static int usbg_write_int(char *path, char *name, char *file, int value, } #define usbg_write_dec(p, n, f, v) usbg_write_int(p, n, f, v, "%d\n") +#define usbg_write_hex(p, n, f, v) usbg_write_int(p, n, f, v, "0x%x\n") #define usbg_write_hex16(p, n, f, v) usbg_write_int(p, n, f, v, "0x%04x\n") #define usbg_write_hex8(p, n, f, v) usbg_write_int(p, n, f, v, "0x%02x\n") +#define usbg_write_bool(p, n, f, v) usbg_write_dec(p, n, f, !!v) -static inline int usbg_write_string(char *path, char *name, char *file, - char *buf) +static inline int usbg_write_string(const char *path, const char *name, + const char *file, const char *buf) { return usbg_write_buf(path, name, file, buf); } @@ -506,6 +609,7 @@ static inline void usbg_free_function(usbg_function *f) { free(f->path); free(f->name); + free(f->label); free(f); } @@ -527,6 +631,12 @@ static void usbg_free_gadget(usbg_gadget *g) { usbg_config *c; usbg_function *f; + + if (g->last_failed_import) { + config_destroy(g->last_failed_import); + free(g->last_failed_import); + } + while (!TAILQ_EMPTY(&g->configs)) { c = TAILQ_FIRST(&g->configs); TAILQ_REMOVE(&g->configs, c, cnode); @@ -542,20 +652,41 @@ static void usbg_free_gadget(usbg_gadget *g) free(g); } +static void usbg_free_udc(usbg_udc *u) +{ + free(u->name); + free(u); +} + static void usbg_free_state(usbg_state *s) { usbg_gadget *g; + usbg_udc *u; + while (!TAILQ_EMPTY(&s->gadgets)) { g = TAILQ_FIRST(&s->gadgets); TAILQ_REMOVE(&s->gadgets, g, gnode); usbg_free_gadget(g); } + + while (!TAILQ_EMPTY(&s->udcs)) { + u = TAILQ_FIRST(&s->udcs); + TAILQ_REMOVE(&s->udcs, u, unode); + usbg_free_udc(u); + } + + if (s->last_failed_import) { + config_destroy(s->last_failed_import); + free(s->last_failed_import); + } + free(s->path); + free(s->configfs_path); free(s); } -static usbg_gadget *usbg_allocate_gadget(char *path, char *name, +static usbg_gadget *usbg_allocate_gadget(const char *path, const char *name, usbg_state *parent) { usbg_gadget *g; @@ -564,9 +695,11 @@ static usbg_gadget *usbg_allocate_gadget(char *path, char *name, if (g) { TAILQ_INIT(&g->functions); TAILQ_INIT(&g->configs); + g->last_failed_import = NULL; g->name = strdup(name); g->path = strdup(path); g->parent = parent; + g->udc = NULL; if (!(g->name) || !(g->path)) { free(g->name); @@ -615,6 +748,8 @@ out: return c; } +static int usbg_rm_ms_function(usbg_function *f, int opts); + static usbg_function *usbg_allocate_function(const char *path, usbg_function_type type, const char *instance, usbg_gadget *parent) { @@ -626,6 +761,7 @@ static usbg_function *usbg_allocate_function(const char *path, if (!f) goto out; + f->label = NULL; type_name = usbg_get_function_type_str(type); if (!type_name) { free(f); @@ -644,6 +780,16 @@ static usbg_function *usbg_allocate_function(const char *path, f->parent = parent; f->type = type; + /* only composed functions (with subdirs) require this callback */ + switch (usbg_lookup_function_attrs_type(type)) { + case USBG_F_ATTRS_MS: + f->rm_callback = usbg_rm_ms_function; + break; + default: + f->rm_callback = NULL; + break; + } + if (!(f->path)) { free(f->name); free(f->path); @@ -655,7 +801,7 @@ out: return f; } -static usbg_binding *usbg_allocate_binding(char *path, char *name, +static usbg_binding *usbg_allocate_binding(const char *path, const char *name, usbg_config *parent) { usbg_binding *b; @@ -677,20 +823,108 @@ static usbg_binding *usbg_allocate_binding(char *path, char *name, return b; } +static usbg_udc *usbg_allocate_udc(usbg_state *parent, const char *name) +{ + usbg_udc *u; + + u = malloc(sizeof(*u)); + if (!u) + goto out; + + u->gadget = NULL; + u->parent = parent; + u->name = strdup(name); + if (!u->name) { + free(u); + u = NULL; + } + + out: + return u; +} + +static int ubsg_rm_file(const char *path, const char *name) +{ + int ret = USBG_SUCCESS; + int nmb; + char buf[USBG_MAX_PATH_LENGTH]; + + nmb = snprintf(buf, sizeof(buf), "%s/%s", path, name); + if (nmb < sizeof(buf)) { + nmb = unlink(buf); + if (nmb != 0) + ret = usbg_translate_error(errno); + } else { + ret = USBG_ERROR_PATH_TOO_LONG; + } + + return ret; +} + +static int usbg_rm_dir(const char *path, const char *name) +{ + int ret = USBG_SUCCESS; + int nmb; + char buf[USBG_MAX_PATH_LENGTH]; + + nmb = snprintf(buf, sizeof(buf), "%s/%s", path, name); + if (nmb < sizeof(buf)) { + nmb = rmdir(buf); + if (nmb != 0) + ret = usbg_translate_error(errno); + } else { + ret = USBG_ERROR_PATH_TOO_LONG; + } + + return ret; +} + +static int usbg_rm_all_dirs(const char *path) +{ + int ret = USBG_SUCCESS; + int n, i; + struct dirent **dent; + + n = scandir(path, &dent, file_select, alphasort); + if (n >= 0) { + for (i = 0; i < n; ++i) { + if (ret == USBG_SUCCESS) + ret = usbg_rm_dir(path, dent[i]->d_name); + + free(dent[i]); + } + free(dent); + } else { + ret = usbg_translate_error(errno); + } + + return ret; +} + +char *usbg_ether_ntoa_r(const struct ether_addr *addr, char *buf) +{ + sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", + addr->ether_addr_octet[0], addr->ether_addr_octet[1], + addr->ether_addr_octet[2], addr->ether_addr_octet[3], + addr->ether_addr_octet[4], addr->ether_addr_octet[5]); + return buf; +} + static int usbg_parse_function_net_attrs(usbg_function *f, - usbg_function_attrs *f_attrs) + usbg_f_net_attrs *f_net_attrs) { struct ether_addr *addr; - char str_addr[40]; + struct ether_addr addr_buf; + char str_addr[USBG_MAX_STR_LENGTH]; int ret; ret = usbg_read_string(f->path, f->name, "dev_addr", str_addr); if (ret != USBG_SUCCESS) goto out; - addr = ether_aton(str_addr); + addr = ether_aton_r(str_addr, &addr_buf); if (addr) { - f_attrs->net.dev_addr = *addr; + f_net_attrs->dev_addr = *addr; } else { ret = USBG_ERROR_IO; goto out; @@ -700,19 +934,215 @@ static int usbg_parse_function_net_attrs(usbg_function *f, if (ret != USBG_SUCCESS) goto out; - addr = ether_aton(str_addr); + addr = ether_aton_r(str_addr, &addr_buf); if (addr) { - f_attrs->net.host_addr = *addr; + f_net_attrs->host_addr = *addr; } else { ret = USBG_ERROR_IO; goto out; } - ret = usbg_read_string(f->path, f->name, "ifname", f_attrs->net.ifname); + ret = usbg_read_dec(f->path, f->name, "qmult", &(f_net_attrs->qmult)); if (ret != USBG_SUCCESS) goto out; - ret = usbg_read_dec(f->path, f->name, "qmult", &(f_attrs->net.qmult)); + ret = usbg_read_string_alloc(f->path, f->name, "ifname", + &(f_net_attrs->ifname)); +out: + return ret; +} + +static int usbg_parse_function_ms_lun_attrs(const char *path, const char *lun, + usbg_f_ms_lun_attrs *lun_attrs) +{ + int ret; + + memset(lun_attrs, 0, sizeof(*lun_attrs)); + + ret = sscanf(lun, "lun.%d", &lun_attrs->id); + if (ret != 1) + goto out; + + ret = usbg_read_bool(path, lun, "cdrom", &(lun_attrs->cdrom)); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_read_bool(path, lun, "ro", &(lun_attrs->ro)); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_read_bool(path, lun, "nofua", &(lun_attrs->nofua)); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_read_bool(path, lun, "removable", &(lun_attrs->removable)); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_read_string_alloc(path, lun, "file", + &(lun_attrs->filename)); + +out: + return ret; +} + +static inline int lun_select(const struct dirent *dent) +{ + int ret; + int id; + + ret = file_select(dent); + if (!ret) + goto out; + + ret = sscanf(dent->d_name, "lun.%d", &id); +out: + return ret; +} + +static inline int lun_sort(const struct dirent **d1, const struct dirent **d2) +{ + int ret; + int id1, id2; + + ret = sscanf((*d1)->d_name, "lun.%d", &id1); + if (ret != 1) + goto err; + + ret = sscanf((*d2)->d_name, "lun.%d", &id2); + if (ret != 1) + goto err; + + if (id1 < id2) + ret = 1; + + return id1 < id2 ? -1 : id1 > id2; +err: + /* + * This should not happened because dentries has been + * already checked by lun_select function. This + * error procedure is just in case. + */ + return -1; +} + +static int usbg_parse_function_ms_attrs(usbg_function *f, + usbg_f_ms_attrs *f_ms_attrs) +{ + int ret; + int nmb; + int i = 0; + char fpath[USBG_MAX_PATH_LENGTH]; + usbg_f_ms_lun_attrs *lun_attrs; + usbg_f_ms_lun_attrs **luns; + struct dirent **dent; + + ret = usbg_read_bool(f->path, f->name, "stall", + &(f_ms_attrs->stall)); + if (ret != USBG_SUCCESS) + goto out; + + + nmb = snprintf(fpath, sizeof(fpath), "%s/%s/", + f->path, f->name); + if (nmb >= sizeof(fpath)) { + ret = USBG_ERROR_PATH_TOO_LONG; + goto out; + } + + nmb = scandir(fpath, &dent, lun_select, lun_sort); + if (nmb < 0) { + ret = usbg_translate_error(errno); + goto out; + } + + luns = calloc(nmb + 1, sizeof(*luns)); + if (!luns) { + ret = USBG_ERROR_NO_MEM; + goto err; + } + + f_ms_attrs->luns = luns; + f_ms_attrs->nluns = nmb; + + for (i = 0; i < nmb; i++) { + lun_attrs = malloc(sizeof(*lun_attrs)); + if (!lun_attrs) { + ret = USBG_ERROR_NO_MEM; + goto err; + } + + ret = usbg_parse_function_ms_lun_attrs(fpath, dent[i]->d_name, + lun_attrs); + if (ret != USBG_SUCCESS) { + free(lun_attrs); + goto err; + } + + luns[i] = lun_attrs; + free(dent[i]); + } + free(dent); + + return USBG_SUCCESS; + +err: + while (i < nmb) { + free(dent[i]); + ++i; + } + free(dent); + + usbg_cleanup_function_attrs( + container_of((usbg_f_attrs *)f_ms_attrs, + usbg_function_attrs, attrs)); +out: + return ret; +} + +static int usbg_parse_function_midi_attrs(usbg_function *f, + usbg_f_midi_attrs *attrs) +{ + int ret; + + ret = usbg_read_dec(f->path, f->name, "index", &(attrs->index)); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_read_string_alloc(f->path, f->name, "id", &(attrs->id)); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_read_dec(f->path, f->name, "in_ports", (int*)&(attrs->in_ports)); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_read_dec(f->path, f->name, "out_ports", (int*)&(attrs->out_ports)); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_read_dec(f->path, f->name, "buflen", (int*)&(attrs->buflen)); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_read_dec(f->path, f->name, "qlen", (int*)&(attrs->qlen)); + if (ret != USBG_SUCCESS) + goto out; + +out: + return ret; +} + +static int usbg_parse_function_loopback_attrs(usbg_function *f, + usbg_f_loopback_attrs *attrs) +{ + int ret; + + ret = usbg_read_dec(f->path, f->name, "buflen", (int *)&(attrs->buflen)); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_read_dec(f->path, f->name, "qlen", (int *)&(attrs->qlen)); out: return ret; @@ -722,34 +1152,70 @@ static int usbg_parse_function_attrs(usbg_function *f, usbg_function_attrs *f_attrs) { int ret; + int attrs_type; - switch (f->type) { - case F_SERIAL: - case F_ACM: - case F_OBEX: + attrs_type = usbg_lookup_function_attrs_type(f->type); + if (attrs_type < 0) { + ret = attrs_type; + goto out; + } + + switch (attrs_type) { + case USBG_F_ATTRS_SERIAL: + f_attrs->header.attrs_type = USBG_F_ATTRS_SERIAL; ret = usbg_read_dec(f->path, f->name, "port_num", - &(f_attrs->serial.port_num)); + &(f_attrs->attrs.serial.port_num)); break; - case F_ECM: - case F_SUBSET: - case F_NCM: - case F_EEM: - case F_RNDIS: - ret = usbg_parse_function_net_attrs(f, f_attrs); + + case USBG_F_ATTRS_NET: + f_attrs->header.attrs_type = USBG_F_ATTRS_NET; + ret = usbg_parse_function_net_attrs(f, &(f_attrs->attrs.net)); break; - case F_PHONET: - ret = usbg_read_string(f->path, f->name, "ifname", - f_attrs->phonet.ifname); + + case USBG_F_ATTRS_PHONET: + f_attrs->header.attrs_type = USBG_F_ATTRS_PHONET; + ret = usbg_read_string_alloc(f->path, f->name, "ifname", + &(f_attrs->attrs.phonet.ifname)); break; + + case USBG_F_ATTRS_FFS: + { + usbg_f_ffs_attrs *ffs_attrs = &(f_attrs->attrs.ffs); + + f_attrs->header.attrs_type = USBG_F_ATTRS_FFS; + ffs_attrs->dev_name = strdup(f->instance); + if (!ffs_attrs->dev_name) + ret = USBG_ERROR_NO_MEM; + else + ret = USBG_SUCCESS; + break; + } + + case USBG_F_ATTRS_MS: + f_attrs->header.attrs_type = USBG_F_ATTRS_MS; + ret = usbg_parse_function_ms_attrs(f, &(f_attrs->attrs.ms)); + break; + + case USBG_F_ATTRS_MIDI: + f_attrs->header.attrs_type = USBG_F_ATTRS_MIDI; + ret = usbg_parse_function_midi_attrs(f, &(f_attrs->attrs.midi)); + break; + + case USBG_F_ATTRS_LOOPBACK: + f_attrs->header.attrs_type = USBG_F_ATTRS_LOOPBACK; + ret = usbg_parse_function_loopback_attrs(f, &(f_attrs->attrs.loopback)); + break; + default: ERROR("Unsupported function type\n"); ret = USBG_ERROR_NOT_SUPPORTED; + break; } - +out: return ret; } -static int usbg_parse_functions(char *path, usbg_gadget *g) +static int usbg_parse_functions(const char *path, usbg_gadget *g) { usbg_function *f; int i, n; @@ -794,7 +1260,7 @@ out: return ret; } -static int usbg_parse_config_attrs(char *path, char *name, +static int usbg_parse_config_attrs(const char *path, const char *name, usbg_config_attrs *c_attrs) { int buf, ret; @@ -811,7 +1277,7 @@ static int usbg_parse_config_attrs(char *path, char *name, return ret; } -static int usbg_parse_config_strs(char *path, char *name, +static int usbg_parse_config_strs(const char *path, const char *name, int lang, usbg_config_strs *c_strs) { DIR *dir; @@ -838,8 +1304,7 @@ static int usbg_parse_config_strs(char *path, char *name, return ret; } -static int usbg_parse_config_binding(usbg_config *c, char *bpath, - int path_size) +static int usbg_parse_config_binding(usbg_config *c, char *bpath, int path_size) { int nmb; int ret; @@ -850,7 +1315,7 @@ static int usbg_parse_config_binding(usbg_config *c, char *bpath, usbg_function *f; usbg_binding *b; - nmb = readlink(bpath, target, sizeof(target)); + nmb = readlink(bpath, target, sizeof(target) - 1 ); if (nmb < 0) { ret = usbg_translate_error(errno); goto out; @@ -953,7 +1418,7 @@ out: return ret; } -static int usbg_parse_configs(char *path, usbg_gadget *g) +static int usbg_parse_configs(const char *path, usbg_gadget *g) { int i, n; int ret = USBG_SUCCESS; @@ -985,7 +1450,7 @@ out: return ret; } -static int usbg_parse_gadget_attrs(char *path, char *name, +static int usbg_parse_gadget_attrs(const char *path, const char *name, usbg_gadget_attrs *g_attrs) { int buf, ret; @@ -998,12 +1463,6 @@ static int usbg_parse_gadget_attrs(char *path, char *name, else goto out; - ret = usbg_read_hex(path, name, "bcdDevice", &buf); - if (ret == USBG_SUCCESS) - g_attrs->bcdDevice = (uint16_t) buf; - else - goto out; - ret = usbg_read_hex(path, name, "bDeviceClass", &buf); if (ret == USBG_SUCCESS) g_attrs->bDeviceClass = (uint8_t)buf; @@ -1040,11 +1499,17 @@ static int usbg_parse_gadget_attrs(char *path, char *name, else goto out; + ret = usbg_read_hex(path, name, "bcdDevice", &buf); + if (ret == USBG_SUCCESS) + g_attrs->bcdDevice = (uint16_t) buf; + else + goto out; + out: return ret; } -static int usbg_parse_gadget_strs(char *path, char *name, int lang, +static int usbg_parse_gadget_strs(const char *path, const char *name, int lang, usbg_gadget_strs *g_strs) { int ret; @@ -1085,12 +1550,17 @@ out: static inline int usbg_parse_gadget(usbg_gadget *g) { int ret; + char buf[USBG_MAX_STR_LENGTH]; /* UDC bound to, if any */ - ret = usbg_read_string(g->path, g->name, "UDC", g->udc); + ret = usbg_read_string(g->path, g->name, "UDC", buf); if (ret != USBG_SUCCESS) goto out; + g->udc = usbg_get_udc(g->parent, buf); + if (g->udc) + g->udc->gadget = g; + ret = usbg_parse_functions(g->path, g); if (ret != USBG_SUCCESS) goto out; @@ -1100,7 +1570,7 @@ out: return ret; } -static int usbg_parse_gadgets(char *path, usbg_state *s) +static int usbg_parse_gadgets(const char *path, usbg_state *s) { usbg_gadget *g; int i, n; @@ -1135,18 +1605,86 @@ static int usbg_parse_gadgets(char *path, usbg_state *s) return ret; } -static int usbg_init_state(char *path, usbg_state *s) +static int usbg_parse_udcs(usbg_state *s) { + usbg_udc *u; + int n, i; int ret = USBG_SUCCESS; + struct dirent **dent; + + n = scandir("/sys/class/udc", &dent, file_select, alphasort); + if (n < 0) { + ret = usbg_translate_error(errno); + goto out; + } + + for (i = 0; i < n; ++i) { + if (ret == USBG_SUCCESS) { + u = usbg_allocate_udc(s, dent[i]->d_name); + if (u) + TAILQ_INSERT_TAIL(&s->udcs, u, unode); + else + ret = USBG_ERROR_NO_MEM; + } + + free(dent[i]); + } + free(dent); + +out: + return ret; +} + +static usbg_state *usbg_allocate_state(const char *configfs_path, char *path) +{ + usbg_state *s; + + s = malloc(sizeof(*s)); + if (!s) + goto err; + + s->configfs_path = strdup(configfs_path); + if (!s->configfs_path) + goto cpath_failed; /* State takes the ownership of path and should free it */ s->path = path; + s->last_failed_import = NULL; TAILQ_INIT(&s->gadgets); + TAILQ_INIT(&s->udcs); + + return s; - ret = usbg_parse_gadgets(path, s); +cpath_failed: + free(s); +err: + return NULL; +} + +static int usbg_parse_state(usbg_state *s) +{ + int ret = USBG_SUCCESS; + + /* + * USBG_ERROR_NOT_FOUND is returned if we are running on machine where + * there is no udc support in kernel (no /sys/class/udc dir). + * This check allows to run library on such machine or if we don't + * have rights to read this directory. + * User will be able to finish init function and manage gadgets but + * wont be able to bind it as there is no UDC. + */ + ret = usbg_parse_udcs(s); + if (ret != USBG_SUCCESS && ret != USBG_ERROR_NOT_FOUND && + ret != USBG_ERROR_NO_ACCESS) { + ERROR("Unable to parse udcs"); + goto out; + } + + ret = usbg_parse_gadgets(s->path, s); if (ret != USBG_SUCCESS) - ERRORNO("unable to parse %s\n", path); + ERROR("unable to parse %s\n", s->path); +out: return ret; } @@ -1154,13 +1692,14 @@ static int usbg_init_state(char *path, usbg_state *s) * User API */ -int usbg_init(char *configfs_path, usbg_state **state) +int usbg_init(const char *configfs_path, usbg_state **state) { int ret = USBG_SUCCESS; DIR *dir; char *path; + usbg_state *s; - ret = asprintf(&path, "%s/usb_gadget", configfs_path); + ret = asprintf(&path, "%s/" GADGETS_DIR, configfs_path); if (ret < 0) return USBG_ERROR_NO_MEM; else @@ -1168,21 +1707,33 @@ int usbg_init(char *configfs_path, usbg_state **state) /* Check if directory exist */ dir = opendir(path); - if (dir) { - closedir(dir); - *state = malloc(sizeof(usbg_state)); - ret = *state ? usbg_init_state(path, *state) - : USBG_ERROR_NO_MEM; - if (*state && ret != USBG_SUCCESS) { - ERRORNO("couldn't init gadget state\n"); - usbg_free_state(*state); - } - } else { + if (!dir) { ERRORNO("couldn't init gadget state\n"); ret = usbg_translate_error(errno); - free(path); + goto err; } + closedir(dir); + s = usbg_allocate_state(configfs_path, path); + if (!s) { + ret = USBG_ERROR_NO_MEM; + goto err; + } + + ret = usbg_parse_state(s); + if (ret != USBG_SUCCESS) { + ERROR("couldn't init gadget state\n"); + usbg_free_state(s); + goto out; + } + + *state = s; + + return ret; + +err: + free(path); +out: return ret; } @@ -1191,20 +1742,25 @@ void usbg_cleanup(usbg_state *s) usbg_free_state(s); } +const char *usbg_get_configfs_path(usbg_state *s) +{ + return s ? s->configfs_path : NULL; +} + size_t usbg_get_configfs_path_len(usbg_state *s) { - return s ? strlen(s->path) : USBG_ERROR_INVALID_PARAM; + return s ? strlen(s->configfs_path) : USBG_ERROR_INVALID_PARAM; } -int usbg_get_configfs_path(usbg_state *s, char *buf, size_t len) +int usbg_cpy_configfs_path(usbg_state *s, char *buf, size_t len) { - int ret = USBG_SUCCESS; - if (s && buf) - strncpy(buf, s->path, len); - else - ret = USBG_ERROR_INVALID_PARAM; + if (!s || !buf || len == 0) + return USBG_ERROR_INVALID_PARAM; - return ret; + buf[--len] = '\0'; + strncpy(buf, s->configfs_path, len); + + return USBG_SUCCESS; } usbg_gadget *usbg_get_gadget(usbg_state *s, const char *name) @@ -1241,6 +1797,17 @@ usbg_config *usbg_get_config(usbg_gadget *g, int id, const char *label) return c; } +usbg_udc *usbg_get_udc(usbg_state *s, const char *name) +{ + usbg_udc *u; + + TAILQ_FOREACH(u, &s->udcs, unode) + if (!strcmp(u->name, name)) + return u; + + return NULL; +} + usbg_binding *usbg_get_binding(usbg_config *c, const char *name) { usbg_binding *b; @@ -1263,10 +1830,259 @@ usbg_binding *usbg_get_link_binding(usbg_config *c, usbg_function *f) return NULL; } -static int usbg_create_empty_gadget(usbg_state *s, char *name, usbg_gadget **g) +int usbg_rm_binding(usbg_binding *b) +{ + int ret = USBG_SUCCESS; + usbg_config *c; + + if (!b) + return USBG_ERROR_INVALID_PARAM; + + c = b->parent; + + ret = ubsg_rm_file(b->path, b->name); + if (ret == USBG_SUCCESS) { + TAILQ_REMOVE(&(c->bindings), b, bnode); + usbg_free_binding(b); + } + + return ret; +} + +int usbg_rm_config(usbg_config *c, int opts) +{ + int ret = USBG_ERROR_INVALID_PARAM; + usbg_gadget *g; + + if (!c) + return ret; + + g = c->parent; + + if (opts & USBG_RM_RECURSE) { + /* Recursive flag was given + * so remove all bindings and strings */ + char spath[USBG_MAX_PATH_LENGTH]; + int nmb; + usbg_binding *b; + + while (!TAILQ_EMPTY(&c->bindings)) { + b = TAILQ_FIRST(&c->bindings); + ret = usbg_rm_binding(b); + if (ret != USBG_SUCCESS) + goto out; + } + + nmb = snprintf(spath, sizeof(spath), "%s/%s/%s", c->path, + c->name, STRINGS_DIR); + if (nmb >= sizeof(spath)) { + ret = USBG_ERROR_PATH_TOO_LONG; + goto out; + } + + ret = usbg_rm_all_dirs(spath); + if (ret != USBG_SUCCESS) + goto out; + } + + ret = usbg_rm_dir(c->path, c->name); + if (ret == USBG_SUCCESS) { + TAILQ_REMOVE(&(g->configs), c, cnode); + usbg_free_config(c); + } + +out: + return ret; +} + +static int usbg_rm_ms_function(usbg_function *f, int opts) +{ + int ret; + int nmb; + int i; + char lpath[USBG_MAX_PATH_LENGTH]; + struct dirent **dent; + + ret = snprintf(lpath, sizeof(lpath), "%s/%s/", f->path, f->name); + if (ret >= sizeof(lpath)) { + ret = USBG_ERROR_PATH_TOO_LONG; + goto out; + } + + nmb = scandir(lpath, &dent, lun_select, lun_sort); + if (nmb < 0) { + ret = usbg_translate_error(errno); + goto out; + } + + for (i = nmb - 1; i > 0; --i) { + ret = usbg_rm_dir(lpath, dent[i]->d_name); + free(dent[i]); + if (ret) + goto err_free_dent_loop; + } + free(dent[0]); + free(dent); + + return USBG_SUCCESS; + +err_free_dent_loop: + while (--i >= 0) + free(dent[i]); + free(dent[i]); +out: + return ret; +} + +int usbg_rm_function(usbg_function *f, int opts) +{ + int ret = USBG_ERROR_INVALID_PARAM; + usbg_gadget *g; + + if (!f) + return ret; + + g = f->parent; + + if (opts & USBG_RM_RECURSE) { + /* Recursive flag was given + * so remove all bindings to this function */ + usbg_config *c; + usbg_binding *b; + + TAILQ_FOREACH(c, &g->configs, cnode) { + b = TAILQ_FIRST(&c->bindings); + while (b != NULL) { + if (b->target == f) { + usbg_binding *b_next = TAILQ_NEXT(b, bnode); + ret = usbg_rm_binding(b); + if (ret != USBG_SUCCESS) + return ret; + + b = b_next; + } else { + b = TAILQ_NEXT(b, bnode); + } + } /* while */ + } /* TAILQ_FOREACH */ + } + + if (f->rm_callback) { + ret = f->rm_callback(f, opts); + if (ret != USBG_SUCCESS) + goto out; + } + + ret = usbg_rm_dir(f->path, f->name); + if (ret == USBG_SUCCESS) { + TAILQ_REMOVE(&(g->functions), f, fnode); + usbg_free_function(f); + } + +out: + return ret; +} + +int usbg_rm_gadget(usbg_gadget *g, int opts) +{ + int ret = USBG_ERROR_INVALID_PARAM; + usbg_state *s; + if (!g) + goto out; + + s = g->parent; + + if (opts & USBG_RM_RECURSE) { + /* Recursive flag was given + * so remove all configs and functions + * using recursive flags */ + usbg_config *c; + usbg_function *f; + int nmb; + char spath[USBG_MAX_PATH_LENGTH]; + + while (!TAILQ_EMPTY(&g->configs)) { + c = TAILQ_FIRST(&g->configs); + ret = usbg_rm_config(c, opts); + if (ret != USBG_SUCCESS) + goto out; + } + + while (!TAILQ_EMPTY(&g->functions)) { + f = TAILQ_FIRST(&g->functions); + ret = usbg_rm_function(f, opts); + if (ret != USBG_SUCCESS) + goto out; + } + + nmb = snprintf(spath, sizeof(spath), "%s/%s/%s", g->path, + g->name, STRINGS_DIR); + if (nmb >= sizeof(spath)) { + ret = USBG_ERROR_PATH_TOO_LONG; + goto out; + } + + ret = usbg_rm_all_dirs(spath); + if (ret != USBG_SUCCESS) + goto out; + } + + ret = usbg_rm_dir(g->path, g->name); + if (ret == USBG_SUCCESS) { + TAILQ_REMOVE(&(s->gadgets), g, gnode); + usbg_free_gadget(g); + } + +out: + return ret; +} + +int usbg_rm_config_strs(usbg_config *c, int lang) +{ + int ret = USBG_SUCCESS; + int nmb; + char path[USBG_MAX_PATH_LENGTH]; + + if (!c) + return USBG_ERROR_INVALID_PARAM; + + nmb = snprintf(path, sizeof(path), "%s/%s/%s/0x%x", c->path, c->name, + STRINGS_DIR, lang); + if (nmb < sizeof(path)) + ret = usbg_rm_dir(path, ""); + else + ret = USBG_ERROR_PATH_TOO_LONG; + + return ret; +} + +int usbg_rm_gadget_strs(usbg_gadget *g, int lang) +{ + int ret = USBG_SUCCESS; + int nmb; + char path[USBG_MAX_PATH_LENGTH]; + + if (!g) + return USBG_ERROR_INVALID_PARAM; + + nmb = snprintf(path, sizeof(path), "%s/%s/%s/0x%x", g->path, g->name, + STRINGS_DIR, lang); + if (nmb < sizeof(path)) + ret = usbg_rm_dir(path, ""); + else + ret = USBG_ERROR_PATH_TOO_LONG; + + return ret; +} + + +static int usbg_create_empty_gadget(usbg_state *s, const char *name, + usbg_gadget **g) { char gpath[USBG_MAX_PATH_LENGTH]; + char buf[USBG_MAX_STR_LENGTH]; int nmb; + usbg_gadget *gad; int ret = USBG_SUCCESS; nmb = snprintf(gpath, sizeof(gpath), "%s/%s", s->path, name); @@ -1276,33 +2092,39 @@ static int usbg_create_empty_gadget(usbg_state *s, char *name, usbg_gadget **g) } *g = usbg_allocate_gadget(s->path, name, s); - if (*g) { - usbg_gadget *gad = *g; /* alias only */ - - ret = mkdir(gpath, S_IRWXU|S_IRWXG|S_IRWXO); - if (ret == 0) { - /* Should be empty but read the default */ - ret = usbg_read_string(gad->path, gad->name, "UDC", - gad->udc); - if (ret != USBG_SUCCESS) - rmdir(gpath); - } else { - ret = usbg_translate_error(errno); - } + if (!*g) { + ret = USBG_ERROR_NO_MEM; + goto out; + } + gad = *g; /* alias only */ + + ret = mkdir(gpath, S_IRWXU|S_IRWXG|S_IRWXO); + if (ret == 0) { + /* Should be empty but read the default */ + ret = usbg_read_string(gad->path, gad->name, + "UDC", buf); if (ret != USBG_SUCCESS) { - usbg_free_gadget(*g); - *g = NULL; + rmdir(gpath); + } else { + gad->udc = usbg_get_udc(s, buf); + if (gad->udc) + gad->udc->gadget = gad; } } else { - ret = USBG_ERROR_NO_MEM; + ret = usbg_translate_error(errno); + } + + if (ret != USBG_SUCCESS) { + usbg_free_gadget(*g); + *g = NULL; } out: return ret; } -int usbg_create_gadget_vid_pid(usbg_state *s, char *name, +int usbg_create_gadget_vid_pid(usbg_state *s, const char *name, uint16_t idVendor, uint16_t idProduct, usbg_gadget **g) { int ret; @@ -1336,8 +2158,9 @@ int usbg_create_gadget_vid_pid(usbg_state *s, char *name, return ret; } -int usbg_create_gadget(usbg_state *s, char *name, - usbg_gadget_attrs *g_attrs, usbg_gadget_strs *g_strs, usbg_gadget **g) +int usbg_create_gadget(usbg_state *s, const char *name, + const usbg_gadget_attrs *g_attrs, const usbg_gadget_strs *g_strs, + usbg_gadget **g) { usbg_gadget *gad; int ret; @@ -1377,39 +2200,147 @@ int usbg_get_gadget_attrs(usbg_gadget *g, usbg_gadget_attrs *g_attrs) : USBG_ERROR_INVALID_PARAM; } +const char *usbg_get_gadget_name(usbg_gadget *g) +{ + return g ? g->name : NULL; +} + size_t usbg_get_gadget_name_len(usbg_gadget *g) { return g ? strlen(g->name) : USBG_ERROR_INVALID_PARAM; } -int usbg_get_gadget_name(usbg_gadget *g, char *buf, size_t len) +int usbg_cpy_gadget_name(usbg_gadget *g, char *buf, size_t len) { - int ret = USBG_SUCCESS; - if (g && buf) - strncpy(buf, g->name, len); - else - ret = USBG_ERROR_INVALID_PARAM; + if (!g || !buf || len == 0) + return USBG_ERROR_INVALID_PARAM; - return ret; + buf[--len] = '\0'; + strncpy(buf, g->name, len); + + return USBG_SUCCESS; } -size_t usbg_get_gadget_udc_len(usbg_gadget *g) +const char *usbg_get_udc_name(usbg_udc *u) { - return g ? strlen(g->udc) : USBG_ERROR_INVALID_PARAM; + return u ? u->name : NULL; } -int usbg_get_gadget_udc(usbg_gadget *g, char *buf, size_t len) +size_t usbg_get_udc_name_len(usbg_udc *u) { - int ret = USBG_SUCCESS; - if (g && buf) - strncpy(buf, g->udc, len); - else - ret = USBG_ERROR_INVALID_PARAM; + return u ? strlen(u->name) : USBG_ERROR_INVALID_PARAM; +} + +int usbg_cpy_udc_name(usbg_udc *u, char *buf, size_t len) +{ + if (!u || !buf || len == 0) + return USBG_ERROR_INVALID_PARAM; + + buf[--len] = '\0'; + strncpy(buf, u->name, len); + + return USBG_SUCCESS; +} + +int usbg_set_gadget_attr(usbg_gadget *g, usbg_gadget_attr attr, int val) +{ + const char *attr_name; + int ret = USBG_ERROR_INVALID_PARAM; + if (!g) + goto out; + + attr_name = usbg_get_gadget_attr_str(attr); + if (!attr_name) + goto out; + + ret = usbg_write_hex(g->path, g->name, attr_name, val); + +out: return ret; } -int usbg_set_gadget_attrs(usbg_gadget *g, usbg_gadget_attrs *g_attrs) +int usbg_get_gadget_attr(usbg_gadget *g, usbg_gadget_attr attr) +{ + const char *attr_name; + int ret = USBG_ERROR_INVALID_PARAM; + + if (!g) + goto out; + + attr_name = usbg_get_gadget_attr_str(attr); + if (!attr_name) + goto out; + + usbg_read_hex(g->path, g->name, attr_name, &ret); + +out: + return ret; +} + +usbg_udc *usbg_get_gadget_udc(usbg_gadget *g) +{ + usbg_udc *u = NULL; + + if (!g) + goto out; + /* + * if gadget was enabled we have to check if kernel + * didn't modify the UDC file due to some errors. + * For example some FFS daemon could just get + * a segmentation fault or sth + */ + if (g->udc) { + char buf[USBG_MAX_STR_LENGTH]; + int ret; + + ret = usbg_read_string(g->path, g->name, "UDC", buf); + if (ret != USBG_SUCCESS) + goto out; + + if (!strcmp(g->udc->name, buf)) { + /* Gadget is still assigned to this UDC */ + u = g->udc; + } else { + /* Kernel decided to detach this gadget */ + g->udc->gadget = NULL; + g->udc = NULL; + } + } + +out: + return u; +} + +usbg_gadget *usbg_get_udc_gadget(usbg_udc *u) +{ + usbg_gadget *g = NULL; + + if (!u) + goto out; + /* + * if gadget was enabled on this UDC we have to check if kernel + * didn't modify this due to some errors. + * For example some FFS daemon could just get a segmentation fault + * what causes detach of gadget + */ + if (u->gadget) { + usbg_udc *u_checked; + + u_checked = usbg_get_gadget_udc(u->gadget); + if (u_checked) { + g = u->gadget; + } else { + u->gadget->udc = NULL; + u->gadget = NULL; + } + } + +out: + return g; +} + +int usbg_set_gadget_attrs(usbg_gadget *g, const usbg_gadget_attrs *g_attrs) { int ret; if (!g || !g_attrs) @@ -1511,7 +2442,7 @@ int usbg_get_gadget_strs(usbg_gadget *g, int lang, g_strs) : USBG_ERROR_INVALID_PARAM; } -static int usbg_check_dir(char *path) +static int usbg_check_dir(const char *path) { int ret = USBG_SUCCESS; DIR *dir; @@ -1526,8 +2457,40 @@ static int usbg_check_dir(char *path) return ret; } +int usbg_set_gadget_str(usbg_gadget *g, usbg_gadget_str str, int lang, + const char *val) +{ + const char *str_name; + int ret = USBG_ERROR_INVALID_PARAM; + char path[USBG_MAX_PATH_LENGTH]; + int nmb; + + if (!g) + goto out; + + str_name = usbg_get_gadget_str_name(str); + if (!str_name) + goto out; + + nmb = snprintf(path, sizeof(path), "%s/%s/%s/0x%x", g->path, g->name, + STRINGS_DIR, lang); + if (nmb >= sizeof(path)) { + ret = USBG_ERROR_PATH_TOO_LONG; + goto out; + } + + ret = usbg_check_dir(path); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_write_string(path, "", str_name, val); + +out: + return ret; +} + int usbg_set_gadget_strs(usbg_gadget *g, int lang, - usbg_gadget_strs *g_strs) + const usbg_gadget_strs *g_strs) { char path[USBG_MAX_PATH_LENGTH]; int nmb; @@ -1560,7 +2523,7 @@ out: return ret; } -int usbg_set_gadget_serial_number(usbg_gadget *g, int lang, char *serno) +int usbg_set_gadget_serial_number(usbg_gadget *g, int lang, const char *serno) { int ret = USBG_ERROR_INVALID_PARAM; @@ -1581,7 +2544,7 @@ int usbg_set_gadget_serial_number(usbg_gadget *g, int lang, char *serno) return ret; } -int usbg_set_gadget_manufacturer(usbg_gadget *g, int lang, char *mnf) +int usbg_set_gadget_manufacturer(usbg_gadget *g, int lang, const char *mnf) { int ret = USBG_ERROR_INVALID_PARAM; @@ -1602,7 +2565,7 @@ int usbg_set_gadget_manufacturer(usbg_gadget *g, int lang, char *mnf) return ret; } -int usbg_set_gadget_product(usbg_gadget *g, int lang, char *prd) +int usbg_set_gadget_product(usbg_gadget *g, int lang, const char *prd) { int ret = USBG_ERROR_INVALID_PARAM; @@ -1624,16 +2587,36 @@ int usbg_set_gadget_product(usbg_gadget *g, int lang, char *prd) } int usbg_create_function(usbg_gadget *g, usbg_function_type type, - char *instance, usbg_function_attrs *f_attrs, usbg_function **f) + const char *instance, const usbg_function_attrs *f_attrs, + usbg_function **f) { char fpath[USBG_MAX_PATH_LENGTH]; usbg_function *func; int ret = USBG_ERROR_INVALID_PARAM; int n, free_space; - if (!g || !f || !instance) + if (!g || !f) return ret; + /* if attrs type is set, check if it has correct type */ + if (f_attrs && f_attrs->header.attrs_type) { + int attrs_type; + attrs_type = usbg_lookup_function_attrs_type(type); + if (attrs_type < 0 || attrs_type != f_attrs->header.attrs_type) + return ret; + } + + if (!instance) { + /* If someone creates ffs function and doesn't pass instance name + this means that device name from attrs should be used */ + if (type == F_FFS && f_attrs && f_attrs->attrs.ffs.dev_name) { + instance = f_attrs->attrs.ffs.dev_name; + f_attrs = NULL; + } else { + return ret; + } + } + func = usbg_get_function(g, type, instance); if (func) { ERROR("duplicate function name\n"); @@ -1651,8 +2634,9 @@ int usbg_create_function(usbg_gadget *g, usbg_function_type type, *f = usbg_allocate_function(fpath, type, instance, g); func = *f; if (!func) { - ERRORNO("allocating function\n"); + ERROR("allocating function\n"); ret = USBG_ERROR_NO_MEM; + goto out; } free_space = sizeof(fpath) - n; @@ -1680,10 +2664,11 @@ out: } int usbg_create_config(usbg_gadget *g, int id, const char *label, - usbg_config_attrs *c_attrs, usbg_config_strs *c_strs, usbg_config **c) + const usbg_config_attrs *c_attrs, const usbg_config_strs *c_strs, + usbg_config **c) { char cpath[USBG_MAX_PATH_LENGTH]; - usbg_config *conf; + usbg_config *conf = NULL; int ret = USBG_ERROR_INVALID_PARAM; int n, free_space; @@ -1710,7 +2695,7 @@ int usbg_create_config(usbg_gadget *g, int id, const char *label, *c = usbg_allocate_config(cpath, label, id, g); conf = *c; if (!conf) { - ERRORNO("allocating configuration\n"); + ERROR("allocating configuration\n"); ret = USBG_ERROR_NO_MEM; goto out; } @@ -1718,8 +2703,10 @@ int usbg_create_config(usbg_gadget *g, int id, const char *label, free_space = sizeof(cpath) - n; /* Append string at the end of previous one */ n = snprintf(&(cpath[n]), free_space, "/%s", (*c)->name); - if (n < free_space) { + if (n >= free_space) { ret = USBG_ERROR_PATH_TOO_LONG; + usbg_free_config(conf); + goto out; } ret = mkdir(cpath, S_IRWXU | S_IRWXG | S_IRWXO); @@ -1745,20 +2732,25 @@ out: return ret; } +const char *usbg_get_config_label(usbg_config *c) +{ + return c ? c->label : NULL; +} + size_t usbg_get_config_label_len(usbg_config *c) { return c ? strlen(c->label) : USBG_ERROR_INVALID_PARAM; } -int usbg_get_config_label(usbg_config *c, char *buf, size_t len) +int usbg_cpy_config_label(usbg_config *c, char *buf, size_t len) { - int ret = USBG_SUCCESS; - if (c && buf) - strncpy(buf, c->label, len); - else - ret = USBG_ERROR_INVALID_PARAM; + if (!c || !buf || len == 0) + return USBG_ERROR_INVALID_PARAM; - return ret; + buf[--len] = '\0'; + strncpy(buf, c->label, len); + + return USBG_SUCCESS; } int usbg_get_config_id(usbg_config *c) @@ -1766,27 +2758,32 @@ int usbg_get_config_id(usbg_config *c) return c ? c->id : USBG_ERROR_INVALID_PARAM; } +const char *usbg_get_function_instance(usbg_function *f) +{ + return f ? f->instance : NULL; +} + size_t usbg_get_function_instance_len(usbg_function *f) { return f ? strlen(f->instance) : USBG_ERROR_INVALID_PARAM; } -int usbg_get_function_instance(usbg_function *f, char *buf, size_t len) +int usbg_cpy_function_instance(usbg_function *f, char *buf, size_t len) { - int ret = USBG_SUCCESS; - if (f && buf) - strncpy(buf, f->instance, len); - else - ret = USBG_ERROR_INVALID_PARAM; + if (!f || !buf || len == 0) + return USBG_ERROR_INVALID_PARAM; - return ret; + buf[--len] = '\0'; + strncpy(buf, f->instance, len); + + return USBG_SUCCESS; } -int usbg_set_config_attrs(usbg_config *c, usbg_config_attrs *c_attrs) +int usbg_set_config_attrs(usbg_config *c, const usbg_config_attrs *c_attrs) { int ret = USBG_ERROR_INVALID_PARAM; - if (c && !c_attrs) { + if (c && c_attrs) { ret = usbg_write_dec(c->path, c->name, "MaxPower", c_attrs->bMaxPower); if (ret == USBG_SUCCESS) ret = usbg_write_hex8(c->path, c->name, "bmAttributes", @@ -1822,12 +2819,12 @@ int usbg_get_config_strs(usbg_config *c, int lang, usbg_config_strs *c_strs) } int usbg_set_config_strs(usbg_config *c, int lang, - usbg_config_strs *c_strs) + const usbg_config_strs *c_strs) { return usbg_set_config_string(c, lang, c_strs->configuration); } -int usbg_set_config_string(usbg_config *c, int lang, char *str) +int usbg_set_config_string(usbg_config *c, int lang, const char *str) { int ret = USBG_ERROR_INVALID_PARAM; @@ -1848,7 +2845,7 @@ int usbg_set_config_string(usbg_config *c, int lang, char *str) return ret; } -int usbg_add_config_function(usbg_config *c, char *name, usbg_function *f) +int usbg_add_config_function(usbg_config *c, const char *name, usbg_function *f) { char bpath[USBG_MAX_PATH_LENGTH]; char fpath[USBG_MAX_PATH_LENGTH]; @@ -1861,6 +2858,9 @@ int usbg_add_config_function(usbg_config *c, char *name, usbg_function *f) goto out; } + if (!name) + name = f->name; + b = usbg_get_binding(c, name); if (b) { ERROR("duplicate binding name\n"); @@ -1925,65 +2925,50 @@ usbg_function *usbg_get_binding_target(usbg_binding *b) return b ? b->target : NULL; } -size_t usbg_get_binding_name_len(usbg_binding *b) +const char *usbg_get_binding_name(usbg_binding *b) { - return b ? strlen(b->name) : USBG_ERROR_INVALID_PARAM; + return b ? b->name : NULL; } -int usbg_get_binding_name(usbg_binding *b, char *buf, size_t len) +size_t usbg_get_binding_name_len(usbg_binding *b) { - int ret = USBG_SUCCESS; - if (b && buf) - strncpy(buf, b->name, len); - else - ret = USBG_ERROR_INVALID_PARAM; - - return ret; + return b ? strlen(b->name) : USBG_ERROR_INVALID_PARAM; } -int usbg_get_udcs(struct dirent ***udc_list) +int usbg_cpy_binding_name(usbg_binding *b, char *buf, size_t len) { - int ret = USBG_ERROR_INVALID_PARAM; + if (!b || !buf || len == 0) + return USBG_ERROR_INVALID_PARAM; - if (udc_list) { - ret = scandir("/sys/class/udc", udc_list, file_select, alphasort); - if (ret < 0) - ret = usbg_translate_error(errno); - } + buf[--len] = '\0'; + strncpy(buf, b->name, len); - return ret; + return USBG_SUCCESS; } -int usbg_enable_gadget(usbg_gadget *g, char *udc) +int usbg_enable_gadget(usbg_gadget *g, usbg_udc *udc) { - char gudc[USBG_MAX_STR_LENGTH]; - struct dirent **udc_list; - int i; int ret = USBG_ERROR_INVALID_PARAM; if (!g) return ret; if (!udc) { - ret = usbg_get_udcs(&udc_list); - if (ret >= 0) { - /* Look for default one - first in string order */ - strcpy(gudc, udc_list[0]->d_name); - udc = gudc; - - /** Free the memory */ - for (i = 0; i < ret; ++i) - free(udc_list[i]); - free(udc_list); - } else { + udc = usbg_get_first_udc(g->parent); + if (!udc) return ret; - } } - ret = usbg_write_string(g->path, g->name, "UDC", udc); - - if (ret == USBG_SUCCESS) - strcpy(g->udc, udc); + ret = usbg_write_string(g->path, g->name, "UDC", udc->name); + if (ret == USBG_SUCCESS) { + /* If gadget has been detached and we didn't noticed + * it we have to clean up now. + */ + if (g->udc) + g->udc->gadget = NULL; + g->udc = udc; + udc->gadget = g; + } return ret; } @@ -1992,9 +2977,14 @@ int usbg_disable_gadget(usbg_gadget *g) { int ret = USBG_ERROR_INVALID_PARAM; - if (g) { - strcpy(g->udc, ""); - ret = usbg_write_string(g->path, g->name, "UDC", ""); + if (!g) + return ret; + + ret = usbg_write_string(g->path, g->name, "UDC", "\n"); + if (ret == USBG_SUCCESS) { + if (g->udc) + g->udc->gadget = NULL; + g->udc = NULL; } return ret; @@ -2015,57 +3005,380 @@ int usbg_get_function_attrs(usbg_function *f, usbg_function_attrs *f_attrs) : USBG_ERROR_INVALID_PARAM; } -int usbg_set_function_net_attrs(usbg_function *f, usbg_f_net_attrs *attrs) +static void usbg_cleanup_function_ms_lun_attrs(usbg_f_ms_lun_attrs *lun_attrs) +{ + if (!lun_attrs) + return; + + free((char*)lun_attrs->filename); + lun_attrs->id = -1; +} + +void usbg_cleanup_function_attrs(usbg_function_attrs *f_attrs) +{ + usbg_f_attrs *attrs; + + if (!f_attrs) + return; + + attrs = &f_attrs->attrs; + + switch (f_attrs->header.attrs_type) { + case USBG_F_ATTRS_SERIAL: + break; + + case USBG_F_ATTRS_NET: + free((char*)attrs->net.ifname); + attrs->net.ifname = NULL; + break; + + case USBG_F_ATTRS_PHONET: + free((char*)attrs->phonet.ifname); + attrs->phonet.ifname = NULL; + break; + + case USBG_F_ATTRS_FFS: + free((char*)attrs->ffs.dev_name); + attrs->ffs.dev_name = NULL; + break; + + case USBG_F_ATTRS_MS: + { + int i; + usbg_f_ms_attrs *ms_attrs = &attrs->ms; + + if (!ms_attrs->luns) + goto ms_break; + + for (i = 0; i < ms_attrs->nluns; ++i) { + if (!ms_attrs->luns[i]) + continue; + + usbg_cleanup_function_ms_lun_attrs(ms_attrs->luns[i]); + free(ms_attrs->luns[i]); + } + free(ms_attrs->luns); + ms_attrs->luns = NULL; + ms_attrs->nluns = -1; + ms_break: + break; + } + + case USBG_F_ATTRS_MIDI: + free((char*)attrs->midi.id); + attrs->midi.id = NULL; + break; + + case USBG_F_ATTRS_LOOPBACK: + break; + + default: + ERROR("Unsupported attrs type\n"); + break; + } +} + +int usbg_set_function_net_attrs(usbg_function *f, const usbg_f_net_attrs *attrs) { int ret = USBG_SUCCESS; + char addr_buf[USBG_MAX_STR_LENGTH]; char *addr; - addr = ether_ntoa(&attrs->dev_addr); + /* ifname is read only so we accept only empty string for this param */ + if (attrs->ifname && attrs->ifname[0]) { + ret = USBG_ERROR_INVALID_PARAM; + goto out; + } + + addr = usbg_ether_ntoa_r(&attrs->dev_addr, addr_buf); ret = usbg_write_string(f->path, f->name, "dev_addr", addr); if (ret != USBG_SUCCESS) goto out; - addr = ether_ntoa(&attrs->host_addr); + addr = usbg_ether_ntoa_r(&attrs->host_addr, addr_buf); ret = usbg_write_string(f->path, f->name, "host_addr", addr); if (ret != USBG_SUCCESS) goto out; - ret = usbg_write_string(f->path, f->name, "ifname", attrs->ifname); + ret = usbg_write_dec(f->path, f->name, "qmult", attrs->qmult); + +out: + return ret; +} + +static int usbg_set_f_ms_lun_attrs(const char *path, const char *lun, + usbg_f_ms_lun_attrs *lun_attrs) +{ + int ret; + + ret = usbg_write_bool(path, lun, "cdrom", lun_attrs->cdrom); if (ret != USBG_SUCCESS) goto out; - ret = usbg_write_dec(f->path, f->name, "qmult", attrs->qmult); + ret = usbg_write_bool(path, lun, "ro", lun_attrs->ro); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_write_bool(path, lun, "nofua", lun_attrs->nofua); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_write_bool(path, lun, "removable", lun_attrs->removable); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_write_string(path, lun, "file", + lun_attrs->filename); out: return ret; } -int usbg_set_function_attrs(usbg_function *f, usbg_function_attrs *f_attrs) +static int usbg_set_function_ms_attrs(usbg_function *f, + const usbg_f_ms_attrs *f_attrs) +{ + int ret; + int i, nmb; + int space_left; + char *new_lun_mask; + char lpath[USBG_MAX_PATH_LENGTH]; + char *lpath_end; + DIR *dir; + struct dirent **dent; + + ret = usbg_write_bool(f->path, f->name, "stall", f_attrs->stall); + if (ret != USBG_SUCCESS) + goto out; + + /* lun0 cannot be removed */ + if (!f_attrs->luns || f_attrs->nluns <= 0) + goto out; + + ret = snprintf(lpath, sizeof(lpath), "%s/%s/", f->path, f->name); + if (ret >= sizeof(lpath)) { + ret = USBG_ERROR_PATH_TOO_LONG; + goto out; + } + + lpath_end = lpath + strlen(lpath); + space_left = sizeof(lpath) - (lpath_end - lpath); + + new_lun_mask = calloc(f_attrs->nluns, sizeof (char)); + if (!new_lun_mask) { + ret = USBG_ERROR_NO_MEM; + goto out; + } + + for (i = 0; i < f_attrs->nluns; ++i) { + usbg_f_ms_lun_attrs *lun = f_attrs->luns[i]; + + /* + * id may be left unset in lun attrs but + * if it is set it has to be equal to position + * in lun array + */ + if (lun && lun->id >= 0 && lun->id != i) { + ret = USBG_ERROR_INVALID_PARAM; + goto err_lun_loop; + } + + ret = snprintf(lpath_end, space_left, "/lun.%d/", i); + if (ret >= space_left) { + ret = USBG_ERROR_PATH_TOO_LONG; + goto err_lun_loop; + } + + /* + * Check if dir exist and create it if needed + */ + dir = opendir(lpath); + if (dir) { + closedir(dir); + } else if (errno != ENOENT) { + ret = usbg_translate_error(errno); + goto err_lun_loop; + } else { + ret = mkdir(lpath, S_IRWXU|S_IRWXG|S_IRWXO); + if (!ret) { + /* + * If we have created a new directory in + * this function let's mark it so we can + * cleanup in case of error + */ + new_lun_mask[i] = 1; + } else { + ret = usbg_translate_error(errno); + goto err_lun_loop; + } + } + + /* if attributes has not been provided just go to next one */ + if (!lun) + continue; + + ret = usbg_set_f_ms_lun_attrs(lpath, "", lun); + if (ret != USBG_SUCCESS) + goto err_lun_loop; + } + + /* Check if function has more luns and remove them */ + *lpath_end = '\0'; + i = 0; + nmb = scandir(lpath, &dent, lun_select, lun_sort); + if (nmb < 0) { + ret = usbg_translate_error(errno); + goto err_lun_loop; + } + + for (i = 0; i < f_attrs->nluns; ++i) + free(dent[i]); + + for (; i < nmb; ++i) { + ret = usbg_rm_dir(lpath, dent[i]->d_name); + free(dent[i]); + /* There is no good way to recover form this */ + if (ret != USBG_SUCCESS) + goto err_rm_loop; + } + free(dent); + free(new_lun_mask); + + return USBG_SUCCESS; + +err_rm_loop: + while (++i < nmb) + free(dent[i]); + free(dent); + + i = f_attrs->nluns; +err_lun_loop: + /* array is null terminated so we may access lun[nluns] */ + for (; i >= 0; --i) { + if (!new_lun_mask[i]) + continue; + + ret = snprintf(lpath_end, space_left, "/lun.%d/", i); + if (ret >= space_left) { + /* + * This should not happen because if we were + * able to create this directory we should be + * also able to remove it. + */ + continue; + } + rmdir(lpath); + } + free(new_lun_mask); + +out: + return ret; +} + +int usbg_set_function_midi_attrs(usbg_function *f, + const usbg_f_midi_attrs *attrs) +{ + int ret; + + ret = usbg_write_dec(f->path, f->name, "index", attrs->index); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_write_string(f->path, f->name, "id", attrs->id); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_write_dec(f->path, f->name, "in_ports", attrs->in_ports); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_write_dec(f->path, f->name, "out_ports", attrs->out_ports); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_write_dec(f->path, f->name, "buflen", attrs->buflen); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_write_dec(f->path, f->name, "qlen", attrs->qlen); + +out: + return ret; +} + +int usbg_set_function_loopback_attrs(usbg_function *f, + const usbg_f_loopback_attrs *attrs) +{ + int ret; + + ret = usbg_write_dec(f->path, f->name, "buflen", attrs->buflen); + if (ret != USBG_SUCCESS) + goto out; + + ret = usbg_write_dec(f->path, f->name, "qlen", attrs->qlen); + +out: + return ret; +} + +int usbg_set_function_attrs(usbg_function *f, + const usbg_function_attrs *f_attrs) { int ret = USBG_ERROR_INVALID_PARAM; + int attrs_type; if (!f || !f_attrs) - return USBG_ERROR_INVALID_PARAM; + return ret; - switch (f->type) { - case F_SERIAL: - case F_ACM: - case F_OBEX: - ret = usbg_write_dec(f->path, f->name, "port_num", f_attrs->serial.port_num); + attrs_type = usbg_lookup_function_attrs_type(f->type); + if (attrs_type < 0) + return ret; + + /* if attrs type is set, check if it has correct type */ + if (f_attrs->header.attrs_type && attrs_type != f_attrs->header.attrs_type) + return ret; + + switch (attrs_type) { + case USBG_F_ATTRS_SERIAL: + /* port_num attribute is read only so we accept only 0 + * and do nothing with it */ + ret = f_attrs->attrs.serial.port_num ? USBG_ERROR_INVALID_PARAM + : USBG_SUCCESS; break; - case F_ECM: - case F_SUBSET: - case F_NCM: - case F_EEM: - case F_RNDIS: - ret = usbg_set_function_net_attrs(f, &f_attrs->net); + + case USBG_F_ATTRS_NET: + ret = usbg_set_function_net_attrs(f, &f_attrs->attrs.net); break; - case F_PHONET: - ret = usbg_write_string(f->path, f->name, "ifname", f_attrs->phonet.ifname); + + case USBG_F_ATTRS_PHONET: + /* ifname attribute is read only + * so we accept only empty string */ + ret = f_attrs->attrs.phonet.ifname && f_attrs->attrs.phonet.ifname[0] ? + USBG_ERROR_INVALID_PARAM : USBG_SUCCESS; break; + + case USBG_F_ATTRS_FFS: + /* dev_name is a virtual attribute so allow only to use empty + * empty string which means nop */ + ret = f_attrs->attrs.ffs.dev_name && f_attrs->attrs.ffs.dev_name[0] ? + USBG_ERROR_INVALID_PARAM : USBG_SUCCESS; + break; + + case USBG_F_ATTRS_MS: + ret = usbg_set_function_ms_attrs(f, &f_attrs->attrs.ms); + break; + + case USBG_F_ATTRS_MIDI: + ret = usbg_set_function_midi_attrs(f, &f_attrs->attrs.midi); + break; + + case USBG_F_ATTRS_LOOPBACK: + ret = usbg_set_function_loopback_attrs(f, &f_attrs->attrs.loopback); + break; + default: ERROR("Unsupported function type\n"); ret = USBG_ERROR_NOT_SUPPORTED; + break; } return ret; @@ -2076,7 +3389,8 @@ int usbg_set_net_dev_addr(usbg_function *f, struct ether_addr *dev_addr) int ret = USBG_SUCCESS; if (f && dev_addr) { - char *str_addr = ether_ntoa(dev_addr); + char str_buf[USBG_MAX_STR_LENGTH]; + char *str_addr = usbg_ether_ntoa_r(dev_addr, str_buf); ret = usbg_write_string(f->path, f->name, "dev_addr", str_addr); } else { ret = USBG_ERROR_INVALID_PARAM; @@ -2090,7 +3404,8 @@ int usbg_set_net_host_addr(usbg_function *f, struct ether_addr *host_addr) int ret = USBG_SUCCESS; if (f && host_addr) { - char *str_addr = ether_ntoa(host_addr); + char str_buf[USBG_MAX_STR_LENGTH]; + char *str_addr = usbg_ether_ntoa_r(host_addr, str_buf); ret = usbg_write_string(f->path, f->name, "host_addr", str_addr); } else { ret = USBG_ERROR_INVALID_PARAM; @@ -2125,6 +3440,11 @@ usbg_binding *usbg_get_first_binding(usbg_config *c) return c ? TAILQ_FIRST(&c->bindings) : NULL; } +usbg_udc *usbg_get_first_udc(usbg_state *s) +{ + return s ? TAILQ_FIRST(&s->udcs) : NULL; +} + usbg_gadget *usbg_get_next_gadget(usbg_gadget *g) { return g ? TAILQ_NEXT(g, gnode) : NULL; @@ -2144,3 +3464,9 @@ usbg_binding *usbg_get_next_binding(usbg_binding *b) { return b ? TAILQ_NEXT(b, bnode) : NULL; } + +usbg_udc *usbg_get_next_udc(usbg_udc *u) +{ + return u ? TAILQ_NEXT(u, unode) : NULL; +} + diff --git a/src/usbg_schemes_libconfig.c b/src/usbg_schemes_libconfig.c new file mode 100644 index 0000000..c8944ed --- /dev/null +++ b/src/usbg_schemes_libconfig.c @@ -0,0 +1,2184 @@ +/* + * 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 <errno.h> +#include <stdlib.h> +#include <string.h> +#include <libconfig.h> + +#include "usbg/usbg_internal.h" + +#define USBG_NAME_TAG "name" +#define USBG_ATTRS_TAG "attrs" +#define USBG_STRINGS_TAG "strings" +#define USBG_FUNCTIONS_TAG "functions" +#define USBG_CONFIGS_TAG "configs" +#define USBG_LANG_TAG "lang" +#define USBG_TYPE_TAG "type" +#define USBG_INSTANCE_TAG "instance" +#define USBG_ID_TAG "id" +#define USBG_FUNCTION_TAG "function" +#define USBG_TAB_WIDTH 4 + +static inline int generate_function_label(usbg_function *f, char *buf, int size) +{ + return snprintf(buf, size, "%s_%s", + usbg_get_function_type_str(f->type), f->instance); + +} + +static int usbg_export_binding(usbg_binding *b, config_setting_t *root) +{ + config_setting_t *node; + int ret = USBG_ERROR_NO_MEM; + int cfg_ret; + char label[USBG_MAX_NAME_LENGTH]; + int nmb; + +#define CRETAE_ATTR_STRING(SOURCE, NAME) \ + do { \ + node = config_setting_add(root, NAME, CONFIG_TYPE_STRING); \ + if (!node) \ + goto out; \ + cfg_ret = config_setting_set_string(node, SOURCE); \ + if (cfg_ret != CONFIG_TRUE) { \ + ret = USBG_ERROR_OTHER_ERROR; \ + goto out; \ + } \ + } while (0) + + CRETAE_ATTR_STRING(b->name, USBG_NAME_TAG); + + nmb = generate_function_label(b->target, label, sizeof(label)); + if (nmb >= sizeof(label)) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + CRETAE_ATTR_STRING(label, USBG_FUNCTION_TAG); + +#undef CRETAE_ATTR_STRING + + ret = USBG_SUCCESS; + +out: + return ret; +} + +static int usbg_export_config_bindings(usbg_config *c, config_setting_t *root) +{ + usbg_binding *b; + config_setting_t *node; + int ret = USBG_SUCCESS; + + TAILQ_FOREACH(b, &c->bindings, bnode) { + node = config_setting_add(root, NULL, CONFIG_TYPE_GROUP); + if (!node) { + ret = USBG_ERROR_NO_MEM; + break; + } + + ret = usbg_export_binding(b, node); + if (ret != USBG_SUCCESS) + break; + } + + return ret; +} + +static int usbg_export_config_strs_lang(usbg_config *c, char *lang_str, + config_setting_t *root) +{ + config_setting_t *node; + usbg_config_strs strs; + int lang; + int usbg_ret, cfg_ret, ret2; + int ret = USBG_ERROR_NO_MEM; + + ret2 = sscanf(lang_str, "%x", &lang); + if (ret2 != 1) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + usbg_ret = usbg_get_config_strs(c, lang, &strs); + if (usbg_ret != USBG_SUCCESS) { + ret = usbg_ret; + goto out; + } + + node = config_setting_add(root, USBG_LANG_TAG, CONFIG_TYPE_INT); + if (!node) + goto out; + + cfg_ret = config_setting_set_format(node, CONFIG_FORMAT_HEX); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + cfg_ret = config_setting_set_int(node, lang); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + node = config_setting_add(root, "configuration" , CONFIG_TYPE_STRING); + if (!node) + goto out; + + cfg_ret = config_setting_set_string(node, strs.configuration); + + ret = cfg_ret == CONFIG_TRUE ? USBG_SUCCESS : USBG_ERROR_OTHER_ERROR; +out: + return ret; + +} + +static int usbg_export_config_strings(usbg_config *c, config_setting_t *root) +{ + config_setting_t *node; + int usbg_ret = USBG_SUCCESS; + int nmb, i; + int ret = USBG_ERROR_NO_MEM; + char spath[USBG_MAX_PATH_LENGTH]; + struct dirent **dent; + + nmb = snprintf(spath, sizeof(spath), "%s/%s/%s", c->path, + c->name, STRINGS_DIR); + if (nmb >= sizeof(spath)) { + ret = USBG_ERROR_PATH_TOO_LONG; + goto out; + } + + nmb = scandir(spath, &dent, file_select, alphasort); + if (nmb < 0) { + ret = usbg_translate_error(errno); + goto out; + } + + for (i = 0; i < nmb; ++i) { + node = config_setting_add(root, NULL, CONFIG_TYPE_GROUP); + if (!node) + goto out; + + usbg_ret = usbg_export_config_strs_lang(c, dent[i]->d_name, + node); + if (usbg_ret != USBG_SUCCESS) + break; + + free(dent[i]); + } + /* This loop will be executed only if error occurred in previous one */ + for (; i < nmb; ++i) + free(dent[i]); + + free(dent); + ret = usbg_ret; +out: + return ret; +} + +static int usbg_export_config_attrs(usbg_config *c, config_setting_t *root) +{ + config_setting_t *node; + usbg_config_attrs attrs; + int usbg_ret, cfg_ret; + int ret = USBG_ERROR_NO_MEM; + + usbg_ret = usbg_get_config_attrs(c, &attrs); + if (usbg_ret) { + ret = usbg_ret; + goto out; + } + +#define ADD_CONFIG_ATTR(attr_name) \ + do { \ + node = config_setting_add(root, #attr_name, CONFIG_TYPE_INT); \ + if (!node) \ + goto out; \ + cfg_ret = config_setting_set_format(node, CONFIG_FORMAT_HEX); \ + if (cfg_ret != CONFIG_TRUE) { \ + ret = USBG_ERROR_OTHER_ERROR; \ + goto out; \ + } \ + cfg_ret = config_setting_set_int(node, attrs.attr_name); \ + if (cfg_ret != CONFIG_TRUE) { \ + ret = USBG_ERROR_OTHER_ERROR; \ + goto out; \ + } \ + } while (0) + + ADD_CONFIG_ATTR(bmAttributes); + ADD_CONFIG_ATTR(bMaxPower); + +#undef ADD_CONFIG_ATTR + + ret = USBG_SUCCESS; +out: + return ret; + +} + +/* This function does not export configuration id because it is more of + * a property of gadget which contains this config than config itself */ +static int usbg_export_config_prep(usbg_config *c, config_setting_t *root) +{ + config_setting_t *node; + int ret = USBG_ERROR_NO_MEM; + int usbg_ret; + int cfg_ret; + + node = config_setting_add(root, USBG_NAME_TAG, CONFIG_TYPE_STRING); + if (!node) + goto out; + + cfg_ret = config_setting_set_string(node, c->label); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + node = config_setting_add(root, USBG_ATTRS_TAG, CONFIG_TYPE_GROUP); + if (!node) + goto out; + + usbg_ret = usbg_export_config_attrs(c, node); + if (usbg_ret != USBG_SUCCESS) { + ret = usbg_ret; + goto out; + } + + node = config_setting_add(root, USBG_STRINGS_TAG, CONFIG_TYPE_LIST); + if (!node) + goto out; + + usbg_ret = usbg_export_config_strings(c, node); + if (usbg_ret != USBG_SUCCESS) { + ret = usbg_ret; + goto out; + } + + node = config_setting_add(root, USBG_FUNCTIONS_TAG, CONFIG_TYPE_LIST); + if (!node) + goto out; + + ret = usbg_export_config_bindings(c, node); +out: + return ret; + +} + +static int usbg_export_gadget_configs(usbg_gadget *g, config_setting_t *root) +{ + usbg_config *c; + config_setting_t *node, *id_node; + int ret = USBG_SUCCESS; + int cfg_ret; + + TAILQ_FOREACH(c, &g->configs, cnode) { + node = config_setting_add(root, NULL, CONFIG_TYPE_GROUP); + if (!node) { + ret = USBG_ERROR_NO_MEM; + break; + } + + id_node = config_setting_add(node, USBG_ID_TAG, + CONFIG_TYPE_INT); + if (!id_node) { + ret = USBG_ERROR_NO_MEM; + break; + } + + cfg_ret = config_setting_set_int(id_node, c->id); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + break; + } + + ret = usbg_export_config_prep(c, node); + if (ret != USBG_SUCCESS) + break; + } + + return ret; +} + +static int usbg_export_f_net_attrs(usbg_f_net_attrs *attrs, + config_setting_t *root) +{ + config_setting_t *node; + char *addr; + char addr_buf[USBG_MAX_STR_LENGTH]; + int cfg_ret; + int ret = USBG_ERROR_NO_MEM; + + node = config_setting_add(root, "dev_addr", CONFIG_TYPE_STRING); + if (!node) + goto out; + + addr = usbg_ether_ntoa_r(&attrs->dev_addr, addr_buf); + cfg_ret = config_setting_set_string(node, addr); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + node = config_setting_add(root, "host_addr", CONFIG_TYPE_STRING); + if (!node) + goto out; + + addr = usbg_ether_ntoa_r(&attrs->host_addr, addr_buf); + cfg_ret = config_setting_set_string(node, addr); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + node = config_setting_add(root, "qmult", CONFIG_TYPE_INT); + if (!node) + goto out; + + cfg_ret = config_setting_set_int(node, attrs->qmult); + ret = cfg_ret == CONFIG_TRUE ? 0 : USBG_ERROR_OTHER_ERROR; + + /* if name is read only so we don't export it */ +out: + return ret; + +} + +static int usbg_export_f_ms_lun_attrs(usbg_f_ms_lun_attrs *lattrs, + config_setting_t *root) +{ + config_setting_t *node; + int cfg_ret; + int i; + int ret = USBG_ERROR_NO_MEM; + +#define BOOL_ATTR(_name) { .name = #_name, .value = &lattrs->_name, } + struct { + char *name; + bool *value; + } bool_attrs[] = { + BOOL_ATTR(cdrom), + BOOL_ATTR(ro), + BOOL_ATTR(nofua), + BOOL_ATTR(removable), + }; +#undef BOOL_ATTR + + for (i = 0; i < ARRAY_SIZE(bool_attrs); ++i) { + node = config_setting_add(root, bool_attrs[i].name, CONFIG_TYPE_BOOL); + if (!node) + goto out; + + cfg_ret = config_setting_set_bool(node, *(bool_attrs[i].value)); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + } + + node = config_setting_add(root, "filename", CONFIG_TYPE_STRING); + if (!node) + goto out; + + cfg_ret = config_setting_set_string(node, lattrs->filename); + ret = cfg_ret == CONFIG_TRUE ? USBG_SUCCESS : USBG_ERROR_OTHER_ERROR; + +out: + return ret; +} + +static int usbg_export_f_ms_attrs(usbg_f_ms_attrs *attrs, + config_setting_t *root) +{ + config_setting_t *luns_node, *node; + int i; + int cfg_ret; + int ret = USBG_ERROR_NO_MEM; + + node = config_setting_add(root, "stall", CONFIG_TYPE_BOOL); + if (!node) + goto out; + + cfg_ret = config_setting_set_bool(node, attrs->stall); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + luns_node = config_setting_add(root, "luns", CONFIG_TYPE_LIST); + if (!luns_node) + goto out; + + for (i = 0; i < attrs->nluns; ++i) { + node = config_setting_add(luns_node, "", CONFIG_TYPE_GROUP); + if (!node) + goto out; + + ret = usbg_export_f_ms_lun_attrs(attrs->luns[i], node); + if (ret != USBG_SUCCESS) + goto out; + } + + ret = USBG_SUCCESS; +out: + return ret; + +} + +static int usbg_export_f_midi_attrs(usbg_f_midi_attrs *attrs, + config_setting_t *root) +{ + config_setting_t *node; + int cfg_ret; + int ret = USBG_ERROR_NO_MEM; + +#define ADD_F_MIDI_INT_ATTR(attr, minval) \ + do { \ + if ((int)attrs->attr < minval) { \ + ret = USBG_ERROR_INVALID_VALUE; \ + goto out; \ + } \ + node = config_setting_add(root, #attr, CONFIG_TYPE_INT);\ + if (!node) \ + goto out; \ + cfg_ret = config_setting_set_int(node, attrs->attr); \ + if (cfg_ret != CONFIG_TRUE) { \ + ret = USBG_ERROR_OTHER_ERROR; \ + goto out; \ + } \ + } while (0) + + ADD_F_MIDI_INT_ATTR(index, INT_MIN); + + node = config_setting_add(root, "id", CONFIG_TYPE_STRING); + if (!node) + goto out; + + cfg_ret = config_setting_set_string(node, attrs->id); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + ADD_F_MIDI_INT_ATTR(in_ports, 0); + ADD_F_MIDI_INT_ATTR(out_ports, 0); + ADD_F_MIDI_INT_ATTR(buflen, 0); + ADD_F_MIDI_INT_ATTR(qlen, 0); + +#undef ADD_F_MIDI_INT_ATTR + + ret = USBG_SUCCESS; +out: + return ret; +} + +static int usbg_export_f_loopback_attrs(usbg_f_loopback_attrs *attrs, + config_setting_t *root) +{ + config_setting_t *node; + int cfg_ret; + int ret = USBG_ERROR_NO_MEM; + +#define ADD_F_LOOPBACK_INT_ATTR(attr, minval) \ + do { \ + if ((int)attrs->attr < minval) { \ + ret = USBG_ERROR_INVALID_VALUE; \ + goto out; \ + } \ + node = config_setting_add(root, #attr, CONFIG_TYPE_INT);\ + if (!node) \ + goto out; \ + cfg_ret = config_setting_set_int(node, attrs->attr); \ + if (cfg_ret != CONFIG_TRUE) { \ + ret = USBG_ERROR_OTHER_ERROR; \ + goto out; \ + } \ + } while (0) + + ADD_F_LOOPBACK_INT_ATTR(buflen, 0); + ADD_F_LOOPBACK_INT_ATTR(qlen, 0); + +#undef ADD_F_LOOPBACK_INT_ATTR + + ret = USBG_SUCCESS; +out: + return ret; +} + +static int usbg_export_function_attrs(usbg_function *f, config_setting_t *root) +{ + config_setting_t *node; + usbg_function_attrs f_attrs; + int usbg_ret, cfg_ret; + int ret = USBG_ERROR_NO_MEM; + + usbg_ret = usbg_get_function_attrs(f, &f_attrs); + if (usbg_ret) { + ret = usbg_ret; + goto out; + } + + switch (f_attrs.header.attrs_type) { + case USBG_F_ATTRS_SERIAL: + node = config_setting_add(root, "port_num", CONFIG_TYPE_INT); + if (!node) + goto out; + + cfg_ret = config_setting_set_int(node, f_attrs.attrs.serial.port_num); + ret = cfg_ret == CONFIG_TRUE ? 0 : USBG_ERROR_OTHER_ERROR; + break; + + case USBG_F_ATTRS_NET: + ret = usbg_export_f_net_attrs(&f_attrs.attrs.net, root); + break; + + case USBG_F_ATTRS_MS: + ret = usbg_export_f_ms_attrs(&f_attrs.attrs.ms, root); + break; + + case USBG_F_ATTRS_MIDI: + ret = usbg_export_f_midi_attrs(&f_attrs.attrs.midi, root); + break; + + case USBG_F_ATTRS_LOOPBACK: + ret = usbg_export_f_loopback_attrs(&f_attrs.attrs.loopback, root); + break; + + case USBG_F_ATTRS_PHONET: + /* Don't export ifname because it is read only */ + case USBG_F_ATTRS_FFS: + /* We don't need to export ffs attributes + * due to instance name export */ + ret = USBG_SUCCESS; + break; + default: + ERROR("Unsupported function type\n"); + ret = USBG_ERROR_NOT_SUPPORTED; + } + + usbg_cleanup_function_attrs(&f_attrs); +out: + return ret; +} + +/* This function does not import instance name because this is more property + * of a gadget than a function itself */ +static int usbg_export_function_prep(usbg_function *f, config_setting_t *root) +{ + config_setting_t *node; + int ret = USBG_ERROR_NO_MEM; + int cfg_ret; + + node = config_setting_add(root, USBG_TYPE_TAG, CONFIG_TYPE_STRING); + if (!node) + goto out; + + cfg_ret = config_setting_set_string(node, usbg_get_function_type_str( + f->type)); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + node = config_setting_add(root, USBG_ATTRS_TAG, CONFIG_TYPE_GROUP); + if (!node) + goto out; + + ret = usbg_export_function_attrs(f, node); +out: + return ret; +} + + +static int usbg_export_gadget_functions(usbg_gadget *g, config_setting_t *root) +{ + usbg_function *f; + config_setting_t *node, *inst_node; + int ret = USBG_SUCCESS; + int cfg_ret; + char label[USBG_MAX_NAME_LENGTH]; + char *func_label; + int nmb; + + TAILQ_FOREACH(f, &g->functions, fnode) { + if (f->label) { + func_label = f->label; + } else { + nmb = generate_function_label(f, label, sizeof(label)); + if (nmb >= sizeof(label)) { + ret = USBG_ERROR_OTHER_ERROR; + break; + } + func_label = label; + } + + node = config_setting_add(root, func_label, CONFIG_TYPE_GROUP); + if (!node) { + ret = USBG_ERROR_NO_MEM; + break; + } + + /* Add instance name to identify in this gadget */ + inst_node = config_setting_add(node, USBG_INSTANCE_TAG, + CONFIG_TYPE_STRING); + if (!inst_node) { + ret = USBG_ERROR_NO_MEM; + break; + } + + cfg_ret = config_setting_set_string(inst_node, f->instance); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + break; + } + + ret = usbg_export_function_prep(f, node); + if (ret != USBG_SUCCESS) + break; + } + + return ret; +} + +static int usbg_export_gadget_strs_lang(usbg_gadget *g, const char *lang_str, + config_setting_t *root) +{ + config_setting_t *node; + usbg_gadget_strs strs; + int lang; + int usbg_ret, cfg_ret; + int ret = USBG_ERROR_NO_MEM; + + ret = sscanf(lang_str, "%x", &lang); + if (ret != 1) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + usbg_ret = usbg_get_gadget_strs(g, lang, &strs); + if (usbg_ret != USBG_SUCCESS) { + ret = usbg_ret; + goto out; + } + + node = config_setting_add(root, USBG_LANG_TAG, CONFIG_TYPE_INT); + if (!node) + goto out; + + cfg_ret = config_setting_set_format(node, CONFIG_FORMAT_HEX); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + cfg_ret = config_setting_set_int(node, lang); + if (cfg_ret != CONFIG_TRUE) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + +#define ADD_GADGET_STR(str_name, field) \ + do { \ + node = config_setting_add(root, str_name, CONFIG_TYPE_STRING); \ + if (!node) \ + goto out; \ + cfg_ret = config_setting_set_string(node, strs.field); \ + if (cfg_ret != CONFIG_TRUE) { \ + ret = USBG_ERROR_OTHER_ERROR; \ + goto out; \ + } \ + } while (0) + + ADD_GADGET_STR("manufacturer", str_mnf); + ADD_GADGET_STR("product", str_prd); + ADD_GADGET_STR("serialnumber", str_ser); + +#undef ADD_GADGET_STR + ret = USBG_SUCCESS; +out: + return ret; +} + +static int usbg_export_gadget_strings(usbg_gadget *g, config_setting_t *root) +{ + config_setting_t *node; + int usbg_ret = USBG_SUCCESS; + int nmb, i; + int ret = USBG_ERROR_NO_MEM; + char spath[USBG_MAX_PATH_LENGTH]; + struct dirent **dent; + + nmb = snprintf(spath, sizeof(spath), "%s/%s/%s", g->path, + g->name, STRINGS_DIR); + if (nmb >= sizeof(spath)) { + ret = USBG_ERROR_PATH_TOO_LONG; + goto out; + } + + nmb = scandir(spath, &dent, file_select, alphasort); + if (nmb < 0) { + ret = usbg_translate_error(errno); + goto out; + } + + for (i = 0; i < nmb; ++i) { + node = config_setting_add(root, NULL, CONFIG_TYPE_GROUP); + if (!node) + break; + + usbg_ret = usbg_export_gadget_strs_lang(g, dent[i]->d_name, + node); + if (usbg_ret != USBG_SUCCESS) + break; + + free(dent[i]); + } + /* This loop will be executed only if error occurred in previous one */ + for (; i < nmb; ++i) + free(dent[i]); + + free(dent); + ret = usbg_ret; +out: + return ret; +} + +static int usbg_export_gadget_attrs(usbg_gadget *g, config_setting_t *root) +{ + config_setting_t *node; + usbg_gadget_attrs attrs; + int usbg_ret, cfg_ret; + int ret = USBG_ERROR_NO_MEM; + + usbg_ret = usbg_get_gadget_attrs(g, &attrs); + if (usbg_ret) { + ret = usbg_ret; + goto out; + } + +#define ADD_GADGET_ATTR(attr_name) \ + do { \ + node = config_setting_add(root, #attr_name, CONFIG_TYPE_INT); \ + if (!node) \ + goto out; \ + cfg_ret = config_setting_set_format(node, CONFIG_FORMAT_HEX); \ + if (cfg_ret != CONFIG_TRUE) { \ + ret = USBG_ERROR_OTHER_ERROR; \ + goto out; \ + } \ + cfg_ret = config_setting_set_int(node, attrs.attr_name); \ + if (cfg_ret != CONFIG_TRUE) { \ + ret = USBG_ERROR_OTHER_ERROR; \ + goto out; \ + } \ + } while (0) + + ADD_GADGET_ATTR(bcdUSB); + ADD_GADGET_ATTR(bDeviceClass); + ADD_GADGET_ATTR(bDeviceSubClass); + ADD_GADGET_ATTR(bDeviceProtocol); + ADD_GADGET_ATTR(bMaxPacketSize0); + ADD_GADGET_ATTR(idVendor); + ADD_GADGET_ATTR(idProduct); + ADD_GADGET_ATTR(bcdDevice); + +#undef ADD_GADGET_ATTR + + ret = 0; +out: + return ret; +} + +static int usbg_export_gadget_prep(usbg_gadget *g, config_setting_t *root) +{ + config_setting_t *node; + int ret = USBG_ERROR_NO_MEM; + int usbg_ret; + + /* We don't export name tag because name should be given during + * loading of gadget */ + + node = config_setting_add(root, USBG_ATTRS_TAG, CONFIG_TYPE_GROUP); + if (!node) + goto out; + + usbg_ret = usbg_export_gadget_attrs(g, node); + if (usbg_ret) { + ret = usbg_ret; + goto out; + } + + node = config_setting_add(root, USBG_STRINGS_TAG, + CONFIG_TYPE_LIST); + if (!node) + goto out; + + usbg_ret = usbg_export_gadget_strings(g, node); + if (usbg_ret) { + ret = usbg_ret; + goto out; + } + + node = config_setting_add(root, USBG_FUNCTIONS_TAG, + CONFIG_TYPE_GROUP); + if (!node) + goto out; + + usbg_ret = usbg_export_gadget_functions(g, node); + if (usbg_ret) { + ret = usbg_ret; + goto out; + } + + node = config_setting_add(root, USBG_CONFIGS_TAG, + CONFIG_TYPE_LIST); + if (!node) + goto out; + + usbg_ret = usbg_export_gadget_configs(g, node); + ret = usbg_ret; +out: + return ret; +} + +/* Export gadget/function/config API implementation */ + +int usbg_export_function(usbg_function *f, FILE *stream) +{ + config_t cfg; + config_setting_t *root; + int ret; + + if (!f || !stream) + return USBG_ERROR_INVALID_PARAM; + + config_init(&cfg); + + /* Set format */ + config_set_tab_width(&cfg, USBG_TAB_WIDTH); + + /* Always successful */ + root = config_root_setting(&cfg); + + ret = usbg_export_function_prep(f, root); + if (ret != USBG_SUCCESS) + goto out; + + config_write(&cfg, stream); +out: + config_destroy(&cfg); + return ret; +} + +int usbg_export_config(usbg_config *c, FILE *stream) +{ + config_t cfg; + config_setting_t *root; + int ret; + + if (!c || !stream) + return USBG_ERROR_INVALID_PARAM; + + config_init(&cfg); + + /* Set format */ + config_set_tab_width(&cfg, USBG_TAB_WIDTH); + + /* Always successful */ + root = config_root_setting(&cfg); + + ret = usbg_export_config_prep(c, root); + if (ret != USBG_SUCCESS) + goto out; + + config_write(&cfg, stream); +out: + config_destroy(&cfg); + return ret; +} + +int usbg_export_gadget(usbg_gadget *g, FILE *stream) +{ + config_t cfg; + config_setting_t *root; + int ret; + + if (!g || !stream) + return USBG_ERROR_INVALID_PARAM; + + config_init(&cfg); + + /* Set format */ + config_set_tab_width(&cfg, USBG_TAB_WIDTH); + + /* Always successful */ + root = config_root_setting(&cfg); + + ret = usbg_export_gadget_prep(g, root); + if (ret != USBG_SUCCESS) + goto out; + + config_write(&cfg, stream); +out: + config_destroy(&cfg); + return ret; +} + +#define usbg_config_is_int(node) (config_setting_type(node) == CONFIG_TYPE_INT) +#define usbg_config_is_string(node) \ + (config_setting_type(node) == CONFIG_TYPE_STRING) + +static int split_function_label(const char *label, usbg_function_type *type, + const char **instance) +{ + const char *floor; + char buf[USBG_MAX_NAME_LENGTH]; + int len; + int function_type; + int ret = USBG_ERROR_NOT_FOUND; + + /* We assume that function type string doesn't contain '_' */ + floor = strchr(label, '_'); + /* if phrase before _ is longer than max name length we may + * stop looking */ + len = floor - label; + if (len >= USBG_MAX_NAME_LENGTH || floor == label) + goto out; + + strncpy(buf, label, len); + buf[len] = '\0'; + + function_type = usbg_lookup_function_type(buf); + if (function_type < 0) + goto out; + + *type = (usbg_function_type)function_type; + *instance = floor + 1; + + ret = USBG_SUCCESS; +out: + return ret; +} + +static void usbg_set_failed_import(config_t **to_set, config_t *failed) +{ + if (*to_set != NULL) { + config_destroy(*to_set); + free(*to_set); + } + + *to_set = failed; +} + +static int usbg_import_f_net_attrs(config_setting_t *root, usbg_function *f) +{ + config_setting_t *node; + int ret = USBG_SUCCESS; + int qmult; + struct ether_addr *addr; + struct ether_addr addr_buf; + const char *str; + +#define GET_OPTIONAL_ADDR(NAME) \ + do { \ + node = config_setting_get_member(root, #NAME); \ + if (node) { \ + str = config_setting_get_string(node); \ + if (!str) { \ + ret = USBG_ERROR_INVALID_TYPE; \ + goto out; \ + } \ + \ + addr = ether_aton_r(str, &addr_buf); \ + if (!addr) { \ + ret = USBG_ERROR_INVALID_VALUE; \ + goto out; \ + } \ + ret = usbg_set_net_##NAME(f, addr); \ + if (ret != USBG_SUCCESS) \ + goto out; \ + } \ + } while (0) + + GET_OPTIONAL_ADDR(host_addr); + GET_OPTIONAL_ADDR(dev_addr); + +#undef GET_OPTIONAL_ADDR + + node = config_setting_get_member(root, "qmult"); + if (node) { + if (!usbg_config_is_int(node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto out; + } + qmult = config_setting_get_int(node); + ret = usbg_set_net_qmult(f, qmult); + } + +out: + return ret; +} + +static int usbg_import_f_ms_lun_attrs(usbg_f_ms_lun_attrs *lattrs, + config_setting_t *root) +{ + config_setting_t *node; + int i; + int ret = USBG_ERROR_NO_MEM; + +#define BOOL_ATTR(_name, _default_val) \ + { .name = #_name, .value = &lattrs->_name, } + struct { + char *name; + bool *value; + bool default_val; + } bool_attrs[] = { + BOOL_ATTR(cdrom, false), + BOOL_ATTR(ro, false), + BOOL_ATTR(nofua, false), + BOOL_ATTR(removable, true), + }; +#undef BOOL_ATTR + + memset(lattrs, 0, sizeof(*lattrs)); + lattrs->id = -1; + + for (i = 0; i < ARRAY_SIZE(bool_attrs); ++i) { + *(bool_attrs[i].value) = bool_attrs[i].default_val; + + node = config_setting_get_member(root, bool_attrs[i].name); + if (!node) + continue; + + ret = config_setting_type(node); + switch (ret) { + case CONFIG_TYPE_INT: + *(bool_attrs[i].value) = !!config_setting_get_int(node); + break; + case CONFIG_TYPE_BOOL: + *(bool_attrs[i].value) = config_setting_get_bool(node); + break; + default: + ret = USBG_ERROR_INVALID_TYPE; + goto out; + } + } + + node = config_setting_get_member(root, "filename"); + if (node) { + if (!usbg_config_is_string(node)) { + ret = USBG_ERROR_INVALID_PARAM; + goto out; + } + lattrs->filename = (char *)config_setting_get_string(node); + } else { + lattrs->filename = ""; + } + + ret = USBG_SUCCESS; +out: + return ret; +} + +static int usbg_import_f_ms_attrs(config_setting_t *root, usbg_function *f) +{ + config_setting_t *luns_node, *node; + int i; + int ret = USBG_ERROR_NO_MEM; + usbg_function_attrs attrs; + usbg_f_ms_attrs *ms_attrs = &attrs.attrs.ms; + + memset(&attrs, 0, sizeof(attrs)); + + node = config_setting_get_member(root, "stall"); + if (node) { + ret = config_setting_type(node); + switch (ret) { + case CONFIG_TYPE_INT: + ms_attrs->stall = !!config_setting_get_int(node); + break; + case CONFIG_TYPE_BOOL: + ms_attrs->stall = config_setting_get_bool(node); + break; + default: + ret = USBG_ERROR_INVALID_TYPE; + goto out; + } + } + + luns_node = config_setting_get_member(root, "luns"); + if (!node) { + ret = USBG_ERROR_INVALID_PARAM; + goto out; + } + + if (!config_setting_is_list(luns_node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto out; + } + + ms_attrs->nluns = config_setting_length(luns_node); + + ms_attrs->luns = calloc(ms_attrs->nluns + 1, sizeof(*(ms_attrs->luns))); + if (!ms_attrs->luns) { + ret = USBG_ERROR_NO_MEM; + goto out; + } + + for (i = 0; i < ms_attrs->nluns; ++i) { + node = config_setting_get_elem(luns_node, i); + if (!node) { + ret = USBG_ERROR_INVALID_FORMAT; + goto free_luns; + } + + if (!config_setting_is_group(node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto free_luns; + } + + ms_attrs->luns[i] = malloc(sizeof(*(ms_attrs->luns[i]))); + if (!ms_attrs->luns[i]) { + ret = USBG_ERROR_NO_MEM; + goto free_luns; + } + + ret = usbg_import_f_ms_lun_attrs(ms_attrs->luns[i], node); + if (ret != USBG_SUCCESS) + goto free_luns; + } + + ret = usbg_set_function_attrs(f, &attrs); + +free_luns: + while (--i >= 0) + if (ms_attrs->luns[i]) + free(ms_attrs->luns[i]); + free(ms_attrs->luns); +out: + return ret; + +} + +static int usbg_import_f_midi_attrs(config_setting_t *root, usbg_function *f) +{ + config_setting_t *node; + int ret = USBG_ERROR_NO_MEM; + int tmp; + usbg_function_attrs attrs; + usbg_f_midi_attrs *midi_attrs = &attrs.attrs.midi; + + attrs.header.attrs_type = USBG_F_ATTRS_MIDI; + +#define ADD_F_MIDI_INT_ATTR(attr, defval, minval) \ + do { \ + node = config_setting_get_member(root, #attr); \ + if (node) { \ + if (!usbg_config_is_int(node)) { \ + ret = USBG_ERROR_INVALID_TYPE; \ + goto out; \ + } \ + tmp = config_setting_get_int(node); \ + if (tmp < minval) { \ + ret = USBG_ERROR_INVALID_VALUE; \ + goto out; \ + } \ + midi_attrs->attr = tmp; \ + } else { \ + midi_attrs->attr = defval; \ + } \ + } while (0) + + ADD_F_MIDI_INT_ATTR(index, -1, INT_MIN); + ADD_F_MIDI_INT_ATTR(in_ports, 1, 0); + ADD_F_MIDI_INT_ATTR(out_ports, 1, 0); + ADD_F_MIDI_INT_ATTR(buflen, 256, 0); + ADD_F_MIDI_INT_ATTR(qlen, 32, 0); + +#undef ADD_F_MIDI_INT_ATTR + + node = config_setting_get_member(root, "id"); + if (node) { + if (!usbg_config_is_string(node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto out; + } + + midi_attrs->id = config_setting_get_string(node); + } else { + midi_attrs->id = ""; + } + + + ret = usbg_set_function_attrs(f, &attrs); +out: + return ret; +} + +static int usbg_import_f_loopback_attrs(config_setting_t *root, usbg_function *f) +{ + config_setting_t *node; + int ret = USBG_ERROR_NO_MEM; + int tmp; + usbg_function_attrs attrs; + usbg_f_loopback_attrs *loopback_attrs = &attrs.attrs.loopback; + + attrs.header.attrs_type = USBG_F_ATTRS_LOOPBACK; + +#define ADD_F_LOOPBACK_INT_ATTR(attr, defval, minval) \ + do { \ + node = config_setting_get_member(root, #attr); \ + if (node) { \ + if (!usbg_config_is_int(node)) { \ + ret = USBG_ERROR_INVALID_TYPE; \ + goto out; \ + } \ + tmp = config_setting_get_int(node); \ + if (tmp < minval) { \ + ret = USBG_ERROR_INVALID_VALUE; \ + goto out; \ + } \ + loopback_attrs->attr = tmp; \ + } else { \ + loopback_attrs->attr = defval; \ + } \ + } while (0) + + ADD_F_LOOPBACK_INT_ATTR(buflen, 4096, 0); + ADD_F_LOOPBACK_INT_ATTR(qlen, 32, 0); + +#undef ADD_F_LOOPBACK_INT_ATTR + + ret = usbg_set_function_attrs(f, &attrs); +out: + return ret; +} + +static int usbg_import_function_attrs(config_setting_t *root, usbg_function *f) +{ + int ret = USBG_SUCCESS; + int attrs_type; + + attrs_type = usbg_lookup_function_attrs_type(f->type); + if (attrs_type < 0) { + ret = attrs_type; + goto out; + } + + switch (attrs_type) { + case USBG_F_ATTRS_SERIAL: + /* Don't import port_num because it is read only */ + break; + + case USBG_F_ATTRS_NET: + ret = usbg_import_f_net_attrs(root, f); + break; + + case USBG_F_ATTRS_PHONET: + /* Don't import ifname because it is read only */ + break; + + case USBG_F_ATTRS_FFS: + /* We don't need to import ffs attributes + * due to instance name import */ + break; + + case USBG_F_ATTRS_MS: + ret = usbg_import_f_ms_attrs(root, f); + break; + + case USBG_F_ATTRS_MIDI: + ret = usbg_import_f_midi_attrs(root, f); + break; + + case USBG_F_ATTRS_LOOPBACK: + ret = usbg_import_f_loopback_attrs(root, f); + break; + + default: + ERROR("Unsupported function type\n"); + ret = USBG_ERROR_NOT_SUPPORTED; + break; + } + +out: + return ret; +} + +static int usbg_import_function_run(usbg_gadget *g, config_setting_t *root, + const char *instance, usbg_function **f) +{ + config_setting_t *node; + const char *type_str; + int usbg_ret; + int function_type; + int ret = USBG_ERROR_MISSING_TAG; + + /* function type is mandatory */ + node = config_setting_get_member(root, USBG_TYPE_TAG); + if (!node) + goto out; + + type_str = config_setting_get_string(node); + if (!type_str) { + ret = USBG_ERROR_INVALID_TYPE; + goto out; + } + + /* Check if this type is supported */ + function_type = usbg_lookup_function_type(type_str); + if (function_type < 0) { + ret = USBG_ERROR_NOT_SUPPORTED; + goto out; + } + + /* All data collected, let's get to work and create this function */ + ret = usbg_create_function(g, (usbg_function_type)function_type, + instance, NULL, f); + + if (ret != USBG_SUCCESS) + goto out; + + /* Attrs are optional */ + node = config_setting_get_member(root, USBG_ATTRS_TAG); + if (node) { + usbg_ret = usbg_import_function_attrs(node, *f); + if (usbg_ret != USBG_SUCCESS) { + ret = usbg_ret; + goto out; + } + } +out: + return ret; +} + +static usbg_function *usbg_lookup_function(usbg_gadget *g, const char *label) +{ + usbg_function *f; + int usbg_ret; + + /* check if such function has also been imported */ + TAILQ_FOREACH(f, &g->functions, fnode) { + if (f->label && !strcmp(f->label, label)) + break; + } + + /* if not let's check if label follows the naming convention */ + if (!f) { + usbg_function_type type; + const char *instance; + + usbg_ret = split_function_label(label, &type, &instance); + if (usbg_ret != USBG_SUCCESS) + goto out; + + /* check if such function exist */ + f = usbg_get_function(g, type, instance); + } + +out: + return f; +} + +/* We have a string which should match with one of function names */ +static int usbg_import_binding_string(config_setting_t *root, usbg_config *c) +{ + const char *func_label; + usbg_function *target; + int ret; + + func_label = config_setting_get_string(root); + if (!func_label) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + target = usbg_lookup_function(c->parent, func_label); + if (!target) { + ret = USBG_ERROR_NOT_FOUND; + goto out; + } + + ret = usbg_add_config_function(c, target->name, target); +out: + return ret; +} + +static int usbg_import_binding_group(config_setting_t *root, usbg_config *c) +{ + config_setting_t *node; + const char *func_label, *name; + usbg_function *target; + int ret; + + node = config_setting_get_member(root, USBG_FUNCTION_TAG); + if (!node) { + ret = USBG_ERROR_MISSING_TAG; + goto out; + } + + /* It is allowed to provide link to existing function + * or define unlabeled instance of function in this place */ + if (usbg_config_is_string(node)) { + func_label = config_setting_get_string(node); + if (!func_label) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + target = usbg_lookup_function(c->parent, func_label); + if (!target) { + ret = USBG_ERROR_NOT_FOUND; + goto out; + } + } else if (config_setting_is_group(node)) { + config_setting_t *inst_node; + const char *instance; + + inst_node = config_setting_get_member(node, USBG_INSTANCE_TAG); + if (!inst_node) { + ret = USBG_ERROR_MISSING_TAG; + goto out; + } + + instance = config_setting_get_string(inst_node); + if (!instance) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + + ret = usbg_import_function_run(c->parent, node, + instance, &target); + if (ret != USBG_SUCCESS) + goto out; + } else { + ret = USBG_ERROR_INVALID_TYPE; + goto out; + } + + /* Name tag is optional. When no such tag, default one will be used */ + node = config_setting_get_member(root, USBG_NAME_TAG); + if (node) { + if (!usbg_config_is_string(node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto out; + } + + name = config_setting_get_string(node); + if (!name) { + ret = USBG_ERROR_OTHER_ERROR; + goto out; + } + } else { + name = target->name; + } + + ret = usbg_add_config_function(c, name, target); +out: + return ret; +} + +static int usbg_import_config_bindings(config_setting_t *root, usbg_config *c) +{ + config_setting_t *node; + int ret = USBG_SUCCESS; + int count, i; + + count = config_setting_length(root); + + for (i = 0; i < count; ++i) { + node = config_setting_get_elem(root, i); + + if (usbg_config_is_string(node)) + ret = usbg_import_binding_string(node, c); + else if (config_setting_is_group(node)) + ret = usbg_import_binding_group(node, c); + else + ret = USBG_ERROR_INVALID_TYPE; + + if (ret != USBG_SUCCESS) + break; + } + + return ret; +} + +static int usbg_import_config_strs_lang(config_setting_t *root, usbg_config *c) +{ + config_setting_t *node; + int lang; + const char *str; + usbg_config_strs c_strs = {{0}}; + int ret = USBG_ERROR_INVALID_TYPE; + + node = config_setting_get_member(root, USBG_LANG_TAG); + if (!node) { + ret = USBG_ERROR_MISSING_TAG; + goto out; + } + + if (!usbg_config_is_int(node)) + goto out; + + lang = config_setting_get_int(node); + + /* Configuration string is optional */ + node = config_setting_get_member(root, "configuration"); + if (node) { + if (!usbg_config_is_string(node)) + goto out; + + str = config_setting_get_string(node); + + /* Auto truncate the string to max length */ + strncpy(c_strs.configuration, str, USBG_MAX_STR_LENGTH); + c_strs.configuration[USBG_MAX_STR_LENGTH - 1] = 0; + } + + ret = usbg_set_config_strs(c, lang, &c_strs); + +out: + return ret; +} + +static int usbg_import_config_strings(config_setting_t *root, usbg_config *c) +{ + config_setting_t *node; + int ret = USBG_SUCCESS; + int count, i; + + count = config_setting_length(root); + + for (i = 0; i < count; ++i) { + node = config_setting_get_elem(root, i); + if (!config_setting_is_group(node)) { + ret = USBG_ERROR_INVALID_TYPE; + break; + } + + ret = usbg_import_config_strs_lang(node, c); + if (ret != USBG_SUCCESS) + break; + } + + return ret; +} + +static int usbg_import_config_attrs(config_setting_t *root, usbg_config *c) +{ + config_setting_t *node; + int usbg_ret; + int bmAttributes, bMaxPower; + int ret = USBG_ERROR_INVALID_TYPE; + + node = config_setting_get_member(root, "bmAttributes"); + if (node) { + if (!usbg_config_is_int(node)) + goto out; + + bmAttributes = config_setting_get_int(node); + usbg_ret = usbg_set_config_bm_attrs(c, bmAttributes); + if (usbg_ret != USBG_SUCCESS) { + ret = usbg_ret; + goto out; + } + } + + node = config_setting_get_member(root, "bMaxPower"); + if (node) { + if (!usbg_config_is_int(node)) + goto out; + + bMaxPower = config_setting_get_int(node); + usbg_ret = usbg_set_config_max_power(c, bMaxPower); + if (usbg_ret != USBG_SUCCESS) { + ret = usbg_ret; + goto out; + } + } + + /* Empty attrs section is also considered to be valid */ + ret = USBG_SUCCESS; +out: + return ret; + +} + +static int usbg_import_config_run(usbg_gadget *g, config_setting_t *root, + int id, usbg_config **c) +{ + config_setting_t *node; + const char *name; + usbg_config *newc; + int usbg_ret; + int ret = USBG_ERROR_MISSING_TAG; + + /* + * Label is mandatory, + * if attrs aren't present defaults are used + */ + node = config_setting_get_member(root, USBG_NAME_TAG); + if (!node) + goto out; + + name = config_setting_get_string(node); + if (!name) { + ret = USBG_ERROR_INVALID_TYPE; + goto out; + } + + /* Required data collected, let's create our config */ + usbg_ret = usbg_create_config(g, id, name, NULL, NULL, &newc); + if (usbg_ret != USBG_SUCCESS) { + ret = usbg_ret; + goto out; + } + + /* Attrs are optional */ + node = config_setting_get_member(root, USBG_ATTRS_TAG); + if (node) { + if (!config_setting_is_group(node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto error2; + } + + usbg_ret = usbg_import_config_attrs(node, newc); + if (usbg_ret != USBG_SUCCESS) + goto error; + } + + /* Strings are also optional */ + node = config_setting_get_member(root, USBG_STRINGS_TAG); + if (node) { + if (!config_setting_is_list(node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto error2; + } + + usbg_ret = usbg_import_config_strings(node, newc); + if (usbg_ret != USBG_SUCCESS) + goto error; + } + + /* Functions too, because some config may not be + * fully configured and not contain any function */ + node = config_setting_get_member(root, USBG_FUNCTIONS_TAG); + if (node) { + if (!config_setting_is_list(node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto error2; + } + + usbg_ret = usbg_import_config_bindings(node, newc); + if (usbg_ret != USBG_SUCCESS) + goto error; + } + + *c = newc; + ret = USBG_SUCCESS; +out: + return ret; + +error: + ret = usbg_ret; +error2: + /* We ignore returned value, if function fails + * there is no way to handle it */ + usbg_rm_config(newc, USBG_RM_RECURSE); + return ret; +} + +static int usbg_import_gadget_configs(config_setting_t *root, usbg_gadget *g) +{ + config_setting_t *node, *id_node; + int id; + usbg_config *c; + int ret = USBG_SUCCESS; + int count, i; + + count = config_setting_length(root); + + for (i = 0; i < count; ++i) { + node = config_setting_get_elem(root, i); + if (!node) { + ret = USBG_ERROR_OTHER_ERROR; + break; + } + + if (!config_setting_is_group(node)) { + ret = USBG_ERROR_INVALID_TYPE; + break; + } + + /* Look for id */ + id_node = config_setting_get_member(node, USBG_ID_TAG); + if (!id_node) { + ret = USBG_ERROR_MISSING_TAG; + break; + } + + if (!usbg_config_is_int(id_node)) { + ret = USBG_ERROR_INVALID_TYPE; + break; + } + + id = config_setting_get_int(id_node); + + ret = usbg_import_config_run(g, node, id, &c); + if (ret != USBG_SUCCESS) + break; + } + + return ret; +} + +static int usbg_import_gadget_functions(config_setting_t *root, usbg_gadget *g) +{ + config_setting_t *node, *inst_node; + const char *instance; + const char *label; + usbg_function *f; + int ret = USBG_SUCCESS; + int count, i; + + count = config_setting_length(root); + + for (i = 0; i < count; ++i) { + node = config_setting_get_elem(root, i); + if (!node) { + ret = USBG_ERROR_OTHER_ERROR; + break; + } + + if (!config_setting_is_group(node)) { + ret = USBG_ERROR_INVALID_TYPE; + break; + } + + /* Look for instance name */ + inst_node = config_setting_get_member(node, USBG_INSTANCE_TAG); + if (!inst_node) { + ret = USBG_ERROR_MISSING_TAG; + break; + } + + if (!usbg_config_is_string(inst_node)) { + ret = USBG_ERROR_INVALID_TYPE; + break; + } + + instance = config_setting_get_string(inst_node); + if (!instance) { + ret = USBG_ERROR_OTHER_ERROR; + break; + } + + ret = usbg_import_function_run(g, node, instance, &f); + if (ret != USBG_SUCCESS) + break; + + /* Set the label given by user */ + label = config_setting_name(node); + if (!label) { + ret = USBG_ERROR_OTHER_ERROR; + break; + } + + f->label = strdup(label); + if (!f->label) { + ret = USBG_ERROR_NO_MEM; + break; + } + } + + return ret; +} + +static int usbg_import_gadget_strs_lang(config_setting_t *root, usbg_gadget *g) +{ + config_setting_t *node; + int lang; + const char *str; + usbg_gadget_strs g_strs = {{0}}; + int ret = USBG_ERROR_INVALID_TYPE; + + node = config_setting_get_member(root, USBG_LANG_TAG); + if (!node) { + ret = USBG_ERROR_MISSING_TAG; + goto out; + } + + if (!usbg_config_is_int(node)) + goto out; + + lang = config_setting_get_int(node); + + /* Auto truncate the string to max length */ +#define GET_OPTIONAL_GADGET_STR(NAME, FIELD) \ + do { \ + node = config_setting_get_member(root, #NAME); \ + if (node) { \ + if (!usbg_config_is_string(node)) \ + goto out; \ + str = config_setting_get_string(node); \ + strncpy(g_strs.FIELD, str, USBG_MAX_STR_LENGTH); \ + g_strs.FIELD[USBG_MAX_STR_LENGTH - 1] = '\0'; \ + } \ + } while (0) + + GET_OPTIONAL_GADGET_STR(manufacturer, str_mnf); + GET_OPTIONAL_GADGET_STR(product, str_prd); + GET_OPTIONAL_GADGET_STR(serialnumber, str_ser); + +#undef GET_OPTIONAL_GADGET_STR + + ret = usbg_set_gadget_strs(g, lang, &g_strs); + +out: + return ret; +} + +static int usbg_import_gadget_strings(config_setting_t *root, usbg_gadget *g) +{ + config_setting_t *node; + int ret = USBG_SUCCESS; + int count, i; + + count = config_setting_length(root); + + for (i = 0; i < count; ++i) { + node = config_setting_get_elem(root, i); + if (!config_setting_is_group(node)) { + ret = USBG_ERROR_INVALID_TYPE; + break; + } + + ret = usbg_import_gadget_strs_lang(node, g); + if (ret != USBG_SUCCESS) + break; + } + + return ret; +} + + +static int usbg_import_gadget_attrs(config_setting_t *root, usbg_gadget *g) +{ + config_setting_t *node; + int usbg_ret; + int val; + int ret = USBG_ERROR_INVALID_TYPE; + +#define GET_OPTIONAL_GADGET_ATTR(NAME, FUNC_END, TYPE) \ + do { \ + node = config_setting_get_member(root, #NAME); \ + if (node) { \ + if (!usbg_config_is_int(node)) \ + goto out; \ + val = config_setting_get_int(node); \ + if (val < 0 || val > ((1L << (sizeof(TYPE)*8)) - 1)) { \ + ret = USBG_ERROR_INVALID_VALUE; \ + goto out; \ + } \ + usbg_ret = usbg_set_gadget_##FUNC_END(g, (TYPE)val); \ + if (usbg_ret != USBG_SUCCESS) { \ + ret = usbg_ret; \ + goto out; \ + } \ + } \ + } while (0) + + GET_OPTIONAL_GADGET_ATTR(bcdUSB, device_bcd_usb, uint16_t); + GET_OPTIONAL_GADGET_ATTR(bDeviceClass, device_class, uint8_t); + GET_OPTIONAL_GADGET_ATTR(bDeviceSubClass, device_subclass, uint8_t); + GET_OPTIONAL_GADGET_ATTR(bDeviceProtocol, device_protocol, uint8_t); + GET_OPTIONAL_GADGET_ATTR(bMaxPacketSize0, device_max_packet, uint8_t); + GET_OPTIONAL_GADGET_ATTR(idVendor, vendor_id, uint16_t); + GET_OPTIONAL_GADGET_ATTR(idProduct, product_id, uint16_t); + GET_OPTIONAL_GADGET_ATTR(bcdDevice, device_bcd_device, uint16_t); + +#undef GET_OPTIONAL_GADGET_ATTR + + /* Empty attrs section is also considered to be valid */ + ret = USBG_SUCCESS; +out: + return ret; + +} + +static int usbg_import_gadget_run(usbg_state *s, config_setting_t *root, + const char *name, usbg_gadget **g) +{ + config_setting_t *node; + usbg_gadget *newg; + int usbg_ret; + int ret = USBG_ERROR_MISSING_TAG; + + /* There is no mandatory data in gadget so let's start with + * creating a new gadget */ + usbg_ret = usbg_create_gadget(s, name, NULL, NULL, &newg); + if (usbg_ret != USBG_SUCCESS) { + ret = usbg_ret; + goto out; + } + + /* Attrs are optional */ + node = config_setting_get_member(root, USBG_ATTRS_TAG); + if (node) { + if (!config_setting_is_group(node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto error2; + } + + usbg_ret = usbg_import_gadget_attrs(node, newg); + if (usbg_ret != USBG_SUCCESS) + goto error; + } + + /* Strings are also optional */ + node = config_setting_get_member(root, USBG_STRINGS_TAG); + if (node) { + if (!config_setting_is_list(node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto error2; + } + + usbg_ret = usbg_import_gadget_strings(node, newg); + if (usbg_ret != USBG_SUCCESS) + goto error; + } + + /* Functions too, because some gadgets may not be fully + * configured and don't have any function or have all functions + * defined inline in configurations */ + node = config_setting_get_member(root, USBG_FUNCTIONS_TAG); + if (node) { + if (!config_setting_is_group(node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto error2; + } + usbg_ret = usbg_import_gadget_functions(node, newg); + if (usbg_ret != USBG_SUCCESS) + goto error; + } + + /* Some gadget may not be fully configured + * so configs are also optional */ + node = config_setting_get_member(root, USBG_CONFIGS_TAG); + if (node) { + if (!config_setting_is_list(node)) { + ret = USBG_ERROR_INVALID_TYPE; + goto error2; + } + usbg_ret = usbg_import_gadget_configs(node, newg); + if (usbg_ret != USBG_SUCCESS) + goto error; + } + + *g = newg; + ret = USBG_SUCCESS; +out: + return ret; + +error: + ret = usbg_ret; +error2: + /* We ignore returned value, if function fails + * there is no way to handle it */ + usbg_rm_gadget(newg, USBG_RM_RECURSE); + return ret; +} + +int usbg_import_function(usbg_gadget *g, FILE *stream, const char *instance, + usbg_function **f) +{ + config_t *cfg; + config_setting_t *root; + usbg_function *newf; + int ret, cfg_ret; + + if (!g || !stream || !instance) + return USBG_ERROR_INVALID_PARAM; + + cfg = malloc(sizeof(*cfg)); + if (!cfg) + return USBG_ERROR_NO_MEM; + + config_init(cfg); + + cfg_ret = config_read(cfg, stream); + if (cfg_ret != CONFIG_TRUE) { + usbg_set_failed_import(&g->last_failed_import, cfg); + ret = USBG_ERROR_INVALID_FORMAT; + goto out; + } + + /* Always successful */ + root = config_root_setting(cfg); + + ret = usbg_import_function_run(g, root, instance, &newf); + if (ret != USBG_SUCCESS) { + usbg_set_failed_import(&g->last_failed_import, cfg); + goto out; + } + + if (f) + *f = newf; + + config_destroy(cfg); + free(cfg); + /* Clean last error */ + usbg_set_failed_import(&g->last_failed_import, NULL); +out: + return ret; + +} + +int usbg_import_config(usbg_gadget *g, FILE *stream, int id, usbg_config **c) +{ + config_t *cfg; + config_setting_t *root; + usbg_config *newc; + int ret, cfg_ret; + + if (!g || !stream || id < 0) + return USBG_ERROR_INVALID_PARAM; + + cfg = malloc(sizeof(*cfg)); + if (!cfg) + return USBG_ERROR_NO_MEM; + + config_init(cfg); + + cfg_ret = config_read(cfg, stream); + if (cfg_ret != CONFIG_TRUE) { + usbg_set_failed_import(&g->last_failed_import, cfg); + ret = USBG_ERROR_INVALID_FORMAT; + goto out; + } + + /* Always successful */ + root = config_root_setting(cfg); + + ret = usbg_import_config_run(g, root, id, &newc); + if (ret != USBG_SUCCESS) { + usbg_set_failed_import(&g->last_failed_import, cfg); + goto out; + } + + if (c) + *c = newc; + + config_destroy(cfg); + free(cfg); + /* Clean last error */ + usbg_set_failed_import(&g->last_failed_import, NULL); +out: + return ret; +} + +int usbg_import_gadget(usbg_state *s, FILE *stream, const char *name, + usbg_gadget **g) +{ + config_t *cfg; + config_setting_t *root; + usbg_gadget *newg; + int ret, cfg_ret; + + if (!s || !stream || !name) + return USBG_ERROR_INVALID_PARAM; + + cfg = malloc(sizeof(*cfg)); + if (!cfg) + return USBG_ERROR_NO_MEM; + + config_init(cfg); + + cfg_ret = config_read(cfg, stream); + if (cfg_ret != CONFIG_TRUE) { + usbg_set_failed_import(&s->last_failed_import, cfg); + ret = USBG_ERROR_INVALID_FORMAT; + goto out; + } + + /* Always successful */ + root = config_root_setting(cfg); + + ret = usbg_import_gadget_run(s, root, name, &newg); + if (ret != USBG_SUCCESS) { + usbg_set_failed_import(&s->last_failed_import, cfg); + goto out; + } + + if (g) + *g = newg; + + config_destroy(cfg); + free(cfg); + /* Clean last error */ + usbg_set_failed_import(&s->last_failed_import, NULL); +out: + return ret; +} + +const char *usbg_get_func_import_error_text(usbg_gadget *g) +{ + if (!g || !g->last_failed_import) + return NULL; + + return config_error_text(g->last_failed_import); +} + +int usbg_get_func_import_error_line(usbg_gadget *g) +{ + if (!g || !g->last_failed_import) + return -1; + + return config_error_line(g->last_failed_import); +} + +const char *usbg_get_config_import_error_text(usbg_gadget *g) +{ + if (!g || !g->last_failed_import) + return NULL; + + return config_error_text(g->last_failed_import); +} + +int usbg_get_config_import_error_line(usbg_gadget *g) +{ + if (!g || !g->last_failed_import) + return -1; + + return config_error_line(g->last_failed_import); +} + +const char *usbg_get_gadget_import_error_text(usbg_state *s) +{ + if (!s || !s->last_failed_import) + return NULL; + + return config_error_text(s->last_failed_import); +} + +int usbg_get_gadget_import_error_line(usbg_state *s) +{ + if (!s || !s->last_failed_import) + return -1; + + return config_error_line(s->last_failed_import); +} + diff --git a/src/usbg_schemes_none.c b/src/usbg_schemes_none.c new file mode 100644 index 0000000..915890c --- /dev/null +++ b/src/usbg_schemes_none.c @@ -0,0 +1,94 @@ +/* + * 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" + +int usbg_export_function(__attribute__ ((unused)) usbg_function *f, + __attribute__ ((unused)) FILE *stream) +{ + return USBG_ERROR_NOT_SUPPORTED; +} + +int usbg_export_config(__attribute__ ((unused)) usbg_config *c, + __attribute__ ((unused)) FILE *stream) +{ + return USBG_ERROR_NOT_SUPPORTED; +} + +int usbg_export_gadget(__attribute__ ((unused)) usbg_gadget *g, + __attribute__ ((unused)) FILE *stream) +{ + return USBG_ERROR_NOT_SUPPORTED; +} + +int usbg_import_function(__attribute__ ((unused)) usbg_gadget *g, + __attribute__ ((unused)) FILE *stream, + __attribute__ ((unused)) const char *instance, + __attribute__ ((unused)) usbg_function **f) +{ + return USBG_ERROR_NOT_SUPPORTED; +} + +int usbg_import_config(__attribute__ ((unused)) usbg_gadget *g, + __attribute__ ((unused)) FILE *stream, + __attribute__ ((unused)) int id, + __attribute__ ((unused)) usbg_config **c) +{ + return USBG_ERROR_NOT_SUPPORTED; +} + +int usbg_import_gadget(__attribute__ ((unused)) usbg_state *s, + __attribute__ ((unused)) FILE *stream, + __attribute__ ((unused)) const char *name, + __attribute__ ((unused)) usbg_gadget **g) +{ + return USBG_ERROR_NOT_SUPPORTED; +} + +const char *usbg_get_func_import_error_text( + __attribute__ ((unused)) usbg_gadget *g) +{ + return NULL; +} + +int usbg_get_func_import_error_line(__attribute__ ((unused)) usbg_gadget *g) +{ + return USBG_ERROR_NOT_SUPPORTED; +} + +const char *usbg_get_config_import_error_text( + __attribute__ ((unused)) usbg_gadget *g) +{ + return NULL; +} + +int usbg_get_config_import_error_line(__attribute__ ((unused)) usbg_gadget *g) +{ + return USBG_ERROR_NOT_SUPPORTED; +} + +const char *usbg_get_gadget_import_error_text( + __attribute__ ((unused)) usbg_state *s) +{ + return NULL; +} + +int usbg_get_gadget_import_error_line(__attribute__ ((unused)) usbg_state *s) +{ + return USBG_ERROR_NOT_SUPPORTED; +} + +void config_destroy(__attribute__ ((unused)) config_t *config) +{ + return; +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..01feea4 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,14 @@ +check_PROGRAMS = test +test_SOURCES = test.c usbg-test.c usbg-io-wrappers.c +test_LDFLAGS = -ldl +test_LDFLAGS += $(CMOCKA_LIBS) +test_LDFLAGS += $(LIBCONFIG_LIBS) +test_LDADD = ./libusbgx.so +test_CPPFLAGS = -I$(top_srcdir)/include/ + +./libusbgx.so: + -ln -s $(top_srcdir)/src/.libs/libusbgx.so* . +CLEANFILES = libusbgx.so* + +check_SCRIPTS = ./test.sh +TESTS = $(check_SCRIPTS) diff --git a/tests/test.c b/tests/test.c new file mode 100644 index 0000000..bd17af9 --- /dev/null +++ b/tests/test.c @@ -0,0 +1,2697 @@ +#include <usbg/usbg.h> +#include <stdio.h> +#include <stdarg.h> +#include <setjmp.h> +#include <cmocka.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <getopt.h> +#include <time.h> + +#ifdef HAS_LIBCONFIG +#include <libconfig.h> +#endif + +#include "usbg-test.h" + +/** + * @file tests/test.c + */ + +#define USBG_TEST(name, test, setup, teardown) \ + {name, test, setup, teardown} + +#define FILLED_STR(len, c) \ + { [0 ... len - 2] = c, [len - 1] = '\0' } + +/* two levels of macros allow to strigify result of macro expansion */ +#define STR(s) #s +#define XSTR(s) STR(s) +/* unique string */ +#define UNIQUE XSTR(__COUNTER__) + +#define FUNC_FROM_TYPE(t) { \ + .type = t, \ + .instance = "instance"UNIQUE \ +} + +#define CONF_FROM_BOUND(b) { \ + .label = "c", \ + .id = __COUNTER__, \ + .bound_funcs = b \ +} + +static usbg_gadget_attrs min_gadget_attrs = { + .bcdUSB = 0x0000, + .bDeviceClass = 0x0, + .bDeviceSubClass = 0x0, + .bDeviceProtocol = 0x0, + .bMaxPacketSize0 = 0x0, + .idVendor = 0x0000, + .idProduct = 0x0000, + .bcdDevice = 0x0000 +}; + +static usbg_gadget_attrs max_gadget_attrs = { + .bcdUSB = 0xffff, + .bDeviceClass = 0xff, + .bDeviceSubClass = 0xff, + .bDeviceProtocol = 0xff, + .bMaxPacketSize0 = 0xff, + .idVendor = 0xffff, + .idProduct = 0xffff, + .bcdDevice = 0xffff +}; + +/* PATH_MAX is limit for path length */ +#define LONG_PATH_LEN PATH_MAX/2 +static char long_path_str[] = FILLED_STR(LONG_PATH_LEN, 'x'); + +/* NAME_MAX is limit for filename length */ +static char long_usbg_string[] = FILLED_STR(NAME_MAX, 'x'); + +static usbg_config_strs simple_config_strs= { + .configuration = "configuration string" +}; + +static usbg_config_attrs max_config_attrs = { + .bmAttributes = 0xff, + .bMaxPower = 0xff +}; + +static usbg_config_attrs min_config_attrs = { + .bmAttributes = 0x00, + .bMaxPower = 0x00 +}; + +/** + * @brief Simplest udcs names + * @details Used to go through init when testing other things + */ +static char *simple_udcs[] = { + "UDC1", + "UDC2", + NULL +}; + +static char *long_udcs[] = { + long_usbg_string, + "UDC1", + NULL +}; + +/** + * @brief Simplest functions names + * @details Used to go through init when testing other things + */ +static struct test_function simple_funcs[] = { + FUNC_FROM_TYPE(F_ECM), + FUNC_FROM_TYPE(F_ACM), + TEST_FUNCTION_LIST_END +}; + +/** + * @brief All functions types + * @details When testing with this in state, check if all func types are + * processed correctly + */ +static struct test_function all_funcs[] = { + FUNC_FROM_TYPE(F_SERIAL), + FUNC_FROM_TYPE(F_ACM), + FUNC_FROM_TYPE(F_OBEX), + FUNC_FROM_TYPE(F_ECM), + FUNC_FROM_TYPE(F_SUBSET), + FUNC_FROM_TYPE(F_NCM), + FUNC_FROM_TYPE(F_EEM), + FUNC_FROM_TYPE(F_RNDIS), + FUNC_FROM_TYPE(F_PHONET), + FUNC_FROM_TYPE(F_FFS), + TEST_FUNCTION_LIST_END +}; + +static struct test_function same_type_funcs[] = { + FUNC_FROM_TYPE(F_SERIAL), + FUNC_FROM_TYPE(F_SERIAL), + FUNC_FROM_TYPE(F_SERIAL), + TEST_FUNCTION_LIST_END +}; + +/** + * @brief No functions at all + * @details Check if gadget with no functions (or config with no bindings) + * is processed correctly. + */ +static struct test_function no_funcs[] = { + TEST_FUNCTION_LIST_END +}; + +/** + * @brief Simple configs + * @details Used to pass through init when testing other things + */ +static struct test_config simple_confs[] = { + CONF_FROM_BOUND(simple_funcs), + TEST_CONFIG_LIST_END +}; + +/** + * @brief Configs bound to all avaible function types + */ +static struct test_config all_bindings_confs[] = { + CONF_FROM_BOUND(no_funcs), + CONF_FROM_BOUND(all_funcs), + TEST_CONFIG_LIST_END +}; + +#define GADGET(n, u, c, f) \ + { \ + .name = n, \ + .udc = u, \ + .configs = c, \ + .functions = f \ + } + +/** + * @brief Simplest gadget + */ +static struct test_gadget simple_gadgets[] = { + GADGET("g1", "UDC1", simple_confs, simple_funcs), + TEST_GADGET_LIST_END +}; + +/** + * @brief Gadgets with all avaible functions + */ +static struct test_gadget all_funcs_gadgets[] = { + GADGET("all_funcs_gadget1", "UDC1", all_bindings_confs, all_funcs), + TEST_GADGET_LIST_END +}; + +static struct test_gadget long_udc_gadgets[] = { + GADGET("long_udc_gadgets", long_usbg_string, simple_confs, simple_funcs), + TEST_GADGET_LIST_END +}; + +struct test_function_attrs_data { + struct test_state *state; + usbg_function_attrs *attrs; +}; +struct test_data { + struct test_state *state; + struct usbg_state *usbg_state; +}; + +#define FUNC_ATTRS(t, label, a...) { \ + .header = { \ + .attrs_type = t \ + }, \ + .attrs = { \ + .label = { a } \ + }, \ +} + +static usbg_function_attrs simple_serial_attrs = FUNC_ATTRS(USBG_F_ATTRS_SERIAL, serial, 42); +static usbg_function_attrs simple_net_attrs = FUNC_ATTRS(USBG_F_ATTRS_NET, net, {}, {}, "if", 1); +static usbg_function_attrs simple_phonet_attrs = FUNC_ATTRS(USBG_F_ATTRS_PHONET, phonet, "if"); +static usbg_function_attrs writable_serial_attrs = FUNC_ATTRS(USBG_F_ATTRS_SERIAL, serial, 0); +static usbg_function_attrs writable_net_attrs = FUNC_ATTRS(USBG_F_ATTRS_NET, net, {}, {}, "", 42); +static usbg_function_attrs writable_phonet_attrs = FUNC_ATTRS(USBG_F_ATTRS_PHONET, phonet, ""); +static usbg_function_attrs simple_ffs_attrs = FUNC_ATTRS(USBG_F_ATTRS_FFS, ffs, "0"); +static usbg_function_attrs writable_ffs_attrs = FUNC_ATTRS(USBG_F_ATTRS_FFS, ffs, ""); + +struct test_gadget_strs_data { + struct test_state *state; + usbg_gadget_strs *strs; +}; + +#define STATE(p, g, u) { \ + .configfs_path = p, \ + .gadgets = g, \ + .udcs = u \ +} + +/** + * @brief Simple state + */ +static struct test_state simple_state = STATE("config", simple_gadgets, simple_udcs); + +/** + * @brief State with all functions avaible + */ +static struct test_state all_funcs_state = STATE("all_funcs_configfs", all_funcs_gadgets, simple_udcs); + +static struct test_state long_path_state = STATE(long_path_str, simple_gadgets, simple_udcs); + +static struct test_state long_udc_state = STATE("simple_path", long_udc_gadgets, long_udcs); + +static usbg_config_attrs *get_random_config_attrs() +{ + usbg_config_attrs *ret; + + ret = safe_malloc(sizeof(*ret)); + + srand(time(NULL)); + ret->bmAttributes = rand() % max_config_attrs.bmAttributes; + ret->bMaxPower = rand() % max_config_attrs.bMaxPower; + + return ret; +} + +static usbg_gadget_attrs *get_random_gadget_attrs() +{ + usbg_gadget_attrs *ret; + + ret = safe_malloc(sizeof(*ret)); + + srand(time(NULL)); + ret->bcdUSB = rand() % max_gadget_attrs.bcdUSB; + ret->bDeviceClass = rand() % max_gadget_attrs.bDeviceClass; + ret->bDeviceSubClass = rand() % max_gadget_attrs.bDeviceSubClass; + ret->bDeviceProtocol = rand() % max_gadget_attrs.bDeviceProtocol; + ret->bMaxPacketSize0 = rand() % max_gadget_attrs.bMaxPacketSize0; + ret->idVendor = rand() % max_gadget_attrs.idVendor; + ret->idProduct = rand() % max_gadget_attrs.idProduct; + ret->bcdDevice = rand() % max_gadget_attrs.bcdDevice; + + return ret; +} + +/** + * @brief Add given attributes to all configs in state + * @return Prepared state where configs has given attributes + */ +static void *prepare_state_with_config_attrs(struct test_state *state, + usbg_config_attrs *attrs) +{ + struct test_gadget *tg; + struct test_config *tc; + + for (tg = state->gadgets; tg->name; ++tg) + for (tc = tg->configs; tc->label; ++tc) + tc->attrs = attrs; + + state = prepare_state(state); + return state; +} + +static int setup_max_config_attrs_state(void **state) +{ + *state = prepare_state_with_config_attrs(&simple_state, &max_config_attrs); + return 0; +} + +static int setup_min_config_attrs_state(void **state) +{ + *state = prepare_state_with_config_attrs(&simple_state, &min_config_attrs); + return 0; +} + +static int setup_random_config_attrs_state(void **state) +{ + *state = prepare_state_with_config_attrs(&simple_state, get_random_config_attrs()); + return 0; +} + +static int setup_simple_config_strs_state(void **state) +{ + struct test_gadget *tg; + struct test_config *tc; + + for (tg = simple_state.gadgets; tg->name; ++tg) + for (tc = tg->configs; tc->label; ++tc) + tc->strs = &simple_config_strs; + + *state = prepare_state(&simple_state); + return 0; +} + +/** + * @brief Prepare test_state with one gadget containing given function list + * @details For testing only functions. We put them in a gadget as simply + * as possible. + * @param[in] func Pointer to list of functions + * @return Pointer to test state with given functions + */ +static struct test_state *put_func_in_state(struct test_function *func) +{ + struct test_state *st; + struct test_gadget *g; + struct test_config *c; + char **udcs; + + st = safe_calloc(1, sizeof(*st)); + /* Do not need config */ + c = safe_calloc(1, sizeof(*c)); + g = safe_calloc(2, sizeof(*g)); + udcs = safe_calloc(2, sizeof(*udcs)); + + g[0].name = "g1"; + g[0].udc = "UDC1"; + g[0].configs = c; + g[0].functions = func; + g[0].writable = 1; + + udcs[0] = "UDC1"; + g[0].writable = 1; + + st->configfs_path = "config"; + st->gadgets = g; + st->udcs = udcs; + st->writable = 1; + + st = prepare_state(st); + + return st; +} + +/** + * @brief Setup simple state with some gadgets, configs and functions + */ +static int setup_simple_state(void **state) +{ + *state = prepare_state(&simple_state); + return 0; +} + +/** + * @brief Setup state with all avaible functions + */ +static int setup_all_funcs_state(void **state) +{ + *state = prepare_state(&all_funcs_state); + return 0; +} + +/** + * @brief Setup state with few functions of the same type + */ +static int setup_same_type_funcs_state(void **state) +{ + *state = put_func_in_state(same_type_funcs); + return 0; +} + +/** + * @brief Setup state with very long path name + */ +static int setup_long_path_state(void **state) +{ + *state = prepare_state(&long_path_state); + return 0; +} + +/** + * @brief Setup state with long udc name + */ +static int setup_long_udc_state(void **state) +{ + *state = prepare_state(&long_udc_state); + return 0; +} + +/** + * @brief Setup state with gadget strings of random length + * @param[out] state Pointer to pointer to test_gadget_strs_data structure + * with initialized state and strings + */ +static int setup_random_len_gadget_strs_data(void **state) +{ + usbg_gadget_strs *strs; + struct test_gadget_strs_data *data; + + /* will fill memory with zeros */ + strs = safe_calloc(1, sizeof(*strs)); + data = safe_malloc(sizeof(*data)); + + srand(time(NULL)); + + memset(strs->str_ser, 'x', rand() % USBG_MAX_STR_LENGTH); + memset(strs->str_mnf, 'x', rand() % USBG_MAX_STR_LENGTH); + memset(strs->str_prd, 'x', rand() % USBG_MAX_STR_LENGTH); + + data->strs = strs; + + data->state = prepare_state(&simple_state); + *state = data; + return 0; +} + +static void *setup_f_attrs(int f_type, usbg_function_attrs *attrs) +{ + struct test_function_attrs_data *data; + struct test_function *func; + + data = safe_malloc(sizeof(*data)); + + func = safe_calloc(2, sizeof(*func)); + func[0].type = f_type; + func[0].instance = "0"; + func[0].writable = 1; + + data->state = put_func_in_state(func); + data->attrs = attrs; + return data; +} + +static int setup_f_serial_attrs(void **state) +{ + *state = setup_f_attrs(F_SERIAL, &simple_serial_attrs); + return 0; +} + +static int setup_f_serial_writable_attrs(void **state) +{ + *state = setup_f_attrs(F_SERIAL, &writable_serial_attrs); + return 0; +} + +static int setup_f_acm_attrs(void **state) +{ + *state = setup_f_attrs(F_ACM, &simple_serial_attrs); + return 0; +} + +static int setup_f_acm_writable_attrs(void **state) +{ + *state = setup_f_attrs(F_ACM, &writable_serial_attrs); + return 0; +} + +static int setup_f_obex_attrs(void **state) +{ + *state = setup_f_attrs(F_OBEX, &simple_serial_attrs); + return 0; +} + +static int setup_f_obex_writable_attrs(void **state) +{ + *state = setup_f_attrs(F_OBEX, &writable_serial_attrs); + return 0; +} + +static int setup_f_ecm_attrs(void **state) +{ + *state = setup_f_attrs(F_ECM, &simple_net_attrs); + return 0; +} + +static int setup_f_ecm_writable_attrs(void **state) +{ + *state = setup_f_attrs(F_ECM, &writable_net_attrs); + return 0; +} + +static int setup_f_subset_attrs(void **state) +{ + *state = setup_f_attrs(F_SUBSET, &simple_net_attrs); + return 0; +} + +static int setup_f_subset_writable_attrs(void **state) +{ + *state = setup_f_attrs(F_SUBSET, &writable_net_attrs); + return 0; +} + +static int setup_f_ncm_attrs(void **state) +{ + *state = setup_f_attrs(F_NCM, &simple_net_attrs); + return 0; +} + +static int setup_f_ncm_writable_attrs(void **state) +{ + *state = setup_f_attrs(F_NCM, &writable_net_attrs); + return 0; +} + +static int setup_f_eem_attrs(void **state) +{ + *state = setup_f_attrs(F_EEM, &simple_net_attrs); + return 0; +} + +static int setup_f_eem_writable_attrs(void **state) +{ + *state = setup_f_attrs(F_EEM, &writable_net_attrs); + return 0; +} + +static int setup_f_rndis_attrs(void **state) +{ + *state = setup_f_attrs(F_RNDIS, &simple_net_attrs); + return 0; +} + +static int setup_f_rndis_writable_attrs(void **state) +{ + *state = setup_f_attrs(F_RNDIS, &writable_net_attrs); + return 0; +} + +static int setup_f_phonet_attrs(void **state) +{ + *state = setup_f_attrs(F_PHONET, &simple_phonet_attrs); + return 0; +} + +static int setup_f_phonet_writable_attrs(void **state) +{ + *state = setup_f_attrs(F_PHONET, &writable_phonet_attrs); + return 0; +} + +static int setup_f_ffs_attrs(void **state) +{ + *state = setup_f_attrs(F_FFS, &simple_ffs_attrs); + return 0; +} + +static int setup_f_ffs_writable_attrs(void **state) +{ + *state = setup_f_attrs(F_FFS, &writable_ffs_attrs); + return 0; +} + +/** + * @brief Tests usbg_get_gadget function with given state + * @details Check if gadgets are returned correctly + */ +static void test_get_gadget(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_gadget(ts, s, assert_gadget_equal); +} + +/** + * @brief Tests usbg_get_gadget with non-existing gadget name + * @details Check if get_gadget will not find non-existing gadgets and + * will not cause crash. + */ +static void test_get_gadget_fail(void **state) +{ + usbg_gadget *g = NULL; + usbg_state *s = NULL; + struct test_state *st; + + safe_init_with_state(state, &st, &s); + + g = usbg_get_gadget(s, "non-existing-gadget"); + assert_null(g); +} + +/** + * @brief Tests usbg_get_first_gadget function + * @details Check if gadget returned by get_first_gadget is actually first one + */ +static void test_get_first_gadget(void **state) +{ + usbg_gadget *g = NULL; + usbg_state *s = NULL; + struct test_state *st; + + safe_init_with_state(state, &st, &s); + + g = usbg_get_first_gadget(s); + assert_non_null(g); + assert_gadget_equal(g, &st->gadgets[0]); +} + +/** + * @brief Tests get_first_gadget with invalid arguments + */ +static void test_get_first_gadget_fail(void **state) +{ + usbg_gadget *g; + + g = usbg_get_first_gadget(NULL); + assert_null(g); +} + +static void try_get_gadget_name(usbg_gadget *g, struct test_gadget *tg) +{ + const char *name; + + name = usbg_get_gadget_name(g); + assert_string_equal(name, tg->name); +} + +/** + * @brief Tests getting name of gadget + * @details Check if gadget name is returned correctly + */ +static void test_get_gadget_name(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_gadget(ts, s, try_get_gadget_name); +} + +static void try_get_gadget_name_len(usbg_gadget *g, struct test_gadget *tg) +{ + int len; + int expected; + + expected = strlen(tg->name); + len = usbg_get_gadget_name_len(g); + assert_int_equal(len, expected); +} + +/** + * @brief Tests getting name length of gadget + * @details Check if returned name length is equal original + */ +static void test_get_gadget_name_len(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_gadget(ts, s, try_get_gadget_name_len); +} + +/** + * @brief Tests getting name of gadget with invalid arguments + * @details Check if trying to get name of wrong (non-existing) gadget + * will not cause crash, but return NULL as expected. + */ +static void test_get_gadget_name_fail(void **state) +{ + const char *name; + + name = usbg_get_gadget_name(NULL); + assert_null(name); +} + +static void try_cpy_gadget_name(usbg_gadget *g, struct test_gadget *tg) +{ + char name[USBG_MAX_NAME_LENGTH]; + int ret; + + ret = usbg_cpy_gadget_name(g, name, USBG_MAX_NAME_LENGTH); + assert_int_equal(ret, USBG_SUCCESS); + assert_string_equal(name, tg->name); +} + +/** + * @brief Tests copying gadget's name + * @details Check if copying gadget name copy actual name correctly + */ +static void test_cpy_gadget_name(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_gadget(ts, s, try_cpy_gadget_name); +} + +/** + * @brief Test copying gadet name with invalid arguments + * @details Check if trying to copy gadget name into non-existing buffer, + * or give invalid buffer length, or invalid gadget will be handled by library + * and return correct error codes + */ +static void test_cpy_gadget_name_fail(void **state) +{ + usbg_gadget *g = NULL; + usbg_state *s = NULL; + struct test_state *st; + int i = 0; + char name[USBG_MAX_NAME_LENGTH]; + int ret; + + safe_init_with_state(state, &st, &s); + + for (i = 0; st->gadgets[i].name; i++) { + g = usbg_get_gadget(s, st->gadgets[i].name); + assert_non_null(g); + + ret = usbg_cpy_gadget_name(g, name, 0); + assert_int_equal(ret, USBG_ERROR_INVALID_PARAM); + + ret = usbg_cpy_gadget_name(g, NULL, USBG_MAX_NAME_LENGTH); + assert_int_equal(ret, USBG_ERROR_INVALID_PARAM); + } + + ret = usbg_cpy_gadget_name(NULL, name, USBG_MAX_NAME_LENGTH); + assert_int_equal(ret, USBG_ERROR_INVALID_PARAM); +} + +/** + * @brief Tests init by comparing test state and usbg state + * @details Check if usbg state after init match given state and + * if init returned success code + */ +static void test_init(void **state) +{ + usbg_state *s = NULL; + struct test_state *st; + + safe_init_with_state(state, &st, &s); + + assert_state_equal(s, st); +} + +/** + * @brief Test getting function by name + * @param[in] state Pointer to pointer to correctly initialized test_state structure + */ +static void test_get_function(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_function(ts, s, assert_func_equal); +} + +/** + * @brief Tests usbg_get_function with some non-existing functions + * @details Check if get function will return NULL, when invalid + * functions names and types are passed as arguments and will not cause crash. + * @param[in] state Pointer to pointer to correctly initialized test_state structure + */ +static void test_get_function_fail(void **state) +{ + usbg_state *s = NULL; + usbg_gadget *g = NULL; + usbg_function *f = NULL; + struct test_state *st; + + safe_init_with_state(state, &st, &s); + + g = usbg_get_first_gadget(s); + assert_non_null(g); + + f = usbg_get_function(g, F_ACM, "non-existing-instance"); + assert_null(f); + + f = usbg_get_function(g, 9001, "0"); + assert_null(f); +} + + +/** + * @brief Tests function type translation to string + * @param[in] state Pointer to pointer to correctly initialized test_state structure + * @details Check if get_function_type_str returns proper strings for all types. + */ +static void test_get_function_type_str(void **state) +{ + struct { + usbg_function_type type; + const char *str; + } types[] = { + {F_SERIAL, "gser"}, + {F_ACM, "acm"}, + {F_OBEX, "obex"}, + {F_ECM, "ecm"}, + {F_SUBSET, "geth"}, + {F_NCM, "ncm"}, + {F_EEM, "eem"}, + {F_RNDIS, "rndis"}, + {F_PHONET, "phonet"}, + {F_FFS, "ffs"}, + }; + + const char *str; + int i; + + for (i = 0; i < ARRAY_SIZE(types); i++) { + str = usbg_get_function_type_str(types[i].type); + assert_non_null(str); + assert_string_equal(str, types[i].str); + } +} + +static struct { + usbg_gadget_str code; + const char *name; +} gadget_str_names[] = { + {STR_PRODUCT, "product"}, + {STR_MANUFACTURER, "manufacturer"}, + {STR_SERIAL_NUMBER, "serialnumber"}, +}; + +/** + * @brief Tests gadget codeing name getting + * @param[in] state Pointer to pointer to correctly initialized test_state codeucture + * @details Check if usbg_get_gadget_code_name returns proper codeings for all types. + */ +static void test_get_gadget_str_name(void **state) +{ + const char *name; + int i; + + for (i = 0; i < ARRAY_SIZE(gadget_str_names); i++) { + name = usbg_get_gadget_str_name(gadget_str_names[i].code); + assert_non_null(name); + assert_string_equal(name, gadget_str_names[i].name); + } +} + +/** + * @brief Tests gadget codeing code getting by its name + * @param[in] state Pointer to pointer to correctly initialized test_state codeucture + * @details Check if usbg_lookup_gadget_code returns values matching codeings + */ +static void test_lookup_gadget_str(void **state) +{ + int i, code; + + for (i = 0; i < ARRAY_SIZE(gadget_str_names); i++) { + code = usbg_lookup_gadget_str(gadget_str_names[i].name); + assert_return_code(code, 0); + assert_int_equal(code, gadget_str_names[i].code); + } +} + +/** + * @brief Tests function type translation to string with unknown funcs + * @param[in] state Not used parameter + * @details Check if get_function_type_str returns NULL, when given + * function type is unknown. + */ +static void test_get_function_type_str_fail(void **state) +{ + const char *str; + + str = usbg_get_function_type_str(-1); + assert_null(str); +} + +/** + * @brief Get instance of given function and check it + * @param[in] f Usbg function + * @param[in] tf Test function which should match f + */ +static void try_get_function_instance(usbg_function *f, struct test_function *tf) +{ + const char *str; + + str = usbg_get_function_instance(f); + assert_string_equal(str, tf->instance); +} + +/** + * @brief Tests getting function instance from usbg_function structure + * @param[in] state Pointer to pointer to correctly initialized test_state structure + * @details Check if returned instance name is correct. + */ +static void test_get_function_instance(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_function(ts, s, try_get_function_instance); +} + +/** + * @brief Cpy instance of given usbg function and check it + * @param[in] f Usbg function + * @param[in] tf Test function which should match f + */ +static void try_cpy_function_instance(usbg_function *f, struct test_function *tf) +{ + char str[USBG_MAX_NAME_LENGTH]; + int ret; + int small_len = 2; + + ret = usbg_cpy_function_instance(f, str, USBG_MAX_NAME_LENGTH); + assert_int_equal(ret, USBG_SUCCESS); + assert_string_equal(str, tf->instance); + + ret = usbg_cpy_function_instance(f, str, small_len); + assert_int_equal(ret, USBG_SUCCESS); + assert_memory_equal(str, tf->instance, small_len - 1); + assert_int_equal(str[small_len - 1], '\0'); +} + +/** + * @brief Tests copying function instance from usbg_function structure into buffer + * @param[in] state Pointer to pointer to correctly initialized state + * @details Check if buffer contains expected string + */ +static void test_cpy_function_instance(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_function(ts, s, try_cpy_function_instance); +} + +/** + * @brief Get function type and check it + * @param[in] f Usbg function + * @param[in] tf Test function which should match f by type + */ +static void try_get_function_type(usbg_function *f, struct test_function *tf) +{ + usbg_function_type type; + + type = usbg_get_function_type(f); + assert_int_equal(type, tf->type); +} + +/** + * @brief Tests getting function type + * @details Check if getting function type returns what was expected. + * State must be proper (init must end with success). + * @param[in] state Pointer to pointer to correctly initialized state + */ +static void test_get_function_type(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_function(ts, s, try_get_function_type); +} + +/** + * @brief Check if function instance length is correct + * @param[in] f Usbg function + * @param[in] tf Test function which should match f + */ +static void try_get_function_instance_len(usbg_function *f, struct test_function *tf) +{ + size_t len; + len = usbg_get_function_instance_len(f); + assert_int_equal(len, strlen(tf->instance)); +} + +/** + * @brief Tests getting length of function instance name + * @details Check if returned instance name length matches + * actual length of instance name + * @param[in] state Pointer to pointer to correctly initialized state + */ +static void test_get_function_instance_len(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_function(ts, s, try_get_function_instance_len); +} + +/** + * @brief Tests getting configfs path from usbg state + * @param[in,out] state Pointer to pointer to correctly initialized test state. + * When finished, it contains pointer to usbg_state which should be cleaned. + */ +static void test_get_configfs_path(void **state) +{ + usbg_state *s = NULL; + struct test_state *st; + const char *path; + + safe_init_with_state(state, &st, &s); + + path = usbg_get_configfs_path(s); + assert_path_equal(path, st->configfs_path); +} + +/** + * @brief Tests getting configfs path length from usbg state + * @param[in,out] state Pointer to pointer to correctly initialized test state. + * When finished, it contains pointer to usbg_state which should be cleaned. + */ +static void test_get_configfs_path_len(void **state) +{ + usbg_state *s = NULL; + struct test_state *st; + int ret, len; + + safe_init_with_state(state, &st, &s); + + ret = usbg_get_configfs_path_len(s); + len = strlen(st->configfs_path); + assert_int_equal(ret, len); +} + +/** + * @brief Tests copying configfs path into buffer + * @param[in,out] state Pointer to pointer to correctly initialized test state. + * When finished, it contains pointer to usbg_state which should be cleaned. + */ +static void test_cpy_configfs_path(void **state) +{ + usbg_state *s = NULL; + struct test_state *st; + char path[PATH_MAX]; + int ret; + + safe_init_with_state(state, &st, &s); + + ret = usbg_cpy_configfs_path(s, path, PATH_MAX); + assert_int_equal(ret, USBG_SUCCESS); + assert_path_equal(path, st->configfs_path); +} + +/** + * @brief Tests getting config by name + * @param[in,out] state Pointer to pointer to correctly initialized test state. + * When finished, it contains pointer to usbg_state which should be cleaned. + */ +static void test_get_config(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_config(ts, s, assert_config_equal); +} + +static void test_get_config_without_label(void **state) +{ + usbg_state *s = NULL; + usbg_gadget *g = NULL; + usbg_config *c = NULL; + struct test_state *ts; + struct test_gadget *tg; + struct test_config *tc; + + safe_init_with_state(state, &ts, &s); + + for (tg = ts->gadgets; tg->name; tg++) { + g = usbg_get_gadget(s, tg->name); + assert_non_null(g); + for (tc = tg->configs; tc->label; tc++) { + c = usbg_get_config(g, tc->id, NULL); + assert_non_null(c); + assert_config_equal(c, tc); + } + } +} + +/** + * @bried Tests getting non-existing config + * @param[in,out] state Pointer to pointer to correctly initialized test state. + * When finished, it contains pointer to usbg_state which should be cleaned. + */ +static void test_get_config_fail(void **state) +{ + usbg_state *s = NULL; + usbg_gadget *g = NULL; + usbg_config *c = NULL; + struct test_state *ts; + struct test_gadget *tg; + + safe_init_with_state(state, &ts, &s); + + for (tg = ts->gadgets; tg->name; tg++) { + g = usbg_get_gadget(s, tg->name); + assert_non_null(g); + + c = usbg_get_config(g, 0, "non-existing-config"); + assert_null(c); + + c = usbg_get_config(g, -9001, "c"); + assert_null(c); + + c = usbg_get_config(g, -9001, NULL); + assert_null(c); + } +} + +/** + * @brief Get config label and check it + * @param[in] c Usbg config + * @param[in] tc Test config which should match c + */ +static void try_get_config_label(usbg_config *c, struct test_config *tc) +{ + const char *label; + label = usbg_get_config_label(c); + assert_string_equal(label, tc->label); +} + +/** + * @brief Tests getting config label + * @param[in,out] state Pointer to pointer to correctly initialized test state. + * When finished, it contains pointer to usbg_state which should be cleaned. + */ +static void test_get_config_label(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_config(ts, s, try_get_config_label); +} + +/** + * @brief Check config id with test structure + * @param[in] c Usbg config + * @param[in] tc Test config which should match c + */ +static void try_get_config_id(usbg_config *c, struct test_config *tc) +{ + int id; + id = usbg_get_config_id(c); + assert_int_equal(id, tc->id); +} + +/** + * @brief Tests getting config id + * @param[in,out] state Pointer to pointer to correctly initialized test state. + * When finished, it contains pointer to usbg_state which should be cleaned. + */ +static void test_get_config_id(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_config(ts, s, try_get_config_id); +} + +/** + * @brief Test getting given attributes from gadgets present in state + * @param[in] s Pointer to usbg state + * @param[in] ts Pointer to test state matching given usbg state + * @param[in] attrs Pointer to gadget attributes which should be put in + * virtual filesystem for writting by usbg + */ +static void try_get_gadget_attrs(usbg_state *s, struct test_state *ts, + usbg_gadget_attrs *attrs) +{ + usbg_gadget *g = NULL; + usbg_gadget_attrs actual; + struct test_gadget *tg; + int ret; + + for (tg = ts->gadgets; tg->name; tg++) { + g = usbg_get_gadget(s, tg->name); + assert_non_null(g); + + push_gadget_attrs(tg, attrs); + ret = usbg_get_gadget_attrs(g, &actual); + + assert_int_equal(ret, 0); + assert_gadget_attrs_equal(&actual, attrs); + } +} + +/** + * @brief Tests getting gadget attributes + * @param[in] state Pointer to correctly initialized test_state structure + **/ +static void test_get_gadget_attrs(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + + try_get_gadget_attrs(s, ts, &min_gadget_attrs); + try_get_gadget_attrs(s, ts, &max_gadget_attrs); + try_get_gadget_attrs(s, ts, get_random_gadget_attrs()); +} + +/** + * @brief Test setting given attributes on gadgets present in state + * @param[in] s Pointer to usbg state + * @param[in] ts Pointer to test state matching given usbg state + * @param[in] attrs Pointer to gadget attributes to be set + */ +static void try_set_gadget_attrs(usbg_state *s, struct test_state *ts, + usbg_gadget_attrs *attrs) +{ + usbg_gadget *g = NULL; + struct test_gadget *tg; + int ret; + + for (tg = ts->gadgets; tg->name; tg++) { + g = usbg_get_gadget(s, tg->name); + assert_non_null(g); + + pull_gadget_attrs(tg, attrs); + ret = usbg_set_gadget_attrs(g, attrs); + + assert_int_equal(ret, 0); + } +} +/** + * @brief Tests setting gadget attributes + * @param[in] state Pointer to correctly initialized test_state structure + **/ +static void test_set_gadget_attrs(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + + try_set_gadget_attrs(s, ts, &min_gadget_attrs); + try_set_gadget_attrs(s, ts, &max_gadget_attrs); + try_set_gadget_attrs(s, ts, get_random_gadget_attrs()); +} + +/** + * @brief Test setting given attributes on gadgets present in state one by one, + * using functions specific for each attribute + * @param[in] s Pointer to usbg state + * @param[in] ts Pointer to test state matching given usbg state + * @param[in] attrs Pointer to gadget attributes to be set + */ +static void try_set_specific_gadget_attr(usbg_state *s, struct test_state *ts, + usbg_gadget_attrs *attrs) +{ + usbg_gadget *g = NULL; + struct test_gadget *tg; + int ret; + int i; + int attr; + + for (tg = ts->gadgets; tg->name; tg++) { + g = usbg_get_gadget(s, tg->name); + assert_non_null(g); + + for (i = USBG_GADGET_ATTR_MIN; i < USBG_GADGET_ATTR_MAX; i++) { + attr = get_gadget_attr(attrs, i); + pull_gadget_attribute(tg, i, attr); + usbg_set_gadget_attr(g, i, attr); + assert_int_equal(ret, 0); + } + } +} + +/** + * @brief Tests setting gadget attributes one by one + * @param[in] state Pointer to correctly initialized test_state structure + **/ +static void test_set_specific_gadget_attr(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + + try_set_specific_gadget_attr(s, ts, &min_gadget_attrs); + try_set_specific_gadget_attr(s, ts, &max_gadget_attrs); + try_set_specific_gadget_attr(s, ts, get_random_gadget_attrs()); +} + +/** + * @brief Tests getting udc from state + * @param[in] state Pointer to correctly initialized test_state structure + **/ +void test_get_udc(void **state) +{ + struct test_state *ts; + char **tu; + struct test_gadget *tg; + usbg_state *s = NULL; + usbg_udc *u = NULL; + usbg_gadget *g = NULL; + + safe_init_with_state(state, &ts, &s); + + for (tu = ts->udcs; *tu; tu++) { + u = usbg_get_udc(s, *tu); + assert_non_null(u); + assert_string_equal(*tu, u->name); + assert_int_equal(s, u->parent); + } + + for (tg = ts->gadgets; tg->name; tg++) { + u = usbg_get_udc(s, tg->udc); + g = usbg_get_gadget(s, tg->name); + assert_int_equal(u->gadget, g); + } +} + +static void test_get_gadget_attr_str(void **state) +{ + struct { + usbg_gadget_attr attr; + const char *str; + } attrs[] = { + {BCD_USB, "bcdUSB"}, + {B_DEVICE_CLASS, "bDeviceClass"}, + {B_DEVICE_SUB_CLASS, "bDeviceSubClass"}, + {B_DEVICE_PROTOCOL, "bDeviceProtocol"}, + {B_MAX_PACKET_SIZE_0, "bMaxPacketSize0"}, + {ID_VENDOR, "idVendor"}, + {ID_PRODUCT, "idProduct"}, + {BCD_DEVICE, "bcdDevice"}, + }; + + const char *str; + int i, j; + + for (i = 0; i < ARRAY_SIZE(attrs); i++) { + str = usbg_get_gadget_attr_str(attrs[i].attr); + assert_non_null(str); + assert_string_equal(str, attrs[i].str); + } + + /* Check if iteration over values works */ + for (i = USBG_GADGET_ATTR_MIN; i < USBG_GADGET_ATTR_MAX; ++i) { + str = usbg_get_gadget_attr_str(i); + assert_non_null(str); + + for (j = 0; j < ARRAY_SIZE(attrs); ++j) + if (attrs[j].attr == i) { + assert_string_equal(str, attrs[j].str); + break; + } + + assert_int_not_equal(j, ARRAY_SIZE(attrs)); + } +} + +static void test_get_gadget_attr_str_fail(void **state) +{ + const char *str; + + str = usbg_get_gadget_attr_str(USBG_GADGET_ATTR_MIN - 1); + assert_null(str); + + str = usbg_get_gadget_attr_str(USBG_GADGET_ATTR_MAX); + assert_null(str); +} + +/** + * @brief set gadget strings + * @details Also do it one by one + * @param[in] data Pointer to correctly initialized test_gadget_strs_data structure + */ +static void test_set_gadget_strs(void **data) +{ + struct test_gadget_strs_data *ts; + struct test_gadget *tg; + usbg_state *s = NULL; + usbg_gadget *g = NULL; + int i; + int ret; + + ts = (struct test_gadget_strs_data *)(*data); + *data = NULL; + + init_with_state(ts->state, &s); + *data = s; + + for (tg = ts->state->gadgets; tg->name; tg++) { + g = usbg_get_gadget(s, tg->name); + + pull_gadget_strs(tg, LANG_US_ENG, ts->strs); + ret = usbg_set_gadget_strs(g, LANG_US_ENG, ts->strs); + assert_int_equal(ret, 0); + + for (i = 0; i < GADGET_STR_MAX; i++) + pull_gadget_string(tg, LANG_US_ENG, i, get_gadget_str(ts->strs, i)); + + ret = usbg_set_gadget_serial_number(g, LANG_US_ENG, ts->strs->str_ser); + assert_int_equal(ret, 0); + + ret = usbg_set_gadget_manufacturer(g, LANG_US_ENG, ts->strs->str_mnf); + assert_int_equal(ret, 0); + + ret = usbg_set_gadget_product(g, LANG_US_ENG, ts->strs->str_prd); + assert_int_equal(ret, 0); + + for (i = 0; i < GADGET_STR_MAX; i++) + pull_gadget_string(tg, LANG_US_ENG, i, get_gadget_str(ts->strs, i)); + + + ret = usbg_set_gadget_str(g, STR_SERIAL_NUMBER, LANG_US_ENG, ts->strs->str_ser); + assert_int_equal(ret, 0); + + ret = usbg_set_gadget_str(g, STR_MANUFACTURER, LANG_US_ENG, ts->strs->str_mnf); + assert_int_equal(ret, 0); + + ret = usbg_set_gadget_str(g, STR_PRODUCT, LANG_US_ENG, ts->strs->str_prd); + assert_int_equal(ret, 0); + } +} + +/** + * @brief get gadget strings + * @param[in] data Pointer to correctly initialized test_gadget_strs_data structure + */ +static void test_get_gadget_strs(void **data) +{ + struct test_gadget_strs_data *ts; + struct test_gadget *tg; + usbg_state *s = NULL; + usbg_gadget *g = NULL; + usbg_gadget_strs strs; + + ts = (struct test_gadget_strs_data *)(*data); + *data = NULL; + + init_with_state(ts->state, &s); + *data = s; + + for (tg = ts->state->gadgets; tg->name; tg++) { + g = usbg_get_gadget(s, tg->name); + push_gadget_strs(tg, LANG_US_ENG, ts->strs); + usbg_get_gadget_strs(g, LANG_US_ENG, &strs); + assert_gadget_strs_equal(&strs, ts->strs); + } +} + +/** + * @brief Get binding target + * @details Check if given function is target of given binding + * @param[in] tb Test function + * @param[in] b Binding + */ +static void try_get_binding_target(struct test_binding *tb, usbg_binding *b) +{ + usbg_function *f; + + f = usbg_get_binding_target(b); + assert_non_null(f); + assert_func_equal(f, tb->target); +} + +/** + * @brief Test get binding target + * @details Test all bindings present in given state + * @param[in, out] state Pointer to pointer to correctly initialized test state, + * will point to usbg state when finished. + */ +static void test_get_binding_target(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_binding(ts, s, try_get_binding_target); +} + +/** + * @brief Get binding name + * @details Check if name of given binding is equal name of given function + * @param[in] tb Test function + * @param[in] b Binding + */ +static void try_get_binding_name(struct test_binding *tb, usbg_binding *b) +{ + const char *s; + + s = usbg_get_binding_name(b); + assert_non_null(s); + assert_string_equal(s, tb->name); +} + +/** + * @brief Test get bindig name from all binding present in state + * @param[in, out] state Pointer to pointer to correctly initialized test state, + * will point to usbg state when finished. + */ +static void test_get_binding_name(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_binding(ts, s, try_get_binding_name); +} + +/** + * @brief Get binding name length + * @param[in] tb Test function + * @param[in] b Binding + */ +static void try_get_binding_name_len(struct test_binding *tb, usbg_binding *b) +{ + int n; + + n = usbg_get_binding_name_len(b); + assert_int_equal(n, strlen(tb->name)); +} + +/** + * @brief Test get binding name length from all bindings present in state + * @param[in, out] state Pointer to pointer to correctly initialized test state, + * will point to usbg state when finished. + */ +static void test_get_binding_name_len(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_binding(ts, s, try_get_binding_name_len); +} + +/** + * @brief Set config strings + * @param[in] c Config on which string should be set + * @param[in] tc Test config containing strings to be set + */ +static void try_set_config_strs(usbg_config *c, struct test_config *tc) +{ + pull_config_strs(tc, LANG_US_ENG, tc->strs); + usbg_set_config_strs(c, LANG_US_ENG, tc->strs); +} + +/** + * @brief Test setting strings + * @details Set strings in all configs present in state + * @param[in, out] state Pointer to pointer to correctly initialized test state, + * will point to usbg state when finished. + */ +static void test_set_config_strs(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_config(ts, s, try_set_config_strs); +} + +/** + * @brief Set strings one by one on config + * @param[in] c Config on which string should be set + * @param[in] tc Test config containing strings to be set + */ +static void try_set_config_string(usbg_config *c, struct test_config *tc) +{ + pull_config_string(tc, LANG_US_ENG, tc->strs->configuration); + usbg_set_config_string(c, LANG_US_ENG, tc->strs->configuration); +} + +/** + * @brief Test setting strings one by one + * @details Set strings on all configs present in given state + * @param[in, out] state Pointer to pointer to correctly initialized test state, + * will point to usbg state when finished. + */ +static void test_set_config_string(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_config(ts, s, try_set_config_string); +} + +/** + * @brief Get config strings + * @details Assume that given configs have the same strings + * @param[in] c Config from which strings should be get + * @param[in] tc Test config expected to have the same string as c + */ +static void try_get_config_strs(usbg_config *c, struct test_config *tc) +{ + usbg_config_strs strs; + push_config_strs(tc, LANG_US_ENG, tc->strs); + usbg_get_config_strs(c, LANG_US_ENG, &strs); + assert_string_equal(tc->strs->configuration, strs.configuration); +} + +/** + * @brief Test getting congig strings + * @details Get config strings on all configs present in given state + * @param[in, out] state Pointer to pointer to correctly initialized test state, + * will point to usbg state when finished. + */ +static void test_get_config_strs(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_config(ts, s, try_get_config_strs); +} + +/** + * @brief Get config attributes + * @details Assume that attributes which will be returned are the same as + * given test state contains. + * @param[in] c Usbg config + * @param[in] tc Test config with set attributes + */ +static void try_get_config_attrs(usbg_config *c, struct test_config *tc) +{ + usbg_config_attrs attrs; + + push_config_attrs(tc, tc->attrs); + usbg_get_config_attrs(c, &attrs); + assert_config_attrs_equal(tc->attrs, &attrs); +} + +/** + * @brief Test getting config attributes + * @details Get config attributes on all configfs in state + * @param[in, out] state Pointer to pointer to correctly initialized test state, + * will point to usbg state when finished. + */ +static void test_get_config_attrs(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_config(ts, s, try_get_config_attrs); +} + +/** + * @brief Set config attributes in given config + * @param[in] c Usbg config + * @param[in] tc Test config with attributes which will be set + */ +static void try_set_config_attrs(usbg_config *c, struct test_config *tc) +{ + pull_config_attrs(tc, tc->attrs); + usbg_set_config_attrs(c, tc->attrs); +} + +/** + * @brief Test setting config attributes + * @details Set config attributes on all configs in state + * @param[in, out] state Pointer to pointer to correctly initialized test state, + * will point to usbg state when finished. + */ +static void test_set_config_attrs(void **state) +{ + usbg_state *s = NULL; + struct test_state *ts; + + safe_init_with_state(state, &ts, &s); + for_each_test_config(ts, s, try_set_config_attrs); +} + +/** + * @brieg Test creating config + * @details Start with empty gadgets, add all functions from given state + * @param[in, out] state Pointer to pointer to correctly initialized test state, + * will point to usbg state when finished. + */ +static void test_create_config(void **state) +{ + usbg_state *s = NULL; + usbg_gadget *g = NULL; + usbg_config *c = NULL; + struct test_state *ts; + struct test_state *empty; + struct test_gadget *tg; + struct test_config *tc; + + ts = (struct test_state *)(*state); + *state = NULL; + + empty = build_empty_gadget_state(ts); + + init_with_state(empty, &s); + *state = s; + + for (tg = ts->gadgets; tg->name; tg++) { + g = usbg_get_gadget(s, tg->name); + assert_non_null(g); + for (tc = tg->configs; tc->label; tc++) { + pull_create_config(tc); + usbg_create_config(g, tc->id, tc->label, + tc->attrs, tc->strs, &c); + assert_config_equal(c, tc); + } + } +} + +/** + * @brief Start with empty gadget, add all functions from given one + */ +static void test_create_function(void **state) +{ + usbg_state *s = NULL; + usbg_gadget *g = NULL; + usbg_function *f = NULL; + struct test_state *ts; + struct test_state *empty; + struct test_gadget *tg; + struct test_function *tf; + + ts = (struct test_state *)(*state); + *state = NULL; + + empty = build_empty_gadget_state(ts); + + init_with_state(empty, &s); + *state = s; + + for (tg = ts->gadgets; tg->name; tg++) { + g = usbg_get_gadget(s, tg->name); + assert_non_null(g); + for (tf = tg->functions; tf->instance; tf++) { + pull_create_function(tf); + usbg_create_function(g, tf->type, tf->instance, + tf->attrs, &f); + assert_func_equal(f, tf); + } + } +} + +/** + * @brief Test only one given function for attribute getting + * @param[in] state Pointer to pointer to correctly initialized state + */ +static void test_get_function_attrs(void **state) +{ + struct test_function_attrs_data *data; + usbg_state *s; + usbg_function *f; + usbg_gadget *g; + usbg_function_attrs actual; + int ret; + + data = (struct test_function_attrs_data *)(*state); + *state = NULL; + + + init_with_state(data->state, &s); + *state = s; + + g = usbg_get_first_gadget(s); + assert_non_null(g); + f = usbg_get_first_function(g); + assert_non_null(f); + + push_function_attrs(&data->state->gadgets[0].functions[0], data->attrs); + ret = usbg_get_function_attrs(f, &actual); + + assert_int_equal(ret, 0); + assert_function_attrs_equal(&actual, data->attrs, data->attrs->header.attrs_type); + + usbg_cleanup_function_attrs(&actual); +} + +/** + * @brief Test setting attributes in only one given function + * @param[in] state Pointer to pointer to correctly initialized state + */ +static void test_set_function_attrs(void **state) +{ + struct test_function_attrs_data *data; + usbg_state *s; + usbg_function *f; + usbg_gadget *g; + int ret; + + data = (struct test_function_attrs_data *)(*state); + *state = NULL; + + init_with_state(data->state, &s); + *state = s; + + g = usbg_get_first_gadget(s); + assert_non_null(g); + f = usbg_get_first_function(g); + assert_non_null(f); + + pull_function_attrs(&data->state->gadgets[0].functions[0], data->attrs); + ret = usbg_set_function_attrs(f, data->attrs); + + assert_int_equal(ret, 0); +} + +/** + * + * @brief cleanup usbg state + */ +static int teardown_state(void **state) +{ + usbg_state *s = NULL; + + s = (usbg_state *)(*state); + if (s != NULL) + usbg_cleanup(s); + + cleanup_stack(); + return 0; +} + +/* Custom macro for defining test with given name and fixed teardown function */ +#define USBG_TEST_TS(name, test, setup) \ + USBG_TEST(name, test, setup, teardown_state) + +/** + * @page usbg_tests Tests + * @brief This is list of test cases + * @tests_start + */ + +#ifndef DOXYGEN +static struct CMUnitTest tests[] = { +#endif + + /** + * @usbg_test + * @test_desc{test_init_simple, + * Check if init was successfull on simple configfs state, + * usbg_init} + */ + USBG_TEST_TS("test_init_simple", + test_init, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_init_all_funcs, + * Check if init was successfull when all avaible functions + * are present in configfs, + * usbg_init} + */ + USBG_TEST_TS("test_init_all_funcs", + test_init, setup_all_funcs_state), + /** + * @usbg_test + * @test_desc{test_init_long_path, + * Try to initialize libusbg with long configfs path, + * usbg_init} + */ + USBG_TEST_TS("test_init_long_path", + test_init, setup_long_path_state), + /** + * @usbg_test + * @test_desc{test_init_long_udc, + * Try to initialize libusbg with long udc name, + * usbg_init} + */ + USBG_TEST_TS("test_init_long_udc", + test_init, setup_long_udc_state), + /** + * @usbg_test + * @test_desc{test_get_gadget_simple, + * Check if simple gadget will be correcty returned, + * usbg_get_gadget} + */ + USBG_TEST_TS("test_get_gadget_simple", + test_get_gadget, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_gadget_fail_simple, + * Check if getting non-existing and wrong gadgets cause + * expected failure and error codes are correct, + * usbg_get_gadget} + */ + USBG_TEST_TS("test_get_gadget_fail_simple", + test_get_gadget_fail, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_first_gadget_simple, + * Check if gadget returned by get_first_gadget + * is actually first one, + * usbg_get_first_gadget} + */ + USBG_TEST_TS("test_get_first_gadget_simple", + test_get_first_gadget, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_first_gadget_fail, + * Check if getting first gadget from state returns NULL when + * invalid parameters are passed, + * usbg_get_first_gadget} + */ + unit_test(test_get_first_gadget_fail), + /** + * @usbg_test + * @test_desc{test_get_gadget_name_simple, + * Check if returned gadget name matches expected value, + * usbg_get_gadget_name} + */ + USBG_TEST_TS("test_get_gadget_name_simple", + test_get_gadget_name, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_gadget_name_len, + * Check if returned simple gadget name length matches expected value, + * usbg_get_gadget_name} + */ + USBG_TEST_TS("test_get_gadget_name_len_simple", + test_get_gadget_name_len, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_gadget_name_fail, + * Check if trying to get name of invalid gadget + * cause expected failure (name is null), + * usbg_get_gadget_name} + */ + unit_test(test_get_gadget_name_fail), + /** + * @usbg_test + * @test_desc{test_cpy_gadget_name_simple, + * Check if getting simple gadget name into buffer work as expected, + * usbg_cpy_gadget_name} + */ + USBG_TEST_TS("test_cpy_gadget_name_simple", + test_cpy_gadget_name, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_cpy_gadget_name_fail_simple, + * Check if writting gadget name into buffer fail when + * invalid parameters are passed, + * usbg_cpy_gadget_name} + */ + USBG_TEST_TS("test_cpy_gadget_name_fail_simple", + test_cpy_gadget_name_fail, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_function_simple, + * Check if function can be correctly get from simple state, + * usbg_get_function} + */ + USBG_TEST_TS("test_get_function_simple", + test_get_function, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_function_all_funcs, + * Check if getting function work on all function types, + * usbg_get_function} + */ + USBG_TEST_TS("test_get_function_all_funcs", + test_get_function, setup_all_funcs_state), + /** + * @usbg_test + * @test_desc{test_get_function_same_type_funcs, + * Check if having multiple functions with the same type does not + * cause failure + * usbg_get_function} + */ + USBG_TEST_TS("test_get_function_same_type_funcs", + test_get_function, setup_same_type_funcs_state), + /** + * @usbg_test + * @test_desc{test_get_function_fail_simple, + * Check if trying to get invalid function's name ends + * with expected error, + * usbg_get_function} + */ + USBG_TEST_TS("test_get_function_fail_simple", + test_get_function_fail, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_function_instance_simple, + * Check if getting simple instance returns what expected, + * usbg_get_function_instance} + */ + USBG_TEST_TS("test_get_function_instance_simple", + test_get_function_instance, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_cpy_function_instance_simple, + * Check if copying simple instance into buffer returns what expected, + * usbg_cpy_function_instance} + */ + USBG_TEST_TS("test_cpy_function_instance_simple", + test_cpy_function_instance, setup_all_funcs_state), + /** + * @usbg_test + * @test_desc{test_get_function_type_simple, + * Check if function type is returned correctly, + * usbg_get_function_type} + */ + USBG_TEST_TS("test_get_function_type_simple", + test_get_function_type, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_function_type_all_funcs, + * Check if all function types are returned correctly, + * usbg_get_function_type} + */ + USBG_TEST_TS("test_get_function_type_all_funcs", + test_get_function_type, setup_all_funcs_state), + /** + * @usbg_test + * @test_desc{test_get_function_instance_len_simple, + * Check if function instance length is returned correctly, + * usbg_get_function_instance_len} + */ + USBG_TEST_TS("test_get_function_instance_len_simple", + test_get_function_instance_len, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_function_type_str, + * Compare returned function types strings with expected values, + * usbg_get_function_type_str} + */ + unit_test(test_get_function_type_str), + /** + * @usbg_test + * @test_desc{test_get_function_type_str_fail, + * Try to get type string of unknown type, + * usbg_get_function_type_str} + */ + unit_test(test_get_function_type_str_fail), + /** + * @usbg_test + * @test_desc{test_get_configfs_path_simple, + * heck if simple configfs path was returned correctly, + * usbg_get_configfs_path} + */ + USBG_TEST_TS("test_get_configfs_path_simple", + test_get_configfs_path, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_configfs_path_len, + * Check if configfs path length is correctly calculated, + * usbg_get_configfs_path_len} + */ + USBG_TEST_TS("test_get_configfs_path_len_simple", + test_get_configfs_path_len, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_cpy_configfs_path_simple, + * Copy simple configfs path into buffer and compare with original, + * usbg_cpy_configfs_path} + */ + USBG_TEST_TS("test_cpy_configfs_path_simple", + test_cpy_configfs_path, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_config_simple, + * Check if returned simple config matches original one, + * usbg_get_config} + */ + USBG_TEST_TS("test_get_config_simple", + test_get_config, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_config_without_label_simple, + * Check if returned simple config matches original one, + * usbg_get_config} + */ + USBG_TEST_TS("test_get_config_without_label_simple", + test_get_config_without_label, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_config_fail, + * Check if trying to get non-existing or invalid config + * fails as expected, + * usbg_get_config}*/ + USBG_TEST_TS("test_get_config_fail", + test_get_config_fail, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_config_label_simple, + * Check if returned simple config label matches original one, + * usbg_get_config_label} + */ + USBG_TEST_TS("test_get_config_label_simple", + test_get_config_label, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_config_id_simple, + * Check if returned simple config id matches original one, + * usbg_get_config_id} + */ + USBG_TEST_TS("test_get_config_id_simple", + test_get_config_id, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_gadget_attrs_simple, + * Get gadget attributes list and compare them with original, + * usbg_get_gadget_attrs} + */ + USBG_TEST_TS("test_get_gadget_attrs_simple", + test_get_gadget_attrs, setup_simple_state), + /** + * @usbg_tets + * @test_desc{test_set_gadget_attrs_simple, + * Set gadget attributes list\, check if everything is wrote + * as expected, + * usbg_set_gadget_attrs} + */ + USBG_TEST_TS("test_set_gadget_attrs_simple", + test_set_gadget_attrs, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_set_specific_gadget_attr_simple, + * Set gadget attributes one by one, + * usbg_set_gadget_attrs} + */ + USBG_TEST_TS("test_set_specific_gadget_attr_simple", + test_set_specific_gadget_attr, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_udc_simple, + * Get udc name from state, + * usbg_get_udc} + */ + USBG_TEST_TS("test_get_udc_simple", + test_get_udc, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_udc_long, + * Get udc name witch very long name, + * usbg_get_udc} + */ + USBG_TEST_TS("test_get_udc_long", + test_get_udc, setup_long_udc_state), + /** + * @usbg_test + * @test_desc{test_get_gadget_attr_str, + * Compare returned gadget attribute strings witch expected values + * usbg_get_gadget_attr_str} + */ + unit_test(test_get_gadget_attr_str), + /** + * @usbg_test + * @test_desc{test_get_gadget_attr_str_fail, + * Check returned gadget attribute strings for invalid arguments + * usbg_get_gadget_attr_str} + */ + unit_test(test_get_gadget_attr_str_fail), + /** + * @usbg_test + * @test_desc{test_set_gadget_strs_random, + * Set gadget strings of random length, + * usbg_set_gadget_strs} + */ + USBG_TEST_TS("test_set_gadget_strs_random", + test_set_gadget_strs, setup_random_len_gadget_strs_data), + /** + * @usbg_test + * @test_desc{test_get_gadget_strs_random, + * Get gadget strings, + * usbg_get_gadget_strs} + */ + USBG_TEST_TS("test_get_gadget_strs_random", + test_get_gadget_strs, setup_random_len_gadget_strs_data), + /** + * @usbg_test + * @test_desc{test_get_binding_target_simple, + * Get binding target, + * usbg_get_binding_target} + */ + USBG_TEST_TS("test_get_binding_target_simple", + test_get_binding_target, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_binding_name_simple, + * Get binding name, + * usbg_get_binding_name} + */ + USBG_TEST_TS("test_get_binding_name_simple", + test_get_binding_name, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_get_binding_name_len_simple, + * Get binding name length, + * usbg_get_binding_name_len} + */ + USBG_TEST_TS("test_get_binding_name_len_simple", + test_get_binding_name_len, setup_simple_state), + /** + * @usbg_test + * @test_desc{test_set_config_strs_simple, + * Set simple strings in set of configurations, + * usbg_set_config_strs} + */ + USBG_TEST_TS("test_set_config_strs_simple", + test_set_config_strs, setup_simple_config_strs_state), + /** + * @usbg_test + * @test_desc{test_set_config_string_simple, + * Set simple string in set of configurations, + * usbg_set_config_string} + */ + USBG_TEST_TS("test_set_config_string_simple", + test_set_config_string, setup_simple_config_strs_state), + /** + * @usbg_test + * @test_desc{test_get_config_strs_simple, + * Get simple strings from set of configurations, + * usbg_get_config_strs} + */ + USBG_TEST_TS("test_get_config_strs_simple", + test_get_config_strs, setup_simple_config_strs_state), + /** + * @usbg_test + * @test_desc{test_get_config_attrs_max, + * Get config attributes with max values, + * usbg_get_config_attrs} + */ + USBG_TEST_TS("test_get_config_attrs_max", + test_get_config_attrs, setup_max_config_attrs_state), + /** + * @usbg_test + * @test_desc{test_get_config_attrs_min, + * Get config attributes with minimum values, + * usbg_get_config_attrs} + */ + USBG_TEST_TS("test_get_config_attrs_min", + test_get_config_attrs, setup_min_config_attrs_state), + /** + * @usbg_test + * @test_desc{test_get_config_attrs_random, + * Get config attributes with random values, + * usbg_get_config_attrs} + */ + USBG_TEST_TS("test_get_config_attrs_random", + test_get_config_attrs, setup_random_config_attrs_state), + /** + * @usbg_test + * @test_desc{test_set_config_attrs_max, + * Set config attributes with max values, + * usbg_set_config_attrs} + */ + USBG_TEST_TS("test_set_config_attrs_max", + test_set_config_attrs, setup_max_config_attrs_state), + /** + * @usbg_test + * @test_desc{test_set_config_attrs_min, + * Set config attributes with minimum values, + * usbg_set_config_attrs} + */ + USBG_TEST_TS("test_set_config_attrs_min", + test_set_config_attrs, setup_min_config_attrs_state), + /** + * @usbg_test + * @test_desc{test_set_config_attrs_random, + * Set config attributes with random values, + * usbg_set_config_attrs} + */ + USBG_TEST_TS("test_set_config_attrs_random", + test_set_config_attrs, setup_random_config_attrs_state), + /** + * @usbg_test + * @test_desc{test_create_config_random, + * Create config with random attributes + * usbg_create_config} + */ + USBG_TEST_TS("test_create_config_random", + test_create_config, setup_random_config_attrs_state), + /** + * @usbg_test + * @test_desc{test_get_f_serial_attrs, + * Get f_serial function attributes, + * usbg_get_function_attrs} + */ + USBG_TEST_TS("test_get_f_serial_attrs", + test_get_function_attrs, setup_f_serial_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_obex_attrs, + * Get f_obex function attributes, + * usbg_get_function_attrs} + */ + USBG_TEST_TS("test_get_f_obex_attrs", + test_get_function_attrs, setup_f_obex_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_acm_attrs, + * Get f_acm function attributes, + * usbg_get_function_attrs} + */ + USBG_TEST_TS("test_get_f_acm_attrs", + test_get_function_attrs, setup_f_acm_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_ecm_attrs, + * Get f_ecm function attributes, + * usbg_get_function_attrs} + */ + USBG_TEST_TS("test_get_f_ecm_attrs", + test_get_function_attrs, setup_f_ecm_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_eem_attrs, + * Get f_eem function attributes, + * usbg_get_function_attrs} + */ + USBG_TEST_TS("test_get_f_eem_attrs", + test_get_function_attrs, setup_f_eem_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_subset_attrs, + * Get f_subset function attributes, + * usbg_get_function_attrs} + */ + USBG_TEST_TS("test_get_f_subset_attrs", + test_get_function_attrs, setup_f_subset_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_ncm_attrs, + * Get f_ncm function attributes, + * usbg_get_function_attrs} + */ + USBG_TEST_TS("test_get_f_ncm_attrs", + test_get_function_attrs, setup_f_ncm_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_serial_attrs, + * Get f_rndis function attributes, + * usbg_get_function_attrs} + */ + USBG_TEST_TS("test_get_f_rndis_attrs", + test_get_function_attrs, setup_f_rndis_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_phonet_attrs, + * Get f_phonet function attributes, + * usbg_get_function_attrs} + */ + USBG_TEST_TS("test_get_f_phonet_attrs", + test_get_function_attrs, setup_f_phonet_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_serial_attrs, + * Get f_ffs function attributes, + * usbg_get_function_attrs} + */ + USBG_TEST_TS("test_get_f_ffs_attrs", + test_get_function_attrs, setup_f_ffs_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_serial_attrs, + * Set f_serial function attributes, + * usbg_set_function_attrs} + */ + USBG_TEST_TS("test_set_f_serial_attrs", + test_set_function_attrs, setup_f_serial_writable_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_acm_attrs, + * Set f_acm function attributes, + * usbg_set_function_attrs} + */ + USBG_TEST_TS("test_set_f_acm_attrs", + test_set_function_attrs, setup_f_acm_writable_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_serial_obex, + * Set f_obex function attributes, + * usbg_set_function_attrs} + */ + USBG_TEST_TS("test_set_f_obex_attrs", + test_set_function_attrs, setup_f_obex_writable_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_ecm_attrs, + * Set f_ecm function attributes, + * usbg_set_function_attrs} + */ + USBG_TEST_TS("test_set_f_ecm_attrs", + test_set_function_attrs, setup_f_ecm_writable_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_eem_attrs, + * Set f_eem function attributes, + * usbg_set_function_attrs} + */ + USBG_TEST_TS("test_set_f_eem_attrs", + test_set_function_attrs, setup_f_eem_writable_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_subset_attrs, + * Set f_subset function attributes, + * usbg_set_function_attrs} + */ + USBG_TEST_TS("test_set_f_subset_attrs", + test_set_function_attrs, setup_f_subset_writable_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_ncm_attrs, + * Set f_ncm function attributes, + * usbg_set_function_attrs} + */ + USBG_TEST_TS("test_set_f_ncm_attrs", + test_set_function_attrs, setup_f_ncm_writable_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_serial_attrs, + * Set f_rndis function attributes, + * usbg_get_function_attrs} + */ + USBG_TEST_TS("test_set_f_rndis_attrs", + test_set_function_attrs, setup_f_rndis_writable_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_phonet_attrs, + * Set f_phonet function attributes, + * usbg_set_function_attrs} + */ + USBG_TEST_TS("test_set_f_phonet_attrs", + test_set_function_attrs, setup_f_phonet_writable_attrs), + /** + * @usbg_test + * @test_desc{test_get_f_serial_attrs, + * Set f_ffs function attributes, + * usbg_set_function_attrs} + */ + USBG_TEST_TS("test_set_f_ffs_attrs", + test_set_function_attrs, setup_f_ffs_writable_attrs), + /** + * @usbg_test + * @test_desc{test_create_all_functions, + * Create full set of functions in empty state, + * usbg_get_binding_name_len} + */ + USBG_TEST_TS("test_create_all_functions", + test_create_function, setup_all_funcs_state), + /** + * @usbg_test + * @test_desc{test_get_gadget_str_name, + * Compare returned gadget string name with expected + * usbg_get_gadget_str_name} + */ + unit_test(test_get_gadget_str_name), + /** + * @usbg_test + * @test_desc{test_lookup_gadget_str, + * Compare returned gadget string code with expected + * usbg_lookup_gadget_str} + */ + unit_test(test_lookup_gadget_str), + +#ifndef DOXYGEN +}; +#endif + +/** + * @usbg_test + * @tests_end + */ + +#define TESTS_TAG "tests" +/* for autotools compability */ +#define SKIPPED_CODE 77 + +#ifdef HAS_LIBCONFIG + +static int gen_test_config(FILE *output) +{ + config_t cfg; + config_setting_t *root; + config_setting_t *tests_node, *node; + int i; + int ret = SKIPPED_CODE, cfg_ret = 0; + + config_init(&cfg); + config_set_tab_width(&cfg, 4); + + root = config_root_setting(&cfg); + tests_node = config_setting_add(root, TESTS_TAG, CONFIG_TYPE_LIST); + if (!tests_node) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < ARRAY_SIZE(tests); ++i) { + node = config_setting_add(tests_node, NULL, CONFIG_TYPE_STRING); + if (!node) { + ret = -ENOMEM; + goto out; + } + + cfg_ret = config_setting_set_string(node, tests[i].name); + if (cfg_ret != CONFIG_TRUE) { + ret = -EINVAL; + goto out; + } + } + + config_write(&cfg, output); +out: + config_destroy(&cfg); + return ret; +} + +#else + +static int gen_test_config(FILE *output) +{ + fprintf(stderr, "Libconfig is not supported\n"); + return -ENOTSUP; +} + +#endif /* HAS_LIBCONFIG */ + +static int lookup_test(const char *name) +{ + int i; + for (i = 0; i < ARRAY_SIZE(tests); ++i) + if (!strcmp(name, tests[i].name)) + return i; + + return -1; +} + +static void test_skipped(void **state) +{ + skip(); +} + +#ifdef HAS_LIBCONFIG +static int apply_test_config(FILE *input) +{ + config_t cfg; + config_setting_t *root; + config_setting_t *tests_node, *node; + int i, count, ind; + int ret = 0, cfg_ret = 0; + const char *test_name; + char selected[ARRAY_SIZE(tests)]; + + for (i = 0; i < ARRAY_SIZE(selected); ++i) + selected[i] = 0; + + config_init(&cfg); + + cfg_ret = config_read(&cfg, input); + if (cfg_ret != CONFIG_TRUE) { + fprintf(stderr, "Wrong config format\n"); + ret = -EINVAL; + goto out; + } + + root = config_root_setting(&cfg); + tests_node = config_setting_get_member(root, TESTS_TAG); + if (!tests_node || !config_setting_is_list(tests_node)) { + fprintf(stderr, "Missing or incorrect tests list\n"); + ret = -EINVAL; + goto out; + } + + count = config_setting_length(tests_node); + for (i = 0; i < count; ++i) { + node = config_setting_get_elem(tests_node, i); + if (!node) { + ret = -EINVAL; + goto out; + } + + test_name = config_setting_get_string(node); + if (!test_name) { + fprintf(stderr, "Incorrect tests list. Element %d\n", i); + ret = -EINVAL; + goto out; + } + + ind = lookup_test(test_name); + if (ind < 0) { + fprintf(stderr, "Test %s not found.\n", test_name); + ret = -EINVAL; + goto out; + } + + selected[ind] = 1; + } + + for (i = 0; i < ARRAY_SIZE(selected); ++i) { + if (selected[i]) + continue; + + tests[i].test_func = &test_skipped; + tests[i].setup_func = NULL; + tests[i].teardown_func = NULL; + } +out: + config_destroy(&cfg); + return ret; +} + +#else + +static int apply_test_config(FILE *input) +{ + fprintf(stderr, "Libconfig is not supported\n"); + return -ENOTSUP; +} + +#endif /* HAS_LIBCONFIG */ + +static void print_help() +{ + fprintf(stderr, + "libusbgx test suit:\n" + " --generate-config - generates config to stdout and exit\n" + " --use-config - runs test suit using config from stdin\n" + " -h --help - print this message\n" + ); +} + +int main(int argc, char **argv) +{ + enum { + GENERATE_CONFIG = 0x01, + USE_CONFIG = 0x02, + }; + + int options = 0; + int opt; + int ret = -EINVAL; + + static struct option long_options[] = { + {"generate-config", no_argument, 0, 1}, + {"use-config", no_argument, 0, 2}, + {"help", no_argument, 0, 'h'}, + {NULL, 0, 0, 0} + }; + + while (1) { + opt = getopt_long(argc, argv, "h", long_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 1: + options |= GENERATE_CONFIG; + break; + case 2: + options |= USE_CONFIG; + break; + case 'h': + default: + print_help(); + goto out; + } + } + + if (optind < argc || + ((options & GENERATE_CONFIG) && + (options & USE_CONFIG))) { + print_help(); + goto out; + } + + if (options & GENERATE_CONFIG) { + ret = gen_test_config(stdout); + goto out; + } + + if (options & USE_CONFIG) { + ret = apply_test_config(stdin); + if (ret) + goto out; + } + + ret = cmocka_run_group_tests(tests, NULL, NULL); + +out: + return ret; +} diff --git a/tests/test.sh b/tests/test.sh new file mode 100755 index 0000000..45b8e3e --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +#USE_CONFIG=0 +#GENERATE_CONFIG=0 +#HELP=$HELP + +# for autotools compability (config can be passed by environment variable) +if [[ -n $USE_CONFIG ]] +then + CONFIG_FILE=$USE_CONFIG +elif [[ -n $GENERATE_CONFIG ]] +then + CONFIG_FILE=$GENERATE_CONFIG +fi + +function usage { + echo "libusbgx test suit" + echo "Usage: ./test.sh [option]" + echo "Options:" + echo " --generate-config filename - generates config to given file and exit" + echo " --use-config filename - runs test suit using config from given file" + echo " -h --help - print this message" +} + +# Parse arguments + +ARGS=$(getopt --long generate-config:,use-config:,help -o h -- "$@" ) + +if [ $? -ne 0 ] +then + HELP=1 +fi + +eval set -- $ARGS + +while true; do + case $1 in + -h|--help) + HELP=1 + shift + ;; + --use-config) + USE_CONFIG=1 + CONFIG_FILE=$2 + shift 2 + ;; + --generate-config) + GENERATE_CONFIG=1 + CONFIG_FILE=$2 + shift 2 + ;; + --) + shift + break + ;; + *) + HELP=1 + shift + ;; + esac +done + +# Run test with io functions ovverride + +if [[ -n $USE_CONFIG ]] +then + LD_LIBRARY_PATH=. ./test --use-config < $CONFIG_FILE +elif [[ -n $GENERATE_CONFIG ]] +then + LD_LIBRARY_PATH=. ./test --generate-config > $CONFIG_FILE +elif [[ -n $HELP ]] +then + usage + exit 77 # autotools consider it skipped +else + LD_LIBRARY_PATH=. ./test +fi + diff --git a/tests/usbg-io-wrappers.c b/tests/usbg-io-wrappers.c new file mode 100644 index 0000000..d8f471a --- /dev/null +++ b/tests/usbg-io-wrappers.c @@ -0,0 +1,203 @@ +#define _GNU_SOURCE +#include <dirent.h> +#include <stdio.h> +#include <stdarg.h> +#include <setjmp.h> +#include <stdlib.h> +#include <string.h> +#include <stddef.h> +#include <cmocka.h> +#include <dlfcn.h> +#include <errno.h> + +typedef int (*fputs_f_type)(const char *, FILE *); +typedef int (*fflush_f_type)(FILE *); +typedef fflush_f_type ferror_f_type; + +/** + * @brief Simulates opening file + * @details Checks if path is equal expected value and returns given pointer + * from cmocka queue + */ +FILE *fopen(const char *path, const char *mode) +{ + check_expected(path); + return mock_ptr_type(FILE *); +} + +/** + * @brief Simulates closing file + * @details Does absolutely nothing, always acts as successfull close + */ +int fclose(FILE *fp) +{ + check_expected(fp); + return mock_type(int); +} + +/** + * @brief Simulates reading file + * @details Does not read any file, instead returns value from cmocka queue + * @return value specified by caller previously + */ +char *fgets(char *s, int size, FILE *stream) +{ + check_expected(stream); + strncpy(s, mock_ptr_type(char *), size); + return s; +} + +/** + * @brief Simulates opening directory + * @details Does not open any dir, instead returns user-specified value + * @return value specified by caller previously + */ +DIR *opendir(const char *name) +{ + int err; + + check_expected(name); + err = mock_type(int); + if (err) + errno = err; + + return mock_ptr_type(DIR *); +} + +/** + * @brief Simulates closing directory + * @details Does nothing and ends successfully. + */ +int closedir(DIR *dirp) +{ + check_expected(dirp); + return mock_type(int); +} + +/** + * @brief Simulates scanning directory + * @details Checks if dirp has expected value. Then consecutive values from + * cmocka queue are proceed. First value must be integer and indicates number + * of directory entries which should be returned. Next number of values indicate + * names of directory entries. + */ +int scandir(const char *dirp, struct dirent ***namelist, + int (*filter)(const struct dirent *), + int (*compar)(const struct dirent **, const struct dirent **)) +{ + int count; + int i, j = 0; + char *name; + struct dirent **entries; + struct dirent *entry; + int tmp, expected; + + check_expected(dirp); + count = mock_type(int); + + if (count > 0) + entries = calloc(count, sizeof(*entries)); + else + entries = NULL; + + for (i = 0; i < count; i++) { + name = mock_ptr_type(char *); + entry = malloc(sizeof(*entry)); + if (strlen(name) >= NAME_MAX) + fail(); + + strcpy(entry->d_name, name); + entry->d_type = mock_type(unsigned char); + + expected = mock_type(int); + if (filter) { + tmp = filter(entry); + assert_int_equal(tmp, expected); + if (tmp) + entries[j++] = entry; + else + free(entry); + } + } + + if (compar) + qsort(entries, count, sizeof(*entries), + (int (*)(const void *,const void *))compar); + + *namelist = entries; + return j; +} + +/** + * @brief Simultes readlink, with user-specified behavior + * @datails Check if path and bufsiz equal expedted values and + * write to buf string given by cmocka + */ +ssize_t readlink(const char *path, char *buf, size_t bufsiz) +{ + char *res; + int reslen; + + check_expected(path); + check_expected(bufsiz); + res = mock_ptr_type(char *); + reslen = strlen(res); + if (bufsiz <= reslen) + fail(); + + strcpy(buf, res); + + return reslen; +} + +/** + * @brief Simulates puts, with user-specified behavior + * @details Check if user is trying to write expected data + * @return value received from cmocka queue + */ +int fputs(const char *s, FILE *stream) +{ + /* Cmocka (or anything else) may want to print some errors. + * Especially when running fputs itself */ + if (stream == stderr || stream == stdout) { + fputs_f_type orig_fputs; + orig_fputs = (fputs_f_type)dlsym(RTLD_NEXT, "fputs"); + return orig_fputs(s, stream); + } + + check_expected(stream); + check_expected(s); + return mock_type(int); +} + +int mkdir(const char *pathname, mode_t mode) +{ + check_expected(pathname); + check_expected(mode); + return mock_type(int); +} + +/** + * @brief Does nothing. + */ +int fflush(FILE *stream) +{ + if (stream == stderr || stream == stdout) { + fflush_f_type orig_fflush; + orig_fflush = (fflush_f_type)dlsym(RTLD_NEXT, "fflush"); + return orig_fflush(stream); + } + + return 0; +} + +int ferror(FILE *stream) +{ + if (stream == stderr || stream == stdout) { + ferror_f_type orig_ferror; + orig_ferror = (ferror_f_type)dlsym(RTLD_NEXT, "ferror"); + return orig_ferror(stream); + } + + return 0; +} diff --git a/tests/usbg-test.c b/tests/usbg-test.c new file mode 100644 index 0000000..c332795 --- /dev/null +++ b/tests/usbg-test.c @@ -0,0 +1,1389 @@ +#include <usbg/usbg.h> +#include <stdio.h> +#include <stdarg.h> +#include <setjmp.h> +#include <cmocka.h> +#include <stdlib.h> +#include <string.h> +#include <stddef.h> +#include <limits.h> +#include <errno.h> +#include <time.h> + +#include "usbg-test.h" + +static struct simple_stack{ + void *ptr; + struct simple_stack *next; +} *cleanup_top = NULL; + +static const char *gadget_str_names[] = { + "serialnumber", + "manufacturer", + "product" +}; + +static const char *config_attr_names[] = { + "MaxPower", + "bmAttributes" +}; + +static attr_format config_attr_format[] = { + [MAX_POWER] = FORMAT_DEC, + [BM_ATTRIBUTES] = FORMAT_HEX +}; + +void free_later(void *ptr) +{ + struct simple_stack *t; + + t = malloc(sizeof(*t)); + t->ptr = ptr; + t->next = cleanup_top; + cleanup_top = t; +} + +void cleanup_stack() +{ + struct simple_stack *t; + + while (cleanup_top) { + free(cleanup_top->ptr); + t = cleanup_top->next; + free(cleanup_top); + cleanup_top = t; + } +} + +/* Represent last file/dir opened, next should have bigger numbers.*/ +static int file_id = 0; +static int dir_id = 0; + +#define PUSH_FILE(file, content) do {\ + file_id++;\ + expect_path(fopen, path, file);\ + will_return(fopen, file_id);\ + expect_value(fgets, stream, file_id);\ + will_return(fgets, content);\ + expect_value(fclose, fp, file_id);\ + will_return(fclose, 0);\ +} while(0) + +#define PUSH_FILE_ALWAYS(dflt) do {\ + expect_any_count(fopen, path, -1);\ + will_return_always(fopen, 1);\ + expect_any_count(fgets, stream, 1);\ + will_return_always(fgets, dflt);\ + expect_any_count(fclose, fp, 1);\ + will_return_always(fclose, 0);\ +} while(0) + +#define PUSH_EMPTY_DIR(p) do {\ + expect_string(scandir, dirp, p);\ + will_return(scandir, 0);\ +} while(0) + +#define EXPECT_OPENDIR(n) do {\ + dir_id++;\ + expect_path(opendir, name, n);\ + will_return(opendir, 0);\ + will_return(opendir, dir_id);\ + expect_value(closedir, dirp, dir_id);\ + will_return(closedir, 0);\ +} while(0) + +#define EXPECT_OPENDIR_ERROR(n, e) do {\ + expect_path(opendir, name, n);\ + will_return(opendir, e);\ + will_return(opendir, NULL);\ +} while(0) + +#define PUSH_DIR(p, c) do {\ + expect_path(scandir, dirp, p);\ + will_return(scandir, c);\ +} while(0) + +#define PUSH_DIR_ENTRY(name, type) do {\ + will_return(scandir, name);\ + will_return(scandir, type);\ + will_return(scandir, 1);\ +} while(0) + +#define PUSH_LINK(p, c, len) do {\ + expect_path(readlink, path, p);\ + expect_in_range(readlink, bufsiz, len, INT_MAX);\ + will_return(readlink, c);\ +} while(0) + +#define EXPECT_WRITE(file, content) do {\ + file_id++;\ + expect_path(fopen, path, file);\ + will_return(fopen, file_id);\ + expect_value(fputs, stream, file_id);\ + expect_string(fputs, s, content);\ + will_return(fputs, 0);\ + expect_value(fclose, fp, file_id);\ + will_return(fclose, 0);\ +} while(0) + +#define EXPECT_HEX_WRITE(file, content) do {\ + file_id++;\ + expect_path(fopen, path, file);\ + will_return(fopen, file_id);\ + expect_value(fputs, stream, file_id);\ + expect_check(fputs, s, hex_str_equal_display_error, content);\ + will_return(fputs, 0);\ + expect_value(fclose, fp, file_id);\ + will_return(fclose, 0);\ +} while(0) + +#define EXPECT_MKDIR(p) do {\ + expect_path(mkdir, pathname, p);\ + expect_value(mkdir, mode, 00777);\ + will_return(mkdir, 0);\ +} while(0) + +/** + * @brief Compare test gadgets' names + */ +static int test_gadget_cmp(struct test_gadget *a, struct test_gadget *b) +{ + return strcoll(a->name, b->name); +} + +/** + * @brief Compare test functions' names + */ +static int test_function_cmp(struct test_function *a, struct test_function *b) +{ + return strcoll(a->name, b->name); +} + +/** + * @brief Compare test bindings' names + */ +static int test_binding_cmp(struct test_binding *a, struct test_binding *b) +{ + return strcoll(a->name, b->name); +} + +/** + * @brief Compare test configs' names + */ +static int test_config_cmp(struct test_config *a, struct test_config *b) +{ + return strcoll(a->name, b->name); +} + +void prepare_binding(struct test_binding *b, struct test_function *f, char *fpath) +{ + if (!f->name) + prepare_function(f, fpath); + + if (!b->name) { + b->name = strdup(f->name); + if (b->name == NULL) + fail(); + free_later(b->name); + } + + b->target = f; +} + +void prepare_config(struct test_config *c, char *cpath, char *fpath) +{ + int count = 0; + struct test_function *f; + struct test_binding *b; + int i; + + safe_asprintf(&c->name, "%s.%d", c->label, c->id); + + c->path = cpath; + + /* check if bindings has been already filled */ + if (!c->bindings) { + for (f = c->bound_funcs; f->instance; f++) + count++; + + c->bindings = safe_calloc(count + 1, sizeof(*c->bindings)); + } else { + for (b = c->bindings; b->name; b++) + count++; + } + + for (i = 0; i < count; i++) + prepare_binding(&c->bindings[i], &c->bound_funcs[i], fpath); + + qsort(c->bindings, count, sizeof(*c->bindings), + (int (*)(const void *, const void *))test_binding_cmp); + +} + +void prepare_function(struct test_function *f, char *path) +{ + const char *func_type; + + func_type = usbg_get_function_type_str(f->type); + if (func_type == NULL) + fail(); + + safe_asprintf(&f->name, "%s.%s", func_type, f->instance); + + f->path = path; +} + +void prepare_gadget(struct test_state *state, struct test_gadget *g) +{ + struct test_config *c; + struct test_function *f; + char *fpath; + char *cpath; + int count; + + g->path = strdup(state->path); + if (!g->path) + fail(); + + free_later(g->path); + + safe_asprintf(&fpath, "%s/%s/functions", g->path, g->name); + + count = 0; + for (f = g->functions; f->instance; f++) { + prepare_function(f, fpath); + count++; + } + + /* Path needs to be known somehow when list is empty */ + f->path = fpath; + + qsort(g->functions, count, sizeof(*g->functions), + (int (*)(const void *, const void *))test_function_cmp); + + safe_asprintf(&cpath, "%s/%s/configs", g->path, g->name); + + count = 0; + for (c = g->configs; c->label; c++) { + prepare_config(c, cpath, fpath); + count++; + } + + /* Path needs to be known somehow when list is empty */ + c->path = cpath; + + qsort(g->configs, count, sizeof(*g->configs), + (int (*)(const void *, const void *))test_config_cmp); + +} + +static void cpy_test_function(struct test_function *to, + struct test_function *from) +{ + /* Reuse instance */ + to->instance = from->instance; + to->type = from->type; + /* path and name is not being copied because + it has not been allocated now */ + + to->writable = 1; +} + +static struct test_function *dup_test_functions(struct test_function *functions) +{ + struct test_function *f, *nf, *new_functions; + int count = 0; + + for (f = functions; f->instance; ++f) + ++count; + + new_functions = safe_calloc(count + 1, sizeof(*f)); + + for (f = functions, nf = new_functions; f->instance; ++f, ++nf) + cpy_test_function(nf, f); + + return new_functions; +} + +static struct test_function *get_new_binding_target(struct test_function *which, + struct test_function *old, + int count, + struct test_function *new) +{ + struct test_function *ret = NULL; + + /* Should duplicate function? */ + if (which < old || ((which - old) > count)) { + /* We may need to do a deep copy */ + if (!which->writable) { + ret = safe_calloc(1, sizeof(*ret)); + cpy_test_function(ret, which); + } else { + ret = which; + } + } else if (old != new) { + /* Function has been copied in bound_funcs so just + set new address */ + ret = which - old + new; + } else { + /* Functions are reused so leave address as is */ + ret = which; + } + + return ret; +} + +static void cpy_test_binding(struct test_binding *to, + struct test_binding *from, + struct test_function *old, + int func_count, + struct test_function *new) +{ + /* Reuse name */ + to->name = from->name; + to->target = get_new_binding_target(from->target, old, func_count, new); + + to->writable = 1; +} + +static struct test_binding *dup_test_bindings(struct test_binding *bindings, + struct test_function *old, + int func_count, + struct test_function *new) +{ + struct test_binding *b, *nb, *new_bindings; + int count = 0; + + for (b = bindings; b->name; ++b) + ++count; + + new_bindings = safe_calloc(count + 1, sizeof(*b)); + + for (b = bindings, nb = new_bindings; b->name; ++b, ++nb) + cpy_test_binding(nb, b, old, func_count, new); + + return new_bindings; +} + +static void cpy_test_config(struct test_config *to, + struct test_config *from) +{ + int func_count = 0; + struct test_function *f; + struct test_binding *b; + + /* Reuse label */ + to->label = from->label; + to->id = from->id; + to->strs = from->strs; + to->attrs = from->attrs; + + if (from->bound_funcs) { + /* If at least one function is not writable + we have to copy all of them */ + for (f = from->bound_funcs; f->instance; ++f) { + ++func_count; + if (!f->writable && !to->bound_funcs) { + to->bound_funcs = + dup_test_functions(from->bound_funcs); + } + } + + if (!f->name && !to->bound_funcs) + to->bound_funcs = from->bound_funcs; + } + + /* If bindings are set copy also them */ + if (from->bindings) { + /* If at least one function is not writable + we have to copy all of them */ + for (b = from->bindings; b->name; ++b) + if (!b->writable) + to->bindings = + dup_test_bindings(from->bindings, + from->bound_funcs, + func_count, + to->bound_funcs); + + /* if we are reusing binding we have to translate target + address to new one which is writable */ + if (!b->name && !to->bindings) { + to->bindings = from->bindings; + for (b = from->bindings; b->name; ++b) + b->target = + get_new_binding_target( + b->target, + from->bound_funcs, + func_count, + to->bound_funcs); + } + } + + to->writable = 1; +} + +static struct test_config *dup_test_configs(struct test_config *configs) +{ + struct test_config *c, *nc, *new_configs; + int count = 0; + + for (c = configs; c->label; ++c) + ++count; + + new_configs = safe_calloc(count + 1, sizeof(*c)); + + for (c = configs, nc = new_configs; c->label; ++c, ++nc) + cpy_test_config(nc, c); + + return new_configs; +} + +static void cpy_test_gadget(struct test_gadget *to, struct test_gadget *from) +{ + struct test_function *f; + struct test_config *c; + + /* Reuse name and udc */ + to->name = from->name; + to->udc = from->udc; + /* path is not being copied because it has not been allocated */ + + /* If at least one function is not writable + we have to copy all of them */ + for (f = from->functions; f->instance; ++f) + if (!f->writable) { + to->functions = + dup_test_functions(from->functions); + break; + } + + if (!f->name && !to->functions) + to->functions = from->functions; + + + /* If at least one config is not writable + we have to copy all of them */ + for (c = from->configs; c->label; ++c) + if (!c->writable) { + to->configs = dup_test_configs(from->configs); + break; + } + + if (!c->name && !to->configs) + to->configs = from->configs; + + to->writable = 1; +} + +static struct test_gadget *dup_test_gadgets(struct test_gadget *gadgets) +{ + struct test_gadget *g, *ng, *new_gadgets; + int count = 0; + + for (g = gadgets; g->name; ++g) + ++count; + + new_gadgets = safe_calloc(count + 1, sizeof(*g)); + + for (g = gadgets, ng = new_gadgets; g->name; ++g, ++ng) + cpy_test_gadget(ng, g); + + return new_gadgets; +} + +static struct test_state *dup_test_state(struct test_state *state) +{ + struct test_state *new_state; + struct test_gadget *g; + + new_state = safe_calloc(1, sizeof(*new_state)); + + /* We don't copy configfs path because it is never changed + if you would like to free it before test end replace + this code with strdup */ + new_state->configfs_path = state->configfs_path; + + /* path is not being copied because it has not been allocated */ + + /* If at least one gadget is not writable we have to copy all of them */ + for (g = state->gadgets; g->name; ++g) + if (!g->writable) { + new_state->gadgets = + dup_test_gadgets(state->gadgets); + break; + } + + if (!g->name && !new_state->gadgets) + new_state->gadgets = state->gadgets; + + /* udcs are also never changed so leave them as they are */ + new_state->udcs = state->udcs; + + new_state->writable = 1; + + return new_state; +} + +struct test_state *prepare_state(struct test_state *state) +{ + struct test_gadget *g; + struct test_state *new_state; + int count = 0; + + if (!state->writable) + new_state = dup_test_state(state); + else + new_state = state; + + safe_asprintf(&(new_state->path), "%s/usb_gadget", + new_state->configfs_path); + + for (g = new_state->gadgets; g->name; g++) { + prepare_gadget(new_state, g); + count++; + } + + qsort(new_state->gadgets, count, sizeof(*new_state->gadgets), + (int (*)(const void *, const void *))test_gadget_cmp); + + return new_state; +} + +struct test_state *build_empty_gadget_state(struct test_state *ts) +{ + struct test_state *ret; + struct test_gadget *tg; + int count = 0; + + ret = safe_malloc(sizeof(*ret)); + ret->udcs = ts->udcs; + ret->configfs_path = ts->configfs_path; + + for (tg = ts->gadgets; tg->name; ++tg) + count++; + + ret->gadgets = safe_calloc(count+1, sizeof(*ts->gadgets)); + memcpy(ret->gadgets, ts->gadgets, count*sizeof(*ts->gadgets)); + + for (tg = ret->gadgets; tg->name; ++tg) { + tg->configs = safe_calloc(1, sizeof(*tg->configs)); + tg->functions = safe_calloc(1, sizeof(*tg->functions)); + } + + return prepare_state(ret); +} + +/* Simulation of configfs for init */ + +static void push_binding(struct test_config *conf, struct test_binding *binding) +{ + char *s_path; + char *d_path; + + safe_asprintf(&s_path, "%s/%s/%s", conf->path, conf->name, binding->name); + safe_asprintf(&d_path, "%s/%s", binding->target->path, binding->target->name); + + PUSH_LINK(s_path, d_path, USBG_MAX_PATH_LENGTH - 1); +} + +static void push_config(struct test_config *c) +{ + struct test_binding *b; + int count = 0; + char *path; + + safe_asprintf(&path, "%s/%s", c->path, c->name); + + for (b = c->bindings; b->name; b++) + count++; + + PUSH_DIR(path, count); + for (b = c->bindings; b->name; b++) { + PUSH_DIR_ENTRY(b->name, DT_LNK); + push_binding(c, b); + } +} + +static void push_gadget(struct test_gadget *g) +{ + int count; + struct test_config *c; + struct test_function *f; + char *path; + + safe_asprintf(&path, "%s/%s/UDC", g->path, g->name); + PUSH_FILE(path, g->udc); + + count = 0; + for (f = g->functions; f->instance; f++) + count++; + + PUSH_DIR(f->path, count); + for (f = g->functions; f->instance; f++) + PUSH_DIR_ENTRY(f->name, DT_DIR); + + count = 0; + for (c = g->configs; c->label; c++) + count++; + + PUSH_DIR(c->path, count); + for (c = g->configs; c->label; c++) + PUSH_DIR_ENTRY(c->name, DT_DIR); + + for (c = g->configs; c->label; c++) + push_config(c); +} + +void push_init(struct test_state *state) +{ + char **udc; + struct test_gadget *g; + int count = 0; + + EXPECT_OPENDIR(state->path); + + for (udc = state->udcs; *udc; udc++) + count++; + + PUSH_DIR("/sys/class/udc", count); + for (udc = state->udcs; *udc; udc++) + PUSH_DIR_ENTRY(*udc, DT_REG); + + count = 0; + for (g = state->gadgets; g->name; g++) + count++; + + PUSH_DIR(state->path, count); + for (g = state->gadgets; g->name; g++) { + PUSH_DIR_ENTRY(g->name, DT_DIR); + } + + for (g = state->gadgets; g->name; g++) + push_gadget(g); +} + +int get_gadget_attr(usbg_gadget_attrs *attrs, usbg_gadget_attr attr) +{ + int ret = -1; + + switch (attr) { + case BCD_USB: + ret = attrs->bcdUSB; + break; + case B_DEVICE_CLASS: + ret = attrs->bDeviceClass; + break; + case B_DEVICE_SUB_CLASS: + ret = attrs->bDeviceSubClass; + break; + case B_DEVICE_PROTOCOL: + ret = attrs->bDeviceProtocol; + break; + case B_MAX_PACKET_SIZE_0: + ret = attrs->bMaxPacketSize0; + break; + case ID_VENDOR: + ret = attrs->idVendor; + break; + case ID_PRODUCT: + ret = attrs->idProduct; + break; + case BCD_DEVICE: + ret = attrs->bcdDevice; + break; + default: + ret = -1; + break; + } + + return ret; +} + +void pull_gadget_attribute(struct test_gadget *gadget, + usbg_gadget_attr attr, int value) +{ + char *path; + char *content; + + safe_asprintf(&path, "%s/%s/%s", + gadget->path, gadget->name, usbg_get_gadget_attr_str(attr)); + + safe_asprintf(&content, "0x%x\n", value); + + EXPECT_HEX_WRITE(path, content); +} + +void push_gadget_attribute(struct test_gadget *gadget, + usbg_gadget_attr attr, int value) +{ + char *path; + char *content; + + safe_asprintf(&path, "%s/%s/%s", + gadget->path, gadget->name, usbg_get_gadget_attr_str(attr)); + safe_asprintf(&content, "0x%x\n", value); + + PUSH_FILE(path, content); +} + +void push_gadget_attrs(struct test_gadget *gadget, usbg_gadget_attrs *attrs) +{ + int i; + + for (i = USBG_GADGET_ATTR_MIN; i < USBG_GADGET_ATTR_MAX; i++) + push_gadget_attribute(gadget, i, get_gadget_attr(attrs, i)); +} + +void pull_gadget_attrs(struct test_gadget *gadget, usbg_gadget_attrs *attrs) +{ + int i; + + for (i = USBG_GADGET_ATTR_MIN; i < USBG_GADGET_ATTR_MAX; i++) + pull_gadget_attribute(gadget, i, get_gadget_attr(attrs, i)); +} + +void init_with_state(struct test_state *in, usbg_state **out) +{ + int usbg_ret; + + push_init(in); + usbg_ret = usbg_init(in->configfs_path, out); + assert_int_equal(usbg_ret, USBG_SUCCESS); +} + +void safe_init_with_state(void **state, struct test_state **ts, usbg_state **s) +{ + *ts = (struct test_state *)(*state); + *state = NULL; + + init_with_state(*ts, s); + *state = *s; +} + +static int get_config_attr(usbg_config_attrs *attrs, config_attr attr) +{ + int ret; + + switch (attr) { + case MAX_POWER: + ret = attrs->bMaxPower; + break; + case BM_ATTRIBUTES: + ret = attrs->bmAttributes; + break; + default: + ret = -1; + break; + } + + return ret; +} + +void push_config_attribute(struct test_config *config, config_attr attr, + int value) +{ + char *path; + char *content; + + safe_asprintf(&path, "%s/%s/%s", config->path, config->name, config_attr_names[attr]); + + switch (config_attr_format[attr]) { + case FORMAT_HEX: + safe_asprintf(&content, "0x%x\n", value); + break; + case FORMAT_DEC: + safe_asprintf(&content, "%d\n", value); + break; + } + + PUSH_FILE(path, content); +} + + +void push_config_attrs(struct test_config *config, usbg_config_attrs *attrs) +{ + int i; + + for (i = 0; i < CONFIG_ATTR_MAX; ++i) + push_config_attribute(config, i, get_config_attr(attrs, i)); +} + +void pull_config_attribute(struct test_config *config, config_attr attr, + int value) +{ + char *path; + char *content; + + safe_asprintf(&path, "%s/%s/%s", config->path, config->name, config_attr_names[attr]); + + switch (config_attr_format[attr]) { + case FORMAT_HEX: + safe_asprintf(&content, "0x%x\n", value); + break; + case FORMAT_DEC: + safe_asprintf(&content, "%d\n", value); + break; + } + + switch (config_attr_format[attr]) { + case FORMAT_HEX: + EXPECT_HEX_WRITE(path, content); + break; + case FORMAT_DEC: + EXPECT_WRITE(path, content); + break; + } +} + +void pull_config_attrs(struct test_config *config, usbg_config_attrs *attrs) +{ + int i; + + for (i = 0; i < CONFIG_ATTR_MAX; ++i) + pull_config_attribute(config, i, get_config_attr(attrs, i)); +} + +const char *get_gadget_str(usbg_gadget_strs *strs, gadget_str str) +{ + const char *ret = NULL; + + switch (str) { + case STR_SER: + ret = strs->str_ser; + break; + case STR_MNF: + ret = strs->str_mnf; + break; + case STR_PRD: + ret = strs->str_prd; + break; + default: + ret = NULL; + break; + } + + return ret; +} + +static void pull_gadget_str_dir(struct test_gadget *gadget, int lang) +{ + char *dir; + int tmp; + + safe_asprintf(&dir, "%s/%s/strings/0x%x", + gadget->path, gadget->name, lang); + + srand(time(NULL)); + tmp = rand() % 2; + + if (tmp) { + EXPECT_OPENDIR(dir); + } else { + EXPECT_OPENDIR_ERROR(dir, ENOENT); + EXPECT_MKDIR(dir); + } +} + +static void pull_gadget_str(struct test_gadget *gadget, const char *attr_name, + int lang, const char *content) +{ + char *path; + + safe_asprintf(&path, "%s/%s/strings/0x%x/%s", + gadget->path, gadget->name, lang, attr_name); + EXPECT_WRITE(path, content); +} + +void pull_gadget_string(struct test_gadget *gadget, int lang, + gadget_str str, const char *content) +{ + pull_gadget_str_dir(gadget, lang); + pull_gadget_str(gadget, gadget_str_names[str], lang, content); +} + +void pull_gadget_strs(struct test_gadget *gadget, int lang, usbg_gadget_strs *strs) +{ + int i; + + pull_gadget_str_dir(gadget, lang); + for (i = 0; i < GADGET_STR_MAX; i++) + pull_gadget_str(gadget, gadget_str_names[i], lang, get_gadget_str(strs, i)); +} + +static void push_gadget_str_dir(struct test_gadget *gadget, int lang) +{ + char *dir; + + safe_asprintf(&dir, "%s/%s/strings/0x%x", + gadget->path, gadget->name, lang); + + EXPECT_OPENDIR(dir); +} + +static void push_gadget_str(struct test_gadget *gadget, const char *attr_name, + int lang, const char *content) +{ + char *path; + + safe_asprintf(&path, "%s/%s/strings/0x%x/%s", + gadget->path, gadget->name, lang, attr_name); + PUSH_FILE(path, content); +} + +void push_gadget_strs(struct test_gadget *gadget, int lang, usbg_gadget_strs *strs) +{ + int i; + + push_gadget_str_dir(gadget, lang); + for (i = 0; i < GADGET_STR_MAX; i++) + push_gadget_str(gadget, gadget_str_names[i], lang, get_gadget_str(strs, i)); +} + +void pull_config_string(struct test_config *config, int lang, const char *str) +{ + char *path; + int tmp; + + + safe_asprintf(&path, "%s/%s/strings/0x%x", + config->path, config->name, lang); + + srand(time(NULL)); + tmp = rand() % 2; + + if (tmp) { + EXPECT_OPENDIR(path); + } else { + EXPECT_OPENDIR_ERROR(path, ENOENT); + EXPECT_MKDIR(path); + } + + safe_asprintf(&path, "%s/configuration", path); + + EXPECT_WRITE(path, str); +} + +void pull_config_strs(struct test_config *config, int lang, usbg_config_strs *strs) +{ + pull_config_string(config, lang, strs->configuration); +} + +void push_config_string(struct test_config *config, int lang, const char *str) +{ + char *path; + + safe_asprintf(&path, "%s/%s/strings/0x%x", + config->path, config->name, lang); + + EXPECT_OPENDIR(path); + + safe_asprintf(&path, "%s/configuration", path); + + PUSH_FILE(path, str); +} + +void push_config_strs(struct test_config *config, int lang, usbg_config_strs *strs) +{ + push_config_string(config, lang, strs->configuration); +} + +void assert_config_attrs_equal(usbg_config_attrs *actual, usbg_config_attrs *expected) +{ + assert_int_equal(actual->bmAttributes, expected->bmAttributes); + assert_int_equal(actual->bMaxPower, expected->bMaxPower); +} + +void pull_create_config(struct test_config *tc) +{ + char *path; + + safe_asprintf(&path, "%s/%s", tc->path, tc->name); + EXPECT_MKDIR(path); + + if (tc->attrs) + pull_config_attrs(tc, tc->attrs); + if (tc->strs) + pull_config_strs(tc, LANG_US_ENG, tc->strs); +} + +#define ETHER_ADDR_STR_LEN 19 + +static void push_serial_attrs(struct test_function *func, + usbg_f_serial_attrs *attrs) +{ + char *path; + char *content; + + safe_asprintf(&path, "%s/%s/port_num", func->path, func->name); + safe_asprintf(&content, "%d\n", attrs->port_num); + PUSH_FILE(path, content); +} + +static void push_net_attrs(struct test_function *func, + usbg_f_net_attrs *attrs) +{ + char *path; + char *content; + + safe_asprintf(&path, "%s/%s/dev_addr", func->path, func->name); + + content = safe_malloc(ETHER_ADDR_STR_LEN * sizeof(char)); + ether_ntoa_r(&attrs->dev_addr, content); + + PUSH_FILE(path, content); + + path = safe_malloc(USBG_MAX_PATH_LENGTH * sizeof(char)); + sprintf(path, "%s/%s/host_addr", + func->path, func->name); + + content = safe_malloc(ETHER_ADDR_STR_LEN * sizeof(char)); + ether_ntoa_r(&attrs->host_addr, content); + + PUSH_FILE(path, content); + + safe_asprintf(&path, "%s/%s/qmult", func->path, func->name); + safe_asprintf(&content, "%d\n", attrs->qmult); + PUSH_FILE(path, content); + + safe_asprintf(&path, "%s/%s/ifname", func->path, func->name); + safe_asprintf(&content, "%s\n", attrs->ifname); + PUSH_FILE(path, content); +} + +static void push_phonet_attrs(struct test_function *func, + usbg_f_phonet_attrs *attrs) +{ + char *path; + char *content; + + safe_asprintf(&path, "%s/%s/ifname", func->path, func->name); + safe_asprintf(&content, "%s\n", attrs->ifname); + PUSH_FILE(path, content); +} + +void push_function_attrs(struct test_function *func, usbg_function_attrs *function_attrs) +{ + int attrs_type; + usbg_f_attrs *attrs = &function_attrs->attrs; + + attrs_type = usbg_lookup_function_attrs_type(func->type); + + switch (attrs_type) { + case USBG_F_ATTRS_SERIAL: + push_serial_attrs(func, &attrs->serial); + break; + case USBG_F_ATTRS_NET: + push_net_attrs(func, &attrs->net); + break; + case USBG_F_ATTRS_PHONET: + push_phonet_attrs(func, &attrs->phonet); + break; + case USBG_F_ATTRS_FFS: + // ffs does not exist in filesystem + default: + break; + } +} + +static void pull_function_net_attrs(struct test_function *func, usbg_f_net_attrs *attrs) +{ + char *path; + char *content; + + safe_asprintf(&path, "%s/%s/dev_addr", func->path, func->name); + + content = safe_malloc(ETHER_ADDR_STR_LEN * sizeof(char)); + usbg_ether_ntoa_r(&attrs->dev_addr, content); + + EXPECT_WRITE(path, content); + + safe_asprintf(&path, "%s/%s/host_addr", func->path, func->name); + + content = safe_malloc(ETHER_ADDR_STR_LEN * sizeof(char)); + usbg_ether_ntoa_r(&attrs->host_addr, content); + + EXPECT_WRITE(path, content); + + safe_asprintf(&path, "%s/%s/qmult", func->path, func->name); + safe_asprintf(&content, "%d\n", attrs->qmult); + EXPECT_WRITE(path, content); +} + +void pull_function_attrs(struct test_function *func, usbg_function_attrs *attrs) +{ + /* only net attributes are writtable */ + if (attrs->header.attrs_type == USBG_F_ATTRS_NET) + pull_function_net_attrs(func, &attrs->attrs.net); +} + +void pull_create_function(struct test_function *tf) +{ + char *path; + int tmp; + + tmp = asprintf(&path, "%s/%s", tf->path, tf->name); + if (tmp < 0) + fail(); + free_later(path); + + EXPECT_MKDIR(path); + if (tf->attrs) + pull_function_attrs(tf, tf->attrs); +} + +void assert_func_equal(usbg_function *f, struct test_function *expected) +{ + assert_string_equal(f->instance, expected->instance); + assert_int_equal(f->type, expected->type); + assert_path_equal(f->path, expected->path); +} + +void assert_binding_equal(usbg_binding *b, struct test_binding *expected) +{ + assert_string_equal(b->name, expected->name); + assert_func_equal(b->target, expected->target); +} + +void assert_config_equal(usbg_config *c, struct test_config *expected) +{ + int i = 0; + usbg_binding *b; + + assert_int_equal(c->id, expected->id); + assert_string_equal(c->label, expected->label); + assert_path_equal(c->path, expected->path); + usbg_for_each_binding(b, c) + assert_binding_equal(b, &expected->bindings[i++]); +} + +void assert_gadget_equal(usbg_gadget *g, struct test_gadget *expected) +{ + usbg_config *c; + usbg_function *f; + int i; + + assert_string_equal(g->name, expected->name); + assert_path_equal(g->path, expected->path); + + i = 0; + usbg_for_each_function(f, g) + assert_func_equal(f, &expected->functions[i++]); + + i = 0; + usbg_for_each_config(c, g) + assert_config_equal(c, &expected->configs[i++]); +} + +void assert_state_equal(usbg_state *s, struct test_state *expected) +{ + usbg_gadget *g; + int i = 0; + + assert_path_equal(s->path, expected->path); + assert_path_equal(s->configfs_path, expected->configfs_path); + + usbg_for_each_gadget(g, s) + assert_gadget_equal(g, &expected->gadgets[i++]); +} + +#define SIGNUM(x) (((x) > 0) - ((x) < 0)) + +int path_cmp(const char *actual, const char *expected) +{ + const char *a = actual; + const char *b = expected; + + while (*a != '\0' && *b != '\0') { + if (*a != *b) + break; + do + ++a; + while (*a == '/'); + + do + ++b; + while (*b == '/'); + } + + return SIGNUM(*a - *b); +} + +int path_equal_display_error(const LargestIntegralType actual, const LargestIntegralType expected) +{ + if (path_cmp((const char *)actual, (const char *)expected) == 0) { + return 1; + } + + fprintf(stderr, "%s != %s\n", (const char *)actual, (const char *)expected); + return 0; +} + +void assert_path_equal(const char *actual, const char *expected) +{ + if (path_equal_display_error( + cast_to_largest_integral_type(actual), + cast_to_largest_integral_type(expected)) == 0) + fail(); +} + +int hex_str_cmp(const char *actual, const char *expected) +{ + int a, b; + + sscanf(actual, "%x", &a); + sscanf(expected, "%x", &b); + + return SIGNUM(a - b); +} + +int hex_str_equal_display_error(const LargestIntegralType actual, const LargestIntegralType expected) +{ + if (hex_str_cmp((const char *)actual, (const char *)expected) == 0) { + return 1; + } + + fprintf(stderr, "%s != %s\n", (const char *)actual, (const char *)expected); + return 0; +} + +void assert_gadget_attrs_equal(usbg_gadget_attrs *actual, + usbg_gadget_attrs *expected) +{ + int i; + + for (i = USBG_GADGET_ATTR_MIN; i < USBG_GADGET_ATTR_MAX; i++) + assert_int_equal(get_gadget_attr(actual, i), get_gadget_attr(expected, i)); +} + +void assert_gadget_strs_equal(usbg_gadget_strs *actual, usbg_gadget_strs *expected) +{ + int i; + for (i = 0; i < GADGET_STR_MAX; i++) + assert_string_equal(get_gadget_str(actual, i), get_gadget_str(expected, i)); +} + +void assert_f_serial_attrs_equal(usbg_f_serial_attrs *actual, + usbg_f_serial_attrs *expected) +{ + assert_int_equal(actual->port_num, expected->port_num); +} + +static void assert_ether_addrs_equal(const struct ether_addr *ea1, + const struct ether_addr *ea2) +{ + assert_memory_equal(ea1->ether_addr_octet, ea2->ether_addr_octet, + ETHER_ADDR_LEN); +} + +void assert_f_net_attrs_equal(usbg_f_net_attrs *actual, usbg_f_net_attrs *expected) +{ + assert_ether_addrs_equal(&actual->dev_addr, &expected->dev_addr); + assert_ether_addrs_equal(&actual->host_addr, &expected->host_addr); + assert_string_equal(actual->ifname, expected->ifname); + assert_int_equal(actual->qmult, expected->qmult); +} + +void assert_f_phonet_attrs_equal(usbg_f_phonet_attrs *actual, + usbg_f_phonet_attrs *expected) +{ + assert_string_equal(actual->ifname, expected->ifname); +} + +void assert_f_ffs_attrs_equal(usbg_f_ffs_attrs *actual, usbg_f_ffs_attrs *expected) +{ + assert_string_equal(actual->dev_name, expected->dev_name); +} + +void assert_function_attrs_equal(usbg_function_attrs *actual, + usbg_function_attrs *expected, usbg_f_attrs_type type) +{ + switch (type) { + case USBG_F_ATTRS_SERIAL: + assert_f_serial_attrs_equal(&actual->attrs.serial, &expected->attrs.serial); + break; + case USBG_F_ATTRS_NET: + assert_f_net_attrs_equal(&actual->attrs.net, &expected->attrs.net); + break; + case USBG_F_ATTRS_PHONET: + assert_f_phonet_attrs_equal(&actual->attrs.phonet, &expected->attrs.phonet); + break; + case USBG_F_ATTRS_FFS: + assert_f_ffs_attrs_equal(&actual->attrs.ffs, &expected->attrs.ffs); + break; + default: + fail(); + } +} + + +void for_each_test_function(struct test_state *ts, usbg_state *s, FunctionTest fun) +{ + struct test_gadget *tg; + struct test_function *tf; + usbg_gadget *g = NULL; + usbg_function *f = NULL; + + for (tg = ts->gadgets; tg->name; ++tg) { + g = usbg_get_gadget(s, tg->name); + assert_non_null(g); + for (tf = tg->functions; tf->instance; ++tf) { + f = usbg_get_function(g, tf->type, tf->instance); + fun(f, tf); + } + } +} + +void for_each_test_config(struct test_state *ts, usbg_state *s, ConfigTest fun) +{ + usbg_gadget *g = NULL; + usbg_config *c = NULL; + struct test_gadget *tg; + struct test_config *tc; + + for (tg = ts->gadgets; tg->name; tg++) { + g = usbg_get_gadget(s, tg->name); + assert_non_null(g); + for (tc = tg->configs; tc->label; tc++) { + c = usbg_get_config(g, tc->id, tc->label); + fun(c, tc); + } + } +} + +void for_each_binding(struct test_state *ts, usbg_state *s, BindingTestFunc fun) +{ + struct test_gadget *tg; + struct test_config *tc; + struct test_binding *tb; + usbg_gadget *g = NULL; + usbg_config *c = NULL; + usbg_binding *b = NULL; + + for (tg = ts->gadgets; tg->name; tg++) { + g = usbg_get_gadget(s, tg->name); + assert_non_null(g); + for (tc = tg->configs; tc->label; tc++) { + c = usbg_get_config(g, tc->id, tc->label); + assert_non_null(c); + + b = usbg_get_first_binding(c); + for (tb = tc->bindings; tb->name; ++tb) { + assert_non_null(b); + fun(tb, b); + b = usbg_get_next_binding(b); + } + } + } +} + +void for_each_test_gadget(struct test_state *ts, usbg_state *s, GadgetTestFunc fun) +{ + struct test_gadget *tg; + usbg_gadget *g = NULL; + + for (tg = ts->gadgets; tg->name; ++tg) { + g = usbg_get_gadget(s, tg->name); + assert_non_null(g); + fun(g, tg); + } +} diff --git a/tests/usbg-test.h b/tests/usbg-test.h new file mode 100644 index 0000000..127b90e --- /dev/null +++ b/tests/usbg-test.h @@ -0,0 +1,549 @@ +#ifndef USBG_TEST_H +#define USBG_TEST_H + +#include <usbg/usbg.h> +#include <sys/queue.h> +#include "usbg/usbg_internal.h" + +/* Simple structures for defining gadgets. All arrays should be null-terminated.*/ + +/** + * @file tests/usbg-test.h + */ + +struct test_function +{ + usbg_function_type type; + char *instance; + + char *path; + char *name; + usbg_function_attrs *attrs; + int writable; +}; + +struct test_binding +{ + struct test_function *target; + char *name; + int writable; +}; + +struct test_config +{ + char *label; + int id; + struct test_function *bound_funcs; + + struct test_binding *bindings; + char *path; + char *name; + int writable; + usbg_config_strs *strs; + usbg_config_attrs *attrs; +}; + +struct test_gadget +{ + char *name; + char *udc; + struct test_config *configs; + struct test_function *functions; + + char *path; + int writable; +}; + +struct test_state +{ + char *configfs_path; + /* filled by prepare_state() */ + char *path; + struct test_gadget *gadgets; + char **udcs; + int writable; +}; + +typedef enum { + STR_SER = 0, + STR_MNF, + STR_PRD, + GADGET_STR_MAX +} gadget_str; + +typedef enum { + MAX_POWER = 0, + BM_ATTRIBUTES, + CONFIG_ATTR_MAX +} config_attr; + +typedef enum { + FORMAT_HEX, + FORMAT_DEC +} attr_format; + +#define TEST_FUNCTION_LIST_END { \ + .instance = NULL, \ + } + +#define TEST_CONFIG_LIST_END { \ + .label = NULL, \ + .bindings = NULL, \ + } + +#define TEST_GADGET_LIST_END { \ + .name = NULL, \ + .udc = NULL, \ + .configs = NULL, \ + .functions = NULL, \ + } + +#define expect_path(function, param, data) \ + expect_check(function, param, \ + (CheckParameterValue)(path_equal_display_error), data) + +/** + * @brief Prepare given state for using in tests + * @details Generate full pathes to state elements and sort state's content. + * Must be called before pasing state to push_* and pull_* functions. + * @param[in] state State schema used to genrate test state + * @return Pointer to state which can be used for testing. Returned value is + * equal to #state if writable attribute has been set to 1 or pointer + * to newly allocated test_state filled with suitable values. All memory + * allocated in this function is scheduled to free using free_later(). + */ +struct test_state *prepare_state(struct test_state *state); + +/** + * @brief Prepare given config for using in tests + * @details Generate required pathes for given config and sort content + * (i.e. binding list) + * @param[in] c Config to be filled out + * @param[in] cpath Path to configs directory + * @param[in] fpath Path to functions directory + */ +void prepare_config(struct test_config *c, char *cpath, char *fpath); + +/** + * @brief Prepare given function for using in tests + * @details Generate required pathes for given function + * @param[in] f Function to be filled out + * @param[in] path Path to functions directory + */ +void prepare_function(struct test_function *f, char *path); + +/** + * @brief Prepare given gadget for using in tests + * @details Generate required paths for given gadget and sort it's content + * (i.e. functions list and config list) + * @param[in] state Pointer to gadget's parent state + * @param[in] g Gadget to be filled out + */ +void prepare_gadget(struct test_state *state, struct test_gadget *g); + +/** + * @brief Fill given binding with required values + * @details Make given binding point to a function + * @param[in] b Test binding to be prepared + * @param[in] f Function to which binding will point + * @param[in] fpath Path to functions directory + */ +void prepare_binding(struct test_binding *b, struct test_function *f, char *fpath); + +/** + * @brief Prepare fake filesystem to init usbg with given test state + * @details Use wrapped i/o functions to simulate configfs state for usbg. + * Calling usbg_init without preparation and with mocked i/o functions + * may fail. + * @param[in] state Fake state of configfs defined in test + */ +void push_init(struct test_state *state); + +/** + * Prepare specific attributes writting/reading + **/ + +/** + * @brief Prepare for getting config attributes + * @param[in] config Configuration from which attributes will be get + * @param[in] attrs Attributes which will be present in virtual filesystem + */ +void push_config_attrs(struct test_config *config, usbg_config_attrs *attrs); + +/** + * @brief Preapre for setting config attributes + * @param[in] config Configuration on which attributes will be set + * @param[in] attrs Attributes which will be set on given config + */ +void pull_config_attrs(struct test_config *config, usbg_config_attrs *attrs); + +/** + * @brief Get gadget attribute + * @param[in] attrs + * @param[in] attr + */ +int get_gadget_attr(usbg_gadget_attrs *attrs, usbg_gadget_attr attr); + +/** + * @brief Prepare to write given attribute by libusbg + * @param[in] gadget Test gadget related to given attribute + * @param[in] attr Attribute + * @param[in] value Attributes value + **/ +void push_gadget_attribute(struct test_gadget *gadget, + usbg_gadget_attr attr, int value); + +/** + * @brief Prepare to read given attribute by libusbg + * @param[in] gadget Test gadget related to given attribute + * @param[in] attr Attribute + * @param[in] value Attributes value + **/ +void pull_gadget_attribute(struct test_gadget *gadget, + usbg_gadget_attr attr, int value); + +/** + * @brief Prepare fake filesystem to get given gadget attributes + * @details Prepare queue of values passed to wrapped i/o functions, + * all values got from given attributes structure. + * @param[in] gadget Pointer to gadget + * @param[in] attrs Pointer to attributes which gadget should have + * @warning Calling usbg_get_gadget_attrs function whithout this + * preparation and with wrapped i/o may fail. + */ +void push_gadget_attrs(struct test_gadget *gadget, usbg_gadget_attrs *attrs); + +/** + * @brief Prepare fake filesystem for attributes setting attempt. + * @details Prepare queue of values passed to wrapped i/o functions, + * corresponding to functions called on attributes setting + * @param[in] gadget Pointer to gadget + * @param[in] attrs Pointer to expected attributes + * @warning Calling usbg_get_gadget_attrs function whithout this + * preparation and with wrapped i/o may fail. + */ +void pull_gadget_attrs(struct test_gadget *gadget, usbg_gadget_attrs *attrs); + +/** + * @brief Prepare fake filesystem to get given function attributes + * @details Prepare queue of values passed to wrapped i/o functions, + * all values got from given attributes structure. + * @warning Calling usbg_get_function_attrs function whithout this + * preparation and with wrapped i/o may fail. + */ +void push_function_attrs(struct test_function *func, usbg_function_attrs *attrs); + +/** + * @brief Prepare fake filesystem to set given function attributes + * @details Prepare queue of values passed to wrapped i/o functions, + * all values got from given attributes structure. + * @warning Calling usbg_set_function_attrs function whithout this + * preparation and with wrapped i/o may fail. + */ +void pull_function_attrs(struct test_function *func, usbg_function_attrs *attrs); + +/** + * @brief Get gadget string + * @param[in] strs Set of gadget strings + * @param[in] str Identifier of string which should be returned + * @return Selected string from given set of strings + */ +const char *get_gadget_str(usbg_gadget_strs *strs, gadget_str str); + +/** + * @brief Prepare filesystem to set selected gadget string + * @param[in] gadget Gadget on which str will be set + * @param[in] lang Language of string + * @param[in] str String identifier + * @param[in] content String expected to be set + */ +void pull_gadget_string(struct test_gadget *gadget, int lang, + gadget_str str, const char *content); + +/** + * @brief Prepare filesystem to set given gadget strings + * @param[in] gadget Gadget on which strings will be set + * @param[in] lang Language of strings + * @param[in] strs Strings expected to be set + */ +void pull_gadget_strs(struct test_gadget *gadget, int lang, usbg_gadget_strs *strs); + +/** + * @brief prepare for reading gadget's strings + */ +void push_gadget_strs(struct test_gadget *gadget, int lang, usbg_gadget_strs *strs); + +/** + * @brief Prepare for /ref usbg_set_config_string calling + * @details Expect setting the same string as given one + * @param[in] config on which strings will be set + * @param[in] lang Language of strings + * @param[in] str string to be set as configuration string + */ +void pull_config_string(struct test_config *config, int lang, const char *str); + +/** + * @brief Prepare for writting given config strings + * @param[in] config on which strings will be set + * @param[in] lang Language of strings + * @param[in] strs Strings expected to be set + */ +void pull_config_strs(struct test_config *config, int lang, usbg_config_strs *strs); + +/** + * @brief Prepare for /ref usbg_get_config_string calling + * @details Expect setting the same string as given one + * @param[in] config from which strings will be get + * @param[in] lang Language of strings + * @param[in] str string which should be returned as configuration string + */ +void push_config_string(struct test_config *config, int lang, const char *str); + +/** + * @brief Prepare for reading config strings + * @param[in] config from which strings will be get + * @param[in] lang Language of strings + * @param[in] strs Strings which should be returned + */ +void push_config_strs(struct test_config *config, int lang, usbg_config_strs *strs); + +/** + * @brief Prepare for creating config + * @param[in] tc Test config to be created + */ +void pull_create_config(struct test_config *tc); + +/** + * @brief Prepare for creating function + * @param[in] tf Test function to be created + */ +void pull_create_function(struct test_function *tf); + +/** + * @brief Copy state without configs and functions + * @param[in] ts State to bo copied + * @return State with empty gadgets + */ +struct test_state *build_empty_gadget_state(struct test_state *ts); + +/** + * @brief Store given pointer on cleanup stack + * @details All stacked pointers will be freed by calling cleanup_queue. + * This can be used to manage memory needed for single test casees. + */ +void free_later(void *ptr); + +/** + * @brief Cleans up memory no longer needed + * @details Frees all pointer stored on cleanup stack by calling free_later + * @warning Calling this function before end of single test usually make test state + * unusable. Use it only when you no longer need allocated data (at the end of + * test case, in most cases) + */ +void cleanup_stack(); + +/** + * @brief init usbg with given test state + */ +void init_with_state(struct test_state *in, usbg_state **out); + +/** + * @brief Safely initialize usbg state from pointer given to test + * @param[in] state Pointer given to test function + * @param[out] ts Pointer to be filled with test state + * @param[out] s Pointer to be filled with usbg state + */ +void safe_init_with_state(void **state, struct test_state **ts, usbg_state **s); + +/** + * @brief Assert that given config attributes are equal + */ +void assert_config_attrs_equal(usbg_config_attrs *actual, usbg_config_attrs *expected); + +/** + * @brief Assert that given usbg binding matches given test binding + * @param[in] f Pointer to usbg binding struct + * @param[in] expected Pointer to test binding struct with expected values + */ +void assert_binding_equal(usbg_binding *b, struct test_binding *expected); + +/** + * @brief Assert that given usbg function matches given test function + * @param[in] f Pointer to usbg function struct + * @param[in] expected Pointer to test function struct with expected values + */ +void assert_func_equal(usbg_function *f, struct test_function *expected); + +/** + * @brief Assert that given usbg config matches given test config + * @param[in] c Pointer to usbg config struct + * @param[in] expected Pointer to test config struct with expected values + */ +void assert_config_equal(usbg_config *c, struct test_config *expected); + +/** + * @brief Assert that given usbg gadget matches given test gadget + * @param[in] g Pointer to usbg gadget struct + * @param[in] expected Pointer to test gadget struct with expected values + */ +void assert_gadget_equal(usbg_gadget *g, struct test_gadget *expected); + +/** + * @brief Assert that given usbg state matches given test state + * @param[in] s Pointer to usbg state struct + * @param[in] expected Pointer to test state struct with expected values + */ +void assert_state_equal(usbg_state *s, struct test_state *expected); + +/** + * @brief Compare path names + * @details Given pathes don't need to exist + * @return Integer less than, equal to, or greater than zero if a is (respectively) + * less than, equal to, or greater than b. + */ +int path_cmp(const char *a, const char *b); + +/** + * @brief Print error when given paths are not equal + * @return 1 if paths are equal, 0 otherwise + * @note Argument type is defined by cmocka. This specific function type is defined + * as custom comparing function in cmocka framework. + */ +int path_equal_display_error(const LargestIntegralType actual, const LargestIntegralType expected); + +/** + * @brief Compare attributes (as strings) + * @return Integer less than, equal to, or greater than zero if a is (respectively) + * less than, equal to, or greater than b. + */ +int hex_str_cmp(const char *actual, const char *expected); + +/** + * @brief Print error when given attributes are not equal + * @return 1 if attributes are equal, 0 otherwise + * @note Argument type is defined by cmocka. This specific function type is defined + * as custom comparing function in cmocka framework. + */ +int hex_str_equal_display_error(const LargestIntegralType actual, const LargestIntegralType expected); + +/** + * @brief Assert that given path strings are equal + * @details Given pathes don't need to exist + */ +void assert_path_equal(const char *actual, const char *expected); + +/** + * @brief Assert that given usbg gadget attributes sets are equal + * @param[in] actual Pointer to actual gadget attributes structure + * @param[in] expected Pointer to expeced gadget attributes structure + */ +void assert_gadget_attrs_equal(usbg_gadget_attrs *actual, + usbg_gadget_attrs *expected); + +/** + * @brief Assert that given function attributes are the same. + * @param[in] actual Pointer to actual attributes object + * @param[in] expected Pointer to expected attributes obejct + * @param[in] type Type of function, which attributes are checked + */ +void assert_function_attrs_equal(usbg_function_attrs *actual, + usbg_function_attrs *expected, usbg_f_attrs_type type); + +/** + * @brief Assert that given gadget strings are equal + */ +void assert_gadget_strs_equal(usbg_gadget_strs *actual, usbg_gadget_strs *expected); + +/** + * @brief Function that performs some test on given usbg function +*/ +typedef void (*FunctionTest)(usbg_function *f, struct test_function *tf); + +/** + * @brief Call given function for all usb functions present in given state + * @param[in] ts Test state to be tested + * @param[in] s Properly prepared usbg state to be tested + * @param[in] fun Function to be called on each usb function in state + */ +void for_each_test_function(struct test_state *ts, usbg_state *s, FunctionTest fun); + +/** + * @brief Function that performs some test on given usbg config +*/ +typedef void (*ConfigTest)(usbg_config *c, struct test_config *tc); + +/** + * @brief Call given function for all usb configs present in given state + * @param[in] ts Test state to be tested + * @param[in] s Properly prepared usbg state to be tested + * @param[in] fun Function to be called on each usb function in state + */ +void for_each_test_config(struct test_state *ts, usbg_state *s, ConfigTest fun); + +/** + * @brief Function that performs test on given usbg binding + */ +typedef void (*BindingTestFunc)(struct test_binding *tb, usbg_binding *b); + +/** + * @brief Call given function for all usb bindings present in given state + * @param[in] ts Test state to be tested + * @param[in] s Properly prepared usbg state to be tested + * @param[in] fun Function to be called on each usb binding in state + */ +void for_each_binding(struct test_state *ts, usbg_state *s, BindingTestFunc fun); + +/** + * @brief Function that performs test on given usbg gadget + */ +typedef void (*GadgetTestFunc)(usbg_gadget *g, struct test_gadget *tg); + +/** + * @brief Call given function for all usb gadgets present in given state + * @param[in] ts Test state to be tested + * @param[in] s Properly prepared usbg state to be tested + * @param[in] fun Function to be called on each usb gadget in state + */ +void for_each_test_gadget(struct test_state *ts, usbg_state *s, GadgetTestFunc fun); + +static inline void *safe_calloc(int count, size_t size) +{ + void *ptr; + + ptr = calloc(count, size); + if (ptr == NULL) + fail(); + + free_later(ptr); + return ptr; +} + +static inline void *safe_malloc(size_t size) +{ + void *ptr; + + ptr = malloc(size); + if (ptr == NULL) + fail(); + + free_later(ptr); + return ptr; +} + +static inline int safe_asprintf(char **ptr, const char *fmt, ...) +{ + va_list args; + int ret; + + va_start(args, fmt); + ret = vasprintf(ptr, fmt, args); + va_end(args); + + if (ret < 0) + fail(); + + free_later(*ptr); + return ret; +} + +#endif /* USBG_TEST_H */ |