1
0
mirror of git://sigrok.org/libserialport synced 2023-08-10 21:13:24 +03:00

134 Commits

Author SHA1 Message Date
0c3f38b81b linux: freebsd: do check on proper pointer
We want to check here if we added something to the list, not if the
function argument was valid. Problem spotted by Coverity, CID 50754.

Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
2016-02-28 18:01:25 +01:00
3fcdc9f7d5 Bump libtool version (not package version) to 1:0:1.
The last release (0.1.0) had the libtool version (current:revision:age)
set to 0:0:0. Since this release doesn't change/remove any
interfaces (it does *add* new interfaces, though), 'current' and 'age'
are increased and 'revision' is set to 0, resulting in 1:0:1.

http://www.gnu.org/software/libtool/manual/libtool.html#Updating-version-info

Programs using libserialport don't need to be recompiled or relinked.
2016-01-27 12:36:01 +01:00
24a38c8094 NEWS: Last updates for the upcoming 0.1.1 release. 2016-01-27 12:36:01 +01:00
aee7d69195 Fix ERROR_SEM_TIMEOUT issue on Windows.
The sp_blocking_write() call was incorrectly returning an error upon
ERROR_SEM_TIMEOUT. It now returns 0 instead.
2016-01-27 12:35:45 +01:00
888fb45d66 Fix sp_blocking_read_next() implementation on Windows.
A ReadFile() call needed to check the actual number of bytes read,
instead of assuming all requested bytes were read.
2016-01-22 15:32:00 +01:00
02c8a1424d windows: Fix iSerial for composite devices.
USB composite devices can contain an ACM serial interface.
On Windows, the correct iSerial descriptor field is assigned to the parent
(composite) device instead of to the actual serial interface. A bogus
value is returned if the serial interface is asked to provide the S/N.
This patch provides a fallback for this kind of device (tested on Android
with adb + cdc gadgets and on Arduino Zero Programming Port)
2016-01-06 13:54:17 +00:00
aacba60dca macosx: Fix port listing on ElCapitan.
On OSX 10.11 (ElCapitan) the query for IOProviderClass fails to list ACM
devices as USB.
Add a fallback query using IOClass to correctly recognize these devices.
The fix has no effect on previous OSX versions (tested on Mavericks)
2016-01-06 13:54:11 +00:00
8062087ab3 Change debug message to "Port not open" when a closed port is used.
Fixes bug #710.
2015-12-29 13:30:30 +01:00
4d8195fe3f libserialport_internal: use new macro to enable timeradd, etc.
Since glibc 2.20, the usage of _BSD_SOURCE is deprecated. Fix it like
described here:

https://sourceware.org/glibc/wiki/Release/2.20#Deprecation_of__BSD_SOURCE_and__SVID_SOURCE_feature_macros

This fixes bug #716.

Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
2015-12-29 01:16:28 +01:00
ae2c5825e7 windows: Handle ERROR_IO_PENDING from ReadFile in sp_nonblocking_read().
This fixes bug #707.
2015-12-09 12:43:11 +01:00
127d8d0ce7 sp_wait: Avoid overflow of timeout parameter to poll(). 2015-10-13 15:04:03 +01:00
1a584c45b0 Build: Include config.h first in all source files 2015-09-13 20:04:31 +02:00
962143bc5c Build: Use GNUMAKEFLAGS and $@ 2015-09-10 22:19:15 +02:00
c37c9f348d autogen.sh: Create autostuff/ dir if it does not exist 2015-08-27 11:46:06 +02:00
07f61c7131 configure: Enable largefile support on 32-bit systems 2015-08-26 22:49:40 +02:00
87f4a6f021 Clean up .gitignore 2015-08-26 22:01:03 +02:00
f1c916ede1 Build: Clean up configure.ac and Makefile.am
Among other things, rename some config defines so we can take
advantage of standard Autoconf macros.
2015-08-26 21:55:10 +02:00
ef08a6d52b Build: Reduce autogen.sh to trivial stub
Use autoreconf instead of invoking the various Auto tools
separately. Get rid of the Darwin-specific guesswork -- it does
not make sense to handle this at the level of libserialport.

People should set up their ACLOCAL_PATH themselves as appropriate
for their own system; just as they already need to set up various
other paths.
2015-08-26 19:46:43 +02:00
f77bb46df5 Update copyright notices. 2015-05-31 18:26:53 +02:00
d931da1042 Update AUTHORS file. 2015-05-31 18:26:53 +02:00
e2b7ba1214 NEWS: Update for the upcoming 0.1.1 release. 2015-05-30 22:11:46 +02:00
56fe6e3207 doc: Provided channel is binary, not necessarily 8-bit. 2015-05-30 21:03:38 +01:00
6b8eee0682 Fix possible use of uninitialised variable in poll() setup. 2015-05-30 21:38:03 +02:00
49fd7b1bc2 doc: Additional introductory text. 2015-05-30 21:38:03 +02:00
deef6e528c doc: Expanded introductory text. 2015-05-27 11:19:27 +02:00
ec4b55ae25 doc: Additional notes on sp_last_error_{code,message}. 2015-05-27 11:09:26 +02:00
46d8b0a039 Handle the case when /sys/class/tty/ entries are not symlinks.
For example:

$ uname -a
Linux RT-N66U 2.6.22.19 #1 Thu Feb 12 20:33:36 CST 2015 mips GNU/Linux

$ ls -lF /sys/class/tty/
drwxr-xr-x    2 foo  root             0 May 18 18:12 ttyACM0/

$ ls -lF /sys/class/tty/ttyACM0/
-r--r--r--    1 foo  root          4096 May 18 18:12 dev
lrwxrwxrwx    1 foo  root             0 May 18 18:12 device -> ../../../devices/pci0000:00/0000:00:04.1/usb1/1-1/1-1.1/1-1.1:1.0/
lrwxrwxrwx    1 foo  root             0 May 18 18:12 subsystem -> ../../../class/tty/
-rw-r--r--    1 foo  root          4096 May 18 18:12 uevent
2015-05-27 11:09:26 +02:00
e5c2630eee Add new sp_blocking_read_next() function. 2015-05-27 11:09:26 +02:00
0188a545c7 windows: Always check and set ReadTotalTimeoutMultiplier.
This is not currently touched, but will be by the upcoming
sp_blocking_read_next() function.
2015-05-27 11:09:25 +02:00
76222ee578 posix: Make sure blocking operations can't time out without trying. 2015-05-27 11:09:25 +02:00
c3cee38c3b posix: Consistent debug output when blocking operations time out. 2015-05-27 11:09:25 +02:00
772c586133 posix: Move FD_ZERO() and FD_SET() calls outside loops. 2015-05-27 11:09:25 +02:00
27911925c1 windows: Always check return value of GetOverlappedResult(). 2015-05-27 11:09:25 +02:00
bbe566fe1c windows: Restructure if/else blocks with unnecessary indentation. 2015-05-27 11:09:25 +02:00
8ebfaf8966 windows: restart_wait_if_needed(): Inline receive buffer check.
This removes all the debug spam arising from calling sp_input_waiting().
2015-05-27 11:09:25 +02:00
21ae5ce197 windows: restart_wait_if_needed(): Skip buffer check if no bytes read. 2015-05-27 11:09:25 +02:00
94a314a806 windows: Refactor duplicated code into a static function. 2015-05-27 11:09:25 +02:00
27a58c0521 windows: Avoid unnecessary calls to SetCommTimeouts().
This fixes bug #586.
2015-05-27 11:09:13 +02:00
9a0c4bbdbe windows: Strip CR/LF from end of system error messages.
This fixes bug #585.
2015-05-27 11:08:30 +02:00
480d750c59 doc: correct structure name in sp_get_port_handle() documentation. 2015-05-06 16:33:13 +01:00
ff6da776e8 libserialport.h.in: Fix/update some API docs. 2015-05-06 17:06:06 +02:00
6392641545 sp_list_ports(): Actually set list_ptr NULL as documented. 2015-05-06 17:06:06 +02:00
6aad5bdd47 Fix a potential segfault in sp_get_config_*(). 2015-05-06 17:06:06 +02:00
5f64c28551 Fix a potential segfault in sp_get_port_handle(). 2015-05-06 17:06:05 +02:00
eb50b1aca6 windows.c: Constify a few function parameters. 2015-04-19 19:50:56 +02:00
9eb9f07130 sp_get_port_usb_vid_pid(): Document that usb_vid/usb_pid can be NULL. 2015-04-19 19:18:05 +02:00
1b91c6eadb Rename timeout to timeout_ms for clarity in a few places. 2015-04-19 19:01:08 +02:00
025c264448 autotools: Don't set/override "user variables".
This fixes bug #577.
2015-04-19 19:01:08 +02:00
a630a469f6 Makefile.am: Simplify LDFLAGS handling a bit. 2015-04-19 19:01:08 +02:00
f14809393a Constify the sp_get_port_description() parameter. 2015-04-19 19:01:08 +02:00
ad036cc8ef Constify the sp_get_port_transport() parameter. 2015-04-19 18:28:10 +02:00
3dd4a2ae39 configure.ac: Drop no longer needed SP_PKGLIBS. 2015-04-19 18:21:19 +02:00
35578b063e Various Doxygen comment updates and fixes.
- Add some missing @param lines.

 - Add [in], [out], or [in,out] markers for @param tags.

 - Add explicit "Must not be NULL" comments for parameters where
   the caller should not pass in a NULL.
2015-04-19 18:16:01 +02:00
e4f44cf5f2 libserialport.h.in: Drop unneeded <windows.h> #include.
This is not needed and would pollute the namespace of every
libserialport user on Windows.
2015-04-19 18:15:29 +02:00
13efecf888 libserialport.h.in: Add @since tags for new enums. 2015-04-19 18:14:48 +02:00
3f2f48fc01 Set package version to 0.1.1, use "@since 0.1.1" Doxygen tags.
The next release will not contain any backwards-incompatible
API or ABI changes, thus changing the package version to 0.1.1.
2015-04-19 18:13:53 +02:00
777347a6bd windows: Drop no longer needed -luuid.
Also, use SP_LIBS in Makefile.am.

This is build-tested using a native MinGW-w64 based toolchain
as well as a MinGW-w64 based cross-compile setup.
2015-04-18 21:54:42 +02:00
8c6de591a5 configure.ac: Minor whitespace cosmetics. 2015-04-18 21:54:42 +02:00
1ff591d7b3 Doxyfile: Update to Doxygen 1.8.8 template.
(by running 'doxygen -u')
2015-04-18 21:54:42 +02:00
5ca38cdfe0 configure.ac: Add #include guard to config.h. 2015-04-18 21:54:42 +02:00
613c48f191 Fix a compiler warning when -Wshadow is used.
CC       serialport.lo
  In file included from ../serialport.c:25:0:
  ../serialport.c: In function 'get_config':
  ../libserialport_internal.h:227:25: warning: declaration of 'ret' shadows a previous local [-Wshadow]
   #define TRY(x) do { int ret = x; if (ret != SP_OK) RETURN_CODEVAL(ret); } while (0)
                           ^
  ../serialport.c:1566:3: note: in expansion of macro 'TRY'
     TRY(get_baudrate(port->fd, &config->baudrate));
     ^
  ../serialport.c:1543:6: warning: shadowed declaration is here [-Wshadow]
    int ret = get_flow(port->fd, data);
        ^

Also, add -Wshadow to the list of default compiler options.
2015-04-18 21:54:42 +02:00
b3e619c8b2 autogen.sh: Drop obsolete MinGW/MSYS items.
For Windows builds (which require MinGW-w64) we currently support:

 - cross-builds using MXE (mxe.cc) and possibly other cross-compile setups

 - native builds using MSYS2 (sf.net/projects/msys2/)

Neither of those require explicitly specifying ACLOCAL_DIR.
2015-04-07 02:35:03 +02:00
f11a67ed5f README: Refer to MSYS2 only.
The "old" MSYS related to the "old" and unsupported MinGW (from
mingw.org) may or may not work properly, so only refer to MSYS2 which
we currently recommend and which has been tested.
2015-04-07 01:32:40 +02:00
144a598f19 Minor Doxygen improvements and fixes. 2015-04-07 01:32:40 +02:00
3cb7aa98c2 configure.ac: Use $host_os instead of $target_os.
$build and $build_os refer to the machine on which the software is
built, $host and $host_os refer to the machine for which the software
is (cross-)built.

The variables $target and $target_os (despite the confusing names)
are only relevant in the context of building cross-compilers.
2015-04-07 00:49:27 +02:00
cd71af4b93 README: Add link to online API docs. 2015-04-03 22:18:56 +02:00
a24671ade9 README: Mention FreeBSD port enumeration support. 2015-04-03 22:17:57 +02:00
1cda3e3dfc configure.ac: Add -Wmissing-prototypes. 2015-04-03 22:13:06 +02:00
23e7fa372a Fix two compiler warnings on platforms without enumeration support.
serialport.c: In function 'sp_list_ports':
  serialport.c:326:6: warning: unused variable 'ret' [-Wunused-variable]
    int ret;
        ^
  serialport.c:325:19: warning: unused variable 'list' [-Wunused-variable]
    struct sp_port **list;
                     ^
2015-04-03 22:11:07 +02:00
ac323d80e0 add_handle(): Fix a realloc() related issue. 2015-04-03 22:02:42 +02:00
e019e72ec0 Fix a compiler warning.
This fixes the following scan-build warning:

  serialport.c: In function 'sp_list_ports':
  serialport.c:354:1: warning: control reaches end of non-void function [-Wreturn-type]
2015-04-03 20:50:22 +02:00
b344a40bb2 Fix indentation of a switch(). 2015-04-03 17:51:47 +02:00
948f63f0fd add_handle(): Fix a memory leak.
This fixes the following scan-build warning:

  serialport.c:1170:3: warning: Potential leak of memory pointed to by 'new_handles'
                  RETURN_ERROR(SP_ERR_MEM, "Mask array realloc() failed");
                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2015-04-03 17:51:41 +02:00
5122d60849 Use libtool's -no-undefined option on all platforms. 2015-04-03 17:48:14 +02:00
8d12e620f2 Clarify sp_list_ports() code.
This also fixes the following scan-build warning:

  serialport.c:335:15: warning: Result of 'malloc' is converted to a
  pointer of type 'struct sp_port *', which is incompatible with sizeof
  operand type 'struct sp_port **'
          if (!(list = malloc(sizeof(struct sp_port **))))
                       ^~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
2015-04-03 17:46:26 +02:00
34442106ef windows: Set fAbortOnError=FALSE.
Since we're not handling and clearing receive errors (framing, parity and
overrun), we should turn them off to avoid crashing out with
ERROR_OPERATION_ABORTED in various calls if they occur. Invalid data
will then simply not end up in the buffer. This is consistent with our
current behaviour on posix/termios systems.

It might be nice to be able to inform calling code about receive errors, but I
can't see a good way to do this in a cross-platform way at the moment.

This fixes (parts of) bug #341.
2015-03-29 22:18:46 +02:00
47fcf8ec85 windows: Revise management of WaitCommEvent() operations.
This fixes (at least parts of) bug #341.
2015-03-29 22:18:46 +02:00
6c6aebe78c Make std_baudrates[] static (only used in one file). 2015-03-26 23:57:56 +01:00
dc422c04af Various cosmetic and consistency fixes. 2015-03-26 23:57:56 +01:00
2b40f81454 libserialport_internal.h: Add #include guard. 2015-03-26 23:57:56 +01:00
cfa5af67fd Fix some typos. 2015-03-26 23:57:55 +01:00
64155cba8a linux_termios.h: Add missing #include of "config.h". 2015-03-25 14:52:54 +01:00
7c081505e5 Generate a config.h rather than passing defines on command line.
Fixes bug #553.
2015-03-24 23:15:32 +00:00
7c1101dc08 Fix misleading snprintf() arguments. 2015-02-20 18:57:08 +01:00
ea17bfca3d If present, add usb_serial to description for better identification 2015-02-20 18:54:12 +01:00
5497520891 Handle enumeration of native UART ports on FreeBSD 2015-02-16 23:25:14 +01:00
ccd512d51c Implement FreeBSD OS-specific serial port detection and query 2015-02-15 21:28:15 +01:00
f446cfbf65 windows: Fix restart of RX/error wait operation after reads.
This fixes bug #421.
2015-02-15 20:52:45 +01:00
e31f2c6b8b Fix free() of uninitialized pointer.
Explicitly set port->usb_path to NULL, otherwise a free(port->usb_path)
can happen where port->usb_path is uninitialized.

This closes bug #512.
2014-12-05 18:42:43 +01:00
b328a48b0f windows: Properly handle return value of SetupDiOpenDevRegKey().
This fixes bug #499.
2014-11-27 17:42:28 +01:00
59182fbbbf linux: Check for BOTHER macro at configure time.
This fixes bug #363.
2014-11-01 18:36:09 +01:00
ad7498553c Flush only the requested buffers
Fix the sp_flush() so that it flushes only the requested buffers
also in unix.
2014-10-12 21:38:03 +02:00
d66bbceaa7 Fix typos. 2014-10-05 02:38:35 +02:00
78940e6921 windows: Fix compile issues with multiply or not defined GUIDs.
This fixes bug #416.
2014-09-25 23:04:06 +02:00
6c444ade55 windows: Indentation (cosmetics only). 2014-09-24 12:54:36 +02:00
7aeb6736bd windows: Fix various memory leaks.
This fixes bug #419.
2014-09-24 12:52:21 +02:00
276ef1b92f Add SP_MODE_READ_WRITE enum value. 2014-09-20 21:24:31 +02:00
eb82be98a7 README: Add URL to the libserialport website. 2014-09-07 19:31:23 +02:00
a46ee5b4d7 README: Mention that only MinGW-w64 works (but not MinGW). 2014-09-07 19:27:02 +02:00
da8730199a windows: -no-undefined is required to make a DLL. 2014-09-07 19:18:22 +02:00
6692d8c13f windows: Add -lsetupapi when building for WIN32. 2014-09-07 19:18:19 +02:00
d179da05c0 windows: Include proper GUID defining header to fix build.
The initguid.h header must appear early or it does not have
an effect, at least on cross-build from Linux.
2014-09-07 18:51:04 +02:00
653cef3ab1 linux: remove readlinkat() dependency 2014-09-04 20:13:55 +02:00
546e9ae5c5 linux_termios: fix return types to actually match returned values
This fixes the following warning :

linux_termios.c: In function 'get_termios_get_ioctl':
linux_termios.c:42:2: warning: overflow in implicit constant conversion [-Woverflow]
  return TCGETS2;
  ^

This fixes bug #396.
2014-09-04 20:13:55 +02:00
23ef3bf125 Fix Mac OS X build (untested). 2014-08-27 14:56:19 +02:00
d38c7d2c70 Fix Windows build. 2014-08-27 14:53:19 +02:00
0666ccc7ca linux: Fix warning by marking vid, pid variables unsigned. 2014-08-24 14:08:00 +01:00
7f450f46bd Compile with -pedantic. 2014-08-24 14:06:21 +01:00
c51b846eee linux: Define feature macros to get required functions.
This is needed to build with -std=c99.
2014-08-24 14:06:21 +01:00
7890cef6cf Revise debug macros to work in strict C99. 2014-08-24 14:06:21 +01:00
9caa2e86aa Specialise RETURN_VALUE macro into RETURN_{INT,STRING,POINTER}.
This avoids the need to pass the required format string on every
call and also eliminates the need for the non-standard typeof() call.
2014-08-24 13:31:20 +01:00
79a8004637 libserialport.h.in: remove trailing commas in enum definitions
This makes it easier to use libserialport on projects that build with
the GCC -pedantic flag enabled.
2014-08-24 11:56:45 +01:00
942a6d349e Bump package version to 0.2.0. 2014-08-20 23:40:19 +02:00
f3ccf157cb Rename sp_get_signals() parameter to avoid name clash with Qt headers. 2014-08-20 22:39:29 +02:00
900904638b configure.ac: We no longer need to set WINVER explicitly.
Since we now require MinGW-w64, we no longer have to set WINVER manually,
since MinGW-w64 defaults to WINVER=0x0502 (which corresponds to
Windows Server 2003 SP1 / Windows XP SP2 apparently, which is fine for us).
2014-08-05 21:22:59 +02:00
34906a229b windows.c: Remove MinGW workarounds, require MinGW-w64 instead.
We're switching from MinGW (i686-pc-mingw32.static in MXE)...

  http://mingw.org/

...to MinGW-w64 (i686-w64-mingw32.static in MXE):

  http://mingw-w64.sourceforge.net/

The latter is a fork which nowadays seems to be more active and also to
have a more complete set of win32 API headers and libs.

History:
http://sourceforge.net/p/mingw-w64/wiki2/History/

This closes bug #393.
2014-08-05 21:22:50 +02:00
8eb18f7b5a windows.c: Define a bunch of stuff that's not in MinGW, yet.
Without these defineѕ the build fails with various (many) compile errors.
2014-07-31 23:14:35 +02:00
9a8d04bca6 configure.ac: MinGW build fix (setting WINVER=0x0501).
Without this the build fails with:

  windows.c:40:43: error: 'WC_NO_BEST_FIT_CHARS' undeclared (first use in
  this function)
2014-07-31 23:11:40 +02:00
5a8810e2b2 serialport.c: Fix an 'unused variable' compiler warning. 2014-07-31 23:09:24 +02:00
12056e2f75 Avoid use of struct serial_struct on systems which don't have it (Android)
This fixes bug #376.
2014-07-06 14:16:56 +02:00
5bd33b7c8b Don't use readlinkat if it does not exist
This fixes bug #377.
2014-07-06 14:15:46 +02:00
970f279ae4 Introduce SP_API/SP_PRIV to mark visibility of functions. 2014-07-02 14:43:32 +02:00
e4ce975a54 Fix build for platforms without enumeration & port metadata support. 2014-06-14 12:27:54 +01:00
48a4076f69 Move list_ports() implementations to OS-specific files. 2014-06-14 01:28:49 +02:00
235269990b Fix missing variable on macosx. 2014-06-13 23:47:45 +02:00
e59ecb3359 Ignore build leftovers. 2014-06-11 20:41:20 +02:00
65172beffe Check bus:address and vid:pid validity. 2014-06-11 17:21:51 +02:00
e33dcf90a0 Tidy up and split most OS-specific code to separate files. 2014-06-11 15:10:29 +01:00
a93fb46888 Add new APIs to get some USB descriptor details for USB serial ports. 2014-06-10 23:51:11 +02:00
98cac5d63e Remove the udev dependency and parse the /sys hierarchy instead. 2014-06-10 22:17:07 +02:00
073c86bd07 Simplify Mac OS implementation of sp_list_ports(). 2014-06-10 22:17:07 +02:00
f095f84164 Remove useless declaration. 2014-06-10 22:17:07 +02:00
da2ede60e4 Remove duplicated include. 2014-06-10 22:16:46 +02:00
8edf649caf Add format string support to RETURN_ERROR() and RETURN_FAIL(). 2014-06-02 12:08:54 +02:00
19 changed files with 3274 additions and 1002 deletions

33
.gitignore vendored
View File

@ -1,19 +1,18 @@
*.[ao]
*.l[ao]
.deps/
.libs/
INSTALL
Makefile
Makefile.in
aclocal.m4
autom4te.cache/
autostuff/
config.log
config.status
configure
libserialport.la
libserialport.pc
libserialport.h
libtool
serialport.lo
serialport.o
linux_termios.lo
linux_termios.o
/INSTALL
/Makefile
/Makefile.in
/aclocal.m4
/autom4te.cache/
/autostuff/
/config.*
/configure
/configure.lineno
/libserialport-*.tar.*
/libserialport.h
/libserialport.pc
/libtool
stamp-h?

24
AUTHORS
View File

@ -2,6 +2,26 @@
AUTHORS
-------------------------------------------------------------------------------
Please check the source code files and/or git history and/or ChangeLog for
a list of all authors and contributors.
Martin Ling conceived the idea for the library, designed the API and wrote much
of the implementation and documentation.
The initial codebase was adapted from serial code in libsigrok, written by Bert
Vermeulen and Uwe Hermann, who gave permission for it to be relicensed under
the LGPL3+ for inclusion in libserialport.
The package is maintained by Uwe Hermann, with input from Martin Ling.
Aurelien Jacobs made several contributions, including the USB metadata API,
improvements to the port enumeration code, and eliminating the previous
dependence on libudev for enumeration on Linux.
Uffe Jakobsen contributed the FreeBSD support.
Matthias Heidbrink contributed support for non-standard baudrates on Mac OS X.
Bug fixes have been contributed by Bert Vermeulen, silverbuddy, Marcus
Comstedt, Antti Nykanen, Michael B. Trausch and Janne Huttunen.
For a full history of contributions, see the git history.
Others have helped the project by contributing bug reports and discussion.

131
Doxyfile
View File

@ -1,4 +1,4 @@
# Doxyfile 1.8.6
# Doxyfile 1.8.8
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@ -70,6 +70,14 @@ OUTPUT_DIRECTORY = doxy
CREATE_SUBDIRS = NO
# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
# characters to appear in the names of generated files. If set to NO, non-ASCII
# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
# U+3044.
# The default value is: NO.
ALLOW_UNICODE_NAMES = NO
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all constant output in the proper language.
@ -261,9 +269,12 @@ OPTIMIZE_OUTPUT_VHDL = NO
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make
# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
# (default is Fortran), use: inc=Fortran f=C.
# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
# Fortran. In the later case the parser tries to guess whether the code is fixed
# or free formatted code, this is the default for Fortran type files), VHDL. For
# instance to make doxygen treat .inc files as Fortran files (default is PHP),
# and .f files as C (default is Fortran), use: inc=Fortran f=C.
#
# Note For files without extension you can use no_extension as a placeholder.
#
@ -669,8 +680,7 @@ LAYOUT_FILE =
# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
# For LaTeX the style of the bibliography can be controlled using
# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
# search path. Do not use file names with spaces, bibtex cannot handle them. See
# also \cite for info how to create references.
# search path. See also \cite for info how to create references.
CITE_BIB_FILES =
@ -967,6 +977,25 @@ USE_HTAGS = NO
VERBATIM_HEADERS = YES
# If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the
# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
# cost of reduced performance. This can be particularly helpful with template
# rich C++ code for which doxygen's built-in parser lacks the necessary type
# information.
# Note: The availability of this option depends on whether or not doxygen was
# compiled with the --with-libclang option.
# The default value is: NO.
CLANG_ASSISTED_PARSING = NO
# If clang assisted parsing is enabled you can provide the compiler with command
# line options that you would normally use when invoking the compiler. Note that
# the include paths will already be set by doxygen for the files and directories
# specified with INPUT and INCLUDE_PATH.
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
CLANG_OPTIONS =
#---------------------------------------------------------------------------
# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
@ -1059,13 +1088,15 @@ HTML_FOOTER =
HTML_STYLESHEET =
# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user-
# defined cascading style sheet that is included after the standard style sheets
# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# cascading style sheets that are included after the standard style sheets
# created by doxygen. Using this option one can overrule certain style aspects.
# This is preferred over using HTML_STYLESHEET since it does not replace the
# standard style sheet and is therefor more robust against future updates.
# Doxygen will copy the style sheet file to the output directory. For an example
# see the documentation.
# Doxygen will copy the style sheet files to the output directory.
# Note: The order of the extra stylesheet files is of importance (e.g. the last
# stylesheet in the list overrules the setting of the previous ones in the
# list). For an example see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET =
@ -1230,7 +1261,8 @@ GENERATE_CHI = NO
CHM_INDEX_ENCODING =
# The BINARY_TOC flag controls whether a binary table of contents is generated (
# YES) or a normal table of contents ( NO) in the .chm file.
# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it
# enables the Previous and Next buttons.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
@ -1470,11 +1502,11 @@ SEARCHENGINE = YES
# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
# implemented using a web server instead of a web client using Javascript. There
# are two flavours of web server based searching depending on the
# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for
# searching and an index file used by the script. When EXTERNAL_SEARCH is
# enabled the indexing and searching needs to be provided by external tools. See
# the section "External Indexing and Searching" for details.
# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
# setting. When disabled, doxygen will generate a PHP script for searching and
# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
# and searching needs to be provided by external tools. See the section
# "External Indexing and Searching" for details.
# The default value is: NO.
# This tag requires that the tag SEARCHENGINE is set to YES.
@ -1602,17 +1634,19 @@ EXTRA_PACKAGES =
#
# Note: Only use a user-defined header if you know what you are doing! The
# following commands have a special meaning inside the header: $title,
# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will
# replace them by respectively the title of the page, the current date and time,
# only the current date, the version number of doxygen, the project name (see
# PROJECT_NAME), or the project number (see PROJECT_NUMBER).
# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
# $projectbrief, $projectlogo. Doxygen will replace $title with the empy string,
# for the replacement values of the other commands the user is refered to
# HTML_HEADER.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HEADER =
# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
# generated LaTeX document. The footer should contain everything after the last
# chapter. If it is left blank doxygen will generate a standard footer.
# chapter. If it is left blank doxygen will generate a standard footer. See
# LATEX_HEADER for more information on how to generate a default footer and what
# special commands can be used inside the footer.
#
# Note: Only use a user-defined footer if you know what you are doing!
# This tag requires that the tag GENERATE_LATEX is set to YES.
@ -1636,7 +1670,7 @@ LATEX_EXTRA_FILES =
PDF_HYPERLINKS = YES
# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
# the PDF file directly from the LaTeX files. Set this option to YES to get a
# higher quality PDF documentation.
# The default value is: YES.
@ -1762,6 +1796,13 @@ MAN_OUTPUT = man
MAN_EXTENSION = .3
# The MAN_SUBDIR tag determines the name of the directory created within
# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
# MAN_EXTENSION with the initial . removed.
# This tag requires that the tag GENERATE_MAN is set to YES.
MAN_SUBDIR =
# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
# will generate one additional man file for each entity documented in the real
# man page(s). These additional files only source the real man page, but without
@ -1789,18 +1830,6 @@ GENERATE_XML = NO
XML_OUTPUT = xml
# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a
# validating XML parser to check the syntax of the XML files.
# This tag requires that the tag GENERATE_XML is set to YES.
XML_SCHEMA =
# The XML_DTD tag can be used to specify a XML DTD, which can be used by a
# validating XML parser to check the syntax of the XML files.
# This tag requires that the tag GENERATE_XML is set to YES.
XML_DTD =
# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
# listings (including syntax highlighting and cross-referencing information) to
# the XML output. Note that enabling this will significantly increase the size
@ -1828,6 +1857,15 @@ GENERATE_DOCBOOK = NO
DOCBOOK_OUTPUT = docbook
# If the DOCBOOK_PROGRAMLISTING tag is set to YES doxygen will include the
# program listings (including syntax highlighting and cross-referencing
# information) to the DOCBOOK output. Note that enabling this will significantly
# increase the size of the DOCBOOK output.
# The default value is: NO.
# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
DOCBOOK_PROGRAMLISTING = NO
#---------------------------------------------------------------------------
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
@ -1947,9 +1985,9 @@ PREDEFINED =
EXPAND_AS_DEFINED =
# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
# remove all refrences to function-like macros that are alone on a line, have an
# all uppercase name, and do not end with a semicolon. Such function macros are
# typically used for boiler-plate code, and will confuse the parser if not
# remove all references to function-like macros that are alone on a line, have
# an all uppercase name, and do not end with a semicolon. Such function macros
# are typically used for boiler-plate code, and will confuse the parser if not
# removed.
# The default value is: YES.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
@ -1969,7 +2007,7 @@ SKIP_FUNCTION_MACROS = YES
# where loc1 and loc2 can be relative or absolute paths or URLs. See the
# section "Linking to external documentation" for more information about the use
# of tag files.
# Note: Each tag file must have an unique name (where the name does NOT include
# Note: Each tag file must have a unique name (where the name does NOT include
# the path). If a tag file is not located in the directory in which doxygen is
# run, you must also specify the path to the tagfile here.
@ -2047,7 +2085,7 @@ HIDE_UNDOC_RELATIONS = NO
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
# Bell Labs. The other options in this section have no effect if this option is
# set to NO
# The default value is: NO.
# The default value is: YES.
HAVE_DOT = YES
@ -2061,7 +2099,7 @@ HAVE_DOT = YES
DOT_NUM_THREADS = 0
# When you want a differently looking font n the dot files that doxygen
# When you want a differently looking font in the dot files that doxygen
# generates you can specify the font name using DOT_FONTNAME. You need to make
# sure dot is able to find the font, which can be done by putting it in a
# standard location or by setting the DOTFONTPATH environment variable or by
@ -2199,7 +2237,9 @@ DIRECTORY_GRAPH = YES
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
# to make the SVG files visible in IE 9+ (other browsers do not have this
# requirement).
# Possible values are: png, jpg, gif and svg.
# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
# gif:cairo:gd, gif:gd, gif:gd:gd and svg.
# The default value is: png.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2242,6 +2282,15 @@ MSCFILE_DIRS =
DIAFILE_DIRS =
# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
# path where java can find the plantuml.jar file. If left blank, it is assumed
# PlantUML is not used or called during a preprocessing step. Doxygen will
# generate a warning when it encounters a \startuml command in this case and
# will not generate output for the diagram.
# This tag requires that the tag HAVE_DOT is set to YES.
PLANTUML_JAR_PATH =
# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
# that will be shown in the graph. If the number of nodes in a graph becomes
# larger than this value, doxygen will truncate the graph, which is visualized

View File

@ -19,22 +19,36 @@
##
ACLOCAL_AMFLAGS = -I autostuff
AM_LIBTOOLFLAGS = --silent
GNUMAKEFLAGS = --no-print-directory
AM_CPPFLAGS = -I$(top_srcdir)
# Enable more compiler warnings.
AM_CFLAGS = -std=c99 -Wall -Wextra -pedantic -Wmissing-prototypes -Wshadow
lib_LTLIBRARIES = libserialport.la
libserialport_la_SOURCES = serialport.c
libserialport_la_SOURCES = serialport.c libserialport_internal.h
if LINUX
libserialport_la_SOURCES += linux_termios.c linux_termios.h
libserialport_la_SOURCES += linux.c linux_termios.c linux_termios.h
endif
if WIN32
libserialport_la_SOURCES += windows.c
endif
if MACOSX
libserialport_la_SOURCES += macosx.c
endif
if FREEBSD
libserialport_la_SOURCES += freebsd.c
endif
libserialport_la_LIBADD = $(LIBOBJS)
libserialport_la_LIBADD = $(SP_LIBS)
libserialport_la_LDFLAGS = $(SP_LIB_LDFLAGS)
libserialport_la_LDFLAGS = -version-info $(SP_LIB_VERSION) -no-undefined
if MACOSX
libserialport_la_LDFLAGS += -framework IOKit -framework CoreFoundation
endif
library_includedir = $(includedir)
library_include_HEADERS = libserialport.h
nodist_include_HEADERS = libserialport.h
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libserialport.pc
@ -43,11 +57,12 @@ EXTRA_DIST = Doxyfile
MAINTAINERCLEANFILES = ChangeLog
.PHONY: ChangeLog
.PHONY: ChangeLog doc
ChangeLog:
git --git-dir $(top_srcdir)/.git log > ChangeLog || touch ChangeLog
git --git-dir '$(top_srcdir)/.git' log >$@ || touch $@
dist-hook: ChangeLog
doc: $(library_include_HEADERS) $(top_srcdir)/Doxyfile
doc: $(nodist_include_HEADERS) $(top_srcdir)/Doxyfile
doxygen $(top_srcdir)/Doxyfile

76
NEWS
View File

@ -1,3 +1,79 @@
0.1.1 (2016-01-27)
------------------
Note: This release does NOT change the libserialport API or ABI in
backwards-incompatible ways. Programs using libserialport should
continue to work fine without recompiling or relinking.
* New API calls:
- sp_get_port_description(): Obtain a user friendly port description.
- sp_get_port_transport(): Obtain the underlying transport type.
- sp_get_port_usb_bus_address(): Obtain the USB bus number & device address.
- sp_get_port_usb_vid_pid(): Obtain the USB VID and PID.
- sp_get_port_usb_manufacturer(): Obtain the USB manufacturer string.
- sp_get_port_usb_product(): Obtain the USB product string.
- sp_get_port_usb_serial(): Obtain the USB serial number string.
- sp_get_port_bluetooth_address(): Obtain the Bluetooth MAC address.
- sp_blocking_read_next(): Read bytes from the specified serial port,
returning as soon as any data is available.
* API related additions:
- enum sp_mode: Add SP_MODE_READ_WRITE.
- Add enum sp_transport with the following enum entries:
SP_TRANSPORT_NATIVE, SP_TRANSPORT_USB, SP_TRANSPORT_BLUETOOTH.
* Fix the build on platforms where port enumeration and/or port
metadata is not available or implemented.
* Build system:
- Don't set/override "user variables" such as CFLAGS or LDFLAGS, since
those are meant to be controlled by the "user" (bug #577).
- Modernize and cleanup autotools setup.
* Remove trailing commas in libserialport.h enum definitions to allow
the code to build with the -pedantic compiler option.
* Fix various issues to allow successful builds with -std=c99.
* Fix various (non-fatal) compiler warnings.
* Fix various memory leaks (bug #419, among others).
* Fix a potential overflow of the timeout parameter of poll().
* Fix ReadFile() ERROR_IO_PENDING in sp_nonblocking_read() (bug #707).
* Fix a glibc >= 2.20 compiler warning for deprecated _BSD_SOURCE (bug #716).
* Rename a 'signals' parameter to 'signal_mask' to avoid a conflict with Qt.
* Fix two potential segfaults in sp_get_port_handle() and sp_get_config_*()
(which only occurred if the user incorrectly passed in a NULL argument).
* sp_list_ports(): Actually set list_ptr NULL as documented.
* Use "Port not open" debug message when a closed port is used (bug #710).
* Linux:
- The libudev requirement has been dropped (/sys is now used directly).
- Fix a build issue in the get_termios_get_ioctl() call (bug #396).
- Fix sp_flush() so that it only flushes the requested buffers.
- Fix a build error if BOTHER is not available (bug #363).
- If present, add the USB "description" for better port identification.
- Handle the case when /sys/class/tty/ entries are not symlinks.
* Windows:
- MinGW-w64 (mingw-w64.sf.net) is now required for building, and MinGW
(mingw.org) is no longer supported (bug #393).
- Fix a build issue due to a missing <initguid.h> #include.
- Fix a build issue due to the missing -lsetupapi linker argument.
- Use libtool's -no-undefined option to allow shared (DLL) builds.
- Fix a compile error due to multiply or not defined GUIDs (bug #416).
- Fix a build issue related to the SP_PRIV/SP_API macros (bug #553).
- Fix a bug where an uninitialized pointer was free()'d (bug #512).
- Fix a bug wrt the SetupDiOpenDevRegKey() return code (bug #499).
- Fix a bug wrt restart of RX/error wait operations after read (bug #421).
- Fix a bug wrt WaitCommEvents() and/or fAbortOnError handling (bug #341).
- Fix USB iSerial queries on USB composite devices.
- Strip CR/LF from end of system error messages (bug #585).
- Avoid unnecessary calls to the SetCommTimeouts() function (bug #586).
- Fix a bug in the Windows implementation of sp_blocking_read_next().
- Fix an ERROR_SEM_TIMEOUT related issue in sp_blocking_write().
* FreeBSD:
- Implement serial port enumeration for FreeBSD systems.
* Android:
- Fix a portability issue wrt the unavailable 'serial_struct' (bug #376).
- Fix a portability issue wrt a readlinkat() call (bug #377).
* Mac OS X:
- Fix port listing on Mac OS X 10.11 (El Capitan).
* README: Various documentation updates and fixes.
* AUTHORS: Add/update list of contributors.
* Doxygen API docs: Various documentation updates and fixes.
0.1.0 (2014-05-06)
------------------

37
README
View File

@ -11,6 +11,7 @@ transparently on any platform supported by the library.
The operations that are supported are:
- Port enumeration (obtaining a list of serial ports on the system).
- Obtaining port metadata (USB device information, Bluetooth address, etc).
- Opening and closing ports.
- Setting port parameters (baud rate, parity, etc).
- Reading, writing and flushing data.
@ -24,33 +25,29 @@ Status
The library should build and work on any Windows or Unix-based system. If it
does not, please submit a bug.
Enumeration is currently only implemented on Windows, Mac OS X and Linux. On
other systems enumeration is not supported, but ports can still be opened by
name and then used.
Enumeration is currently implemented on Windows, Mac OS X, FreeBSD and Linux.
On other systems enumeration is not supported, but ports can still be opened
by name and then used.
If you know how to enumerate available ports on another OS, please submit a bug
with this information, or better still a patch implementing it.
Future
======
Future versions will add additional API calls for obtaining metadata about a
port, e.g. for USB devices the USB VID and PID of the underlying device.
Dependencies
============
On Linux, libudev is required. On other systems no other libraries are required.
The libudev dependency could be eliminated in favour of direct sysfs queries at
the cost of some brevity. This is not currently a priority but if you feel like
doing this feel free to submit a patch.
No other libraries are required.
Building
========
The package uses a GNU style build system and requires a Unix style shell.
On Windows it can be built with the MinGW toolchain and MSYS environment.
Windows builds can be created natively with the MinGW-w64 toolchain and
MSYS2 environment, or cross-compiled using a MinGW-w64 toolchain:
http://mingw-w64.sourceforge.net/
The "old" MinGW from http://mingw.org/ is not supported.
Run "./autogen.sh" to generate the build system, "./configure" to setup, then
"make" to build the library and "make install" to install it.
@ -59,3 +56,13 @@ API
===
Doxygen API documentation is included.
It can also be viewed online at:
http://sigrok.org/api/libserialport/unstable/
Website
=======
http://sigrok.org/wiki/Libserialport

View File

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/sh -e
##
## This file is part of the libserialport project.
##
@ -19,42 +19,8 @@
## along with this program. If not, see <http://www.gnu.org/licenses/>.
##
OS=`uname`
LIBTOOLIZE=libtoolize
ACLOCAL_DIR=
if [ "x$OS" = "xDarwin" ]; then
LIBTOOLIZE=glibtoolize
if [ -d /sw/share/aclocal ]; then
# fink installs aclocal macros here
ACLOCAL_DIR="-I /sw/share/aclocal"
elif [ -d /opt/local/share/aclocal ]; then
# Macports installs aclocal macros here
ACLOCAL_DIR="-I /opt/local/share/aclocal"
elif [ -d /usr/local/share/aclocal ]; then
# Homebrew installs aclocal macros here
ACLOCAL_DIR="-I /usr/local/share/aclocal"
elif [ -d /usr/share/aclocal ]; then
# Xcode installs aclocal macros here
ACLOCAL_DIR="-I /usr/share/aclocal"
fi
elif [ "x$OS" = "xMINGW32_NT-5.1" ]; then
# Windows XP
ACLOCAL_DIR="-I /usr/local/share/aclocal"
elif [ "x$OS" = "xMINGW32_NT-6.0" ]; then
# Windows Vista
ACLOCAL_DIR="-I /usr/local/share/aclocal"
elif [ "x$OS" = "xMINGW32_NT-6.1" ]; then
# Windows 7
ACLOCAL_DIR="-I /usr/local/share/aclocal"
fi
echo "Generating build system..."
${LIBTOOLIZE} --install --copy --quiet || exit 1
aclocal ${ACLOCAL_DIR} || exit 1
automake --add-missing --copy || exit 1
autoconf || exit 1
test -n "$srcdir" || srcdir=`dirname "$0"`
test -n "$srcdir" || srcdir=.
test -d "$srcdir/autostuff" || mkdir "$srcdir/autostuff"
autoreconf --force --install --verbose "$srcdir"

View File

@ -1,32 +1,38 @@
/**
* Set the {2} for the specified serial port.
*
* @param port Pointer to port structure.
* @param {0} {3}.
* @param[in] port Pointer to a port structure. Must not be NULL.
* @param[in] {0} {3}.
*
* @return SP_OK upon success, a negative error code otherwise.
*
* @since 0.1.0
*/
enum sp_return sp_set_{0}(struct sp_port *port, {1} {0});
/**
* Get the {2} from a port configuration.
*
* The user should allocate a variable of type {1} and pass a pointer to this
* to receive the result.
* The user should allocate a variable of type {1} and
* pass a pointer to this to receive the result.
*
* @param config Pointer to configuration structure.
* @param {0}_ptr Pointer to variable to store result.
* @param[in] config Pointer to a configuration structure. Must not be NULL.
* @param[out] {0}_ptr Pointer to a variable to store the result. Must not be NULL.
*
* @return SP_OK upon success, a negative error code otherwise.
*
* @since 0.1.0
*/
enum sp_return sp_get_config_{0}(const struct sp_port_config *config, {1} *{0}_ptr);
/**
* Set the {2} in a port configuration.
*
* @param config Pointer to configuration structure.
* @param {0} {3}, or -1 to retain current setting.
* @param[in] config Pointer to a configuration structure. Must not be NULL.
* @param[in] {0} {3}, or -1 to retain the current setting.
*
* @return SP_OK upon success, a negative error code otherwise.
*
* @since 0.1.0
*/
enum sp_return sp_set_config_{0}(struct sp_port_config *config, {1} {0});

View File

@ -24,117 +24,124 @@ AC_PREREQ([2.63])
# libserialport package version number (NOT the same as shared lib version!).
m4_define([sp_package_version_major], [0])
m4_define([sp_package_version_minor], [1])
m4_define([sp_package_version_micro], [0])
m4_define([sp_package_version_micro], [1])
m4_define([sp_package_version], [sp_package_version_major.sp_package_version_minor.sp_package_version_micro])
AC_INIT([libserialport], [sp_package_version], [martin-libserialport@earth.li],
[libserialport], [http://sigrok.org/wiki/Libserialport])
AC_CONFIG_HEADERS([config.h libserialport.h])
AC_CONFIG_MACRO_DIR([autostuff])
AC_CONFIG_AUX_DIR([autostuff])
AC_CANONICAL_TARGET
AH_BOTTOM([#if HAVE_STRUCT_TERMIOS_C_ISPEED && HAVE_STRUCT_TERMIOS_C_OSPEED
# define HAVE_TERMIOS_SPEED 1
#endif
#if HAVE_STRUCT_TERMIOS2_C_ISPEED && HAVE_STRUCT_TERMIOS2_C_OSPEED
# define HAVE_TERMIOS2_SPEED 1
#endif])
# We require at least automake 1.11 (needed for 'silent rules').
AM_INIT_AUTOMAKE([1.11 -Wall -Werror check-news])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
AM_INIT_AUTOMAKE([1.11 -Wall -Werror no-define check-news])
AM_SILENT_RULES([yes])
m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
# Enable more compiler warnings via -Wall and -Wextra.
CFLAGS="$CFLAGS -Wall -Wextra"
# Checks for programs.
AC_PROG_CC
AC_PROG_CPP
AC_PROG_INSTALL
AC_PROG_LN_S
# Initialize libtool.
LT_INIT
# Initialize pkg-config.
# We require at least 0.22, as "Requires.private" behaviour changed there.
PKG_PROG_PKG_CONFIG([0.22])
AC_DEFINE([SP_PACKAGE_VERSION_MAJOR], [sp_package_version_major], [.])
AC_DEFINE([SP_PACKAGE_VERSION_MINOR], [sp_package_version_minor], [.])
AC_DEFINE([SP_PACKAGE_VERSION_MICRO], [sp_package_version_micro], [.])
AC_DEFINE([SP_PACKAGE_VERSION_STRING], ["sp_package_version"], [.])
AC_SUBST([SP_PACKAGE_VERSION], [sp_package_version])
# Library version for libserialport (NOT the same as the package version).
# Carefully read the libtool docs before updating these numbers!
# The algorithm for determining which number to change (and how) is nontrivial!
# http://www.gnu.org/software/libtool/manual/libtool.html#Updating-version-info
SP_LIB_VERSION_CURRENT=0
SP_LIB_VERSION_CURRENT=1
SP_LIB_VERSION_REVISION=0
SP_LIB_VERSION_AGE=0
SP_LIB_VERSION="$SP_LIB_VERSION_CURRENT:$SP_LIB_VERSION_REVISION:$SP_LIB_VERSION_AGE"
SP_LIB_LDFLAGS="-version-info $SP_LIB_VERSION"
AC_SUBST(SP_LIB_VERSION_CURRENT)
AC_SUBST(SP_LIB_VERSION_REVISION)
AC_SUBST(SP_LIB_VERSION_AGE)
AC_SUBST(SP_LIB_VERSION)
AC_SUBST(SP_LIB_LDFLAGS)
SP_LIB_VERSION_AGE=1
AC_SUBST([SP_LIB_VERSION],
["$SP_LIB_VERSION_CURRENT:$SP_LIB_VERSION_REVISION:$SP_LIB_VERSION_AGE"])
# Checks for libraries.
AC_DEFINE_UNQUOTED([SP_LIB_VERSION_CURRENT], [$SP_LIB_VERSION_CURRENT], [.])
AC_DEFINE_UNQUOTED([SP_LIB_VERSION_REVISION], [$SP_LIB_VERSION_REVISION], [.])
AC_DEFINE_UNQUOTED([SP_LIB_VERSION_AGE], [$SP_LIB_VERSION_AGE], [.])
AC_DEFINE_UNQUOTED([SP_LIB_VERSION_STRING], ["$SP_LIB_VERSION"], [.])
# This variable collects the pkg-config names of all detected libs.
# It is then used to construct the "Requires.private:" field in the
# libserialport.pc file.
SP_PKGLIBS=""
AM_CONDITIONAL([LINUX], [test -z "${host_os##linux*}"])
AM_CONDITIONAL([WIN32], [test -z "${host_os##mingw*}" || test -z "${host_os##cygwin*}"])
AM_CONDITIONAL([MACOSX], [test -z "${host_os##darwin*}"])
AM_CONDITIONAL([FREEBSD], [test -z "${host_os##freebsd*}"])
case $target_os in
*linux*)
AM_CONDITIONAL([LINUX], true)
# On Linux libudev is currently required for enumeration.
AC_ARG_WITH([libudev],
[AS_HELP_STRING([--with-libudev],
[use libudev for serial port enumeration @<:@default=check@:>@])],
[], [with_libudev=check])
AS_IF([test "x$with_libudev" != xno], [
PKG_CHECK_MODULES([libudev], [libudev >= 0],
[CFLAGS="$CFLAGS $libudev_CFLAGS"; LIBS="$LIBS $libudev_LIBS";
SP_PKGLIBS="$SP_PKGLIBS libudev"; AC_DEFINE(HAVE_LIBUDEV)])], [])
;;
*darwin*)
AM_CONDITIONAL([LINUX], false)
LDFLAGS="$LDFLAGS -Wl,-framework -Wl,IOKit -Wl,-framework -Wl,CoreFoundation"
AC_CHECK_HEADER(IOKit/IOKitLib.h, [], [AC_MSG_ERROR([IOKit/IOKitLib.h not found])])
;;
*)
AM_CONDITIONAL([LINUX], false)
esac
AM_COND_IF([WIN32], [SP_LIBS='-lsetupapi'], [SP_LIBS=])
AC_SUBST([SP_LIBS])
AC_SUBST(SP_PKGLIBS)
AM_COND_IF([MACOSX], [AC_CHECK_HEADER([IOKit/IOKitLib.h], [],
[AC_MSG_ERROR([IOKit/IOKitLib.h not found])])])
AS_CASE([$host_os], [linux*|darwin*|mingw*|cygwin*|freebsd*],, [
AC_DEFINE([NO_ENUMERATION], [1], [Enumeration is unsupported.])
AC_DEFINE([NO_PORT_METADATA], [1], [Port metadata is unavailable.])
])
AC_SYS_LARGEFILE
# Define size_t if not defined as standard.
AC_TYPE_SIZE_T
# Check for specific termios structures.
AC_CHECK_TYPE([struct termios2], [AC_DEFINE(HAVE_TERMIOS2, 1)], [], [[#include <linux/termios.h>]])
AC_CHECK_TYPE([struct termiox], [AC_DEFINE(HAVE_TERMIOX, 1)], [], [[#include <linux/termios.h>]])
AC_CHECK_MEMBERS([struct termios.c_ispeed, struct termios.c_ospeed],
[AC_DEFINE(HAVE_TERMIOS_SPEED, 1)], [], [[#include <linux/termios.h>]])
AC_CHECK_MEMBERS([struct termios2.c_ispeed, struct termios2.c_ospeed],
[AC_DEFINE(HAVE_TERMIOS2_SPEED, 1)], [], [[#include <linux/termios.h>]])
AC_CHECK_TYPES([struct termios2, struct termiox],,,
[[#include <linux/termios.h>]])
AC_CHECK_MEMBERS([struct termios.c_ispeed, struct termios.c_ospeed,
struct termios2.c_ispeed, struct termios2.c_ospeed],,,
[[#include <linux/termios.h>]])
AC_SUBST(MAKEFLAGS, '--no-print-directory')
AC_SUBST(AM_LIBTOOLFLAGS, '--silent')
# Check for the BOTHER definition, needed for setting arbitrary baud rates.
# We can't just #ifdef BOTHER in the code, because of the separation between
# code using libc headers and code using kernel termios.h headers.
AC_CHECK_DECLS([BOTHER],,, [[#include <linux/termios.h>]])
SP_PACKAGE_VERSION_MAJOR=sp_package_version_major
SP_PACKAGE_VERSION_MINOR=sp_package_version_minor
SP_PACKAGE_VERSION_MICRO=sp_package_version_micro
SP_PACKAGE_VERSION=sp_package_version
# Check for serial_struct.
AC_CHECK_TYPES([struct serial_struct],,, [[#include <linux/serial.h>]])
AC_SUBST(SP_PACKAGE_VERSION_MAJOR)
AC_SUBST(SP_PACKAGE_VERSION_MINOR)
AC_SUBST(SP_PACKAGE_VERSION_MICRO)
AC_SUBST(SP_PACKAGE_VERSION)
AC_CACHE_CHECK([for visibility control], [sp_cv_visibility_control], [
sp_saved_CFLAGS=$CFLAGS
CFLAGS="$CFLAGS -Werror"
AC_COMPILE_IFELSE([AC_LANG_SOURCE(
[[__attribute__((visibility("hidden"))) void foo(void) {}]])],
[sp_cv_visibility_control=attribute],
[AC_COMPILE_IFELSE([AC_LANG_SOURCE(
[[__declspec(dllexport) void foo(void) {}]])],
[sp_cv_visibility_control=declspec],
[sp_cv_visibility_control=none])])
CFLAGS=$sp_saved_CFLAGS
])
AS_CASE([$sp_cv_visibility_control],
[attribute], [SP_API='__attribute__((visibility("default")))'
SP_PRIV='__attribute__((visibility("hidden")))'],
[declspec], [SP_API='__declspec(dllexport)' SP_PRIV=],
[SP_API= SP_PRIV=])
AC_DEFINE_UNQUOTED([SP_API], [$SP_API], [Macro preceding public API functions])
AC_DEFINE_UNQUOTED([SP_PRIV], [$SP_PRIV], [Macro preceding private functions])
AC_CONFIG_FILES([Makefile libserialport.h libserialport.pc])
AC_CONFIG_FILES([Makefile libserialport.pc])
AC_OUTPUT
echo
echo "libserialport configuration summary:"
echo
echo " - Package version (major.minor.micro): $SP_PACKAGE_VERSION"
echo " - Library version (current:revision:age): $SP_LIB_VERSION"
echo " - Prefix: $prefix"
echo " - Building on: $build"
echo " - Building for: $host"
echo
cat >&AS_MESSAGE_FD <<_EOF
libserialport configuration summary:
- Package version (major.minor.micro): $SP_PACKAGE_VERSION
- Library version (current:revision:age): $SP_LIB_VERSION
- Prefix: $prefix
- Building on: $build
- Building for: $host
_EOF

384
freebsd.c Normal file
View File

@ -0,0 +1,384 @@
/*
* This file is part of the libserialport project.
*
* Copyright (C) 2015 Uffe Jakobsen <uffe@uffe.org>
*
* This program 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 3 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* FreeBSD platform specific serial port information:
*
* FreeBSD has two device nodes for each serial port:
*
* 1) a call-in port
* 2) a call-out port
*
* Quoting FreeBSD Handbook section 26.2.1
* (https://www.freebsd.org/doc/handbook/serial.html)
*
* In FreeBSD, each serial port is accessed through an entry in /dev.
* There are two different kinds of entries:
*
* 1) Call-in ports are named /dev/ttyuN where N is the port number,
* starting from zero. If a terminal is connected to the first serial port
* (COM1), use /dev/ttyu0 to refer to the terminal. If the terminal is on
* the second serial port (COM2), use /dev/ttyu1, and so forth.
* Generally, the call-in port is used for terminals. Call-in ports require
* that the serial line assert the "Data Carrier Detect" signal to work
* correctly.
*
* 2) Call-out ports are named /dev/cuauN on FreeBSD versions 10.x and higher
* and /dev/cuadN on FreeBSD versions 9.x and lower. Call-out ports are
* usually not used for terminals, but are used for modems. The call-out
* port can be used if the serial cable or the terminal does not support
* the "Data Carrier Detect" signal.
*
* FreeBSD also provides initialization devices (/dev/ttyuN.init and
* /dev/cuauN.init or /dev/cuadN.init) and locking devices (/dev/ttyuN.lock
* and /dev/cuauN.lock or /dev/cuadN.lock). The initialization devices are
* used to initialize communications port parameters each time a port is
* opened, such as crtscts for modems which use RTS/CTS signaling for flow
* control. The locking devices are used to lock flags on ports to prevent
* users or programs changing certain parameters.
*
* In line with the above device naming USB-serial devices have
* the following naming:
*
* 1) call-in ports: /dev/ttyUN where N is the port number
* 2) call-out ports: /dev/cuaUN where N is the port number
*
* See also: ucom(4), https://www.freebsd.org/cgi/man.cgi?query=ucom
*
* Getting USB serial port device description:
*
* In order to query USB serial ports for device description - the mapping
* between the kernel device driver instances must be correlated with the
* above mentioned device nodes.
*
* 1) For each USB-serial port (/dev/cuaUN) use libusb to lookup the device
* description and the name of the kernel device driver instance.
* 2) Query sysctl for the kernel device driver instance info.
* 3) Derive the ttyname and (serial) port count for the kernel device
* driver instance.
* 4) If sysctl ttyname and port matches /dev/cuaUN apply the libusb
* device description.
*/
#include <config.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <dirent.h>
#include <libusb20.h>
#include <libusb20_desc.h>
#include "libserialport.h"
#include "libserialport_internal.h"
#define DEV_CUA_PATH "/dev/cua"
//#define DBG(...) printf(__VA_ARGS__)
#define DBG(...)
static char *strrspn(const char *s, const char *charset)
{
char *t = (char *)s + strlen(s);
while (t != (char *)s)
if (!strchr(charset, *(--t)))
return ++t;
return t;
}
static int strend(const char *str, const char *pattern)
{
size_t slen = strlen(str);
size_t plen = strlen(pattern);
if (slen >= plen)
return (!memcmp(pattern, (str + slen - plen), plen));
return 0;
}
static int libusb_query_port(struct libusb20_device *dev, int idx,
char **drv_name_str, char **drv_inst_str)
{
int rc;
char *j;
char sbuf[FILENAME_MAX];
if (!drv_name_str || !drv_inst_str)
return -1;
rc = libusb20_dev_kernel_driver_active(dev, idx);
if (rc < 0)
return rc;
sbuf[0] = 0;
libusb20_dev_get_iface_desc(dev, idx, (char *)&sbuf,
(uint8_t)sizeof(sbuf) - 1);
if (sbuf[0] == 0)
return rc;
DBG("Device interface descriptor: idx=%003d '%s'\n", idx, sbuf);
j = strchr(sbuf, ':');
if (j > sbuf) {
sbuf[j - sbuf] = 0;
/*
* The device driver name may contain digits that
* is not a part of the device instance number - like "u3g".
*/
j = strrspn(sbuf, "0123456789");
if (j > sbuf) {
*drv_name_str = strndup(sbuf, j - sbuf);
*drv_inst_str = strdup((j));
}
}
return rc;
}
static int sysctl_query_dev_drv(const char *drv_name_str,
const char *drv_inst_str, char **ttyname, int *const ttyport_cnt)
{
int rc;
char sbuf[FILENAME_MAX];
char tbuf[FILENAME_MAX];
size_t tbuf_len;
if (!ttyname || !ttyport_cnt)
return -1;
snprintf(sbuf, sizeof(sbuf), "dev.%s.%s.ttyname", drv_name_str,
drv_inst_str);
tbuf_len = sizeof(tbuf) - 1;
if ((rc = sysctlbyname(sbuf, tbuf, &tbuf_len, NULL, 0)) != 0)
return rc;
tbuf[tbuf_len] = 0;
*ttyname = strndup(tbuf, tbuf_len);
DBG("sysctl: '%s' (%d) (%d): '%.*s'\n", sbuf, rc, (int)tbuf_len,
(int)tbuf_len, tbuf);
snprintf(sbuf, sizeof(sbuf), "dev.%s.%s.ttyports",
drv_name_str, drv_inst_str);
tbuf_len = sizeof(tbuf) - 1;
rc = sysctlbyname(sbuf, tbuf, &tbuf_len, NULL, 0);
if (rc == 0) {
*ttyport_cnt = *(uint32_t *)tbuf;
DBG("sysctl: '%s' (%d) (%d): '%d'\n", sbuf, rc,
(int)tbuf_len, (int)ttyport_cnt);
} else {
*ttyport_cnt = 0;
}
return rc;
}
static int populate_port_struct_from_libusb_desc(struct sp_port *const port,
struct libusb20_device *dev)
{
char tbuf[FILENAME_MAX];
/* Populate port structure from libusb description. */
struct LIBUSB20_DEVICE_DESC_DECODED *dev_desc =
libusb20_dev_get_device_desc(dev);
if (!dev_desc)
return -1;
port->transport = SP_TRANSPORT_USB;
port->usb_vid = dev_desc->idVendor;
port->usb_pid = dev_desc->idProduct;
port->usb_bus = libusb20_dev_get_bus_number(dev);
port->usb_address = libusb20_dev_get_address(dev);
if (libusb20_dev_req_string_simple_sync
(dev, dev_desc->iManufacturer, tbuf, sizeof(tbuf)) == 0) {
port->usb_manufacturer = strdup(tbuf);
}
if (libusb20_dev_req_string_simple_sync
(dev, dev_desc->iProduct, tbuf, sizeof(tbuf)) == 0) {
port->usb_product = strdup(tbuf);
}
if (libusb20_dev_req_string_simple_sync
(dev, dev_desc->iSerialNumber, tbuf, sizeof(tbuf)) == 0) {
port->usb_serial = strdup(tbuf);
}
/* If present, add serial to description for better identification. */
tbuf[0] = '\0';
if (port->usb_product && port->usb_product[0])
strncat(tbuf, port->usb_product, sizeof(tbuf) - 1);
else
strncat(tbuf, libusb20_dev_get_desc(dev), sizeof(tbuf) - 1);
if (port->usb_serial && port->usb_serial[0]) {
strncat(tbuf, " ", sizeof(tbuf) - 1);
strncat(tbuf, port->usb_serial, sizeof(tbuf) - 1);
}
port->description = strdup(tbuf);
port->bluetooth_address = NULL;
return 0;
}
SP_PRIV enum sp_return get_port_details(struct sp_port *port)
{
int rc;
struct libusb20_backend *be;
struct libusb20_device *dev, *dev_last;
char tbuf[FILENAME_MAX];
char *cua_sfx;
int cua_dev_found;
uint8_t idx;
int sub_inst;
DBG("Portname: '%s'\n", port->name);
if (!strncmp(port->name, DEV_CUA_PATH, strlen(DEV_CUA_PATH))) {
cua_sfx = port->name + strlen(DEV_CUA_PATH);
DBG("'%s': '%s'\n", DEV_CUA_PATH, cua_sfx);
} else {
RETURN_ERROR(SP_ERR_ARG, "Device name not recognized");
}
/* Native UART enumeration. */
if ((cua_sfx[0] == 'u') || (cua_sfx[0] == 'd')) {
port->transport = SP_TRANSPORT_NATIVE;
snprintf(tbuf, sizeof(tbuf), "cua%s", cua_sfx);
port->description = strdup(tbuf);
RETURN_OK();
}
/* USB device enumeration. */
dev = dev_last = NULL;
be = libusb20_be_alloc_default();
cua_dev_found = 0;
while (cua_dev_found == 0) {
dev = libusb20_be_device_foreach(be, dev_last);
if (!dev)
break;
libusb20_dev_open(dev, 0);
DBG("Device descriptor: '%s'\n", libusb20_dev_get_desc(dev));
for (idx = 0; idx <= UINT8_MAX - 1; idx++) {
char *drv_name_str = NULL;
char *drv_inst_str = NULL;
char *ttyname = NULL;
int ttyport_cnt;
rc = libusb_query_port(dev, idx, &drv_name_str, &drv_inst_str);
if (rc == 0) {
rc = sysctl_query_dev_drv(drv_name_str,
drv_inst_str, &ttyname, &ttyport_cnt);
if (rc == 0) {
/* Handle multiple subinstances of serial ports in the same driver instance. */
for (sub_inst = 0; sub_inst < ttyport_cnt; sub_inst++) {
if (ttyport_cnt == 1)
snprintf(tbuf, sizeof(tbuf), "%s", ttyname);
else
snprintf(tbuf, sizeof(tbuf), "%s.%d", ttyname, sub_inst);
if (!strcmp(cua_sfx, tbuf)) {
DBG("MATCH: '%s' == '%s'\n", cua_sfx, tbuf);
cua_dev_found = 1;
populate_port_struct_from_libusb_desc(port, dev);
break; /* Break out of sub instance loop. */
}
}
}
}
/* Clean up. */
if (ttyname)
free(ttyname);
if (drv_name_str)
free(drv_name_str);
if (drv_inst_str)
free(drv_inst_str);
if (cua_dev_found)
break; /* Break out of USB device port idx loop. */
}
libusb20_dev_close(dev);
dev_last = dev;
}
libusb20_be_free(be);
if (cua_dev_found == 0)
DBG("WARN: Found no match '%s' %s'\n", port->name, cua_sfx);
RETURN_OK();
}
SP_PRIV enum sp_return list_ports(struct sp_port ***list)
{
DIR *dir;
struct dirent entry;
struct dirent *result;
struct termios tios;
char name[PATH_MAX];
int fd, ret;
DEBUG("Enumerating tty devices");
if (!(dir = opendir("/dev")))
RETURN_FAIL("Could not open dir /dev");
DEBUG("Iterating over results");
while (!readdir_r(dir, &entry, &result) && result) {
ret = SP_OK;
if (entry.d_type != DT_CHR)
continue;
if (strncmp(entry.d_name, "cuaU", 4) != 0)
if (strncmp(entry.d_name, "cuau", 4) != 0)
if (strncmp(entry.d_name, "cuad", 4) != 0)
continue;
if (strend(entry.d_name, ".init"))
continue;
if (strend(entry.d_name, ".lock"))
continue;
snprintf(name, sizeof(name), "/dev/%s", entry.d_name);
DEBUG_FMT("Found device %s", name);
/* Check that we can open tty/cua device in rw mode - we need that. */
if ((fd = open(name, O_RDWR | O_NONBLOCK | O_NOCTTY | O_TTY_INIT | O_CLOEXEC)) < 0) {
DEBUG("Open failed, skipping");
continue;
}
/* Sanity check if we got a real tty. */
if (!isatty(fd)) {
close(fd);
continue;
}
ret = tcgetattr(fd, &tios);
close(fd);
if (ret < 0 || cfgetospeed(&tios) <= 0 || cfgetispeed(&tios) <= 0)
continue;
DEBUG_FMT("Found port %s", name);
DBG("%s: %s\n", __func__, entry.d_name);
*list = list_append(*list, name);
if (!*list) {
SET_ERROR(ret, SP_ERR_MEM, "List append failed");
break;
}
}
closedir(dir);
return ret;
}

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,8 @@ includedir=@includedir@
Name: libserialport
Description: Cross-platform serial port access library.
URL: http://sigrok.org/wiki/Libserialport
Requires.private: @SP_PKGLIBS@
Version: @VERSION@
Requires.private:
Version: @SP_PACKAGE_VERSION@
Libs: -L${libdir} -lserialport
Libs.private: @SP_LIBS@
Cflags: -I${includedir}

237
libserialport_internal.h Normal file
View File

@ -0,0 +1,237 @@
/*
* This file is part of the libserialport project.
*
* Copyright (C) 2014 Martin Ling <martin-libserialport@earth.li>
* Copyright (C) 2014 Aurelien Jacobs <aurel@gnuage.org>
*
* This program 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 3 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LIBSERIALPORT_LIBSERIALPORT_INTERNAL_H
#define LIBSERIALPORT_LIBSERIALPORT_INTERNAL_H
#ifdef __linux__
/* For timeradd, timersub, timercmp. */
#define _BSD_SOURCE 1 /* for glibc < 2.19 */
#define _DEFAULT_SOURCE 1 /* for glibc >= 2.20 */
#endif
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <limits.h>
#ifdef _WIN32
#include <windows.h>
#include <tchar.h>
#include <setupapi.h>
#include <cfgmgr32.h>
#undef DEFINE_GUID
#define DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
static const GUID name = { l,w1,w2,{ b1,b2,b3,b4,b5,b6,b7,b8 } }
#include <usbioctl.h>
#include <usbiodef.h>
#else
#include <limits.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <limits.h>
#include <poll.h>
#endif
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/serial/IOSerialKeys.h>
#include <IOKit/serial/ioss.h>
#include <sys/syslimits.h>
#endif
#ifdef __linux__
#include <dirent.h>
#ifndef __ANDROID__
#include "linux/serial.h"
#endif
#include "linux_termios.h"
/* TCGETX/TCSETX is not available everywhere. */
#if defined(TCGETX) && defined(TCSETX) && defined(HAVE_STRUCT_TERMIOX)
#define USE_TERMIOX
#endif
#endif
/* TIOCINQ/TIOCOUTQ is not available everywhere. */
#if !defined(TIOCINQ) && defined(FIONREAD)
#define TIOCINQ FIONREAD
#endif
#if !defined(TIOCOUTQ) && defined(FIONWRITE)
#define TIOCOUTQ FIONWRITE
#endif
/* Non-standard baudrates are not available everywhere. */
#if (defined(HAVE_TERMIOS_SPEED) || defined(HAVE_TERMIOS2_SPEED)) && defined(HAVE_DECL_BOTHER)
#define USE_TERMIOS_SPEED
#endif
struct sp_port {
char *name;
char *description;
enum sp_transport transport;
int usb_bus;
int usb_address;
int usb_vid;
int usb_pid;
char *usb_manufacturer;
char *usb_product;
char *usb_serial;
char *bluetooth_address;
#ifdef _WIN32
char *usb_path;
HANDLE hdl;
COMMTIMEOUTS timeouts;
OVERLAPPED write_ovl;
OVERLAPPED read_ovl;
OVERLAPPED wait_ovl;
DWORD events;
BYTE pending_byte;
BOOL writing;
BOOL wait_running;
#else
int fd;
#endif
};
struct sp_port_config {
int baudrate;
int bits;
enum sp_parity parity;
int stopbits;
enum sp_rts rts;
enum sp_cts cts;
enum sp_dtr dtr;
enum sp_dsr dsr;
enum sp_xonxoff xon_xoff;
};
struct port_data {
#ifdef _WIN32
DCB dcb;
#else
struct termios term;
int controlbits;
int termiox_supported;
int rts_flow;
int cts_flow;
int dtr_flow;
int dsr_flow;
#endif
};
#ifdef _WIN32
typedef HANDLE event_handle;
#else
typedef int event_handle;
#endif
/* Standard baud rates. */
#ifdef _WIN32
#define BAUD_TYPE DWORD
#define BAUD(n) {CBR_##n, n}
#else
#define BAUD_TYPE speed_t
#define BAUD(n) {B##n, n}
#endif
struct std_baudrate {
BAUD_TYPE index;
int value;
};
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
extern void (*sp_debug_handler)(const char *format, ...);
/* Debug output macros. */
#define DEBUG_FMT(fmt, ...) do { \
if (sp_debug_handler) \
sp_debug_handler(fmt ".\n", __VA_ARGS__); \
} while (0)
#define DEBUG(msg) DEBUG_FMT(msg, NULL)
#define DEBUG_ERROR(err, msg) DEBUG_FMT("%s returning " #err ": " msg, __func__)
#define DEBUG_FAIL(msg) do { \
char *errmsg = sp_last_error_message(); \
DEBUG_FMT("%s returning SP_ERR_FAIL: " msg ": %s", __func__, errmsg); \
sp_free_error_message(errmsg); \
} while (0);
#define RETURN() do { \
DEBUG_FMT("%s returning", __func__); \
return; \
} while (0)
#define RETURN_CODE(x) do { \
DEBUG_FMT("%s returning " #x, __func__); \
return x; \
} while (0)
#define RETURN_CODEVAL(x) do { \
switch (x) { \
case SP_OK: RETURN_CODE(SP_OK); \
case SP_ERR_ARG: RETURN_CODE(SP_ERR_ARG); \
case SP_ERR_FAIL: RETURN_CODE(SP_ERR_FAIL); \
case SP_ERR_MEM: RETURN_CODE(SP_ERR_MEM); \
case SP_ERR_SUPP: RETURN_CODE(SP_ERR_SUPP); \
default: RETURN_CODE(SP_ERR_FAIL); \
} \
} while (0)
#define RETURN_OK() RETURN_CODE(SP_OK);
#define RETURN_ERROR(err, msg) do { \
DEBUG_ERROR(err, msg); \
return err; \
} while (0)
#define RETURN_FAIL(msg) do { \
DEBUG_FAIL(msg); \
return SP_ERR_FAIL; \
} while (0)
#define RETURN_INT(x) do { \
int _x = x; \
DEBUG_FMT("%s returning %d", __func__, _x); \
return _x; \
} while (0)
#define RETURN_STRING(x) do { \
char *_x = x; \
DEBUG_FMT("%s returning %s", __func__, _x); \
return _x; \
} while (0)
#define RETURN_POINTER(x) do { \
void *_x = x; \
DEBUG_FMT("%s returning %p", __func__, _x); \
return _x; \
} while (0)
#define SET_ERROR(val, err, msg) do { DEBUG_ERROR(err, msg); val = err; } while (0)
#define SET_FAIL(val, msg) do { DEBUG_FAIL(msg); val = SP_ERR_FAIL; } while (0)
#define TRACE(fmt, ...) DEBUG_FMT("%s(" fmt ") called", __func__, __VA_ARGS__)
#define TRACE_VOID() DEBUG_FMT("%s() called", __func__)
#define TRY(x) do { int retval = x; if (retval != SP_OK) RETURN_CODEVAL(retval); } while (0)
SP_PRIV struct sp_port **list_append(struct sp_port **list, const char *portname);
/* OS-specific Helper functions. */
SP_PRIV enum sp_return get_port_details(struct sp_port *port);
SP_PRIV enum sp_return list_ports(struct sp_port ***list);
#endif

247
linux.c Normal file
View File

@ -0,0 +1,247 @@
/*
* This file is part of the libserialport project.
*
* Copyright (C) 2013 Martin Ling <martin-libserialport@earth.li>
* Copyright (C) 2014 Aurelien Jacobs <aurel@gnuage.org>
*
* This program 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 3 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include "libserialport.h"
#include "libserialport_internal.h"
SP_PRIV enum sp_return get_port_details(struct sp_port *port)
{
/*
* Description limited to 127 char, anything longer
* would not be user friendly anyway.
*/
char description[128];
int bus, address;
unsigned int vid, pid;
char manufacturer[128], product[128], serial[128];
char baddr[32];
const char dir_name[] = "/sys/class/tty/%s/device/%s%s";
char sub_dir[32] = "", file_name[PATH_MAX];
char *ptr, *dev = port->name + 5;
FILE *file;
int i, count;
struct stat statbuf;
if (strncmp(port->name, "/dev/", 5))
RETURN_ERROR(SP_ERR_ARG, "Device name not recognized");
snprintf(file_name, sizeof(file_name), "/sys/class/tty/%s", dev);
if (lstat(file_name, &statbuf) == -1)
RETURN_ERROR(SP_ERR_ARG, "Device not found");
if (!S_ISLNK(statbuf.st_mode))
snprintf(file_name, sizeof(file_name), "/sys/class/tty/%s/device", dev);
count = readlink(file_name, file_name, sizeof(file_name));
if (count <= 0 || count >= (int)(sizeof(file_name) - 1))
RETURN_ERROR(SP_ERR_ARG, "Device not found");
file_name[count] = 0;
if (strstr(file_name, "bluetooth"))
port->transport = SP_TRANSPORT_BLUETOOTH;
else if (strstr(file_name, "usb"))
port->transport = SP_TRANSPORT_USB;
if (port->transport == SP_TRANSPORT_USB) {
for (i = 0; i < 5; i++) {
strcat(sub_dir, "../");
snprintf(file_name, sizeof(file_name), dir_name, dev, sub_dir, "busnum");
if (!(file = fopen(file_name, "r")))
continue;
count = fscanf(file, "%d", &bus);
fclose(file);
if (count != 1)
continue;
snprintf(file_name, sizeof(file_name), dir_name, dev, sub_dir, "devnum");
if (!(file = fopen(file_name, "r")))
continue;
count = fscanf(file, "%d", &address);
fclose(file);
if (count != 1)
continue;
snprintf(file_name, sizeof(file_name), dir_name, dev, sub_dir, "idVendor");
if (!(file = fopen(file_name, "r")))
continue;
count = fscanf(file, "%4x", &vid);
fclose(file);
if (count != 1)
continue;
snprintf(file_name, sizeof(file_name), dir_name, dev, sub_dir, "idProduct");
if (!(file = fopen(file_name, "r")))
continue;
count = fscanf(file, "%4x", &pid);
fclose(file);
if (count != 1)
continue;
port->usb_bus = bus;
port->usb_address = address;
port->usb_vid = vid;
port->usb_pid = pid;
snprintf(file_name, sizeof(file_name), dir_name, dev, sub_dir, "product");
if ((file = fopen(file_name, "r"))) {
if ((ptr = fgets(description, sizeof(description), file))) {
ptr = description + strlen(description) - 1;
if (ptr >= description && *ptr == '\n')
*ptr = 0;
port->description = strdup(description);
}
fclose(file);
}
if (!file || !ptr)
port->description = strdup(dev);
snprintf(file_name, sizeof(file_name), dir_name, dev, sub_dir, "manufacturer");
if ((file = fopen(file_name, "r"))) {
if ((ptr = fgets(manufacturer, sizeof(manufacturer), file))) {
ptr = manufacturer + strlen(manufacturer) - 1;
if (ptr >= manufacturer && *ptr == '\n')
*ptr = 0;
port->usb_manufacturer = strdup(manufacturer);
}
fclose(file);
}
snprintf(file_name, sizeof(file_name), dir_name, dev, sub_dir, "product");
if ((file = fopen(file_name, "r"))) {
if ((ptr = fgets(product, sizeof(product), file))) {
ptr = product + strlen(product) - 1;
if (ptr >= product && *ptr == '\n')
*ptr = 0;
port->usb_product = strdup(product);
}
fclose(file);
}
snprintf(file_name, sizeof(file_name), dir_name, dev, sub_dir, "serial");
if ((file = fopen(file_name, "r"))) {
if ((ptr = fgets(serial, sizeof(serial), file))) {
ptr = serial + strlen(serial) - 1;
if (ptr >= serial && *ptr == '\n')
*ptr = 0;
port->usb_serial = strdup(serial);
}
fclose(file);
}
/* If present, add serial to description for better identification. */
if (port->usb_serial && strlen(port->usb_serial)) {
snprintf(description, sizeof(description),
"%s - %s", port->description, port->usb_serial);
if (port->description)
free(port->description);
port->description = strdup(description);
}
break;
}
} else {
port->description = strdup(dev);
if (port->transport == SP_TRANSPORT_BLUETOOTH) {
snprintf(file_name, sizeof(file_name), dir_name, dev, "", "address");
if ((file = fopen(file_name, "r"))) {
if ((ptr = fgets(baddr, sizeof(baddr), file))) {
ptr = baddr + strlen(baddr) - 1;
if (ptr >= baddr && *ptr == '\n')
*ptr = 0;
port->bluetooth_address = strdup(baddr);
}
fclose(file);
}
}
}
RETURN_OK();
}
SP_PRIV enum sp_return list_ports(struct sp_port ***list)
{
char name[PATH_MAX], target[PATH_MAX];
struct dirent entry, *result;
#ifdef HAVE_STRUCT_SERIAL_STRUCT
struct serial_struct serial_info;
int ioctl_result;
#endif
char buf[sizeof(entry.d_name) + 23];
int len, fd;
DIR *dir;
int ret = SP_OK;
struct stat statbuf;
DEBUG("Enumerating tty devices");
if (!(dir = opendir("/sys/class/tty")))
RETURN_FAIL("Could not open /sys/class/tty");
DEBUG("Iterating over results");
while (!readdir_r(dir, &entry, &result) && result) {
snprintf(buf, sizeof(buf), "/sys/class/tty/%s", entry.d_name);
if (lstat(buf, &statbuf) == -1)
continue;
if (!S_ISLNK(statbuf.st_mode))
snprintf(buf, sizeof(buf), "/sys/class/tty/%s/device", entry.d_name);
len = readlink(buf, target, sizeof(target));
if (len <= 0 || len >= (int)(sizeof(target) - 1))
continue;
target[len] = 0;
if (strstr(target, "virtual"))
continue;
snprintf(name, sizeof(name), "/dev/%s", entry.d_name);
DEBUG_FMT("Found device %s", name);
if (strstr(target, "serial8250")) {
/*
* The serial8250 driver has a hardcoded number of ports.
* The only way to tell which actually exist on a given system
* is to try to open them and make an ioctl call.
*/
DEBUG("serial8250 device, attempting to open");
if ((fd = open(name, O_RDWR | O_NONBLOCK | O_NOCTTY)) < 0) {
DEBUG("Open failed, skipping");
continue;
}
#ifdef HAVE_STRUCT_SERIAL_STRUCT
ioctl_result = ioctl(fd, TIOCGSERIAL, &serial_info);
#endif
close(fd);
#ifdef HAVE_STRUCT_SERIAL_STRUCT
if (ioctl_result != 0) {
DEBUG("ioctl failed, skipping");
continue;
}
if (serial_info.type == PORT_UNKNOWN) {
DEBUG("Port type is unknown, skipping");
continue;
}
#endif
}
DEBUG_FMT("Found port %s", name);
*list = list_append(*list, name);
if (!*list) {
SET_ERROR(ret, SP_ERR_MEM, "List append failed");
break;
}
}
closedir(dir);
return ret;
}

View File

@ -33,40 +33,42 @@
* TCSETX/TCGETX ioctls used with struct termiox, others do not.
*/
#include <config.h>
#include <stdlib.h>
#include <linux/termios.h>
#include "linux_termios.h"
int get_termios_get_ioctl(void)
SP_PRIV unsigned long get_termios_get_ioctl(void)
{
#ifdef HAVE_TERMIOS2
#ifdef HAVE_STRUCT_TERMIOS2
return TCGETS2;
#else
return TCGETS;
#endif
}
int get_termios_set_ioctl(void)
SP_PRIV unsigned long get_termios_set_ioctl(void)
{
#ifdef HAVE_TERMIOS2
#ifdef HAVE_STRUCT_TERMIOS2
return TCSETS2;
#else
return TCSETS;
#endif
}
int get_termios_size(void)
SP_PRIV size_t get_termios_size(void)
{
#ifdef HAVE_TERMIOS2
#ifdef HAVE_STRUCT_TERMIOS2
return sizeof(struct termios2);
#else
return sizeof(struct termios);
#endif
}
#if defined(HAVE_TERMIOS_SPEED) || defined(HAVE_TERMIOS2_SPEED)
int get_termios_speed(void *data)
#if (defined(HAVE_TERMIOS_SPEED) || defined(HAVE_TERMIOS2_SPEED)) && defined(HAVE_DECL_BOTHER)
SP_PRIV int get_termios_speed(void *data)
{
#ifdef HAVE_TERMIOS2
#ifdef HAVE_STRUCT_TERMIOS2
struct termios2 *term = (struct termios2 *) data;
#else
struct termios *term = (struct termios *) data;
@ -77,9 +79,9 @@ int get_termios_speed(void *data)
return term->c_ispeed;
}
void set_termios_speed(void *data, int speed)
SP_PRIV void set_termios_speed(void *data, int speed)
{
#ifdef HAVE_TERMIOS2
#ifdef HAVE_STRUCT_TERMIOS2
struct termios2 *term = (struct termios2 *) data;
#else
struct termios *term = (struct termios *) data;
@ -90,13 +92,13 @@ void set_termios_speed(void *data, int speed)
}
#endif
#ifdef HAVE_TERMIOX
int get_termiox_size(void)
#ifdef HAVE_STRUCT_TERMIOX
SP_PRIV size_t get_termiox_size(void)
{
return sizeof(struct termiox);
}
int get_termiox_flow(void *data, int *rts, int *cts, int *dtr, int *dsr)
SP_PRIV int get_termiox_flow(void *data, int *rts, int *cts, int *dtr, int *dsr)
{
struct termiox *termx = (struct termiox *) data;
int flags = 0;
@ -109,7 +111,7 @@ int get_termiox_flow(void *data, int *rts, int *cts, int *dtr, int *dsr)
return flags;
}
void set_termiox_flow(void *data, int rts, int cts, int dtr, int dsr)
SP_PRIV void set_termiox_flow(void *data, int rts, int cts, int dtr, int dsr)
{
struct termiox *termx = (struct termiox *) data;

View File

@ -20,13 +20,15 @@
#ifndef LIBSERIALPORT_LINUX_TERMIOS_H
#define LIBSERIALPORT_LINUX_TERMIOS_H
int get_termios_get_ioctl(void);
int get_termios_set_ioctl(void);
int get_termios_size(void);
int get_termios_speed(void *data);
void set_termios_speed(void *data, int speed);
int get_termiox_size(void);
int get_termiox_flow(void *data, int *rts, int *cts, int *dtr, int *dsr);
void set_termiox_flow(void *data, int rts, int cts, int dtr, int dsr);
#include <stdlib.h>
SP_PRIV unsigned long get_termios_get_ioctl(void);
SP_PRIV unsigned long get_termios_set_ioctl(void);
SP_PRIV size_t get_termios_size(void);
SP_PRIV int get_termios_speed(void *data);
SP_PRIV void set_termios_speed(void *data, int speed);
SP_PRIV size_t get_termiox_size(void);
SP_PRIV int get_termiox_flow(void *data, int *rts, int *cts, int *dtr, int *dsr);
SP_PRIV void set_termiox_flow(void *data, int rts, int cts, int dtr, int dsr);
#endif

243
macosx.c Normal file
View File

@ -0,0 +1,243 @@
/*
* This file is part of the libserialport project.
*
* Copyright (C) 2013-2014 Martin Ling <martin-libserialport@earth.li>
* Copyright (C) 2014 Aurelien Jacobs <aurel@gnuage.org>
*
* This program 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 3 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include "libserialport.h"
#include "libserialport_internal.h"
SP_PRIV enum sp_return get_port_details(struct sp_port *port)
{
/*
* Description limited to 127 char, anything longer
* would not be user friendly anyway.
*/
char description[128];
int bus, address, vid, pid = -1;
char manufacturer[128], product[128], serial[128];
CFMutableDictionaryRef classes;
io_iterator_t iter;
io_object_t ioport, ioparent;
CFTypeRef cf_property, cf_bus, cf_address, cf_vendor, cf_product;
Boolean result;
char path[PATH_MAX], class[16];
DEBUG("Getting serial port list");
if (!(classes = IOServiceMatching(kIOSerialBSDServiceValue)))
RETURN_FAIL("IOServiceMatching() failed");
if (IOServiceGetMatchingServices(kIOMasterPortDefault, classes,
&iter) != KERN_SUCCESS)
RETURN_FAIL("IOServiceGetMatchingServices() failed");
DEBUG("Iterating over results");
while ((ioport = IOIteratorNext(iter))) {
if (!(cf_property = IORegistryEntryCreateCFProperty(ioport,
CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0))) {
IOObjectRelease(ioport);
continue;
}
result = CFStringGetCString(cf_property, path, sizeof(path),
kCFStringEncodingASCII);
CFRelease(cf_property);
if (!result || strcmp(path, port->name)) {
IOObjectRelease(ioport);
continue;
}
DEBUG_FMT("Found port %s", path);
IORegistryEntryGetParentEntry(ioport, kIOServicePlane, &ioparent);
if ((cf_property=IORegistryEntrySearchCFProperty(ioparent,kIOServicePlane,
CFSTR("IOClass"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents))) {
if (CFStringGetCString(cf_property, class, sizeof(class),
kCFStringEncodingASCII) &&
strstr(class, "USB")) {
DEBUG("Found USB class device");
port->transport = SP_TRANSPORT_USB;
}
CFRelease(cf_property);
}
if ((cf_property=IORegistryEntrySearchCFProperty(ioparent,kIOServicePlane,
CFSTR("IOProviderClass"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents))) {
if (CFStringGetCString(cf_property, class, sizeof(class),
kCFStringEncodingASCII) &&
strstr(class, "USB")) {
DEBUG("Found USB class device");
port->transport = SP_TRANSPORT_USB;
}
CFRelease(cf_property);
}
IOObjectRelease(ioparent);
if ((cf_property = IORegistryEntrySearchCFProperty(ioport,kIOServicePlane,
CFSTR("USB Interface Name"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
(cf_property = IORegistryEntrySearchCFProperty(ioport,kIOServicePlane,
CFSTR("USB Product Name"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
(cf_property = IORegistryEntrySearchCFProperty(ioport,kIOServicePlane,
CFSTR("Product Name"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
(cf_property = IORegistryEntryCreateCFProperty(ioport,
CFSTR(kIOTTYDeviceKey), kCFAllocatorDefault, 0))) {
if (CFStringGetCString(cf_property, description, sizeof(description),
kCFStringEncodingASCII)) {
DEBUG_FMT("Found description %s", description);
port->description = strdup(description);
}
CFRelease(cf_property);
} else {
DEBUG("No description for this device");
}
cf_bus = IORegistryEntrySearchCFProperty(ioport, kIOServicePlane,
CFSTR("USBBusNumber"),
kCFAllocatorDefault,
kIORegistryIterateRecursively
| kIORegistryIterateParents);
cf_address = IORegistryEntrySearchCFProperty(ioport, kIOServicePlane,
CFSTR("USB Address"),
kCFAllocatorDefault,
kIORegistryIterateRecursively
| kIORegistryIterateParents);
if (cf_bus && cf_address &&
CFNumberGetValue(cf_bus , kCFNumberIntType, &bus) &&
CFNumberGetValue(cf_address, kCFNumberIntType, &address)) {
DEBUG_FMT("Found matching USB bus:address %03d:%03d", bus, address);
port->usb_bus = bus;
port->usb_address = address;
}
if (cf_bus)
CFRelease(cf_bus);
if (cf_address)
CFRelease(cf_address);
cf_vendor = IORegistryEntrySearchCFProperty(ioport, kIOServicePlane,
CFSTR("idVendor"),
kCFAllocatorDefault,
kIORegistryIterateRecursively
| kIORegistryIterateParents);
cf_product = IORegistryEntrySearchCFProperty(ioport, kIOServicePlane,
CFSTR("idProduct"),
kCFAllocatorDefault,
kIORegistryIterateRecursively
| kIORegistryIterateParents);
if (cf_vendor && cf_product &&
CFNumberGetValue(cf_vendor , kCFNumberIntType, &vid) &&
CFNumberGetValue(cf_product, kCFNumberIntType, &pid)) {
DEBUG_FMT("Found matching USB VID:PID %04X:%04X", vid, pid);
port->usb_vid = vid;
port->usb_pid = pid;
}
if (cf_vendor)
CFRelease(cf_vendor);
if (cf_product)
CFRelease(cf_product);
if ((cf_property = IORegistryEntrySearchCFProperty(ioport,kIOServicePlane,
CFSTR("USB Vendor Name"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents))) {
if (CFStringGetCString(cf_property, manufacturer, sizeof(manufacturer),
kCFStringEncodingASCII)) {
DEBUG_FMT("Found manufacturer %s", manufacturer);
port->usb_manufacturer = strdup(manufacturer);
}
CFRelease(cf_property);
}
if ((cf_property = IORegistryEntrySearchCFProperty(ioport,kIOServicePlane,
CFSTR("USB Product Name"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents))) {
if (CFStringGetCString(cf_property, product, sizeof(product),
kCFStringEncodingASCII)) {
DEBUG_FMT("Found product name %s", product);
port->usb_product = strdup(product);
}
CFRelease(cf_property);
}
if ((cf_property = IORegistryEntrySearchCFProperty(ioport,kIOServicePlane,
CFSTR("USB Serial Number"), kCFAllocatorDefault,
kIORegistryIterateRecursively | kIORegistryIterateParents))) {
if (CFStringGetCString(cf_property, serial, sizeof(serial),
kCFStringEncodingASCII)) {
DEBUG_FMT("Found serial number %s", serial);
port->usb_serial = strdup(serial);
}
CFRelease(cf_property);
}
IOObjectRelease(ioport);
break;
}
IOObjectRelease(iter);
RETURN_OK();
}
SP_PRIV enum sp_return list_ports(struct sp_port ***list)
{
CFMutableDictionaryRef classes;
io_iterator_t iter;
char path[PATH_MAX];
io_object_t port;
CFTypeRef cf_path;
Boolean result;
int ret = SP_OK;
DEBUG("Creating matching dictionary");
if (!(classes = IOServiceMatching(kIOSerialBSDServiceValue))) {
SET_FAIL(ret, "IOServiceMatching() failed");
goto out_done;
}
DEBUG("Getting matching services");
if (IOServiceGetMatchingServices(kIOMasterPortDefault, classes,
&iter) != KERN_SUCCESS) {
SET_FAIL(ret, "IOServiceGetMatchingServices() failed");
goto out_done;
}
DEBUG("Iterating over results");
while ((port = IOIteratorNext(iter))) {
cf_path = IORegistryEntryCreateCFProperty(port,
CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
if (cf_path) {
result = CFStringGetCString(cf_path, path, sizeof(path),
kCFStringEncodingASCII);
CFRelease(cf_path);
if (result) {
DEBUG_FMT("Found port %s", path);
if (!(*list = list_append(*list, path))) {
SET_ERROR(ret, SP_ERR_MEM, "List append failed");
IOObjectRelease(port);
goto out;
}
}
}
IOObjectRelease(port);
}
out:
IOObjectRelease(iter);
out_done:
return ret;
}

File diff suppressed because it is too large Load Diff

553
windows.c Normal file
View File

@ -0,0 +1,553 @@
/*
* This file is part of the libserialport project.
*
* Copyright (C) 2013-2014 Martin Ling <martin-libserialport@earth.li>
* Copyright (C) 2014 Aurelien Jacobs <aurel@gnuage.org>
*
* This program 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 3 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include "libserialport.h"
#include "libserialport_internal.h"
/* USB path is a string of at most 8 decimal numbers < 128 separated by dots. */
#define MAX_USB_PATH ((8 * 3) + (7 * 1) + 1)
static void enumerate_hub(struct sp_port *port, const char *hub_name,
const char *parent_path, DEVINST dev_inst);
static char *wc_to_utf8(PWCHAR wc_buffer, ULONG size)
{
WCHAR wc_str[(size / sizeof(WCHAR)) + 1];
char *utf8_str;
/* Zero-terminate the wide char string. */
memcpy(wc_str, wc_buffer, size);
wc_str[sizeof(wc_str) - 1] = 0;
/* Compute the size of the UTF-8 converted string. */
if (!(size = WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, wc_str, -1,
NULL, 0, NULL, NULL)))
return NULL;
/* Allocate UTF-8 output buffer. */
if (!(utf8_str = malloc(size)))
return NULL;
/* Actually converted to UTF-8. */
if (!WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, wc_str, -1,
utf8_str, size, NULL, NULL)) {
free(utf8_str);
return NULL;
}
return utf8_str;
}
static char *get_root_hub_name(HANDLE host_controller)
{
USB_ROOT_HUB_NAME root_hub_name;
PUSB_ROOT_HUB_NAME root_hub_name_wc;
char *root_hub_name_utf8;
ULONG size = 0;
/* Compute the size of the root hub name string. */
if (!DeviceIoControl(host_controller, IOCTL_USB_GET_ROOT_HUB_NAME, 0, 0,
&root_hub_name, sizeof(root_hub_name), &size, NULL))
return NULL;
/* Allocate wide char root hub name string. */
size = root_hub_name.ActualLength;
if (!(root_hub_name_wc = malloc(size)))
return NULL;
/* Actually get the root hub name string. */
if (!DeviceIoControl(host_controller, IOCTL_USB_GET_ROOT_HUB_NAME,
NULL, 0, root_hub_name_wc, size, &size, NULL)) {
free(root_hub_name_wc);
return NULL;
}
/* Convert the root hub name string to UTF-8. */
root_hub_name_utf8 = wc_to_utf8(root_hub_name_wc->RootHubName, size);
free(root_hub_name_wc);
return root_hub_name_utf8;
}
static char *get_external_hub_name(HANDLE hub, ULONG connection_index)
{
USB_NODE_CONNECTION_NAME ext_hub_name;
PUSB_NODE_CONNECTION_NAME ext_hub_name_wc;
char *ext_hub_name_utf8;
ULONG size;
/* Compute the size of the external hub name string. */
ext_hub_name.ConnectionIndex = connection_index;
if (!DeviceIoControl(hub, IOCTL_USB_GET_NODE_CONNECTION_NAME,
&ext_hub_name, sizeof(ext_hub_name),
&ext_hub_name, sizeof(ext_hub_name), &size, NULL))
return NULL;
/* Allocate wide char external hub name string. */
size = ext_hub_name.ActualLength;
if (size <= sizeof(ext_hub_name)
|| !(ext_hub_name_wc = malloc(size)))
return NULL;
/* Get the name of the external hub attached to the specified port. */
ext_hub_name_wc->ConnectionIndex = connection_index;
if (!DeviceIoControl(hub, IOCTL_USB_GET_NODE_CONNECTION_NAME,
ext_hub_name_wc, size,
ext_hub_name_wc, size, &size, NULL)) {
free(ext_hub_name_wc);
return NULL;
}
/* Convert the external hub name string to UTF-8. */
ext_hub_name_utf8 = wc_to_utf8(ext_hub_name_wc->NodeName, size);
free(ext_hub_name_wc);
return ext_hub_name_utf8;
}
static char *get_string_descriptor(HANDLE hub_device, ULONG connection_index,
UCHAR descriptor_index)
{
char desc_req_buf[sizeof(USB_DESCRIPTOR_REQUEST) +
MAXIMUM_USB_STRING_LENGTH] = { 0 };
PUSB_DESCRIPTOR_REQUEST desc_req = (void *)desc_req_buf;
PUSB_STRING_DESCRIPTOR desc = (void *)(desc_req + 1);
ULONG size = sizeof(desc_req_buf);
desc_req->ConnectionIndex = connection_index;
desc_req->SetupPacket.wValue = (USB_STRING_DESCRIPTOR_TYPE << 8)
| descriptor_index;
desc_req->SetupPacket.wIndex = 0;
desc_req->SetupPacket.wLength = size - sizeof(*desc_req);
if (!DeviceIoControl(hub_device,
IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,
desc_req, size, desc_req, size, &size, NULL)
|| size < 2
|| desc->bDescriptorType != USB_STRING_DESCRIPTOR_TYPE
|| desc->bLength != size - sizeof(*desc_req)
|| desc->bLength % 2)
return NULL;
return wc_to_utf8(desc->bString, desc->bLength);
}
static void enumerate_hub_ports(struct sp_port *port, HANDLE hub_device,
ULONG nb_ports, const char *parent_path, DEVINST dev_inst)
{
char path[MAX_USB_PATH];
ULONG index = 0;
for (index = 1; index <= nb_ports; index++) {
PUSB_NODE_CONNECTION_INFORMATION_EX connection_info_ex;
ULONG size = sizeof(*connection_info_ex) + (30 * sizeof(USB_PIPE_INFO));
if (!(connection_info_ex = malloc(size)))
break;
connection_info_ex->ConnectionIndex = index;
if (!DeviceIoControl(hub_device,
IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX,
connection_info_ex, size,
connection_info_ex, size, &size, NULL)) {
/*
* Try to get CONNECTION_INFORMATION if
* CONNECTION_INFORMATION_EX did not work.
*/
PUSB_NODE_CONNECTION_INFORMATION connection_info;
size = sizeof(*connection_info) + (30 * sizeof(USB_PIPE_INFO));
if (!(connection_info = malloc(size))) {
free(connection_info_ex);
continue;
}
connection_info->ConnectionIndex = index;
if (!DeviceIoControl(hub_device,
IOCTL_USB_GET_NODE_CONNECTION_INFORMATION,
connection_info, size,
connection_info, size, &size, NULL)) {
free(connection_info);
free(connection_info_ex);
continue;
}
connection_info_ex->ConnectionIndex = connection_info->ConnectionIndex;
connection_info_ex->DeviceDescriptor = connection_info->DeviceDescriptor;
connection_info_ex->DeviceIsHub = connection_info->DeviceIsHub;
connection_info_ex->DeviceAddress = connection_info->DeviceAddress;
free(connection_info);
}
if (connection_info_ex->DeviceIsHub) {
/* Recursively enumerate external hub. */
PCHAR ext_hub_name;
if ((ext_hub_name = get_external_hub_name(hub_device, index))) {
snprintf(path, sizeof(path), "%s%ld.",
parent_path, connection_info_ex->ConnectionIndex);
enumerate_hub(port, ext_hub_name, path, dev_inst);
}
free(connection_info_ex);
} else {
snprintf(path, sizeof(path), "%s%ld",
parent_path, connection_info_ex->ConnectionIndex);
/* Check if this device is the one we search for. */
if (strcmp(path, port->usb_path)) {
free(connection_info_ex);
continue;
}
/* Finally grab detailed information regarding the device. */
port->usb_address = connection_info_ex->DeviceAddress + 1;
port->usb_vid = connection_info_ex->DeviceDescriptor.idVendor;
port->usb_pid = connection_info_ex->DeviceDescriptor.idProduct;
if (connection_info_ex->DeviceDescriptor.iManufacturer)
port->usb_manufacturer = get_string_descriptor(hub_device,index,
connection_info_ex->DeviceDescriptor.iManufacturer);
if (connection_info_ex->DeviceDescriptor.iProduct)
port->usb_product = get_string_descriptor(hub_device, index,
connection_info_ex->DeviceDescriptor.iProduct);
if (connection_info_ex->DeviceDescriptor.iSerialNumber) {
port->usb_serial = get_string_descriptor(hub_device, index,
connection_info_ex->DeviceDescriptor.iSerialNumber);
if (port->usb_serial == NULL) {
//composite device, get the parent's serial number
char device_id[MAX_DEVICE_ID_LEN];
if (CM_Get_Parent(&dev_inst, dev_inst, 0) == CR_SUCCESS) {
if (CM_Get_Device_IDA(dev_inst, device_id, sizeof(device_id), 0) == CR_SUCCESS)
port->usb_serial = strdup(strrchr(device_id, '\\')+1);
}
}
}
free(connection_info_ex);
break;
}
}
}
static void enumerate_hub(struct sp_port *port, const char *hub_name,
const char *parent_path, DEVINST dev_inst)
{
USB_NODE_INFORMATION hub_info;
HANDLE hub_device;
ULONG size = sizeof(hub_info);
char *device_name;
/* Open the hub with its full name. */
if (!(device_name = malloc(strlen("\\\\.\\") + strlen(hub_name) + 1)))
return;
strcpy(device_name, "\\\\.\\");
strcat(device_name, hub_name);
hub_device = CreateFile(device_name, GENERIC_WRITE, FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
free(device_name);
if (hub_device == INVALID_HANDLE_VALUE)
return;
/* Get the number of ports of the hub. */
if (DeviceIoControl(hub_device, IOCTL_USB_GET_NODE_INFORMATION,
&hub_info, size, &hub_info, size, &size, NULL))
/* Enumerate the ports of the hub. */
enumerate_hub_ports(port, hub_device,
hub_info.u.HubInformation.HubDescriptor.bNumberOfPorts, parent_path, dev_inst);
CloseHandle(hub_device);
}
static void enumerate_host_controller(struct sp_port *port,
HANDLE host_controller_device,
DEVINST dev_inst)
{
char *root_hub_name;
if ((root_hub_name = get_root_hub_name(host_controller_device))) {
enumerate_hub(port, root_hub_name, "", dev_inst);
free(root_hub_name);
}
}
static void get_usb_details(struct sp_port *port, DEVINST dev_inst_match)
{
HDEVINFO device_info;
SP_DEVINFO_DATA device_info_data;
ULONG i, size = 0;
device_info = SetupDiGetClassDevs(&GUID_CLASS_USB_HOST_CONTROLLER, NULL, NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
device_info_data.cbSize = sizeof(device_info_data);
for (i = 0; SetupDiEnumDeviceInfo(device_info, i, &device_info_data); i++) {
SP_DEVICE_INTERFACE_DATA device_interface_data;
PSP_DEVICE_INTERFACE_DETAIL_DATA device_detail_data;
DEVINST dev_inst = dev_inst_match;
HANDLE host_controller_device;
device_interface_data.cbSize = sizeof(device_interface_data);
if (!SetupDiEnumDeviceInterfaces(device_info, 0,
&GUID_CLASS_USB_HOST_CONTROLLER,
i, &device_interface_data))
continue;
if (!SetupDiGetDeviceInterfaceDetail(device_info,&device_interface_data,
NULL, 0, &size, NULL)
&& GetLastError() != ERROR_INSUFFICIENT_BUFFER)
continue;
if (!(device_detail_data = malloc(size)))
continue;
device_detail_data->cbSize = sizeof(*device_detail_data);
if (!SetupDiGetDeviceInterfaceDetail(device_info,&device_interface_data,
device_detail_data, size, &size,
NULL)) {
free(device_detail_data);
continue;
}
while (CM_Get_Parent(&dev_inst, dev_inst, 0) == CR_SUCCESS
&& dev_inst != device_info_data.DevInst) { }
if (dev_inst != device_info_data.DevInst) {
free(device_detail_data);
continue;
}
port->usb_bus = i + 1;
host_controller_device = CreateFile(device_detail_data->DevicePath,
GENERIC_WRITE, FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if (host_controller_device != INVALID_HANDLE_VALUE) {
enumerate_host_controller(port, host_controller_device, dev_inst_match);
CloseHandle(host_controller_device);
}
free(device_detail_data);
}
SetupDiDestroyDeviceInfoList(device_info);
return;
}
SP_PRIV enum sp_return get_port_details(struct sp_port *port)
{
/*
* Description limited to 127 char, anything longer
* would not be user friendly anyway.
*/
char description[128];
SP_DEVINFO_DATA device_info_data = { .cbSize = sizeof(device_info_data) };
HDEVINFO device_info;
int i;
device_info = SetupDiGetClassDevs(NULL, 0, 0,
DIGCF_PRESENT | DIGCF_ALLCLASSES);
if (device_info == INVALID_HANDLE_VALUE)
RETURN_FAIL("SetupDiGetClassDevs() failed");
for (i = 0; SetupDiEnumDeviceInfo(device_info, i, &device_info_data); i++) {
HKEY device_key;
DEVINST dev_inst;
char value[8], class[16];
DWORD size, type;
CONFIGRET cr;
/* Check if this is the device we are looking for. */
device_key = SetupDiOpenDevRegKey(device_info, &device_info_data,
DICS_FLAG_GLOBAL, 0,
DIREG_DEV, KEY_QUERY_VALUE);
if (device_key == INVALID_HANDLE_VALUE)
continue;
size = sizeof(value);
if (RegQueryValueExA(device_key, "PortName", NULL, &type, (LPBYTE)value,
&size) != ERROR_SUCCESS || type != REG_SZ) {
RegCloseKey(device_key);
continue;
}
RegCloseKey(device_key);
value[sizeof(value)-1] = 0;
if (strcmp(value, port->name))
continue;
/* Check port transport type. */
dev_inst = device_info_data.DevInst;
size = sizeof(class);
cr = CR_FAILURE;
while (CM_Get_Parent(&dev_inst, dev_inst, 0) == CR_SUCCESS &&
(cr = CM_Get_DevNode_Registry_PropertyA(dev_inst,
CM_DRP_CLASS, 0, class, &size, 0)) != CR_SUCCESS) { }
if (cr == CR_SUCCESS) {
if (!strcmp(class, "USB"))
port->transport = SP_TRANSPORT_USB;
}
/* Get port description (friendly name). */
dev_inst = device_info_data.DevInst;
size = sizeof(description);
while ((cr = CM_Get_DevNode_Registry_PropertyA(dev_inst,
CM_DRP_FRIENDLYNAME, 0, description, &size, 0)) != CR_SUCCESS
&& CM_Get_Parent(&dev_inst, dev_inst, 0) == CR_SUCCESS) { }
if (cr == CR_SUCCESS)
port->description = strdup(description);
/* Get more informations for USB connected ports. */
if (port->transport == SP_TRANSPORT_USB) {
char usb_path[MAX_USB_PATH] = "", tmp[MAX_USB_PATH];
char device_id[MAX_DEVICE_ID_LEN];
/* Recurse over parents to build the USB device path. */
dev_inst = device_info_data.DevInst;
do {
/* Verify that this layer of the tree is USB related. */
if (CM_Get_Device_IDA(dev_inst, device_id,
sizeof(device_id), 0) != CR_SUCCESS
|| strncmp(device_id, "USB\\", 4))
continue;
/* Discard one layer for composite devices. */
char compat_ids[512], *p = compat_ids;
size = sizeof(compat_ids);
if (CM_Get_DevNode_Registry_PropertyA(dev_inst,
CM_DRP_COMPATIBLEIDS, 0,
&compat_ids,
&size, 0) == CR_SUCCESS) {
while (*p) {
if (!strncmp(p, "USB\\COMPOSITE", 13))
break;
p += strlen(p) + 1;
}
if (*p)
continue;
}
/* Stop the recursion when reaching the USB root. */
if (!strncmp(device_id, "USB\\ROOT", 8))
break;
/* Prepend the address of current USB layer to the USB path. */
DWORD address;
size = sizeof(address);
if (CM_Get_DevNode_Registry_PropertyA(dev_inst, CM_DRP_ADDRESS,
0, &address, &size, 0) == CR_SUCCESS) {
strcpy(tmp, usb_path);
snprintf(usb_path, sizeof(usb_path), "%d%s%s",
(int)address, *tmp ? "." : "", tmp);
}
} while (CM_Get_Parent(&dev_inst, dev_inst, 0) == CR_SUCCESS);
port->usb_path = strdup(usb_path);
/* Wake up the USB device to be able to read string descriptor. */
char *escaped_port_name;
HANDLE handle;
if (!(escaped_port_name = malloc(strlen(port->name) + 5)))
RETURN_ERROR(SP_ERR_MEM, "Escaped port name malloc failed");
sprintf(escaped_port_name, "\\\\.\\%s", port->name);
handle = CreateFile(escaped_port_name, GENERIC_READ, 0, 0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, 0);
free(escaped_port_name);
CloseHandle(handle);
/* Retrieve USB device details from the device descriptor. */
get_usb_details(port, device_info_data.DevInst);
}
break;
}
SetupDiDestroyDeviceInfoList(device_info);
RETURN_OK();
}
SP_PRIV enum sp_return list_ports(struct sp_port ***list)
{
HKEY key;
TCHAR *value, *data;
DWORD max_value_len, max_data_size, max_data_len;
DWORD value_len, data_size, data_len;
DWORD type, index = 0;
char *name;
int name_len;
int ret = SP_OK;
DEBUG("Opening registry key");
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DEVICEMAP\\SERIALCOMM"),
0, KEY_QUERY_VALUE, &key) != ERROR_SUCCESS) {
SET_FAIL(ret, "RegOpenKeyEx() failed");
goto out_done;
}
DEBUG("Querying registry key value and data sizes");
if (RegQueryInfoKey(key, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
&max_value_len, &max_data_size, NULL, NULL) != ERROR_SUCCESS) {
SET_FAIL(ret, "RegQueryInfoKey() failed");
goto out_close;
}
max_data_len = max_data_size / sizeof(TCHAR);
if (!(value = malloc((max_value_len + 1) * sizeof(TCHAR)))) {
SET_ERROR(ret, SP_ERR_MEM, "Registry value malloc failed");
goto out_close;
}
if (!(data = malloc((max_data_len + 1) * sizeof(TCHAR)))) {
SET_ERROR(ret, SP_ERR_MEM, "Registry data malloc failed");
goto out_free_value;
}
DEBUG("Iterating over values");
while (
value_len = max_value_len + 1,
data_size = max_data_size,
RegEnumValue(key, index, value, &value_len,
NULL, &type, (LPBYTE)data, &data_size) == ERROR_SUCCESS)
{
if (type == REG_SZ) {
data_len = data_size / sizeof(TCHAR);
data[data_len] = '\0';
#ifdef UNICODE
name_len = WideCharToMultiByte(CP_ACP, 0, data, -1, NULL, 0, NULL, NULL);
#else
name_len = data_len + 1;
#endif
if (!(name = malloc(name_len))) {
SET_ERROR(ret, SP_ERR_MEM, "Registry port name malloc failed");
goto out;
}
#ifdef UNICODE
WideCharToMultiByte(CP_ACP, 0, data, -1, name, name_len, NULL, NULL);
#else
strcpy(name, data);
#endif
DEBUG_FMT("Found port %s", name);
if (!(*list = list_append(*list, name))) {
SET_ERROR(ret, SP_ERR_MEM, "List append failed");
free(name);
goto out;
}
free(name);
}
index++;
}
out:
free(data);
out_free_value:
free(value);
out_close:
RegCloseKey(key);
out_done:
return ret;
}