summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore29
-rw-r--r--AUTHORS2
-rw-r--r--ChangeLog7
-rw-r--r--INSTALL2
-rw-r--r--Makefile.am7
-rw-r--r--README4
-rw-r--r--configure.ac47
-rw-r--r--doc/gadget_schemes.txt301
-rw-r--r--doc/tests_guideline.txt166
-rw-r--r--doxygen.cfg.in (renamed from doxygen.cfg)9
-rw-r--r--examples/Makefile.am11
-rw-r--r--examples/gadget-acm-ecm.c25
-rw-r--r--examples/gadget-export.c81
-rw-r--r--examples/gadget-ffs.c156
-rw-r--r--examples/gadget-import.c78
-rw-r--r--examples/gadget-midi.c119
-rw-r--r--examples/gadget-ms.c161
-rw-r--r--examples/gadget-vid-pid-remove.c108
-rw-r--r--examples/show-gadgets.c139
-rw-r--r--examples/show-udcs.c61
-rw-r--r--include/usbg/usbg.h612
-rw-r--r--include/usbg/usbg_internal.h174
-rw-r--r--libusbgx.pc.in (renamed from libusbg.pc.in)4
-rw-r--r--src/Makefile.am15
-rw-r--r--src/usbg.c1984
-rw-r--r--src/usbg_schemes_libconfig.c2184
-rw-r--r--src/usbg_schemes_none.c94
-rw-r--r--tests/Makefile.am14
-rw-r--r--tests/test.c2697
-rwxr-xr-xtests/test.sh78
-rw-r--r--tests/usbg-io-wrappers.c203
-rw-r--r--tests/usbg-test.c1389
-rw-r--r--tests/usbg-test.h549
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
diff --git a/AUTHORS b/AUTHORS
index f9b069c..6fea705 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1 +1,3 @@
Matt Porter <mporter@linaro.org>
+Krzysztof Opasiak <k.opasiak@samsung.com>
+
diff --git a/ChangeLog b/ChangeLog
index 295762b..63ba0f3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/INSTALL b/INSTALL
index 786ecee..0d9d67f 100644
--- a/INSTALL
+++ b/INSTALL
@@ -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
diff --git a/README b/README
index fba9959..08622a3 100644
--- a/README
+++ b/README
@@ -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/
diff --git a/src/usbg.c b/src/usbg.c
index d73943c..b9c53e8 100644
--- a/src/usbg.c
+++ b/src/usbg.c
@@ -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 */