Compare commits

...

83 Commits

Author SHA1 Message Date
David Given
f9b7c7f6c1 Briefly experiment with area sizing. 2021-12-15 22:36:11 +01:00
David Given
855d346b8a More UI tweaking. Begining to think that libui doesn't quite do what we want
(don't seem to be able to set the size of an Area?).
2021-12-15 22:31:37 +01:00
David Given
473e81a2da Fixup after merge. 2021-12-15 20:20:42 +01:00
David Given
d925391d57 Merge from master. 2021-12-15 20:17:11 +01:00
David Given
63a5954dfa Merge pull request #390 from davidgiven/victor9k
Victor 9000 fixes
2021-12-15 15:08:41 +00:00
David Given
897931f273 Merge pull request #386 from davidgiven/pulsefix
Various firmware fixes
2021-12-14 21:07:06 +00:00
David Given
03e80b2f1c Fix the format so it no longer discards sector 0. 2021-12-14 21:10:38 +01:00
David Given
53c8ec864c Clean up the GreaseWeazle bandwidth tester. Sometimes it doesn't read the right
amount of data from the device?
2021-12-14 18:01:53 +00:00
David Given
5d3002f118 Announce the device serial number on connection again. 2021-12-13 23:24:02 +01:00
David Given
5eeb52660c Update documentation for the USB autodetection. 2021-12-13 23:19:16 +01:00
David Given
3dfafaa278 GreaseWeazle autodetection now works (at least on Linux). 2021-12-13 23:09:33 +01:00
David Given
462bd9ae5e Rewrite the sampler pulse detection... again. 2021-12-12 23:13:23 +00:00
David Given
dab0fcc7c0 Hopefully fix Linux build. 2021-12-12 21:15:30 +01:00
David Given
b8a3e8085e Fix after merge. 2021-12-12 19:58:19 +00:00
David Given
c9d1d72ba3 Merge in the libusbp changes. 2021-12-12 19:50:33 +00:00
David Given
ccf5d513d2 Merge from master. 2021-12-12 19:49:32 +00:00
David Given
d157b7b05d Make libusbp work on Windows; rework the Microsoft-specific bits of the
firmware to match. This does at least allow us to get rid of the patcher.
2021-12-12 17:25:24 +00:00
dg
05981ac46c Make work on OSX. 2021-12-12 15:40:12 +00:00
David Given
08615a5954 Make libusbp build, at least on Linux; port fluxengineusb to use it. 2021-12-12 16:29:51 +01:00
David Given
b6dbe26c7e Raw import of libusbp. 2021-12-11 18:59:44 +01:00
David Given
8a460b3e56 Add tabs. 2021-12-11 12:43:53 +01:00
David Given
012f1a872b Merge from master. 2021-12-11 12:34:51 +01:00
David Given
4f0d61b379 Merge pull request #388 from davidgiven/supercardpro
Tell the various IBM decoders which sectors to expect, for more reliable retries.
2021-12-11 11:26:51 +00:00
David Given
9e5e494f88 Merge from master. 2021-12-11 12:18:06 +01:00
David Given
b15fd05e8d Add support in the IBM decoder for trackdata protos, and then configure the IBM
profiles to know about which sectors it should see in the result image to allow
retries if sectors aren't found.
2021-12-11 12:17:32 +01:00
David Given
faabc28d1b Merge pull request #387 from davidgiven/supercardpro
Refactor the serial port code.
2021-12-10 23:45:49 +00:00
David Given
4b815846ee ...and fix for OSX. 2021-12-10 23:35:58 +00:00
David Given
fe8be18c4c Fix the serial port code on Windows. 2021-12-10 23:29:20 +00:00
David Given
519c30321d Split the serial port code off into its own file so we can use it for the SCP
driver.
2021-12-11 00:06:14 +01:00
David Given
77be88a5bb Add a package that the tests need. 2021-12-10 21:25:00 +01:00
David Given
8d04931f9f Turns out the high density pin is asserted when _double_ density. Set it
correctly.
2021-12-10 20:07:02 +00:00
David Given
3d1ee7a43e Don't spin in an infinite loop if the decoder tries to find a data record and
the matcher fails to find anything, leaving the seek point unchanged.
2021-12-10 19:56:00 +00:00
David Given
2584a25527 Update components and binary. 2021-12-10 19:55:06 +00:00
David Given
0647a82d38 The tests will run on Windows now. 2021-12-10 19:54:31 +00:00
David Given
c56459c98c Add back the pulse converter, as a 12MHz sample clock only allows pulses which
are 83ns long, which I think is too big. We now sample the index signal at
64MHz which allows pulses down to 16ns.
2021-12-10 17:40:58 +00:00
David Given
3347d057fe Merge from master. 2021-12-09 23:26:58 +01:00
David Given
7b0a4a057d Merge pull request #376 from tdaede/scpfix2
Fix SCP writer to always write complete revolutions.
2021-12-09 22:26:36 +00:00
David Given
eb0bb2cc2f Rework container properties to be more elegant. More UI stuff. 2021-12-09 23:00:37 +01:00
David Given
5ff7651218 Don't use sem_open on Windows. 2021-12-09 17:32:22 +00:00
David Given
dcb3761c9b Work on some menu stuff. 2021-12-09 00:28:34 +01:00
David Given
6fa83f4dcb Miscellaneous tidying and fix a crash on exit. 2021-12-08 23:22:33 +01:00
David Given
001c65dba8 Add the list of configs to the GUI. 2021-12-08 22:51:48 +01:00
David Given
969a08bd9a Turns out I don't need component builders any more. 2021-12-08 22:01:11 +01:00
David Given
3c3e520594 Merge pull request #378 from tdaede/ibm_n88basic
Add ibm_n88basic format.
2021-12-08 19:56:35 +00:00
dg
ed9ffc9091 Headers for OSX. 2021-12-08 19:40:19 +00:00
David Given
5d0750e835 Make the threading stuff actually work. 2021-12-08 20:34:06 +01:00
David Given
3283d6e8c0 Switch to named semaphores, because OSX. 2021-12-08 19:46:47 +01:00
David Given
1b96f60b2a Merge from master. 2021-12-08 19:39:59 +01:00
David Given
a15d78c040 Add some threading primitives. 2021-12-07 22:50:03 +00:00
David Given
40b7f57116 Add a Windows manifest and rc file to make the GUI run there. 2021-12-07 22:49:49 +00:00
Thomas Daede
68314cd44d Add n88basic format.
This isn't really intended to be used directly (bare images are
normally .d88 etc) but is useful for testing the FM encoder.
2021-12-07 14:39:31 -08:00
David Given
17787b97d4 Merge pull request #371 from tdaede/d88hd
Set media density automatically for D88 format.
2021-12-07 21:54:58 +00:00
David Given
da84297b2c Merge pull request #380 from tdaede/last_fluxmap
SCP: Only write the last continuous fluxmap.
2021-12-07 21:47:54 +00:00
David Given
e3e3bb770d Merge pull request #379 from tdaede/no_disk_sector_check
Remove per-track sector check from D88.
2021-12-07 21:45:47 +00:00
David Given
9d4f180741 Merge pull request #381 from hharte/fixnorthstar
Fix North Star after decoder update
2021-12-07 21:44:26 +00:00
dg
91281bb58b Make OSX happy. 2021-12-07 21:02:30 +00:00
David Given
4ddd7d04b5 Overhaul the C++ GUI API so it actually looks reasonable. 2021-12-07 22:00:41 +01:00
David Given
316836d9f6 Merge pull request #382 from darkstar/protofix
Fix a small typo in proto.cc
2021-12-07 17:24:24 +00:00
Michael D
aa3b0a117a Fix a small typo in proto.cc
Also add long-forms "yes" and "no" as valid options
2021-12-07 18:10:53 +01:00
Howard M. Harte
0f7df3281f NorthStar: Fix nsiimagewriter image size in informational message. 2021-12-06 17:57:08 -10:00
Howard M. Harte
4a91a35799 Fix the North Star decoder after the PLL upgrade. 2021-12-06 17:55:54 -10:00
dg
36c2263675 Fix the Micropolis decoder after the PLL upgrade. 2021-12-06 22:20:18 +00:00
David Given
28068d8d31 Merge pull request #375 from davidgiven/fl2
Make the new FL2 format the default.
2021-12-06 22:14:33 +00:00
dg
97cc54a07a Some C++ abstractions for the UI code. 2021-12-06 21:29:07 +00:00
Thomas Daede
0c3c08db36 SCP: Only write the last continuous fluxmap. 2021-12-06 11:47:42 -08:00
Thomas Daede
bb5d62fe69 Remove per-track sector check from D88.
It's no longer needed to be this strict.
2021-12-06 10:13:30 -08:00
David Given
376270dd53 Merge pull request #377 from tdaede/d88trackfill
Set FM gap fill byte in D88.
2021-12-06 16:21:13 +00:00
Thomas Daede
9056b23eaa Set FM gap fill byte in D88. 2021-12-06 08:10:02 -08:00
Thomas Daede
634be4ae51 Fix SCP writer to always write complete revolutions.
There were a couple of problems that needed to be fixed:

- Fluxmaps can be produced with zero index pulses by encoders.
This is somewhat ambiguous but it's assumed that this means there
is exactly one revolution.

- The SCP unindexed mode wasn't properly supported, as it should
create equal-length revolutions not aligned to indexes. However,
fluxmaps don't contain any information on rotation speed. Therefore,
drop the ability to create unindexed SCPs and slightly change the
meaning of the option to not include pre-index data.

- Real flux reads can potentially produce duplicate index pulses.
Avoid creating zero-length revs.

- Fix off-by-one error for rev count.
2021-12-06 07:41:16 -08:00
dtrg
99901f365e Fix package name. 2021-12-06 12:57:54 +00:00
David Given
422620f4fe Merge pull request #374 from davidgiven/ibm
Make the gap fill byte configurable.
2021-12-05 22:27:45 +00:00
dg
dd3cb93ed9 Finally do some drawing. 2021-12-05 21:50:13 +00:00
David Given
dbb422a410 Typo fix. 2021-12-05 21:18:24 +00:00
David Given
68712b8be7 Try adding a drawable to see what happens. 2021-12-05 21:16:43 +00:00
David Given
da7e2b1b54 Add a menu. 2021-12-05 20:41:20 +00:00
David Given
8cff85ad3d Make the GUI prototype build on Windows. 2021-12-05 20:28:11 +00:00
dg
9ffde65bb0 Update dependencies. 2021-12-05 20:03:46 +00:00
dg
5759bc602f Tweak so that GTK builds again. 2021-12-05 20:02:17 +00:00
dg
4bb6fdc79a Make the GUI example build on OSX. 2021-12-05 19:54:19 +00:00
dg
bd019d5d70 Create new branch named "libui" 2021-12-05 18:31:59 +00:00
dg
ebb5c17be4 Make the IBM format gap fill byte configurable. 2021-12-05 16:43:45 +00:00
dg
1e99fe5b04 Merge from master. 2021-12-05 16:37:04 +00:00
Thomas Daede
66f82f764d Set media density automatically for D88 format. 2021-12-04 10:31:17 -08:00
173 changed files with 17258 additions and 913 deletions

View File

@@ -10,7 +10,7 @@ jobs:
with:
fetch-depth: 1
- name: apt
run: sudo apt update && sudo apt install libusb-1.0-0-dev libsqlite3-dev ninja-build protobuf-compiler
run: sudo apt update && sudo apt install libudev-dev libsqlite3-dev ninja-build protobuf-compiler libgtk-3-dev
- name: make
run: make
@@ -45,6 +45,7 @@ jobs:
zip
mingw-w64-i686-protobuf
vim
diffutils
- uses: actions/checkout@v1
with:
fetch-depth: 1

View File

@@ -26,6 +26,7 @@ jobs:
zip
mingw-w64-i686-protobuf
vim
diffutils
- uses: actions/checkout@v2
with:
fetch-depth: 1

View File

@@ -1,249 +1,249 @@
:4000000000800020110000004110000041100000064A08B5136843F020031360044B1A6803F53F5302331A6001F028F8E8460040FA46004010B5054C237833B9044B13B163
:400040000448AFF300800123237010BD6881FF1F00000000C8380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF0000000041
:400080006C81FF1FC8380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1FBC
:4000000000800020110000009D1000009D100000064A08B5136843F020031360044B1A6803F53F5302331A6001F056F8E8460040FA46004010B5054C237833B9044B13B17D
:400040000448AFF300800123237010BD6881FF1F00000000E8380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF0000000021
:400080006C81FF1FE8380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1F9C
:4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8881FF1F3F000080114BDA68196919B9FD
:4001000001221A75597514E09969521A19698A4294BF587D00201875187D094908B1002204E0086982428CBF002201224A75DA689A611B7D13B1002002F046B9704700BF70
:400140008881FF1F10B5C4B2204601F059F90128FAD110BD08B572B60F4B0F49DA680132DA60DA690132C82A08BF0022DA611A6AD8690132A72A08BF00220A621B6A002B13
:400180000CBF02230023002814BF184643F0010002F082FE62B608BD8881FF1F38B50446C5B2284602F0B2F8062002F0CFFA44F00200C0B202F0AAF8062002F0C7FA28460D
:4001C00002F0A4F8BDE83840062002F0A9BA10B5642402F095F828B9FFF7E0FF013CF8D1204610BD012010BD70B5C4B2054620460E4601F005F9012805D0204601F01EFA34
:400200002846FFF79FFF204601F002F9314605460246204601F0BEF9204601F0F1F80028FAD1284670BD000038B5044D0024285D013402F03BFA402CF9D138BDAC81FF1FCB
:4002400008B502F055FC002002F05EFC02F070FC02F07AFC80B208BD10B50446012002F06DF8642002F05CFAFFF7EAFF2080002002F064F8642002F053FAFFF7E1FF60809C
:4002800010BD08B502F060FD002002F069FD02F07BFD02F085FD80B208BD10B50446FFF796FF322002F03CFAFFF7EBFF20800120FFF774FF322002F033FAFFF7E2FF6080D2
:4002C00010BD0FB400B593B014AB53F8042B402102A8019302F0E0FE02A802F07CF802F086F813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF723FF627816
:400300002146BDE81040042001F0D2B8DC38000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8643043B1002001F0DAFFEE
:40034000002002F06DFD002384F8643010BD00BF8881FF1F38B5104D837895F8672004469A4204D0FFF7E4FF002385F86A302368C5F865302279094B1A71A378002B14BF45
:400380000220012002F04CFDE07802F043FD2079BDE8384002F07ABD8881FF1FED81FF1F38B50D4C94F8645065B904F16500FFF7D1FF012001F09EFF4FF47A7002F0B0F968
:4003C00084F86A50E368E366012384F86430BDE8384002F0E3B900BF8881FF1FF8B5214C0546FFF7DDFF94F86A3003B15DB91E48FFF767FFFFF7EBFE0120002384F86A007C
:40040000236702F0A3F92A46216F1848FFF759FF144E0027236F9D4216D001F071FF00B13767236F9D4205DD0120FFF7B7FE336F013305E005DA0020FFF7B0FE336F013B0B
:40044000336702F0ABF9E5E7322002F069F92A2DCCBF0020012002F025FDBDE8F8400448FFF72FBF8881FF1FE9380000F03800000D3900002DE9F04F99B062B602F0F8F947
:400480009E49042002F01CFA9D4801F045FF9D4802F0E8FC9C4801F079FF02F0C9FB02F09BFA002002F0BCFC01F094FF0221002000F05CFF954C012001F0D4F8002384F890
:4004C0006730FFF76DFFFFF782FE84F87400FFF72FFF012384F86730FFF762FFFFF777FE84F87500FFF724FF894B94F87400894994F875202546002A14BF0A461A4600286F
:4005000008BF19468448FFF7DCFE0321084602F025F9264602F042F994F8643043B1EA6EEB689B1A41F28832934201D9FFF700FF00F054FF18B97948FFF7C3FE04E000F079
:4005400053FF0028F7D10BE000F048FF10B902F025F9F9E77248FFF7B4FE032001F06EF8032000F04DFF0128D4D16E48FFF7F2FE6D490320FFF738FE94F876106B48FFF7F9
:40058000A0FE94F87630023B142B00F2D683DFE813F01500D4031E00D4032400D4035000D4037600D403D900D403C101D4030803D4032C03D4033303D4034D0303238DF851
:4005C00020308DF821300F238DF822302AE394F87800FFF703FF564B21E340F2DC57FFF7DFFE00232375E068227D02F0FF012AB9EB681B1ABB42F7DD0B4611E083B100227E
:40060000174696F87810F068277594F814E0BEF1000F02D1EB681B1AF7E701329142F3DA07228DF8202004228DF82120ADF82230F8E20220FFF786FD4FF000080DF1200A54
:4006400002F0ACF84FF480790027C9EB0803DA1907F80A200137402FF9D10220FFF772FD3A465146022000F023FFB9F10109EBD108F10108B8F1400FE2D12E4B38E04FF076
:40068000010A4FF000080DF1200B02F087F84FF0000959460120FFF7A7FD08EB090300270493049B1BF807203B44DBB29A4209D08DE80C0041463B464A461F48FFF701FEE0
:4006C0004FF0000A0137402FEBD109F10109B9F5807FDED108F10108B8F1400FD5D151461648FFF7EEFDBAF1000F00F01B81144B1B8807A8ADF81C3095E200BF5501000004
:40070000F900000091000000C50000008881FF1F1F3900001B390000223900003A3900004D390000ED81FF1FFE81FF1F57390000CC380000CE380000663900008239000026
:40074000D0380000206FFFF749FE94F8780001F0F5FD94F8780001F0D9FD02F009FCB94BDFF8FC821A78002702F0FB021A701A7842F001021A701A7802F0FE021A701A7880
:4007800002F0FE021A7002F0F7FB0220FFF7DAFC012141F6FF734FF48042084602F046FB84F8B60001F068FF08F807000137402FF8D1DFF8B0A200270AF195091FFA89F816
:4007C0000137402F14BF3A4600221AF8010F2244062392F82420402101F082FF424646F240419AF8000001F08DFF08F14008402F1FFA88F8E4D196F8793053B196F87C30C8
:40080000336100233375237D002BFCD000233375336100234FF0FF32236062602372236894F8B600234493F8241001F0DDFE94F8B60001F09BFE012194F8B60001F06EFEAA
:400840002368002BFCD0002398467360D6F80CA0012701F0A3FFE368B4F87A20CAEB030393420DD367B1042195F8B60001F0C8FE94F8B60001F0D4FE0028F9D107463072CF
:40088000237AFBB96A682B689A4202D1002FE0D118E00220FFF756FC6968402209EB8111022000F005FE6A68674B01321340002BBEBF03F1FF3363F03F03013308F1010820
:4008C0006360C6E70220277AFFF73CFC00221146022000F0EDFD0220FFF734FCFFB2FFF7A3FC002001F012FD37B15848FFF7E9FC0220FFF70DFD06E0554B08A81B88ADF871
:400900002030FFF7F3FC227D4146237A5148FFF7D8FC15E25048FFF7D4FCD4F87A7017F03F0701D0032009E2286FFFF757FD95F8780001F003FD95F8780001F0E7FC012054
:4009400001F002FD02F014FB444BDFF814811A7842F004021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F003FB01214FF4804341F6FF72084601F0CB
:40098000E9FC85F8B60001F077FE08F807000137402FF8D1DFF8CC90002709F195031FFA83F804930137402F14BF3A46002219F8010F2244052392F82420402101F090FEB2
:4009C000414646F2475299F8000001F09BFE08F14008402F1FFA88F8E4D100274FF0FF33376098467360BB463B46D6F87A9037725FEA99190CBF4FF0010A4FF0000A2168C1
:400A0000114A01310A40002ABCBF02F1FF3262F03F026068B8BF013282426FD02BB1227A002A7AD12A7D002A77D12068049A059302EB8010BAF1000F16D040223F2102F079
:400A4000F7FA1CE0906400403F0000808C390000D2380000A6390000B939000097650040AC81FF1FAB81FF1F014601370120FFF7BBFBC7EB0903D3F1000A4AEB030A216882
:400A8000B34A01310A40002ABEBF02F1FF3262F03F02013222606268059B01322ED12A683F2A2BD14FF00008C5F8048001F080FC85F808806B6895F8B6002B4493F82410D9
:400AC00001F092FD95F8B60001F050FD012195F8B60001F023FD95F87E302B6185F81480237D002BFCD04FF00008012086F8148001F06AFC404601F027FC00E023B1237A54
:400B00005BB92B7D4BB90123626842453FF477AF0BF1010BD5F8048071E701F04FFC012001F012FC002001F04FFC042194F8B60001F066FD94F8B60001F072FD8046002873
:400B4000F8D196F8B60001F0FFFC337D327A0293012303920193CDF800A05B463A4649467C48FFF7AEFBC6F81080BAF1000F0BD0FFF75AFB002001F0C9FB237A63B1764854
:400B8000FFF79FFB0220D9E0B945F1D073490120FFF72AFB0137F7E77148FFF792FB714B3DE094F8780001F0C9FB206FFFF716FC6D48FFF786FB94F87930236100232375F8
:400BC000237D002BFCD0012001F0FEFB00233375237D002BFCD0002001F0F6FB002363483361FFF76EFB624B19E0002084F86A00FFF7F4FB5F4B12E094F8743023B195F870
:400C000075200AB985F8782094F875201AB113B9012385F878305848FFF79CFB574B1B88ADF8203008A8FFF761FB89E0FFF780FB02F07CF8002002F01FF82A2701F04AFFE9
:400C4000002001F0EDFE3A46002108A802F0F0F917238DF820308DF8217001F09FFD002001F048FB002002F0DBF8C82001F058FD0DF12200FFF7F0FA0DF13600FFF70DFB01
:400C800001F08CFD012002F0CBF8322001F048FD0DF12600FFF7E0FA0DF13A00FFF7FDFA012001F027FB4FF4967001F039FD01F075FD0DF12E00FFF7CFFA0DF14200FFF71B
:400CC000ECFA002001F016FB4FF4967001F028FD01F064FD022002F0A3F8322001F020FD0DEB0700FFF7B8FA0DF13E00FFF7D5FA012001F0FFFA4FF4967001F011FD01F040
:400D00004DFD0DF13200FFF7A7FA0DF14600FFF7C4FA002001F0EEFA4FF4967001F000FD01F03CFD002002F07BF8002384F86A3001F07EFF01F050FE74E70120FFF7E8FA91
:400D4000032000F07BFC0E48FFF7BBFAFFF7E2BB3F000080C3390000F33900004092FF1FFD390000D4380000053A0000133A0000D6380000D8380000FE81FF1FDA380000E3
:400D8000203A00002DE9F04172B6884B61221A70A3F5F06301221A801924854A9C7092E803008033062283F8002283E80300522203F580731A707F4B7F4A1B787F4EDBB2FE
:400DC000137040F618027E4B00251A8041F2512223F8022C33784FF4F07003F0010343EA450502F0BDF8013C05F003052ED0032DF0D1744B4FF480721A8007221A70724A20
:400E0000002548211570917002221D705D7103F8032C0422DA716D4A6D4C13786D4E43F00103137012F8013C062743F0030302F8013C2378012243F0800323705B4B1A70F9
:400E4000654A137843F02003137000E0FEE707FB056300219A881868013502F0E9F8072DF5D15E485E4E002550F8041F05F1105303F1480221F0FF074933C9B20B44520042
:400E80005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1514A002313609360136193614F4B504F1A68504BDFF888811A604F4B1A684F4B1A604F4A7B
:400EC000137843F002031370137C43F0020313742378A2F5863243F040032370413A137843F010031370464A464B07CA03C31A80454A2833106843F8250C127903F8212CA9
:400F0000424A07CA03C31A80414AE83B07CA03C31A80404A083307CA03C31A803E4A3F4BA2F5616203CBC2F8100EC2F8141E1378042043F008031370394B02F5AA521B78A7
:400F40003D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0030388F800302F4B48221A702E4A402313702E49937013729372082382F81F32BF
:400F800020220A7048710A72294A0A20137001F0DDFB284B88F8006044223D70264D1A7094E80F0007C52B80BDE8F081004800404C0A00480F010049A1460040254200408F
:400FC000224200400440004006400040A2430040A0430040253A0000E8460040FCFFFF478C00004800760040540A0048F846004020760040580A004828760040035001404D
:401000000C0A0048C0510040180A0048200A00482C0A0048380A004832510040440A0048CF0100491D51004001590040235B0040585B004076580040B0430040F9460040F2
:4010400008B501F0C9FF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC086005E001F0B8FF0C2303604FF0FF33184608BDCC80FF1F9093FF1F80B5114817
:40108000114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7114653F8046C8C1AA64202D041F8045BF9E701381033E5E701F094FFFFF7DAF9D9
:4010C000FEE700BF01000000F43B0000124A134B10B51A60124A134C1368134843F4007313600023032B98BF54F823204FEA830188BF0E4A0133302B4250F3D10C4B1A7814
:401100000C4B1A700C4B084A1A60FFF73BFEBDE8104001F0EDB900BF0004FA050CED00E014ED00E0000000000080FF1F41100000BC760040C080FF1F08ED00E0F8B501F042
:4011400017FF4B4A01271378022643F001031370137C484C43F001031374474B02F5E3521F700B3203F8946C1378054603F07F031370002001F0EAFA2378404A03F0F903F2
:4011800023701378384603F0DF03137023783B43237001F0DBFA282001F0D8FA384B30461A7802F07F021A701A7802F0BF021A7023783343237001F0C9FA2378314A43F0C6
:4011C000040323700023137053702F4AFF2199540133092BFBD1284601F0CEFE0721172001F0FCFA2949172001F0EAFA0721182001F0F4FA2649182001F0E2FA0721152033
:4012000001F0ECFA2349152001F0DAFA0721052001F0E4FA2049052001F0D2FA0721062001F0DCFA1D49062001F0CAFA0721084601F0D4FA1A49072001F0C2FA07210820F8
:4012400001F0CCFA1749082001F0BAFA0021162001F0C4FA1449162001F0B2FA07210C2001F0BCFABDE8F84010490C2001F0A8BAA5430040944300409D6000401260004076
:40128000F851004084600040B592FF1F0B1B000045190000091B00003D1A0000691A0000991A0000D11A0000111B0000851B0000214B224A10B5187000231370204A40209B
:4012C0001370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A604FF080721A604FF400121A6020221A601860802018604FF480701860174804702E
:401300004FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B03221A70802203F8202C012001F018FE0D4B04221A7010BDD092FF1FD692FF1F39
:40134000D492FF1FD592FF1FD192FF1FC092FF1FD392FF1F4893FF1F00E100E09E6000409C600040286000401260004070B5074C054623780E461BB9FFF7E0FE0123237031
:4013800031462846BDE87040FFF792BF8092FF1F0A4A002313700A4A13700A4A13700A4A13700A4A13700A4A13700A4A13700A4B03221A70802203F8202C7047D692FF1F4E
:4013C000D492FF1FD592FF1FD192FF1FC092FF1FD392FF1F4893FF1F28600040014B1878704700BFD592FF1F044B1A7802F0FF001AB118780022C0B21A707047D492FF1F52
:40140000024A0C2303FB002040787047DC92FF1F431E072B0CD8074A064B00010344805C5B7800F00F0043EA0020023880B2704700207047FC5F00401A4A38B50C2303FBA9
:4014400000231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032D18D8DFE805F002070C110021084601F01BF80DE00021084600F0FAFF08E0002180
:40148000084600F0D9FF03E00021084600F0B8FF054B1855EDB2072D03D801F0EDF8034B185538BDDC92FF1FAC92FF1FB592FF1F431E072B2DE9F0470446894615465CD857
:4014C0002F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BFD588DB7884BFC5F50075ADB2254A43EA15230601B354B244EBB28AF80130224BD4
:401500001A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4F385D01F011F811232946FE2218F8040001F0D6F806F5C04278321FFA89F118F8D2
:40154000040001F0DFF8124D18F80410385D01F04BF80121385D00F0E1FF735D43F002037355735D03F0FD037355BDE8F08703FB04746379DBB28AF80230BDE8F08700BFE7
:40158000DC92FF1FFC5F0040B592FF1FAC92FF1F706000402DE9F047044615468846002940D0431E072B3FD8FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D1DE
:4015C00041462046FFF738FFDFF868A027011AF8040000F0B9FF1223FE222946305D01F07FF807F5C0411FFA88F27831305D01F089F8DFF84490315D1AF8040000F0F4FFE9
:4016000001211AF8040000F089FF17F8093043F0020307F8093017F8093003F0FD0307F8093002E00D4600E000252846BDE8F087B592FF1FAC92FF1F70600040431E072BA7
:401640000AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BFDC92FF1FFE5F0040431E072B9FBF024B000108221A547047FE5F004030B51A4A1A491B4D0A
:401680000878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2E85450680133013050601088013880B21080ECE718460B780E4C082B0E4A00D003
:4016C00040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087030BD00BF4C93FF1F4893FF1F00600040C492FF1FC192FF1FD692FF1FD292FF1FE1
:401700004993FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012013707047D692FF1FD292FF1FC192FF1F4893FF1F4993FF1F30B5164B16491B78E1
:401740000A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B15568215C013BC9B229705168DBB20131516011880130013989B21180ECE7094A1370B3
:40178000094A137883F080031370084B0B221A7030BD00BF296000404C93FF1F00600040C492FF1F4993FF1FD292FF1FC192FF1F064A06231370064A01201370054B802273
:4017C0001A70054B00221A70704700BFD692FF1FC192FF1FD292FF1F4993FF1F054B9A683AB19A68044910709A680988518000229A607047C492FF1F4C93FF1F08B5124BAC
:401800001A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F06003202B05D0402B06D043B900F012FC04E001F0A5FB01E000F046FD10B9034B1C
:4018400003221A7008BD00BF28600040C192FF1F0060004008B5084A084B0120197813880B449BB21380064B00221A70FFF7B6FF044B03221A7008BD4C93FF1F4893FF1F1D
:40188000D692FF1FC192FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE80840FFF7D8BFBDE80840FFF746BF0320FFF795FF034B03221A7008BD00BFD692FF1FCC
:4018C000C192FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BFD692FF1FC192FF1F08B50A4B1A7832B11A78094942F080020A7000221A70074B00220120D1
:401900001A70FFF76BFF054B03221A7008BD00BFC092FF1F08600040D692FF1FC192FF1F074B1B78DBB2042B05D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF70479E
:40194000D692FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF174B1B78190609D538BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B066B
:401980001BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078DBB2000605D41378DBB20B700B7803F00F0328788342F1D138BD38BD2860004067
:4019C000C192FF1FD292FF1F4993FF1F29600040054A00231380054A916819B191680B7092685380704700BF4C93FF1FC492FF1F0E4808B503889BB213B9FFF783FE13E0D1
:401A00000B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934238BF0380FFF728FE012008BDC492FF1FD692FF1FD292FF1F00600040084B012220
:401A40001A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370470B600040DC92FF1F094B02221A700F3B93F82230074B1A7E02F00302012A1EBF0F
:401A8000DA7E82F08002DA7601225A76704700BF0B600040DC92FF1F0B4B04221A700F3B93F83230094B93F8242002F00302012A1EBF93F8272082F0800283F827200122E0
:401AC00083F82520704700BF0B600040DC92FF1F0B4B08221A700F3B93F84230094B93F8302002F00302012A1EBF93F8332082F0800283F83320012283F83120704700BFF5
:401B00000B600040DC92FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF0000C31B3CA0720D5144A4FEA031E7244947850782040C5070DD507FB03652C799F
:401B4000240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425792D0658BF84F801C090700133DBB24908D7E7F0BD9F600040DC92FF1F70600040D5
:401B8000FE5F004000F0ACBC70B50446184B88B003AA03F11006154618685968083303C5B3422A46F7D11B782B70FCB12223237001AD03232846637000F08AFE00222046DB
:401BC0001146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AC1700371417100F10400EAD108B070BD4F3A00002DE9F0431C4D01222E460C2093
:401C00001F274FF0800E4FF0080C194B00FB02581401234418705F70164998F805902144B9F1000F07D098F8044024064CBF887081F802C001E081F802E000FB0261CC880F
:401C40000132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1054BFF221A70BDE8F08300BFDC92FF1F70600040FC5F00400A600040064B074A70
:401C80001B7802EBC30253681A7C824286BF03EBC003586900207047D092FF1FB03A00002DE9F84F424B1A78002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC368147810
:401CC000007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF80420597891425ED19A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B01E
:401D000009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000F0BDAB9F1010F0CBF4FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF20
:401D40004FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F808A0CAF3072A81F800A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFAAB
:401D8000494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288C2F307224A71083394E7BDE8F88F00BFD592FF1FDC92FF1FD192FF1FFC5F004052
:401DC00070600040C292FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300406908BDD592FF1F00212DE9F84F0B464E4E0C2707FB01F4013132190929335553
:401E00004FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B1A78002A7FD0187801250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0B5
:401E4000400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C36803EB02094B4531D093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BF50
:401E8000A1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78BAF1000F06F0030607DA012E0CBF07260D264E7181F8018006E0012E0CBF052673
:401EC00009264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C200833934209D013F8081C13F80A5C01F07F0100FB01418D72F2E7FFF767FF114B01211860E6
:401F000000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8F84FFFF767BEBDE8F88F00BFDC92FF1FC292FF1F4A93FF1FD592FF1FD392FF1FCE
:401F4000D892FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F0010191700021D170517841F002015170127912F0800F074A1A4414BF8D238923CF
:401F80009370FFF715BC0020704700BF00600040DC92FF1FFC5F004030B4194B1A7902F07F02531E072B27D8164B0C2404FB02339978154D01F0FE0199700021D970294600
:401FC0001201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D18C7006E00D2303E0012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF9C
:4020000000600040DC92FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE43681B798B4201D2012909D8074A0848535CDBB24354A3780120DBB2535410BD56
:40204000002010BDD592FF1F00600040C292FF1F4A93FF1F38B58A4A8A4C13780021DBB221801806517840F18D800A2900F20581DFE811F05D000301030103010301030167
:402080000B0003017E0003018200D3787C49012B09D17D4B1A787D4B03EBC2035B685B686360122310E0CB78022B12D18878FFF7E5FD002800F0E180436863606368DA7885
:4020C00063689B7843EA02232380BDE83840FFF78FBCCB78032B26D16D4B00228878D5B2854209D3664A91786A4AEE2908BF1346634A917881B106E0187801320028F1D025
:4021000018780344EAE764499278097C914203D16248FFF739FD614B1A78002A00F0AD801A78228018E0BDE8384000F029BF13F0030313D0022B40F0A0802380504B0C21C4
:402140001B7903F07F02564B01FB02339A78554BD2B21A7000225A706360B6E702222280514A11784F4AC9B2117053706260ACE7012323804D4BEFE70123238013794C4AC4
:402180001344E9E701390A2977D8DFE801F037764F76067676760A7620009378454ADBB25AE0937803F0FF0153B9404B1A7891425FD01970404B01201870FFF715FE58E082
:4021C000481EC0B2FFF75AFD0028EED155E0FFF71DFF002851D02A4A384913791279DBB2D2B20A70364A3249D25CCB5C9A4240D0314B01221A70FFF753FD3AE003F00303EE
:40220000012B2BD009D3022B37D11D4B9B78002B33D1BDE83840FFF7BFBE194B9B78012B2BD1214A137803F0FD0315E003F00303012B13D008D3022B1FD1114B9B78E3B9A4
:40224000BDE83840FFF77EBE0D4B9B78012B14D1154A137843F0020313700AE0084B1A795AB998781B791749DBB2CA5C22EA0002CA54BDE83840FFF79BBA002038BD00BFEC
:4022800000600040C492FF1FD092FF1FB03A0000143B00009C3A0000873B00006893FF1FDC92FF1F8192FF1FD392FF1FD592FF1FC292FF1FC092FF1FD492FF1FD192FF1FF4
:4022C0004A93FF1FD792FF1F074B1A78120609D55B78012B06D1054B054A5A6012781A80FFF786BB0020704700600040C492FF1F743A0000014B1870704700BF76650040FA
:40230000014B1878704700BF67640040014B1870704700BF77640040064A0123136002F688321268E0211064034A1170A2F540721360704780E100E000E400E0014B187039
:40234000704700BF74640040014B1870704700BF7565004073B515461E460B4C05230022019200920A4601461846237000F064F932462946207800F01FF90221207800F0B7
:4023800009F9207802B070BDD080FF1F064A0423136002F688321268E0219064034A1170A2F202321360704780E100E002E400E0014B04221A60704700E100E0014B04228E
:4023C0001A60704780E100E0014B1870704700BF74650040704738B505460078012428B100F066FD285D0134E4B2F8E738BD08B50D2000F05DFDBDE808400A2000F058BDDC
:40240000F7B516461F460B4C00230325019300930A4601462846257000F00EF93A463146207800F0C9F80221207800F0B3F8207803B0F0BDE080FF1FF7B516461F460B4C05
:4024400000230225019300930A4601462846257000F0F2F83A463146207800F0ADF82946207800F097F8207803B0F0BDE180FF1FF7B516461F460B4C002301250193009322
:402480000A4601462846257000F0D6F83A463146207800F091F80221207800F07BF8207803B0F0BDE280FF1F73B515461E460B4C0023019300930A4601461846237000F026
:4024C000BBF832462946207800F076F80221207800F060F8207802B070BD00BFE380FF1F024B1878C0F38010704700BF8F450040074A7F23802113705170064A013BDBB2F7
:4025000002F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD8084B0001C25C11B142F0200201E002F0DF02C254C25C42F00102C2540020704780
:40254000012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C25400207047012070471070004017280DD8074900010B4603441A7942F004021A7150
:40258000435C43F00103435400207047012070471070004017280BD8064A0001835C490003F0F10301F00E011943815400207047012070471070004041F6FF73994208BFD2
:4025C0004FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC280002001207047000017289FBF034B00011954002088BF0120704700BF197000402C
:4026000017289FBF054B00011A5C01F007019DBF1143195400200120704700BF1470004017289FBF034B0001185C00F0070088BFFF20704714700040172810B51AD8C000B7
:4026400001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA0213DBB263709DF80C30002003F00F03A370E07010BD012010BD10B500F079FC0A4A00
:402680005378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F06BFC204610BD00BFE480FF1F030610B5044611D400F05CFC084AE300117803F1FE
:4026C000804303F5F04319705378147001335370BDE8104000F050BC10BD00BFE480FF1F30B504060CD411F4704509D1C40004F1804404F5F0442180A270E370284630BDB0
:40270000012030BD03065FBFC00000F1804000F5F04081805ABFC280002001207047000038B50446084DB4F5004F05D9286800F017FCA4F50044F6E7034B58686043BDE875
:40274000384000F00DBC00BFEC80FF1F024B1B7A584300F005BC00BFEC80FF1F0E4B00F003001A78490102F0FC02104318701A7801F0600142F080021A701A7802F07F024E
:402780001A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B01221A70704784430040044B00F00F021B6853F8220043F82210704700BF08ED00E008
:4027C000054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0000F16040490100F56440C9B2017070470F4B10B50F4900240F205C609C60DC60F4
:402800001C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A1360084B4FF400421C60C3F8E82010BD8492FF1F9D28000010E000E0EC80FF1FC6
:4028400014E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034A136843F00103136008BD00BF10E000E010B5054CA3691BB9FFF7BAFF0123A36179
:40288000BDE81040FFF7E8BF8492FF1F024B1868C0F30040704700BF10E000E038B5FFF7F5FF012808D1054D002455F8243003B198470134052CF8D138BD00BF8892FF1FAE
:4028C000024B03EB80035868596070478492FF1F134B144A1B78DBB20360127843EA0223114A0360127843EA0243104A0360127843EA026303600E4B0E4A1B78DBB2436031
:40290000127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF0301004904010049EC46004002010049010100490001004905010049060100490D
:4029400010B500F015FB204A044613780A2043F002031370137C43F00203137412F80A3C43F0010302F80A3C937943F00103937102F5AB52137843F003031370134B18223F
:402980001A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222183B1A70094A137843F008031370FFF7CAFE064B10222046BDE8DE
:4029C00010401A6000F0D8BAAB4300400E5900402F5B004080E200E008B500F0C9FA0F4A137803F0FE031370A2F5AA521D3A137803F0FD031370137C03F0FD03137412F8E1
:402A00000A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F0AFBA00BF08590040044A137803F03F0343EA8010C0B21070704700BF08590040082804D00A280CBF9D
:402A40008223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B33
:402A80001A8070470A590040603A00005293FF1F5493FF1F5893FF1F08B5102000F0A6F907210420FFF79AFE07490420FFF788FE064A0C20137843F006031370FFF7BCFFBA
:402AC000034B00221A8008BD912B0000095900405093FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF72ABFA092FF1F044B1A7802F0FB021A701A7842F0010256
:402B00001A7070470859004010B5084B1C7814F0010403D10028F9D0002404E02046FFF715FE024B1B78204610BD00BF09590040034A044B1B881088181A00B2704700BF1D
:402B40005893FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F0C1
:402B800000B270475293FF1F5493FF1F5093FF1F7047000010B500F0EBF9214A044613780A2043F001031370137C43F00103137412F80A3C43F0020302F80A3C937943F0DB
:402BC0000203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222123B1A7067
:402C0000094A137843F008031370FFF79FFD074B08222046BDE810401A6000F0ADB900BFAB43004006590040275B004080E200E008B500F09DF90F4A137803F0FE0313708E
:402C4000A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F80A3C937903F0FD039371BDE8084000F083B900BF00590040044A137803F03F0361
:402C800043EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A80AE
:402CC00042F210734B4341F2883103F6C41393FBF1F305490B60054B1A807047025900406A3A00005E93FF1F6493FF1F5C93FF1F08B5102000F084F807210320FFF76EFD92
:402D000007490320FFF75CFD064A0C20137843F006031370FFF7BCFF034B00221A8008BDE92D0000015900406093FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040C8
:402D4000FFF728BFA192FF1F044B1A7802F0FB021A701A7842F001021A7070470059004010B5084B1C7814F0010403D10028F9D0002404E02046FFF7E9FC024B1B78204621
:402D800010BD00BF01590040034A044B1B881088181A00B2704700BF5C93FF1FA05B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107381
:402DC00000B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475E93FF1F6493FF1F6093FF1F70470000034A00F0F800137803431370704700BF02410040A1
:402E0000034A00F0F800137803431370704700BF06410040014B1870704700BF72640040014B1870704700BF7864004073B515461E460B4C04230022019200920A46014603
:402E400018462370FFF7F8FB324629462078FFF7B3FB02212078FFF79DFB207802B070BDFC80FF1F074A0223136002F688321268E0215064044A11706FF440710A4413608D
:402E8000704700BF80E100E001E400E0014B1870704700BF75640040014B1870704700BF76640040014B1870704700BF79640040FEB5494652465B460EB407462449096800
:402EC0008A46244A12682448022100F071F8030020480068C018204900F06AF8143883460121C9430C460125002600F041F8814651460B7823400B705846013000F030F81C
:402F00003800F04028400B78234003430B70584600F026F80136072EF2D9002001300138013001200B78234003430B705846043000F016F8484600F01FF800BF00BF00BF12
:402F40000EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F00C20100000000000230800803D000BF01380046FCD17047EFF3108072B6704780F31088704700BF77
:402F8000094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A00E013790020704700600040DC92FF1F002902D0B0FBF1F0704708B14FF0FF3097
:402FC00000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BF6081FF1F0E4B70B51E460E4C0025E41AA410A54204D056F8253098470135F8E75C
:4030000000F0DEFD084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BDCC3B0000CC3B0000CC3B0000D43B000003460244934202D003F8011BFAE770475A
:4030400030B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBF234604F1FF33029305934FF6FF7300910491ADF80E3002461E9B6946284600F0D4
:4030800073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A46014603480068FFF7CBFF03B05DF804FB6081FF1F2DE9F0478E6882469E420C46D9
:4030C000914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F57B1C43449D4238BF1D4653050FD5294600F04AFB064698B13A46216900F0D2FA24
:40310000A38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FA0C23CAF80030A3894FF0FF3043F04003A381BDE8F08726613E44266046466561E0
:40314000ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E442660BDE8F08700002DE9F04F9DB003938B8980461C060D4616460DD50B695BB958
:40318000402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF82930DFF89CB130238DF82A3037463C4614F8013B1BB9B7EB060910D003E0252BD0
:4031C000F9D02746F3E74B46324629464046FFF771FF013000F0A780099B4B4409933B78002B00F0A08000234FF0FF3204930793059206938DF853301A93012605222178C6
:403200004E4800F041FA671C049B38B14B4A3C46801A06FA00F018430490EFE7D90644BF20228DF853201A0744BF2B228DF8532022782A2A03D0079A00210A200BE0039AA2
:40324000111D12680391002A10DA524243F00200079204900BE027463B780134303B092B03D800FB02320121F5E701B107923B782E2B1ED17B782A2B0AD1039B02371A1DAA
:403280001B680392002BB8BF4FF0FF33059310E0002319460593781C0A2407463A780130303A092A03D804FB01210123F5E703B1059103223978224800F0E6F940B1402309
:4032C000CBEB000003FA00F0049B013718430490397806221B487E1C8DF8281000F0D4F988B1194B33B9039B073323F007030833039314E003AB00932A46144B04A940468F
:40330000AFF3008007E003AB00932A460F4B04A9404600F093F8B0F1FF3F824603D0099B5344099342E7AB895B0601D4099801E04FF0FF301DB0BDE8F08F00BF9B3B000071
:40334000A13B0000A53B000000000000B53000002DE9F04791461F460A698B6806469342B8BF1346C9F8003091F843200C46DDF8208012B10133C9F800302368990642BF15
:40338000D9F800300233C9F80030256815F0060510D104F1190A07E00123524639463046C04701301AD00135E368D9F800209B1A9D42F1DB94F843302268003318BF01230D
:4033C00092060FD5E118302081F843005A1C94F845102244023382F8431003E04FF0FF30BDE8F08704F1430239463046C0470130F4D02268D9F80050E36802F00602042AD7
:4034000008BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED184FF000091A344D4509D00123224639463046C0470130D5D009F10109F3E70020BDE8F0872DE9F0438A
:4034400017460A7E85B06E2A984606460C460C9B01F1430E00F0AE8011D8632A22D009D8002A00F0BB80582A40F0CA8081F84520834955E0642A1ED0692A1CD0C0E0732A65
:4034800000F0B08009D86F2A2ED0702A40F0B8800A6842F020020A603EE0752A24D0782A3AD0ADE01A6801F14205111D1960136884F84230A8E021681A6811F0800F02D098
:4034C000111D196008E011F0400F02F10401196002D0B2F9003000E01368002B3CDA2D225B4284F8432037E021681A6811F0800F02D0111D196007E011F0400F02F104010E
:40350000196001D0138800E01368227E5C496F2A14BF0A2208221BE078225A4984F845202268186812F0800F00F104051D6003D1550601D5038800E00368D00744BF42F032
:40354000200222601BB9226822F0200222601022002084F8430001E049490A226568002DA56008DB206820F0040020602BB9002D7DD175460CE0002B79D07546B3FBF2F020
:4035800002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0708D5236962689A42DEBF302305F8013C05F1FF35C5EB0E0323612EE008681A6810F0800F496903D0FC
:4035C000101D1860136808E010F0400F02F104001860136801D0198000E0196000232361754616E01A68111D1960156800216268284600F049F808B1401B6060636804E0BC
:4036000004F1420584F8422001232361002384F84330CDF800803B4603AA21463046FFF797FE013002D14FF0FF3026E023692A4639463046C0470130F5D023689B0710D563
:40364000002504F1190907E001234A4639463046C0470130E7D00135E368039A9B1A9D42F2DBE068039B9842B8BF184605E00B7804F1420584F842308AE705B0BDE8F083AB
:403680004F3A0000AC3B000010B5C9B202449042034605D01C7801308C42F8D1184610BD002010BD10B5431E0A44914204D011F8014B03F8014FF8E710BD884210B501EBCE
:4036C000020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD994204D011F8014B02F8014FF8E710BD38B50546002944D051F8043C0C1F002BF2
:40370000B8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A018834201BF18685B681218226063600C6023E0A24203D813465A68002AF9D118681918DB
:40374000A1420BD12168014458188242196013D110685268014419605A600DE002D90C232B6009E021686018824201BF106852680918216062605C602846BDE8384000F0C4
:4037800098B838BDA892FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA94202D90C23336046E000F082F8234B1C681A462146A1B10B685B1B0ED40B2B03D94E
:4037C0000B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E7174C23681BB9304600F052F820602946304600F04DF8431C18D0C41C24F00304D4
:40380000A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BD211A304600F034F80130EBD10C233360304600F03EF8002070BD00BFA892FF1F09
:40384000A492FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF2846F8BD00F027F885420ED929463846FFF78BFF044650B131462A46FFF713FF3146E1
:403880003846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7D8FB431C02D1236803B12B6038BD8C93FF1F7047704751F8040C0028BEBF091851F8F0
:4038C000043CC0180438704700000000050209020B020D020F021102130215027265706C792030782530327800686F6D696E6700626567696E6E696E67207365656B2066CB
:40390000726F6D20256420746F2025640066696E6973686564207365656B00796573006E6F00647269766520303A20257320647269766520313A2025730057616974696E5F
:403940006720666F72205553422E2E2E0055534220726561647900636F6D6D616E6420307825303278006661696C2025642B25642B2564203D3D2025642C206E6F7420254E
:4039800064007061737365643D256400756E64657272756E206166746572202564207061636B65747300636F756E743D256420693D256420643D256400636D645F77726997
:4039C000746500703D25642063723D25642063773D256420663D256420773D256420696E6465783D256420756E64657272756E3D256400756E64657272756E21007375635E
:403A0000636573730073746172742065726173696E670073746F702065726173696E670069646C6500005100401000405100403000000001400010001401400008004001A3
:403A400040000A004C0140000200500140200030313233343536373839414243444546000001000000040000001000010000000400000010280000000001040001000000C2
:403A800000000000000157494E5553420000303030303100000000000000000012034D005300460054003100300030000100000001000000B83A000001000000873B0000A5
:403AC000000000000000000001000000D03A000001000000593B000004000000F23A0000000000000000000000000000F03A0000FF00000001024000FF00000082024000C7
:403B0000FF00000003034000FF00000084034000FF00020304030904160346006C007500780045006E00670069006E0065002A0343006F0077006C00610072006B00200034
:403B400054006500630068006E006F006C006F0067006900650073000009022E0001010080320904000004FF00000107050102400000070582024000000705030340000AE8
:403B80000705840340000A12010002FF0001080912006E0100020180014300232D302B2000686C4C0065666745464700303132333435363738396162636465660000000069
:403BC000F8B500BFF8BC08BC9E46704759000000CD100000F8B500BFF8BC08BC9E46704735000000F83B0000C880FF1FA00000002812000000000000000000009093FF1FA8
:403C0000FF000000675000400C00000007000000FFFFFFFF7F8000003F0000000000007D00FA0000400000000090D003FF0000000000000000000000000000000000000028
:403C400000000000000000000000000000000000993B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070
:403C8000000000000000000000000000000000000081FF1F000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065
:403CC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C4
:403D00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083
:403D40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043
:4001000001221A75597514E09969521A19698A4294BF587D00201875187D094908B1002204E0086982428CBF002201224A75DA689A611B7D13B1002002F056B9704700BF60
:400140008881FF1F10B5C4B2204601F087F90128FAD110BD08B572B60F4B0F49DA680132DA60DA690132C82A08BF0022DA611A6AD8690132A72A08BF00220A621B6A002BE5
:400180000CBF02230023002814BF184643F0010002F092FE62B608BD8881FF1F38B50446C5B2284602F0C2F8062002F0DFFA44F00200C0B202F0BAF8062002F0D7FA2846BD
:4001C00002F0B4F8BDE83840062002F0B9BA10B5642402F0A5F828B9FFF7E0FF013CF8D1204610BD012010BD70B5C4B2054620460E4601F033F9012805D0204601F04CFAA8
:400200002846FFF79FFF204601F030F9314605460246204601F0ECF9204601F01FF90028FAD1284670BD000038B5044D0024285D013402F04BFA402CF9D138BDAC81FF1F30
:4002400008B502F065FC002002F06EFC02F080FC02F08AFC80B208BD10B50446012002F07DF8642002F06CFAFFF7EAFF2080002002F074F8642002F063FAFFF7E1FF60801C
:4002800010BD08B502F070FD002002F079FD02F08BFD02F095FD80B208BD10B50446FFF796FF322002F04CFAFFF7EBFF20800120FFF774FF322002F043FAFFF7E2FF608072
:4002C00010BD0FB400B593B014AB53F8042B402102A8019302F0F0FE02A802F08CF802F096F813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF723FF6278E6
:400300002146BDE81040042001F000B9043A000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8643043B1002001F0EAFF85
:40034000002002F07DFD002384F8643010BD00BF8881FF1F38B5124D837895F8672004469A4204D0FFF7E4FF002385F86A302368C5F8653022790B4B1A71A378002B14BF31
:400380000220012002F05CFDE078B0FA80F0400902F050FD2079BDE8384002F087BD00BF8881FF1FED81FF1F38B50D4C94F8645065B904F16500FFF7CDFF012001F0AAFFDC
:4003C0004FF47A7002F0BCF984F86A50E368E366012384F86430BDE8384002F0EFB900BF8881FF1FF8B5214C0546FFF7DDFF94F86A3003B15DB91E48FFF763FFFFF7E7FECE
:400400000120002384F86A00236702F0AFF92A46216F1848FFF755FF144E0027236F9D4216D001F07DFF00B13767236F9D4205DD0120FFF7B3FE336F013305E005DA002053
:40044000FFF7ACFE336F013B336702F0B7F9E5E7322002F075F92A2DCCBF0020012002F031FDBDE8F8400448FFF72BBF8881FF1F113A0000183A0000353A00002DE9F04F70
:4004800099B062B602F004FA9E49042002F028FA9D4801F051FF9D4802F0F4FC9C4801F085FF02F0D5FB02F0A7FA002002F0C8FC01F0A0FF0221002000F086FF954C012011
:4004C00001F0FEF8002384F86730FFF76DFFFFF77EFE84F87400FFF72BFF012384F86730FFF762FFFFF773FE84F87500FFF720FF894B94F87400894994F875202546002AA4
:4005000014BF0A461A46002808BF19468448FFF7D8FE0321084602F031F9264602F04EF994F8643043B1EA6EEB689B1A41F28832934201D9FFF7FCFE00F07EFF18B9794820
:40054000FFF7BFFE04E000F07DFF0028F7D10BE000F072FF10B902F031F9F9E77248FFF7B0FE032001F098F8032000F077FF0128D4D16E48FFF7EEFE6D490320FFF734FE85
:4005800094F876106B48FFF79CFE94F87630023B142B00F2D683DFE813F01500D4031E00D4032400D4035000D4037600D403D900D403C101D4030803D4032C03D40333036C
:4005C000D4034D0303238DF820308DF821300F238DF822302AE394F87800FFF703FF564B21E340F2DC57FFF7DFFE00232375E068227D02F0FF012AB9EB681B1ABB42F7DD44
:400600000B4611E083B10022174696F87810F068277594F814E0BEF1000F02D1EB681B1AF7E701329142F3DA07228DF8202004228DF82120ADF82230F8E20220FFF782FD2F
:400640004FF000080DF1200A02F0B8F84FF480790027C9EB0803DA1907F80A200137402FF9D10220FFF76EFD3A465146022000F04DFFB9F10109EBD108F10108B8F1400F58
:40068000E2D12E4B38E04FF0010A4FF000080DF1200B02F093F84FF0000959460120FFF7A3FD08EB090300270493049B1BF807203B44DBB29A4209D08DE80C0041463B4641
:4006C0004A461F48FFF7FDFD4FF0000A0137402FEBD109F10109B9F5807FDED108F10108B8F1400FD5D151461648FFF7EAFDBAF1000F00F01B81144B1B8807A8ADF81C30AD
:4007000095E200BF55010000F900000091000000C50000008881FF1F473A0000433A00004A3A0000623A0000753A0000ED81FF1FFE81FF1F7F3A0000EC380000EE380000BE
:400740008E3A0000AA3A0000F0380000206FFFF749FE94F8780001F001FE94F8780001F0E5FD02F015FCB94BDFF8FC821A78002702F0FB021A701A7842F001021A701A789D
:4007800002F0FE021A701A7802F0FE021A7002F003FC0220FFF7D6FC012141F6FF734FF48042084602F052FB84F8B60001F074FF08F807000137402FF8D1DFF8B0A200271A
:4007C0000AF195091FFA89F80137402F14BF3A4600221AF8010F2244062392F82420402101F08EFF424646F24B419AF8000001F099FF08F14008402F1FFA88F8E4D196F859
:40080000793053B196F87C30336100233375237D002BFCD000233375336100234FF0FF32236062602372236894F8B600234493F8241001F0E9FE94F8B60001F0A7FE01214A
:4008400094F8B60001F07AFE2368002BFCD0002398467360D6F80CA0012701F0AFFFE368B4F87A20CAEB030393420DD367B1042195F8B60001F0D4FE94F8B60001F0E0FEE1
:400880000028F9D107463072237AFBB96A682B689A4202D1002FE0D118E00220FFF752FC6968402209EB8111022000F02FFE6A68674B01321340002BBEBF03F1FF3363F091
:4008C0003F03013308F101086360C6E70220277AFFF738FC00221146022000F017FE0220FFF730FCFFB2FFF79FFC002001F01EFD37B15848FFF7E5FC0220FFF709FD06E06E
:40090000554B08A81B88ADF82030FFF7EFFC227D4146237A5148FFF7D4FC15E25048FFF7D0FCD4F87A7017F03F0701D0032009E2286FFFF757FD95F8780001F00FFD95F829
:40094000780001F0F3FC012001F00EFD02F020FB444BDFF814811A7842F004021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F00FFB01214FF4804315
:4009800041F6FF72084601F0F5FC85F8B60001F083FE08F807000137402FF8D1DFF8CC90002709F195031FFA83F804930137402F14BF3A46002219F8010F2244052392F8D7
:4009C0002420402101F09CFE414646F2495299F8000001F0A7FE08F14008402F1FFA88F8E4D100274FF0FF33376098467360BB463B46D6F87A9037725FEA99190CBF4FF060
:400A0000010A4FF0000A2168114A01310A40002ABCBF02F1FF3262F03F026068B8BF013282426FD02BB1227A002A7AD12A7D002A77D12068049A059302EB8010BAF1000F36
:400A400016D040223F2102F003FB1CE09B6400403F000080B43A0000F2380000CE3A0000E13A000099650040AC81FF1FAB81FF1F014601370120FFF7B7FBC7EB0903D3F10C
:400A8000000A4AEB030A2168B34A01310A40002ABEBF02F1FF3262F03F02013222606268059B01322ED12A683F2A2BD14FF00008C5F8048001F08CFC85F808806B6895F8DC
:400AC000B6002B4493F8241001F09EFD95F8B60001F05CFD012195F8B60001F02FFD95F87E302B6185F81480237D002BFCD04FF00008012086F8148001F076FC404601F0B4
:400B000033FC00E023B1237A5BB92B7D4BB90123626842453FF477AF0BF1010BD5F8048071E701F05BFC012001F01EFC002001F05BFC042194F8B60001F072FD94F8B60011
:400B400001F07EFD80460028F8D196F8B60001F00BFD337D327A0293012303920193CDF800A05B463A4649467C48FFF7AAFBC6F81080BAF1000F0BD0FFF756FB002001F028
:400B8000D5FB237A63B17648FFF79BFB0220D9E0B945F1D073490120FFF726FB0137F7E77148FFF78EFB714B3DE094F8780001F0D5FB206FFFF716FC6D48FFF782FB94F8A5
:400BC0007930236100232375237D002BFCD0012001F00AFC00233375237D002BFCD0002001F002FC002363483361FFF76AFB624B19E0002084F86A00FFF7F4FB5F4B12E003
:400C000094F8743023B195F875200AB985F8782094F875201AB113B9012385F878305848FFF798FB574B1B88ADF8203008A8FFF75DFB89E0FFF77CFB02F088F8002002F0FA
:400C40002BF82A2701F056FF002001F0F9FE3A46002108A802F0FCF917238DF820308DF8217001F0ABFD002001F054FB002002F0E7F8C82001F064FD0DF12200FFF7ECFA35
:400C80000DF13600FFF709FB01F098FD012002F0D7F8322001F054FD0DF12600FFF7DCFA0DF13A00FFF7F9FA012001F033FB4FF4967001F045FD01F081FD0DF12E00FFF7AC
:400CC000CBFA0DF14200FFF7E8FA002001F022FB4FF4967001F034FD01F070FD022002F0AFF8322001F02CFD0DEB0700FFF7B4FA0DF13E00FFF7D1FA012001F00BFB4FF4FE
:400D0000967001F01DFD01F059FD0DF13200FFF7A3FA0DF14600FFF7C0FA002001F0FAFA4FF4967001F00CFD01F048FD002002F087F8002384F86A3001F08AFF01F05CFE97
:400D400074E70120FFF7E4FA032000F0A5FC0E48FFF7B7FAFFF7E2BB3F000080EB3A00001B3B00004092FF1F253B0000F43800002D3B00003B3B0000F6380000F8380000ED
:400D8000FE81FF1FFA380000483B00000F4B1A78120616D55878C0B2012814D11B79DBB2042B02D0052B05D07047094A094B5A60282203E0084A074B5A60E0221A8000F04C
:400DC00045BE002070470120704700BF00600040FC380000C492FF1F243900002DE9F04172B68A4B61221A70A3F5F06301221A801924874A9C7092E803008033062283F8F7
:400E0000002283E80300522203F580731A70814B814A1B78814EDBB2137040F61802804B00251A8041F2512223F8022C33784FF4F07003F0010343EA450502F0A1F8013CE3
:400E400005F003052ED0032DF0D1764B4FF480721A8007221A70744A002548211570917002221D705D7103F8032C0422DA716F4A6F4C13786F4E43F00103137012F8013C9A
:400E8000062743F0030302F8013C2378012243F0800323705D4B1A70674A137843F02003137000E0FEE707FB056300219A881868013502F0CDF8072DF5D16048604E0025F3
:400EC00050F8041F05F1105303F14A0221F0FF074B33C9B20B4452005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1534A00231360936013619361FD
:400F0000514B524F1A68524BDFF88C811A60514B1A6803F1784303F5D6431A604E4A137843F002031370137C43F0020313742378A2F5863243F040032370413A137843F047
:400F400010031370454A464B07CA03C31A80454A2833106843F8250C127903F8212C424A07CA03C31A80414AE83B07CA03C31A803F4A083307CA03C31A803E4A3E4BA2F5B0
:400F8000616203CBC2F8100EC2F8141E1378042043F008031370394B02F5AA521B783D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0030326
:400FC00088F800302E4B48221A702E4A402313702D49937013729372082382F81F3220220A7048710A72294A0A20137001F0BEFB274B88F8006044223D70264D1A7094E8E4
:401000000F0007C52B80BDE8F08100BF004800401C0B00480F010049A146004025420040224200400440004006400040A2430040A04300404D3B0000E8460040FCFFFF470A
:401040009000004800760040240B0048F846004020760040280B004803500140DC0A0048C0510040E80A0048F00A0048FC0A0048080B004832510040140B0048CF01004921
:401080001D51004001590040235B0040585B004076580040B0430040F946004008B501F0ABFF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC086005E043
:4010C00001F09AFF0C2303604FF0FF33184608BDCC80FF1F9093FF1F80B51148114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7114653F88D
:40110000046C8C1AA64202D041F8045BF9E701381033E5E701F076FFFFF7B0F9FEE700BF01000000E03C0000124A134B10B51A60124A134C1368134843F40073136000238F
:40114000032B98BF54F823204FEA830188BF0E4A0133302B4250F3D10C4B1A780C4B1A700C4B084A1A60FFF739FEBDE8104001F0CFB900BF0004FA050CED00E014ED00E016
:40118000000000000080FF1F9D100000BC760040C080FF1F08ED00E0F8B501F0F9FE4B4A01271378022643F001031370137C484C43F001031374474B02F5E3521F700B3215
:4011C00003F8946C1378054603F07F031370002001F0CCFA2378404A03F0F90323701378384603F0DF03137023783B43237001F0BDFA282001F0BAFA384B30461A7802F089
:401200007F021A701A7802F0BF021A7023783343237001F0ABFA2378314A43F0040323700023137053702F4AFF2199540133092BFBD1284601F0B0FE0721172001F0DEFA5F
:401240002949172001F0CCFA0721182001F0D6FA2649182001F0C4FA0721152001F0CEFA2349152001F0BCFA0721052001F0C6FA2049052001F0B4FA0721062001F0BEFA17
:401280001D49062001F0ACFA0721084601F0B6FA1A49072001F0A4FA0721082001F0AEFA1749082001F09CFA0021162001F0A6FA1449162001F094FA07210C2001F09EFA0C
:4012C000BDE8F84010490C2001F08ABAA5430040944300409D60004012600040F851004084600040B592FF1F671B0000A1190000651B0000991A0000C51A0000F51A0000BA
:401300002D1B00006D1B0000E11B0000214B224A10B5187000231370204A40201370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A604FF080729F
:401340001A604FF400121A6020221A601860802018604FF480701860174804704FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B03221A70D2
:40138000802203F8202C012001F0FAFD0D4B04221A7010BDD092FF1FD692FF1FD492FF1FD592FF1FD192FF1FC092FF1FD392FF1F4893FF1F00E100E09E6000409C600040AF
:4013C000286000401260004070B5074C054623780E461BB9FFF7E0FE0123237031462846BDE87040FFF792BF8092FF1F0A4A002313700A4A13700A4A13700A4A13700A4ADE
:4014000013700A4A13700A4A13700A4B03221A70802203F8202C7047D692FF1FD492FF1FD592FF1FD192FF1FC092FF1FD392FF1F4893FF1F28600040014B1878704700BFC1
:40144000D592FF1F044B1A7802F0FF001AB118780022C0B21A707047D492FF1F024A0C2303FB002040787047DC92FF1F431E072B0CD8074A064B00010344805C5B7800F0CD
:401480000F0043EA0020023880B2704700207047FC5F00401A4A38B50C2303FB00231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032D18D8DFE8AD
:4014C00005F002070C110021084600F0FDFF0DE00021084600F0DCFF08E00021084600F0BBFF03E00021084600F09AFF054B1855EDB2072D03D801F0CFF8034B185538BDA0
:40150000DC92FF1FAC92FF1FB592FF1F431E072B2DE9F0470446894615465CD82F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BFD588DB7824
:4015400084BFC5F50075ADB2254A43EA15230601B354B244EBB28AF80130224B1A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4F385D00F016
:40158000F3FF11232946FE2218F8040001F0B8F806F5C04278321FFA89F118F8040001F0C1F8124D18F80410385D01F02DF80121385D00F0C3FF735D43F002037355735D92
:4015C00003F0FD037355BDE8F08703FB04746379DBB28AF80230BDE8F08700BFDC92FF1FFC5F0040B592FF1FAC92FF1F706000402DE9F047044615468846002940D0431E54
:40160000072B3FD8FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D141462046FFF738FFDFF868A027011AF8040000F09BFF1223FE222946305D01F061F807F557
:40164000C0411FFA88F27831305D01F06BF8DFF84490315D1AF8040000F0D6FF01211AF8040000F06BFF17F8093043F0020307F8093017F8093003F0FD0307F8093002E099
:401680000D4600E000252846BDE8F087B592FF1FAC92FF1F70600040431E072B0AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BFDC92FF1FFE5F0040C3
:4016C000431E072B9FBF024B000108221A547047FE5F004030B51A4A1A491B4D0878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2E854506895
:401700000133013050601088013880B21080ECE718460B780E4C082B0E4A00D040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087030BD00BF12
:401740004C93FF1F4893FF1F00600040C492FF1FC192FF1FD692FF1FD292FF1F4993FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012013707047C2
:40178000D692FF1FD292FF1FC192FF1F4893FF1F4993FF1F30B5164B16491B780A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B15568215C013B85
:4017C000C9B229705168DBB20131516011880130013989B21180ECE7094A1370094A137883F080031370084B0B221A7030BD00BF296000404C93FF1F00600040C492FF1F4C
:401800004993FF1FD292FF1FC192FF1F064A06231370064A01201370054B80221A70054B00221A70704700BFD692FF1FC192FF1FD292FF1F4993FF1F054B9A683AB19A682B
:40184000044910709A680988518000229A607047C492FF1F4C93FF1F08B5124B1A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F06003202BFD
:4018800005D0402B06D043B900F012FC04E001F087FB01E0FFF77AFA10B9034B03221A7008BD00BF28600040C192FF1F0060004008B5084A084B0120197813880B449BB208
:4018C0001380064B00221A70FFF7B6FF044B03221A7008BD4C93FF1F4893FF1FD692FF1FC192FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE80840FFF7D8BF34
:40190000BDE80840FFF746BF0320FFF795FF034B03221A7008BD00BFD692FF1FC192FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BFD692FF1FC192FF1FCE
:4019400008B50A4B1A7832B11A78094942F080020A7000221A70074B002201201A70FFF76BFF054B03221A7008BD00BFC092FF1F08600040D692FF1FC192FF1F074B1B782C
:40198000DBB2042B05D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF7047D692FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF174B1B782A
:4019C000190609D538BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B061BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078DBB200064D
:401A000005D41378DBB20B700B7803F00F0328788342F1D138BD38BD28600040C192FF1FD292FF1F4993FF1F29600040054A00231380054A916819B191680B7092685380CB
:401A4000704700BF4C93FF1FC492FF1F0E4808B503889BB213B9FFF783FE13E00B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934238BF03806C
:401A8000FFF728FE012008BDC492FF1FD692FF1FD292FF1F00600040084B01221A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370470B600040D9
:401AC000DC92FF1F094B02221A700F3B93F82230074B1A7E02F00302012A1EBFDA7E82F08002DA7601225A76704700BF0B600040DC92FF1F0B4B04221A700F3B93F83230CF
:401B0000094B93F8242002F00302012A1EBF93F8272082F0800283F82720012283F82520704700BF0B600040DC92FF1F0B4B08221A700F3B93F84230094B93F8302002F0F9
:401B40000302012A1EBF93F8332082F0800283F83320012283F83120704700BF0B600040DC92FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF0000C31B372
:401B8000CA0720D5144A4FEA031E7244947850782040C5070DD507FB03652C79240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425792D0658BF05
:401BC00084F801C090700133DBB24908D7E7F0BD9F600040DC92FF1F70600040FE5F004000F08EBC70B50446184B88B003AA03F11006154618685968083303C5B3422A46B4
:401C0000F7D11B782B70FCB12223237001AD03232846637000F06CFE002220461146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AC170037125
:401C4000417100F10400EAD108B070BD773B00002DE9F0431C4D01222E460C201F274FF0800E4FF0080C194B00FB02581401234418705F70164998F805902144B9F1000F62
:401C800007D098F8044024064CBF887081F802C001E081F802E000FB0261CC880132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1054BFF2213
:401CC0001A70BDE8F08300BFDC92FF1F70600040FC5F00400A600040064B074A1B7802EBC30253681A7C824286BF03EBC003586900207047D092FF1F9C3B00002DE9F84F64
:401D0000424B1A78002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC3681478007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF80420597891
:401D400091425ED19A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000F0BDAB9F11C
:401D8000010F0CBF4FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F808A0CAF330
:401DC000072A81F800A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFA494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288C2F3072281
:401E00004A71083394E7BDE8F88F00BFD592FF1FDC92FF1FD192FF1FFC5F004070600040C292FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300406908BDDA
:401E4000D592FF1F00212DE9F84F0B464E4E0C2707FB01F401313219092933554FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B1A78002AC0
:401E80007FD0187801250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C36803EB020993
:401EC0004B4531D093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BFA1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78BAF1000F12
:401F000006F0030607DA012E0CBF07260D264E7181F8018006E0012E0CBF052609264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C200833934209D013F814
:401F4000081C13F80A5C01F07F0100FB01418D72F2E7FFF767FF114B0121186000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8F84FFFF732
:401F800067BEBDE8F88F00BFDC92FF1FC292FF1F4A93FF1FD592FF1FD392FF1FD892FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F00101917098
:401FC0000021D170517841F002015170127912F0800F074A1A4414BF8D2389239370FFF715BC0020704700BF00600040DC92FF1FFC5F004030B4194B1A7902F07F02531EE1
:40200000072B27D8164B0C2404FB02339978154D01F0FE0199700021D97029461201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D18C7006E077
:402040000D2303E0012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF00600040DC92FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE43681B791B
:402080008B4201D2012909D8074A0848535CDBB24354A3780120DBB2535410BD002010BDD592FF1F00600040C292FF1F4A93FF1F38B5874A874C13780021DBB221801806BC
:4020C000517840F188800A2900F20081DFE811F05800FE00FE00FE00FE00FE000B00FE007900FE007D00D3787949012B09D17A4B1A787A4B03EBC2035B685B6863601223C5
:4021000010E0CB78022B12D18878FFF7E5FD002800F0DC80436863606368DA7863689B7843EA02232380BDE83840FFF78FBCCB78032B21D16A4B00228878D5B2854203D3F6
:40214000634A92783AB910E0187801320028F7D018780344F0E75E4A62499278097C914203D16148FFF73EFD5F4B1A78002A00F0AD801A78228018E0BDE8384000F010BF77
:4021800013F0030313D0022B40F0A0802380504B0C211B7903F07F02544B01FB02339A78534BD2B21A7000225A706360BBE702222280504A11784E4AC9B211705370626062
:4021C000B1E7012323804C4BEFE70123238013794A4A1344E9E701390A2977D8DFE801F037764F76067676760A7620009378444ADBB25AE0937803F0FF0153B93E4B1A7892
:4022000091425FD019703F4B01201870FFF71AFE58E0481EC0B2FFF75FFD0028EED155E0FFF722FF002851D0294A374913791279DBB2D2B20A70354A3049D25CCB5C9A4236
:4022400040D0304B01221A70FFF758FD3AE003F00303012B2BD009D3022B37D11C4B9B78002B33D1BDE83840FFF7C4BE184B9B78012B2BD11F4A137803F0FD0315E003F0E5
:402280000303012B13D008D3022B1FD1104B9B78E3B9BDE83840FFF783BE0D4B9B78012B14D1144A137843F0020313700AE0084B1A795AB998781B791549DBB2CA5C22EAEF
:4022C0000002CA54BDE83840FFF7A0BA002038BD00600040C492FF1FD092FF1F9C3B0000003C0000733C00006893FF1FDC92FF1F8192FF1FD392FF1FD592FF1FC292FF1F96
:40230000C092FF1FD492FF1FD192FF1F4A93FF1FD792FF1F014B1870704700BF78650040014B1878704700BF6C640040014B1870704700BF78640040064A0123136002F698
:4023400088321268E0211064034A1170A2F540721360704780E100E000E400E0014B1870704700BF7A650040014B1870704700BF7F64004073B515461E460B4C0523002210
:40238000019200920A4601461846237000F064F932462946207800F01FF90221207800F009F9207802B070BDD080FF1F064A0423136002F688321268E0219064034A117031
:4023C000A2F202321360704780E100E002E400E0014B04221A60704700E100E0014B04221A60704780E100E0014B1870704700BF7E640040704738B505460078012428B1CB
:4024000000F066FD285D0134E4B2F8E738BD08B50D2000F05DFDBDE808400A2000F058BDF7B516461F460B4C00230325019300930A4601462846257000F00EF93A46314617
:40244000207800F0C9F80221207800F0B3F8207803B0F0BDE080FF1FF7B516461F460B4C00230225019300930A4601462846257000F0F2F83A463146207800F0ADF829460B
:40248000207800F097F8207803B0F0BDE180FF1FF7B516461F460B4C00230125019300930A4601462846257000F0D6F83A463146207800F091F80221207800F07BF8207844
:4024C00003B0F0BDE280FF1F73B515461E460B4C0023019300930A4601461846237000F0BBF832462946207800F076F80221207800F060F8207802B070BD00BFE380FF1FB2
:40250000024B1878C0F38010704700BF8F450040074A7F23802113705170064A013BDBB202F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD877
:40254000084B0001C25C11B142F0200201E002F0DF02C254C25C42F00102C25400207047012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C25490
:4025800000207047012070471070004017280DD8074900010B4603441A7942F004021A71435C43F00103435400207047012070471070004017280BD8064A0001835C4900D2
:4025C00003F0F10301F00E011943815400207047012070471070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC28072
:40260000002001207047000017289FBF034B00011954002088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBF1143195400200120704700BF147000408D
:4026400017289FBF034B0001185C00F0070088BFFF20704714700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA021304
:40268000DBB263709DF80C30002003F00F03A370E07010BD012010BD10B500F079FC0A4A5378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F0DE
:4026C0006BFC204610BD00BFE480FF1F030610B5044611D400F05CFC084AE300117803F1804303F5F04319705378147001335370BDE8104000F050BC10BD00BFE480FF1F4C
:4027000030B504060CD411F4704509D1C40004F1804404F5F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5F04081805ABFC28000200120704700000C
:4027400038B50446084DB4F5004F05D9286800F017FCA4F50044F6E7034B58686043BDE8384000F00DBC00BFEC80FF1F024B1B7A584300F005BC00BFEC80FF1F0E4B00F01A
:4027800003001A78490102F0FC02104318701A7801F0600142F080021A701A7802F07F021A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B012277
:4027C0001A70704784430040044B00F00F021B6853F8220043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0089
:4028000000F16040490100F56440C9B2017070470F4B10B50F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A9B
:402840001360084B4FF400421C60C3F8E82010BD8492FF1FBD28000010E000E0EC80FF1F14E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034AF5
:40288000136843F00103136008BD00BF10E000E010B5054CA3691BB9FFF7BAFF0123A361BDE81040FFF7E8BF8492FF1F024B1868C0F30040704700BF10E000E038B5FFF723
:4028C000F5FF012808D1054D002455F8243003B198470134052CF8D138BD00BF8892FF1F024B03EB80035868596070478492FF1F134B144A1B78DBB20360127843EA0223E0
:40290000114A0360127843EA0243104A0360127843EA026303600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF30
:402940000301004904010049EC460040020100490101004900010049050100490601004910B500F015FB204A044613780A2043F002031370137C43F00203137412F80A3C43
:4029800043F0010302F80A3C937943F00103937102F5AB52137843F003031370134B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CBB
:4029C000A3F597530222183B1A70094A137843F008031370FFF7CAFE064B10222046BDE810401A6000F0D8BAAB4300400E5900402F5B004080E200E008B500F0C9FA0F4A73
:402A0000137803F0FE031370A2F5AA521D3A137803F0FD031370137C03F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F0AFBA00BF0859004072
:402A4000044A137803F03F0343EA8010C0B21070704700BF08590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F9101067
:402A80000A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A8070470A590040883B00005293FF1F5493FF1F5893FF1F08B5102000F0A6F9C9
:402AC00007210420FFF79AFE07490420FFF788FE064A0C20137843F006031370FFF7BCFF034B00221A8008BDB12B0000095900405093FF1F10B5054C23781BB9FFF7DCFFF1
:402B000001232370BDE81040FFF72ABFA092FF1F044B1A7802F0FB021A701A7842F001021A7070470859004010B5084B1C7814F0010403D10028F9D0002404E02046FFF7D9
:402B400015FE024B1B78204610BD00BF09590040034A044B1B881088181A00B2704700BF5893FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B98
:402B80001B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475293FF1F5493FF1F5093FF1F7047000010B500F0EBF9214A0446137884
:402BC0000A2043F001031370137C43F00103137412F80A3C43F0020302F80A3C937943F00203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400241
:402C000003F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222123B1A70094A137843F008031370FFF79FFD074B08222046BDE810401A6000F0ADB900BFEB
:402C4000AB43004006590040275B004080E200E008B500F09DF90F4A137803F0FE031370A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F8BA
:402C80000A3C937903F0FD039371BDE8084000F083B900BF00590040044A137803F03F0343EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E04223BE
:402CC00008380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A8070470259004094
:402D0000923B00005E93FF1F6493FF1F5C93FF1F08B5102000F084F807210320FFF76EFD07490320FFF75CFD064A0C20137843F006031370FFF7BCFF034B00221A8008BD88
:402D4000092E0000015900406093FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF728BFA192FF1F044B1A7802F0FB021A701A7842F001021A7070470059004046
:402D800010B5084B1C7814F0010403D10028F9D0002404E02046FFF7E9FC024B1B78204610BD00BF01590040034A044B1B881088181A00B2704700BF5C93FF1FA05B00406B
:402DC0000E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475E93FF1F0D
:402E00006493FF1F6093FF1F70470000034A00F0F800137803431370704700BF02410040034A00F0F800137803431370704700BF06410040014B1870704700BF7C64004043
:402E4000014B1870704700BF7B64004073B515461E460B4C04230022019200920A46014618462370FFF7F8FB324629462078FFF7B3FB02212078FFF79DFB207802B070BDE9
:402E8000FC80FF1F074A0223136002F688321268E0215064044A11706FF440710A441360704700BF80E100E001E400E0014B1870704700BF74640040014B1870704700BFE2
:402EC00075640040014B1870704700BF7D640040FEB5494652465B460EB40746244909688A46244A12682448022100F071F8030020480068C018204900F06AF814388346CB
:402F00000121C9430C460125002600F041F8814651460B7823400B705846013000F030F83800F04028400B78234003430B70584600F026F80136072EF2D900200130013812
:402F4000013001200B78234003430B705846043000F016F8484600F01FF800BF00BF00BF0EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F00C2010000000000FD
:402F80000230800803D000BF01380046FCD17047EFF3108072B6704780F31088704700BF094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A4A
:402FC00000E013790020704700600040DC92FF1F002902D0B0FBF1F0704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BFEC
:403000006081FF1F0E4B70B51E460E4C0025E41AA410A54204D056F8253098470135F8E700F044FE084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BD98
:40304000B83C0000B83C0000B83C0000C03C000003460244934202D003F8011BFAE7704730B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBFA2
:40308000234604F1FF33029305934FF6FF7300910491ADF80E3002461E9B6946284600F073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A464B
:4030C000014603480068FFF7CBFF03B05DF804FB6081FF1F2DE9F0478E6882469E420C46914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F5B9
:403100007B1C43449D4238BF1D4653050FD5294600F04AFB064698B13A46216900F0D2FAA38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FAA0
:403140000C23CAF80030A3894FF0FF3043F04003A381BDE8F08726613E44266046466561ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E44F5
:403180002660BDE8F08700002DE9F04F9DB003938B8980461C060D4616460DD50B695BB9402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF86F
:4031C0002930DFF89CB130238DF82A3037463C4614F8013B1BB9B7EB060910D003E0252BF9D02746F3E74B46324629464046FFF771FF013000F0A780099B4B4409933B7803
:40320000002B00F0A08000234FF0FF3204930793059206938DF853301A930126052221784E4800F041FA671C049B38B14B4A3C46801A06FA00F018430490EFE7D90644BFAF
:4032400020228DF853201A0744BF2B228DF8532022782A2A03D0079A00210A200BE0039A111D12680391002A10DA524243F00200079204900BE027463B780134303B092B51
:4032800003D800FB02320121F5E701B107923B782E2B1ED17B782A2B0AD1039B02371A1D1B680392002BB8BF4FF0FF33059310E0002319460593781C0A2407463A780130D0
:4032C000303A092A03D804FB01210123F5E703B1059103223978224800F0E6F940B14023CBEB000003FA00F0049B013718430490397806221B487E1C8DF8281000F0D4F9CF
:4033000088B1194B33B9039B073323F007030833039314E003AB00932A46144B04A94046AFF3008007E003AB00932A460F4B04A9404600F093F8B0F1FF3F824603D0099B27
:403340005344099342E7AB895B0601D4099801E04FF0FF301DB0BDE8F08F00BF873C00008D3C0000913C000000000000D53000002DE9F04791461F460A698B680646934279
:40338000B8BF1346C9F8003091F843200C46DDF8208012B10133C9F800302368990642BFD9F800300233C9F80030256815F0060510D104F1190A07E0012352463946304631
:4033C000C04701301AD00135E368D9F800209B1A9D42F1DB94F843302268003318BF012392060FD5E118302081F843005A1C94F845102244023382F8431003E04FF0FF3091
:40340000BDE8F08704F1430239463046C0470130F4D02268D9F80050E36802F00602042A08BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED184FF000091A344D45BF
:4034400009D00123224639463046C0470130D5D009F10109F3E70020BDE8F0872DE9F04317460A7E85B06E2A984606460C460C9B01F1430E00F0AE8011D8632A22D009D833
:40348000002A00F0BB80582A40F0CA8081F84520834955E0642A1ED0692A1CD0C0E0732A00F0B08009D86F2A2ED0702A40F0B8800A6842F020020A603EE0752A24D0782A87
:4034C0003AD0ADE01A6801F14205111D1960136884F84230A8E021681A6811F0800F02D0111D196008E011F0400F02F10401196002D0B2F9003000E01368002B3CDA2D228D
:403500005B4284F8432037E021681A6811F0800F02D0111D196007E011F0400F02F10401196001D0138800E01368227E5C496F2A14BF0A2208221BE078225A4984F8452055
:403540002268186812F0800F00F104051D6003D1550601D5038800E00368D00744BF42F0200222601BB9226822F0200222601022002084F8430001E049490A226568002DF0
:40358000A56008DB206820F0040020602BB9002D7DD175460CE0002B79D07546B3FBF2F002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0708D5236962689A42E0
:4035C000DEBF302305F8013C05F1FF35C5EB0E0323612EE008681A6810F0800F496903D0101D1860136808E010F0400F02F104001860136801D0198000E019600023236173
:40360000754616E01A68111D1960156800216268284600F049F808B1401B6060636804E004F1420584F8422001232361002384F84330CDF800803B4603AA21463046FFF70C
:4036400097FE013002D14FF0FF3026E023692A4639463046C0470130F5D023689B0710D5002504F1190907E001234A4639463046C0470130E7D00135E368039A9B1A9D42D0
:40368000F2DBE068039B9842B8BF184605E00B7804F1420584F842308AE705B0BDE8F083773B0000983C000010B5C9B202449042034605D01C7801308C42F8D1184610BD55
:4036C000002010BD10B5431E0A44914204D011F8014B03F8014FF8E710BD884210B501EB020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD71
:40370000994204D011F8014B02F8014FF8E710BD38B50546002944D051F8043C0C1F002BB8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A0188342ED
:4037400001BF18685B681218226063600C6023E0A24203D813465A68002AF9D118681918A1420BD12168014458188242196013D110685268014419605A600DE002D90C232A
:403780002B6009E021686018824201BF106852680918216062605C602846BDE8384000F098B838BDA892FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA9429D
:4037C00002D90C23336046E000F082F8234B1C681A462146A1B10B685B1B0ED40B2B03D90B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E70D
:40380000174C23681BB9304600F052F820602946304600F04DF8431C18D0C41C24F00304A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BD37
:40384000211A304600F034F80130EBD10C233360304600F03EF8002070BD00BFA892FF1FA492FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF2846F5
:40388000F8BD00F027F885420ED929463846FFF78BFF044650B131462A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7F6FB46
:4038C000431C02D1236803B12B6038BD8C93FF1F7047704751F8040C0028BEBF091851F8043CC0180438704700000000050209020B020D020F021102130215022800000013
:40390000000104000100000000000000000157494E55534200003030303031000000000000000000E0000000000105000100D6000000070000002A00440065007600690041
:403940006300650049006E0074006500720066006100630065004700550049004400730000009E0000007B00330064003200370035006300660065002D003500340033000D
:4039800035002D0034006400640035002D0061006300630061002D003900660062003900390035006500320066003600330038007D0000007B00330064003200370035001F
:4039C0006300660065002D0035003400330035002D0034006400640035002D0061006300630061002D003900660062003900390035006500320066003600330038007D0098
:403A0000000000007265706C792030782530327800686F6D696E6700626567696E6E696E67207365656B2066726F6D20256420746F2025640066696E69736865642073652D
:403A4000656B00796573006E6F00647269766520303A20257320647269766520313A2025730057616974696E6720666F72205553422E2E2E0055534220726561647900631F
:403A80006F6D6D616E6420307825303278006661696C2025642B25642B2564203D3D2025642C206E6F74202564007061737365643D256400756E64657272756E2061667479
:403AC0006572202564207061636B65747300636F756E743D256420693D256420643D256400636D645F777269746500703D25642063723D25642063773D256420663D2564F8
:403B000020773D256420696E6465783D256420756E64657272756E3D256400756E64657272756E2100737563636573730073746172742065726173696E670073746F702027
:403B400065726173696E670069646C650000510040100040510040300000000140001000140140000800400140000A004C01400002005001402000303132333435363738CF
:403B80003941424344454600000100000004000000100001000000040000001001000000A43B000001000000733C0000000000000000000001000000BC3B00000100000084
:403BC000453C000004000000DE3B0000000000000000000000000000DC3B0000FF00000001024000FF00000082024000FF00000003034000FF00000084034000FF000203FC
:403C000004030904160346006C007500780045006E00670069006E0065002A0343006F0077006C00610072006B00200054006500630068006E006F006C006F006700690036
:403C4000650073000009022E0001010080320904000004FF00000107050102400000070582024000000705030340000A0705840340000A12010002FF0001080912006E016F
:403C800000020180014300232D302B2000686C4C00656667454647003031323334353637383961626364656600000000F8B500BFF8BC08BC9E4670475900000029110000DA
:403CC000F8B500BFF8BC08BC9E46704735000000E03C0000C880FF1FA000000028120000000000009093FF1FFF000000675000400C00000007000000FFFFFFFF7F8000006F
:403D00003F0000000000007D00FA0000400000000090D003FF0000000000000000000000000000000000000000000000000000000000000000000000853C0000000000006A
:403D400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081FF1F00000000A4
:403D80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003
:403DC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C3
:403E00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082
@@ -4098,51 +4098,51 @@
:40FF80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041
:40FFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
:0200000480007A
:400000000145004009520040015B00400165004047000140380101404E0201404703014046040140440501404C060140490701402C08014031090140040B0140030D014075
:400040006C14014057150140591601404B17014011190140071B01400B400140104101400C4201400F43014002440140054501400A4601400B4701400C480140104901404C
:40008000174C01400C4D014006500140045101407E02080409021082110218021904601561137C402721240A02E40303040E0520064107C00A840B900E840F901001120693
:4000C00013901684179019011A841B481C081DFC1E221F0221C122182324268427902B9C2CE72E10300733E0351F36F83A023B084204460E480449FF4AFF4BFF4F83580B46
:40010000590B5A045B045C995D095F01868088028A8190E7921094019686980F9A409C0F9E20A2E0A618A804AA81AE80B207B4F8D80BDC09DF010022022003010524084089
:4001400009080A080B400C420D200E2010021108130215801610172119011A011C241E201F0C210422082308284029042B88312032643301382039883A013B804240528017
:400180005A215B4A600461016218634278027C028280C06FC2FFC4FDCA07CC0FCE0FD001D60FD80FDE8103140404050806110710081F0A800C010E1E0FE4101F124015102D
:4001C00016E0178218021A111B141C1F1E20211023212408261127142B142D1F2F4032F0330F360F37F03A083B80580B590B5B045C995F018203830884618608873088204C
:4002000089108A508B828DC78E108F2892109304961C97C49A109B04A061A110A204A341A440A630A704AB04AC7CAE02AF04B01FB30FB460B5F4BB08D80BD90BDB04DC99FF
:40024000DF01012A02200301040805400708081009080B8C0C420E200F04104211081504180819221A0C1B011C221D4C1E211F4820042218242025442731285029402B8839
:400280002C022E042F883204331134103508380439813A103D8C3E016230680269806A806D4078027C029308942096E897809C029D249F61A403A50CA620A781AA01AB0889
:4002C000C0EFC2FEC42DCAFFCC67CEDFDE81E208021004400620080C0C010E0210081101140818051A081C04200828202AC02CE02F0130603280341E3501360138203E40ED
:400300005608580459045B045C995D905F0181608209842487108A128B108F10903F93039407950197089B1C9D40A036A209A310A710A82DA901AA12AB04AC40AD7CAF0220
:40034000B11FB238B440B540B607B720BE10D608D80BD90BDB04DC99DD90DF01000201280302040A070409980B800C020E20104212081550170119281A401B401E201FA0A5
:4003800022102662274828022C022E052F043302364237283C203D803F0558805D80600267016B026D106F017C0287208B848F4091409205930496409B209C029F48A0124A
:4003C000A140A208A620A780AD01AE08C07FC23FC4DBCAF8CCF1CEF0D618D818DE80E208E408EA040104020F0440068008700A800C800E200F0110011106130114081602E9
:40040000170218011C0F1EF0200124072608288029052A102B022C082E04310736FF3E403F01580459045B045F018001811C820283E18402852087C0880489828B218C0248
:400440008D088F10900491FF940495089710980599029C049D04A002A1FCA402A520A740A802AC03AE04AF02B007B3FCB503BB20BE01D804D904DB04DC90DF010081012440
:4004800004450520084009080A080B810C080D010E020F041008120113861544172219011C0220042114222C23282740280229012B202D1030083180321033023705396892
:4004C0003A013D82588068066C016D806F048004810184048F0191109220940A9780980299209B019C409F04A401A598A702A880A980AF02C0FFC2FFC4FFCA2BCCCFCE9F8E
:40050000D608E24CE402E605EA04EC02EE0C06010B0410041304160119021C022101240829042C013001310232043304340236083701380239083E553F45560858045904A9
:400540005B045D905F0183018A029501A401AC02B401B501B602B880B920BE50BF10D804D904DB04DF01030804080D800F50100212101410154019101A021E2A1F022210BD
:40058000250226882C802F083308372138023F10584059105DA45E01645266106A406C076D406F09800282C085808608890293409980AF40C022C2B0C4A5CAA0CCA2CE2157
:4005C000D6FCD8F0EA04EE0C9980E208E448EA049980B180E208018002200608070108280B100C020D210E2C1028110F1434152117081828192F1C101E021F2F202821210D
:40060000230224022740282829212B042C202D212E012F0E321F336035803620371F39083E443F1040524720482049FF4AFF4BFF4D204EF05110580459045A045B045C9038
:400640005D095F01610862406340648066406740800F810183028503861087FC88208A018B048C208DFF8E0290039302942395809608970499049A409B209D049E2F9FF9AF
:40068000A023A104A20CA308A423A504A604A740A82FA904AB10AF02B060B3FFB460B61FB822BF04D804D904DB04DC09DF01012202200301040205040608070209880A04D5
:4006C0000B800C080D800E48118012081305144015051610171019881C401D401E041F602118228023112410284129082A102D202E402F6030083240331234013680372A63
:40070000381039423A083C063D803F1041104980510159025A0269806C016D806F0285018D409004914592C2930494429604979199249A089B3B9D40A009A180A22CA548B8
:40074000A722AC02B704C0FFC2FFC47FCAFFCCFFCEFFD004D208E402E808EE1200FF01880403054606FC07B8080C09880A100C0C0EF10F0112081510160417A0180C198849
:400780001A401C011D421E021F04218822022480259A260C2720280C29882A202E022F0431C132FF333F35C137C139A23E043F04580459045B045C905F01800282058521EF
:4007C000870288028C0291279403950896049B389D049F01A118A340A706AC02AF01B006B107B310B401B560B708BE10BF40C006C5ECC803C9FFCAFFCBFFD004D601D8046C
:40080000D904DA04DB04DC99DD09DF01E2C00020010203020406056007100A200C010D800E040F1013801542172819131D042003218022D5240425102605278128102901DB
:400840002A802D202E402F603180321433023680372838043C043D803F11481049104B0264016701684869476A406C02728873068410860194419546978799209B2C9F827A
:40088000A228A448A598A601A722AB40B002B481C0FBC2F4C4F1CAFBCC7FCEF2D204E220E604EA4082108A018E048F029222960197109D209F20A214A302B080B104B6017C
:4008C000E220EA10EC808F209F20AF10B211B520E412EE021B011F083240330836843B408140C630CCF0CE10E62030803204358037083A023D408480914096809740A6046A
:40090000AE80AF01CCF0CE60E210508057208580968897409D809E02A604AB08D460E210EA20832284808E02968097409E02A480A620A720AA24AE04E230E620EC80EE30D9
:400940001501C4045D828740B101D605EE011B04850287108D8097809D82A480A880C608E4020B880C800F108A109798A480A740AB44AF04C20FE404262080018A01974061
:40098000A280A302A620AA40B680C820E620EE8052805302551070017E0190029202A110A280A302AF40B510D4E0DC80DE20EE40052008040E020F801F1053805610588014
:4009C00063028E208F809A109D20A740B120C001C20DC601D407D602E002E402EA04762084809A209C80AF10B004B301B601DE04E801EA08EE01010109010B010D011101C4
:400A00001B0100FF01AB020211050000BF0000A09F001F000000000000000000100000004000000000000000C0000000FF0000B847004700000100008000000282008200DC
:400A400000000000000707000700000027001801270018010004000000050000000000000000000000000000000000000000000000000000000000000000000000000000D8
:400A80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036
:400AC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F6
:400B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B5
:400000000145004008520040015B004001640040020101400B0301403E04014041050140130701405508014056090140510A0140540B01405B0C0140540D0140470E01400D
:40004000380F01400A1501400B1701404B18014059190140521A01404E1B01400B40014012410140144201400C430140044401400D4501400946014011470140084801406A
:4000800011490140154C0140034D014006500140045101407E0208020941118218021901600C61067C4027212D0A8C40E62082608580908091029840AD01B040E008E224DC
:4000C000E680EE020008021003020405052006020B080F041110120117041A021B011C041F0420062107220123082610270428102A082B042F04300731203208330F361052
:40010000371038883A303B083E453F4156085808590A5B045C995D905F0189018B028D039601A002A102A301AC01B202B303B601BE04D608D804D904DB04DC99DD90DF0157
:40014000000A09900A400F10100811081302164119801AA01B221C101F802142231026082A252B012F80300231443220388039023B143F90584059105F80618063046640B5
:4001800068046B026D206E407A107B028110822088088A108E018F4090809102968098409A209D80A502B502C00CC22BC49ECA8FCC0FCE3FD61CD81CDE0AE204E60DEA04B2
:4001C000EE0181108D449010918092609D449E049F80A110A402A701A904AC01B208B720E202E60AEA05EE080004020804010602080409010AF90B140D010E020F2810030B
:4002000012FC134114041580164017011A041B4E1C801E042202234124042620274128042A102B412CFF2DF12F02310F32FF330F35F03B0A3E045804590B5B045C905F0188
:40024000800481078378840485088730880489808C038DFF8E0490019101920294029502970498029C029DA09F48A002A102A304A404A53FA802A908AB10AC05AF80B13F30
:40028000B207B33FB7C0BB80BE04D804D904DC90DF01008801020308048405020708084009220A110C040D090F0911011202132814801568170119041C202104224A230182
:4002C00025052714288629042C022F6430083102324035013714394A3B203F60422043045C405D206D106E407C028D01914293689420952D96909785980A99039A089B0820
:400300009C709D109E069FD5A042A108A280A31CA494A502A65DA702AB02AD10B220B380B610C0FFC2FFC4FFCAFFCCEBCE3FD630DE80E212E410E60AE840EA01EC80000C29
:4003400002F1040C06100A040E021080120C140C164018011A021C031EFC22022608280C2A202CFF34FF3E10560858045B045D905F0180088121830284C085218601870427
:4003800088088B108D2F8E04900291019204941A96209740980899209B019C109E209F2FA046A121A238A30EA408A808A921AB08AD0FAE80B23FB360B480B51FB640B71FD4
:4003C000B908BE44C203C60EC804C9FFCAFFCBFFCF83D804D904DA04DB04DC99DD09DF01000801020309050A070909080A820B040D0A0F091141120813081402152A192051
:400400001D041F9024022508268027082A082B102C402D402E10351437813CA03D0540804240462057405A015B415EA6600263026540661668026A406D90835089408B01C6
:400440008E409142920693489422953D96B197809808994B9A109CF29D309F14A040A31CA480A54AA604A702A814AC08B780C0FFC2FFC4FFCAB0CCF0CEF0D040D6F8D8F8B5
:40048000E280E66CE802EC1000200250061008400A3010621208146216041A031C801D01207C2201261C2A102E10306031013280341F3E043F01580B59045B045C095F01FF
:4004C000802381C8820C85C8860189C88A108C2F8D228FCC9130922F9302942395C8960899029A409C239F48A020A1C8A202A423A604A780A880A9D4AC0FAD80AF01B01FA9
:40050000B161B260B380B480B51FB808B902BE10BF14C062C702C810C9FFCAFFCBFFCD20CEF0D110D804D904DA04DB04DC99DD09DF01E108E240E340E480E640E7400040C6
:40054000012102200505070409220A110C800E040F6010481144148016221710180419021B0C1C201D011E0420C0212122092501281029022B052E202F04311A32603504B8
:4005800036443842392840044A0850085F80660267806B026E807C02810188108B028D018F40900891409348951096209780980899489A109CA29D019E029F14A351A54004
:4005C000A604A702AB80AF40B548C07FC2FFC4FFCA0FCC1FCE0FD004D204D610DE80E008E220E680EA81012002010340070308100A080B100F10100413101408161217100D
:400600001B1C1C1B1D401F20217C2301250227042B102D022F083001316032023418351F36043E40580B590B5B045C995F01804082308610888089018A308B028D028F0133
:4006400090E2920894E296049A109E03A0FCA201A420A6D0AE1CAF01B2E0B301B502B61FB928BBC0BF14D608D80BD918DB04DC99DD90DF0100110501071408800A600C019B
:400680000D100E0A0F60121013041480162218101A801B501D401E121F0C23442409250828812A202B182F02310132403314380139203B843D905C205F80640867026A40A1
:4006C0006D40780279407B017C027F0189088E10C07AC2FDC4B0CA1FCC0FCE3FD630D830DE91E63080028E409240A402AD40B301E204E640EC01EE0A878092409D409F8071
:40070000A402A701A810B614E202EA40EC010040021105C20724082009200A110BC00D020F4810F11202139014F11604179C18101AE11B901E0E1F0320F1220823902480D4
:40074000261127902B902DFC2F0134F035E0360F371F3A803B20580B590B5B045C995F018001821A8411850186068C059C01A703AC05AD03B101B20CB302B410B501B603BE
:40078000BE10BF11C006C6ECC80CC9FFCAFFCBFFD004D601D804D904DA04DB04DC99DD09DF01E2C00080010803800410050109800C420E080F201140166019681B801C02B3
:4007C0001E081F6020042106220223842510264029482C422F6435013640371439083A403B403D203E083F4045104604492050105180680869056A106B236F027040713001
:40080000728173087C127F04842091529280934894A4952596119784980299039A289B149C309D109E069FE1A042A10CA288A314A40CA522A655A722A920B4C8B640B7042C
:40084000C05DC2F1C431CAFACCF0CE72D204DE80E202E608EE10000101120324040706080760088009800A100C0F0D700EF00F01104011421280131014801620187019069A
:400880001A801C011D0420082104220424012708280829042A022E0F2F10318032FF3370350F39203E043F01580459045B045C905F018210838487848A108B848D168E10FE
:4008C0008F41901C920193E49402960497849A1C9B189E03A002A101A208A306A510A610A722AA10AB84ADE7AF08B5F8B61FB707BB80D80BD90BDC99DF01008001050208F7
:400900000410058109080A810B040C020E0A0F2010081202130A166819081E02200821142211232425802610290A2A802B042C022E222F20329235013614395A3C123D0820
:400940003E083F40458046024801498058105B206A407B307C028240860190109157928193409520980299039AE09B0A9C109E02A04BA10CA28EA314A404A611AB40AF2038
:40098000B001C0DFC2FFC47FCAFFCCEDCEFFDE80E402EA80EE021B011F0232203302350836803B408908C630CCF0CE1030013180344037043A013F80822088028B1097407A
:4009C0009D08A108A620AE80AF01CCF0CE60E6805108560883408508932097409A089C419D089F04A002A108A580A740AA09D460E220E610EA20EE808108844185809320A9
:400A000097409C41A002A580AB04E2C0E650EA8014407002C404DC015840601084028C108D20A40AA808B040D401D802E008E402EA011B01811087019A109C40A120A4082A
:400A4000AE10C6080A040B200E200F01861487108E0297019A109D10A120A408AB01B040C20FE408E8012620932097409902A002A108B502C82052015310550870027502C3
:400A80008E0193209902A002A108AA20AF40D4E0DC80DE20EC80EE40051008080C080F041F105120561059206202840887049A109D10A120A202A408C001C20DC601D407CD
:400AC000D80285209D20AF1001010B010D010F0111011D0100FF01AB02021105BF0000A09F001F000000000000000000100000004000000000000000C0000000FF0000B8F6
:400B00004700470000010000800000028200820000000000000707000700000027001801270018010004000000050000000000000000000000000000000000000000000002
:400B40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000075
:400B80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035
:400BC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F5
@@ -4615,12 +4615,12 @@
:0200000490105A
:04000000BC90ACAF55
:0200000490303A
:02000000FEBF41
:0200000090CF9F
:0200000490402A
:4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0
:400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
:400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040
:4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
:0200000490501A
:0C00000000012E16106900002E300F28A1
:0C00000000012E16106900002E30A138FF
:00000001FF

View File

@@ -816,7 +816,7 @@
<Group key="v1">
<Data key="cy_boot" value="cy_boot_v6_10" />
<Data key="Em_EEPROM_Dynamic" value="Em_EEPROM_Dynamic_v2_20" />
<Data key="LIN_Dynamic" value="LIN_Dynamic_v5_0" />
<Data key="LIN_Dynamic" value="LIN_Dynamic_v6_0" />
</Group>
</Group>
<Data key="DataVersionKey" value="2" />

View File

@@ -26,13 +26,6 @@
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="patcher.vbs" persistent="patcher.vbs">
<Hidden v="False" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
<build_action v="OTHER;;;;" />
<PropertyDeltas />
</CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b>
<CyGuid_8b8ab257-35d3-4473-b57b-36315200b38b type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtFileSerialize" version="3" xml_contents_version="1">
<CyGuid_31768f72-0253-412b-af77-e7dba74d1330 type_name="CyDesigner.Common.ProjMgmt.Model.CyPrjMgmtItemSerialize" version="2" name="protocol.h" persistent="..\protocol.h">
<Hidden v="False" />
</CyGuid_31768f72-0253-412b-af77-e7dba74d1330>
@@ -3301,7 +3294,7 @@
<name_val_pair name="c9323d49-d323-40b8-9b59-cc008d68a989@Release@CortexM3@Linker@Optimization@SHARED Optimization Level" v="" />
<name_val_pair name="c9323d49-d323-40b8-9b59-cc008d68a989@Release@CortexM3@Linker@Optimization@SHARED Link Time Optimization" v="" />
<name_val_pair name="c9323d49-d323-40b8-9b59-cc008d68a989@Release@CortexM3@Linker@Optimization@SHARED Fat LTO objects" v="" />
<name_val_pair name="c9323d49-d323-40b8-9b59-cc008d68a989@Release@CortexM3@User Commands@General@Pre Build Commands" v="cscript patcher.vbs" />
<name_val_pair name="c9323d49-d323-40b8-9b59-cc008d68a989@Release@CortexM3@User Commands@General@Pre Build Commands" v="" />
<name_val_pair name="c9323d49-d323-40b8-9b59-cc008d68a989@Release@CortexM3@User Commands@General@Post Build Commands" v="" />
</name>
</platform>

View File

@@ -23,36 +23,49 @@ module Sampler (
reg [5:0] counter;
reg index_q;
reg rdata_q;
reg index_edge;
reg rdata_edge;
reg req_toggle;
reg rdata_toggle;
reg old_rdata_toggle;
reg index_toggle;
reg old_index_toggle;
always @(posedge rdata)
begin
rdata_toggle <= ~rdata_toggle;
end
always @(posedge index)
begin
index_toggle <= ~index_toggle;
end
always @(posedge sampleclock)
begin
if (reset)
begin
old_rdata_toggle <= 0;
old_index_toggle <= 0;
index_edge <= 0;
rdata_edge <= 0;
index_q <= 0;
rdata_q <= 0;
counter <= 0;
req_toggle <= 0;
end
else
begin
/* Both index and rdata are active high -- positive-going edges
* indicate the start of an index pulse and read pulse, respectively.
*/
/* If data_toggle or index_toggle have changed state, this means that they've
* gone high since the last sampleclock. */
index_edge <= index && !index_q;
index_q <= index;
index_edge <= index_toggle != old_index_toggle;
old_index_toggle <= index_toggle;
rdata_edge <= rdata && !rdata_q;
rdata_q <= rdata;
rdata_edge <= rdata_toggle != old_rdata_toggle;
old_rdata_toggle <= rdata_toggle;
if (rdata_edge || index_edge || (counter == 6'h3f)) begin
opcode <= { rdata_edge, index_edge, counter };

View File

Binary file not shown.

View File

@@ -147,7 +147,7 @@ static void set_drive_flags(struct set_drive_frame* flags)
current_drive_flags = *flags;
DRIVESELECT_REG_Write(flags->drive ? 2 : 1); /* select drive 1 or 0 */
DENSITY_REG_Write(flags->high_density); /* density bit */
DENSITY_REG_Write(!flags->high_density); /* double density bit */
INDEX_REG_Write(flags->index_mode);
}
@@ -937,3 +937,75 @@ int main(void)
}
}
}
const uint8_t USBFS_MSOS_CONFIGURATION_DESCR[USBFS_MSOS_CONF_DESCR_LENGTH] = {
/* Length of the descriptor 4 bytes */ 0x28u, 0x00u, 0x00u, 0x00u,
/* Version of the descriptor 2 bytes */ 0x00u, 0x01u,
/* wIndex - Fixed:INDEX_CONFIG_DESCRIPTOR */ 0x04u, 0x00u,
/* bCount - Count of device functions. */ 0x01u,
/* Reserved : 7 bytes */ 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
/* bFirstInterfaceNumber */ 0x00u,
/* Reserved */ 0x01u,
/* compatibleId - "WINUSB\0\0" */ 'W', 'I', 'N', 'U', 'S', 'B', 0, 0,
/* subcompatibleID - "00001\0\0" */ '0', '0', '0', '0', '1',
0x00u, 0x00u, 0x00u,
/* Reserved : 6 bytes */ 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u
};
const uint8_t USBFS_MSOS_EXTENDED_PROPERTIES_DESCR[224] = {
/* Length; 4 bytes */ 224, 0, 0, 0,
/* Version; 2 bytes */ 0x00, 0x01, /* 1.0 */
/* wIndex */ 0x05, 0x00,
/* Number of sections */ 0x01, 0x00,
/* Property section size */ 214, 0, 0, 0,
/* Property data type */ 0x07, 0x00, 0x00, 0x00, /* 7 = REG_MULTI_SZ Unicode */
/* Property name length */ 42, 0,
/* Property name */ 'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0,
'I', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0,
'a', 0, 'c', 0, 'e', 0, 'G', 0, 'U', 0, 'I', 0,
'D', 0, 's', 0, 0, 0,
/* Property data length */ 158, 0, 0, 0,
/* GUID #1 data */ '{', 0, '3', 0, 'd', 0, '2', 0, '7', 0, '5', 0,
'c', 0, 'f', 0, 'e', 0, '-', 0, '5', 0, '4', 0,
'3', 0, '5', 0, '-', 0, '4', 0, 'd', 0, 'd', 0,
'5', 0, '-', 0, 'a', 0, 'c', 0, 'c', 0, 'a', 0,
'-', 0, '9', 0, 'f', 0, 'b', 0, '9', 0, '9', 0,
'5', 0, 'e', 0, '2', 0, 'f', 0, '6', 0, '3', 0,
'8', 0, '}', 0, '\0', 0,
/* GUID #2 data */ '{', 0, '3', 0, 'd', 0, '2', 0, '7', 0, '5', 0,
'c', 0, 'f', 0, 'e', 0, '-', 0, '5', 0, '4', 0,
'3', 0, '5', 0, '-', 0, '4', 0, 'd', 0, 'd', 0,
'5', 0, '-', 0, 'a', 0, 'c', 0, 'c', 0, 'a', 0,
'-', 0, '9', 0, 'f', 0, 'b', 0, '9', 0, '9', 0,
'5', 0, 'e', 0, '2', 0, 'f', 0, '6', 0, '3', 0,
'8', 0, '}', 0, '\0', 0, '\0', 0
};
uint8 USBFS_HandleVendorRqst(void)
{
if (!(USBFS_bmRequestTypeReg & USBFS_RQST_DIR_D2H))
return false;
switch (USBFS_bRequestReg)
{
case USBFS_GET_EXTENDED_CONFIG_DESCRIPTOR:
switch (USBFS_wIndexLoReg)
{
case 4:
USBFS_currentTD.pData = (volatile uint8 *) &USBFS_MSOS_CONFIGURATION_DESCR[0u];
USBFS_currentTD.count = USBFS_MSOS_CONFIGURATION_DESCR[0u];
return USBFS_InitControlRead();
case 5:
USBFS_currentTD.pData = (volatile uint8 *) &USBFS_MSOS_EXTENDED_PROPERTIES_DESCR[0u];
USBFS_currentTD.count = USBFS_MSOS_EXTENDED_PROPERTIES_DESCR[0u];
return USBFS_InitControlRead();
}
default:
break;
}
return true;
}

View File

@@ -1,18 +0,0 @@
const READ = 1
const WRITE = 2
filename = "Generated_Source\PSoC5\USBFS_descr.c"
set fso = CreateObject("Scripting.FileSystemObject")
set file = fso.OpenTextFile(filename, READ)
text = file.ReadAll
file.Close
set r = New RegExp
r.MultiLine = True
r.Pattern = "/\* +compatibleID.*\n.*"
text = r.replace(text, "'W', 'I', 'N', 'U', 'S', 'B', 0, 0,")
set file = fso.CreateTextFile(filename, True)
file.Write text
file.Close

View File

@@ -1,6 +1,10 @@
PACKAGES = zlib sqlite3 libusb-1.0 protobuf gtk+-3.0
PACKAGES = zlib sqlite3 protobuf
ifeq ($(shell uname),Linux)
PACKAGES += gtk+-3.0
endif
export CFLAGS = -ffunction-sections -fdata-sections
export CFLAGS = \
-ffunction-sections -fdata-sections
export CXXFLAGS = $(CFLAGS) \
--std=gnu++2a \
-Wno-deprecated-enum-enum-conversion \
@@ -8,10 +12,18 @@ export CXXFLAGS = $(CFLAGS) \
export LDFLAGS = -pthread
export COPTFLAGS = -Os
export LDOPTFLAGS = -Os -ldl
export LDOPTFLAGS = -Os
export CDBGFLAGS = -O0 -g
export LDDBGFLAGS = -O0 -g -ldl
export LDDBGFLAGS = -O0 -g
ifeq ($(OS), Windows_NT)
else
ifeq ($(shell uname),Darwin)
else
PACKAGES += libudev
endif
endif
ifeq ($(OS), Windows_NT)
export PROTOC = /mingw32/bin/protoc
@@ -20,9 +32,14 @@ export CXX = /mingw32/bin/g++
export AR = /mingw32/bin/ar rc
export RANLIB = /mingw32/bin/ranlib
export STRIP = /mingw32/bin/strip
export WINDRES = /mingw32/bin/windres
export CFLAGS += -I/mingw32/include/libusb-1.0 -I/mingw32/include
export LDFLAGS +=
export LIBS += -L/mingw32/lib -static -lz -lsqlite3 -lusb-1.0 -lprotobuf
export LIBS += -L/mingw32/lib -static -lz -lsqlite3 \
-lsetupapi -lwinusb -lole32 -lprotobuf -luuid
export GUILIBS += -luser32 -lkernel32 -lgdi32 -lcomctl32 -luxtheme -lmsimg32 \
-lcomdlg32 -ld2d1 -ldwrite -lole32 -loleaut32 -loleacc -luuid \
-lwindowscodecs
export EXTENSION = .exe
else
@@ -35,17 +52,26 @@ endif
export PROTOC = protoc
export CC = gcc
export CXX = g++
export COBJC = clang
export AR = ar rc
export RANLIB = ranlib
export STRIP = strip
export CFLAGS += $(shell pkg-config --cflags $(PACKAGES))
export LDFLAGS +=
export LIBS += $(shell pkg-config --libs $(PACKAGES))
export LIBS += $(shell pkg-config --libs $(PACKAGES)) -ldl
export EXTENSION =
ifeq ($(shell uname),Darwin)
AR = ar rcS
RANLIB += -c -no_warning_for_no_symbols
export CC = clang
export CXX = clang++
export COBJC = clang
export LDFLAGS += \
-framework Foundation \
-framework AppKit \
-framework IOKit \
-framework CoreFoundation
endif
endif

View File

@@ -230,6 +230,10 @@ As an exception, `dep/snowhouse` contains the snowhouse assertion library,
taken from https://github.com/banditcpp/snowhouse. It is Boost Standard License
1.0 licensed. Please see the contents of the directory for the full text.
As an exception, `dep/libusbp` contains the libusbp library, taken from
https://github.com/pololu/libusbp. It is MIT licensed. Please see the contents
of the directory for the full text.
As an exception, `dep/libui` contains the libui GUI library, taken from
https://github.com/andlabs/libui. It is MIT licensed. Please see the contents
of the directory for the full text.

View File

@@ -114,6 +114,8 @@ public:
auto idbits = readRawBits(16);
const Bytes idbytes = decodeFmMfm(idbits);
uint8_t id = idbytes.slice(0, 1)[0];
if (eof())
return RecordType::UNKNOWN_RECORD;
seek(here);
switch (id)
@@ -136,6 +138,9 @@ public:
auto bits = readRawBits(recordSize*16);
auto bytes = decodeFmMfm(bits).slice(0, recordSize);
IbmDecoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, _sector->physicalCylinder, _sector->physicalHead);
ByteReader br(bytes);
br.seek(_currentHeaderLength);
br.read_8(); /* skip ID byte */
@@ -148,11 +153,11 @@ public:
if (wantCrc == gotCrc)
_sector->status = Sector::DATA_MISSING; /* correct but unintuitive */
if (_config.swap_sides())
if (trackdata.swap_sides())
_sector->logicalSide ^= 1;
if (_config.ignore_side_byte())
if (trackdata.ignore_side_byte())
_sector->logicalSide = _sector->physicalHead;
if (_config.ignore_track_byte())
if (trackdata.ignore_track_byte())
_sector->logicalTrack = _sector->physicalCylinder;
}
@@ -174,12 +179,42 @@ public:
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
{
IbmDecoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, cylinder, head);
std::set<unsigned> s;
for (int sectorId : _config.sectors().sector())
s.insert(sectorId);
if (trackdata.has_sectors())
{
for (int sectorId : trackdata.sectors().sector())
s.insert(sectorId);
}
else if (trackdata.has_sector_range())
{
int sectorId = trackdata.sector_range().min_sector();
while (sectorId <= trackdata.sector_range().max_sector())
{
s.insert(sectorId);
sectorId++;
}
}
return s;
}
private:
void getTrackFormat(IbmDecoderProto::TrackdataProto& trackdata, unsigned cylinder, unsigned head) const
{
trackdata.Clear();
for (const auto& f : _config.trackdata())
{
if (f.has_cylinder() && (f.cylinder() != cylinder))
continue;
if (f.has_head() && (f.head() != head))
continue;
trackdata.MergeFrom(f);
}
}
private:
const IbmDecoderProto& _config;
unsigned _currentSectorSize;

View File

@@ -101,6 +101,27 @@ private:
}
}
private:
static std::set<unsigned> getSectorIds(const IbmEncoderProto::TrackdataProto& trackdata)
{
std::set<unsigned> s;
if (trackdata.has_sectors())
{
for (int sectorId : trackdata.sectors().sector())
s.insert(sectorId);
}
else if (trackdata.has_sector_range())
{
int sectorId = trackdata.sector_range().min_sector();
while (sectorId <= trackdata.sector_range().max_sector())
{
s.insert(sectorId);
sectorId++;
}
}
return s;
}
public:
std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
{
@@ -109,7 +130,7 @@ public:
getTrackFormat(trackdata, physicalTrack, physicalSide);
int logicalSide = physicalSide ^ trackdata.swap_sides();
for (int sectorId : trackdata.sectors().sector())
for (int sectorId : getSectorIds(trackdata))
{
const auto& sector = image.get(physicalTrack, logicalSide, sectorId);
if (sector)
@@ -133,11 +154,17 @@ public:
encodeMfm(_bits, _cursor, bytes, _lastBit);
};
auto writeFillerRawBytes = [&](int count, uint16_t byte)
{
for (int i=0; i<count; i++)
writeRawBits(byte, 16);
};
auto writeFillerBytes = [&](int count, uint8_t byte)
{
Bytes bytes = { byte };
Bytes b { byte };
for (int i=0; i<count; i++)
writeBytes(bytes);
writeBytes(b);
};
double clockRateUs = 1e3 / trackdata.clock_rate_khz();
@@ -160,9 +187,9 @@ public:
}
}
uint8_t gapFill = trackdata.use_fm() ? 0x00 : 0x4e;
uint16_t gapFill = trackdata.gap_fill_byte();
writeFillerBytes(trackdata.gap0(), gapFill);
writeFillerRawBytes(trackdata.gap0(), gapFill);
if (trackdata.emit_iam())
{
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
@@ -172,7 +199,7 @@ public:
writeRawBits(MFM_IAM_SEPARATOR, 16);
}
writeRawBits(trackdata.use_fm() ? FM_IAM_RECORD : MFM_IAM_RECORD, 16);
writeFillerBytes(trackdata.gap1(), gapFill);
writeFillerRawBytes(trackdata.gap1(), gapFill);
}
int logicalSide = physicalSide ^ trackdata.swap_sides();
@@ -180,7 +207,7 @@ public:
for (int sectorId : trackdata.sectors().sector())
{
if (!first)
writeFillerBytes(trackdata.gap3(), gapFill);
writeFillerRawBytes(trackdata.gap3(), gapFill);
first = false;
const auto& sectorData = image.get(physicalTrack, logicalSide, sectorId);
@@ -227,7 +254,7 @@ public:
writeBytes(header.slice(conventionalHeaderStart));
}
writeFillerBytes(trackdata.gap2(), gapFill);
writeFillerRawBytes(trackdata.gap2(), gapFill);
{
Bytes data;
@@ -264,7 +291,7 @@ public:
if (_cursor >= _bits.size())
Error() << "track data overrun";
while (_cursor < _bits.size())
writeFillerBytes(1, gapFill);
writeFillerRawBytes(1, gapFill);
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(_bits, clockRateUs*1e3);

View File

@@ -2,24 +2,43 @@ syntax = "proto2";
import "lib/common.proto";
// Next: 7
message IbmDecoderProto {
message SectorsProto {
repeated int32 sector = 1 [(help) = "require these sectors to exist for a good read"];
// Next: 10
message TrackdataProto {
message SectorsProto {
repeated int32 sector = 1 [(help) = "require these sectors to exist for a good read"];
}
message SectorRangeProto {
optional int32 min_sector = 1 [(help) = "require these sectors to exist for a good read"];
optional int32 max_sector = 2 [(help) = "require these sectors to exist for a good read"];
}
optional int32 cylinder = 7 [(help) = "if set, the format applies only to this track"];
optional int32 head = 8 [(help) = "if set, the format applies only to this head"];
optional bool ignore_side_byte = 2 [default = false, (help) = "ignore side byte in sector header"];
optional bool ignore_track_byte = 6 [default = false, (help) = "ignore track byte in sector header"];
optional bool swap_sides = 4 [default = false, (help) = "put logical side 1 on physical side 0"];
oneof required_sectors {
SectorsProto sectors = 5 [(help) = "require these sectors to exist for a good read"];
SectorRangeProto sector_range = 9 [(help) = "require these sectors to exist for a good read"];
}
}
optional bool ignore_side_byte = 2 [default = false, (help) = "ignore side byte in sector header"];
optional bool ignore_track_byte = 6 [default = false, (help) = "ignore track byte in sector header"];
optional bool swap_sides = 4 [default = false, (help) = "put logical side 1 on physical side 0"];
optional SectorsProto sectors = 5 [(help) = "require these sectors to exist for a good read"];
repeated TrackdataProto trackdata = 1;
}
message IbmEncoderProto {
// Next: 18
// Next: 20
message TrackdataProto {
message SectorsProto {
repeated int32 sector = 1 [(help) = "write these sectors (in order) on each track"];
}
message SectorRangeProto {
optional int32 min_sector = 1 [(help) = "write these sectors (in order) on each track"];
optional int32 max_sector = 2 [(help) = "write these sectors (in order) on each track"];
}
optional int32 cylinder = 15 [(help) = "if set, the format applies only to this track"];
optional int32 head = 16 [(help) = "if set, the format applies only to this head"];
@@ -36,7 +55,12 @@ message IbmEncoderProto {
optional int32 gap2 = 11 [default=22, (help) = "size of gap 3 (the pre-data gap)"];
optional int32 gap3 = 12 [default=80, (help) = "size of gap 4 (the post-data or format gap)"];
optional bool swap_sides = 14 [default=false, (help) = "swap side bytes when writing"];
optional SectorsProto sectors = 17 [(help) = "write these sectors (in order) on each track"];
optional int32 gap_fill_byte = 18 [default=0x9254, (help) = "16-bit raw bit pattern of gap fill byte"];
oneof required_sectors {
SectorsProto sectors = 17 [(help) = "require these sectors to exist for a good read"];
SectorRangeProto sector_range = 19 [(help) = "require these sectors to exist for a good read"];
}
}
repeated TrackdataProto trackdata = 1;

View File

@@ -37,7 +37,6 @@ public:
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(SECTOR_SYNC_PATTERN, matcher);
if (matcher == &SECTOR_SYNC_PATTERN) {
readRawBits(16);
return SECTOR_RECORD;
}
return UNKNOWN_RECORD;
@@ -45,6 +44,7 @@ public:
void decodeSectorRecord()
{
readRawBits(16);
auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE*16);
auto bytes = decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE);
ByteReader br(bytes);

View File

@@ -127,13 +127,11 @@ public:
if (matcher == &MFM_PATTERN) {
_sectorType = SECTOR_TYPE_MFM;
readRawBits(48);
return SECTOR_RECORD;
}
if (matcher == &FM_PATTERN) {
_sectorType = SECTOR_TYPE_FM;
readRawBits(48);
return SECTOR_RECORD;
}
@@ -155,6 +153,8 @@ public:
headerSize = NORTHSTAR_HEADER_SIZE_SD;
}
readRawBits(48);
auto rawbits = readRawBits(recordSize * 16);
auto bytes = decodeFmMfm(rawbits).slice(0, recordSize);
ByteReader br(bytes);

View File

@@ -1,39 +0,0 @@
// 21 june 2016
#import "uipriv_darwin.h"
// TODOs
// - header cell seems off
// - background color shows up for a line or two below selection
// - editable NSTextFields have no intrinsic width
// - is the Y position of checkbox cells correct?
@implementation tablePart
- (NSView *)mkView:(uiTableModel *)m row:(int)row
{
// if stretchy, don't hug, otherwise hug forcibly
if (self.expand)
[view setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
else
[view setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
}
@end
uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name)
{
uiTableColumn *c;
c = uiprivNew(uiTableColumn);
c->c = [[tableColumn alloc] initWithIdentifier:@""];
c->c.libui_col = c;
// via Interface Builder
[c->c setResizingMask:(NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask)];
// 10.10 adds -[NSTableColumn setTitle:]; before then we have to do this
[[c->c headerCell] setStringValue:uiprivToNSString(name)];
// TODO is this sufficient?
[[c->c headerCell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
c->parts = [NSMutableArray new];
[t->tv addTableColumn:c->c];
return c;
}

View File

@@ -0,0 +1,5 @@
(
(c-default-style . "BSD")
(c-mode . ((c-basic-offset . 4) (tab-width . 4) (indent-tabs-mode . nil)))
(nil . ((fill-column . 80)))
)

130
dep/libusbp/CMakeLists.txt Normal file
View File

@@ -0,0 +1,130 @@
cmake_minimum_required (VERSION 2.8.11)
# Fix behavior of CMAKE_CXX_STANDARD when targeting macOS.
if (POLICY CMP0025)
cmake_policy(SET CMP0025 NEW)
endif ()
# Fix a warning on macOS.
if (POLICY CMP0042)
cmake_policy(SET CMP0042 NEW)
endif ()
# Don't use -rdynamic since it breaks causes musl static linking.
if (POLICY CMP0065)
cmake_policy(SET CMP0065 NEW)
endif ()
project (libusbp)
set (LIBUSBP_VERSION_MAJOR 1)
set (LIBUSBP_VERSION_MINOR 2)
set (LIBUSBP_VERSION_PATCH 0)
# Make 'Release' be the default build type, since the debug builds
# include exported symbols that might cause name conflicts.
if (NOT CMAKE_BUILD_TYPE)
set (CMAKE_BUILD_TYPE "Release" CACHE STRING
"Options are Debug Release RelWithDebInfo MinSizeRel" FORCE)
endif ()
option (BUILD_SHARED_LIBS "Build as shared library" TRUE)
if (NOT BUILD_SHARED_LIBS)
add_definitions (-DLIBUSBP_STATIC)
set (PC_MORE_CFLAGS "-DLIBUSBP_STATIC")
endif ()
set(ENABLE_EXAMPLES FALSE CACHE BOOL
"True if you want to build the examples.")
set(ENABLE_TESTS FALSE CACHE BOOL
"True if you want to build the tests.")
set(LIBUSBP_LOG FALSE CACHE BOOL
"Output log messages to stderr for debugging.")
set(VBOX_LINUX_ON_WINDOWS FALSE CACHE BOOL
"Skip tests known to cause problems on a Linux VirtualBox guest on Windows.")
set(ENABLE_GCOV FALSE CACHE BOOL
"Compile with special options needed for gcov.")
# Our C code uses features from the C99 standard.
macro(use_c99)
if (CMAKE_VERSION VERSION_LESS "3.1")
if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
set (CMAKE_C_FLAGS "--std=gnu99 ${CMAKE_C_FLAGS}")
endif ()
else ()
set (CMAKE_C_STANDARD 99)
endif ()
endmacro(use_c99)
# Our C++ code uses features from the C++11 standard.
macro(use_cxx11)
if (CMAKE_VERSION VERSION_LESS "3.1")
if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
# Use --std=gnu++0x instead of --std=gnu++11 in order to support GCC 4.6.
set (CMAKE_CXX_FLAGS "--std=gnu++0x ${CMAKE_C_FLAGS}")
endif ()
else ()
set (CMAKE_CXX_STANDARD 11)
endif ()
endmacro(use_cxx11)
set (LIBUSBP_VERSION ${LIBUSBP_VERSION_MAJOR}.${LIBUSBP_VERSION_MINOR}.${LIBUSBP_VERSION_PATCH})
if (CMAKE_VERSION VERSION_GREATER "2.8.10")
string(TIMESTAMP YEAR "%Y")
endif ()
find_package(PkgConfig)
# Put libraries and executables in the top level of the build directory
# so that the executables can find the libraries and it is easy to run
# everything.
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
# Warn about everything.
set (CMAKE_C_FLAGS "-Wall -Wextra -pedantic ${CMAKE_C_FLAGS}")
set (CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic ${CMAKE_CXX_FLAGS}")
if (ENABLE_GCOV)
set (CMAKE_C_FLAGS "-fprofile-arcs -ftest-coverage ${CMAKE_C_FLAGS}")
endif ()
if (WIN32)
# Enable correct behavior for the return value of vsnprintf.
add_definitions (-D__USE_MINGW_ANSI_STDIO=1)
# Enable functions only available in Windows Vista and later,
# such as StringCompareEx.
add_definitions (-D_WIN32_WINNT=0x0600 -DNTDDI_VERSION=0x06000000)
endif ()
# Detect Linux.
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set (LINUX 1)
endif ()
# Install the header files into include/
install(FILES include/libusbp.h include/libusbp.hpp
DESTINATION "include/libusbp-${LIBUSBP_VERSION_MAJOR}")
add_subdirectory (src)
if (ENABLE_TESTS)
add_subdirectory (test)
add_subdirectory (manual_tests)
endif ()
if (ENABLE_EXAMPLES)
add_subdirectory (examples)
endif ()
if (WIN32)
add_subdirectory (install_helper)
endif ()

View File

@@ -0,0 +1,74 @@
# Contributing to libusbp
## Development Tools
The following tools are used to help build, test, debug, and document this library:
- USB Test Device A: This is a custom USB device that is used for testing this library. You must have a device like this plugged in if you want to run the full test suite. The `test/firmware/wixel` folder contains firmware that can turn a [Pololu Wixel](https://www.pololu.com/product/1337) into a USB Test Device A, and it should be possible to implement it on other USB-capable boards as well.
- [cmake](http://www.cmake.org)
- [catch](https://github.com/philsquared/Catch)
- [Doxygen](http://www.stack.nl/~dimitri/doxygen/)
- Development environments:
- Windows: [MSYS2](http://msys2.github.io/)
- macOS: [Homebrew](http://brew.sh/)
- Memory leak checkers:
- Windows: Install Dr. Memory and run `drmemory -leaks_only ./run_test.exe`
- Linux: Install Valgrind and run `valgrind ./run_test`
- macOS: Run `MallocStackLogging=1 ./run_test -p`, wait for the tests to finish, press Ctrl+Z, run `leaks run_test`, then finally run `fg` to go back to the stopped test process and end it.
## Underlying API documentation
This section attempts to organize the documentation and information about the underlying APIs used by libusbp, which should be useful to anyone reviewing or editing the code.
### Windows APIs
- [WinUSB](https://msdn.microsoft.com/en-us/library/windows/hardware/ff540196)
- [SetupAPI](https://msdn.microsoft.com/en-us/library/cc185682)
- [PnP Configuration Manager Reference](https://msdn.microsoft.com/en-us/library/windows/hardware/ff549717)
- [Standard USB Identifiers](https://msdn.microsoft.com/en-us/library/windows/hardware/ff553356)
- [What characters or bytes are valid in a USB serial number?](https://msdn.microsoft.com/en-us/library/windows/hardware/dn423379#usbsn)
- [INFO: Windows Rundll and Rundll32 Interface](https://support.microsoft.com/en-us/kb/164787)
- [MSI Custom Action Type 17](https://msdn.microsoft.com/en-us/library/aa368076?f=255&MSPPError=-2147217396)
### Linux APIs
- udev
- [libudev Reference Manual](https://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/ch01.html)
- [Kernel documentation of sysfs-bus-usb](https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-bus-usb)
- [sysfs.c](https://github.com/torvalds/linux/blob/master/drivers/usb/core/sysfs.c)
- [libudev and Sysfs Tutorial](http://www.signal11.us/oss/udev/)
- USB device node
- [USB documentation folder in the kernel tree](https://github.com/torvalds/linux/tree/master/Documentation/usb)
- [USB error code documentation](https://github.com/torvalds/linux/blob/master/Documentation/usb/error-codes.txt)
- [usbdevice_fs.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/usbdevice_fs.h)
- [devio.c](https://github.com/torvalds/linux/blob/master/drivers/usb/core/devio.c)
- [Linux USB Drivers presentation](http://free-electrons.com/doc/linux-usb.pdf)
- [USB Drivers chapter from a book](http://lwn.net/images/pdf/LDD3/ch13.pdf)
- [linux_usbfs.c from libusb](https://github.com/libusb/libusb/blob/master/libusb/os/linux_usbfs.c)
- Error numbers
- [errno.h](https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h)
- [errno-base.h](https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h)
### macOS APIs
- [I/O Kit Framework Reference](https://developer.apple.com/library/mac/documentation/Darwin/Reference/IOKit/index.html#//apple_ref/doc/uid/TP30000815)
- [IOKitLib.h Reference](https://developer.apple.com/library/mac/documentation/IOKit/Reference/IOKitLib_header_reference/)
- [Accessing Hardware From Applications](https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/AccessingHardware)
- [USB Device Interface Guide](https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/USBBook/)
- [IOUSBInterfaceClass.cpp](https://github.com/opensource-apple/IOUSBFamily/blob/master/IOUSBLib/Classes/IOUSBInterfaceClass.cpp)
- [IOUSBInterfaceUserClient.cpp](https://github.com/opensource-apple/IOUSBFamily/blob/master/IOUSBUserClient/Classes/IOUSBInterfaceUserClient.cpp)
- [darwin_usb.c from libusb](https://github.com/libusb/libusb/blob/master/libusb/os/darwin_usb.c)
- [Mach messaging interface (mach ports)](http://www.gnu.org/software/hurd/gnumach-doc/Messaging-Interface.html)
## Future development
Here are some things we might want to work on in future versions of the library:
- Serial port support. (Even just listing the serial ports of a USB device would be useful.)
- Human interface device (HID) support.
- Hotplug support: detect when new devices are added and detect when a specific device is removed.
- Stronger guarantees about thread safety. (This might not require code changes.)
- More options for asynchronous transfers.
- Perhaps use asynchronous operations to implement the synchronous ones, like libusb does. This would fix some quirks on various platforms: it would allow us to have timeouts for interrupt endpoints on Mac OS X and it would probably allow long synchronous operations on Linux to be interrupted with Ctrl+C. The main drawback is that it would add complexity.
- Sending data on OUT pipes.
- A device library template to help people who want to make a cross-platform C or C++ library for a USB device based on libusbp.

26
dep/libusbp/Doxyfile Normal file
View File

@@ -0,0 +1,26 @@
# Doxygen configuration file for generating documentation.
PROJECT_NAME = "libusbp"
OUTPUT_DIRECTORY = docs
INLINE_INHERITED_MEMB = YES
INPUT = README.md PLATFORM_NOTES.md CONTRIBUTING.md THREADS.md include
USE_MDFILE_AS_MAINPAGE = README.md
RECURSIVE = YES
SOURCE_BROWSER = YES
USE_MATHJAX = YES
GENERATE_LATEX = NO
# TYPEDEF_HIDES_STRUCT = YES
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = YES
PREDEFINED = \
LIBUSBP_API= \
LIBUSBP_WARN_UNUSED= \
_WIN32=1 \
__linux__=1 \
__APPLE__=1
EXCLUDE_SYMBOLS = \
LIBUSBP_API \
LIBUSBP_DLL_EXPORT \
LIBUSBP_DLL_IMPORT \
LIBUSBP_WARN_UNUSED

25
dep/libusbp/LICENSE.txt Normal file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2015-2017 Pololu Corporation. For more information, see
http://www.pololu.com/
http://forum.pololu.com/
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,36 @@
# Platform notes
This page documents some notable differences between the platforms that libusbp supports that can affect the library's behavior.
## Permissions
On Linux, a udev rule is typically needed in order to grant permissions for non-root users to directly access USB devices. The simplest way to do this would be to add a file named `usb.rules` in the directory `/etc/udev/rules.d` with the following contents, which grants all users permission to access all USB devices:
SUBSYSTEM=="usb", MODE="0666"
## Multiple handles to the same generic interface
On Linux, you can have multiple simultaneous handles open to the same generic interface of a device. On Windows and macOS, this is not possible, and you will get an error with the code `LIBUSBP_ERROR_ACCESS_DENIED` when trying to open the second handle with `libusbp_generic_handle_open`.
## Timeouts for interrupt IN endpoints
On macOS, you cannot specify a timeout for an interrupt IN endpoint. Doing so will result in the following error when you try to read from the endpoint:
Failed to read from pipe. (iokit/common) invalid argument. Error code 0xe00002c2.
## Serial numbers
On Windows, calling `libusbp_device_get_serial_number` on a device that does not actually have a serial number will retrieve some other type of identifier that contains andpersands (&) and numbers. On other platforms, an error will be returned with the code `LIBUSBP_ERROR_NO_SERIAL_NUMBER`.
## Synchronous operations and Ctrl+C
On Linux, a synchronous (blocking) USB transfer cannot be interrupted by pressing Ctrl+C. Other signals will probably not work either.
## Interface-specific control transfers
Performing control transfers that are directed to a specific interface might not work correctly on all systems, especially if the device is a composite device and the interface you are connected to is not the one addressed in your control transfer. For example, see [this message](https://sourceforge.net/p/libusb/mailman/message/34414447/) from the libusb mailing list.

186
dep/libusbp/README.md Normal file
View File

@@ -0,0 +1,186 @@
# libusbp: Pololu USB Library
Version: 1.2.0<br/>
Release date: 2020-11-16<br/>
[www.pololu.com](https://www.pololu.com/)
The **Pololu USB Library** (also known as **libusbp**) is a cross-platform C library for accessing USB devices.
## Features
- Can retrieve the vendor ID, product ID, revision, and serial number for each connected USB device.
- Can perform I/O on generic (vendor-defined) USB interfaces:
- Synchronous control transfers.
- Synchronous and asynchronous bulk/interrupt transfers on IN endpoints.
- Synchronous bulk/interrupt transfers on OUT endpoints.
- Can retrieve the names of virtual serial ports provided by a specified USB device (e.g. "COM5").
- Provides detailed error information to the caller.
- Each error includes one or more English sentences describing the error, including error codes from underlying APIs.
- Some errors have libusbp-defined error codes that can be used to programmatically decide how to handle the error.
- Provides an object-oriented C++ wrapper (using features of C++11).
- Provides access to underlying identifiers, handles, and file descriptors.
## Supported platforms
The library runs on:
* Microsoft Windows (Windows Vista and later)
* Linux
* macOS (10.11 and later)
## Supported USB devices and interfaces
The library only supports certain types of USB devices.
On Windows, any generic interface that you want to access with this library must use WinUSB as its driver, so you will need to provide a driver package or use some other mechanism to make sure WinUSB is installed. The generic interface must have a registry key named "DeviceInterfaceGUIDs" which is a REG_MULTI_SZ, and the first string in the key must be a valid GUID. The "DeviceInterfaceGUID" key is not supported.
Both composite and non-composite devices are supported.
There is no support for switching device configurations or switching between alternate settings of an interface.
There is no support for accessing a generic interface that is included in an interface association and is not the first interface in the association.
## Platform differences
This library is a relatively simple wrapper around low-level USB APIs provided by each supported platform. Because these APIs have different capabilities and behavior, you should not assume your program will work on a platform on which it has not been tested. Some notable differences are documented in `PLATFORM_NOTES.md`.
## Comparison to libusb
This library has a lot in common with [libusb 1.0](http://libusb.info/), which has been around for longer and has many more features. However, libusbp does have some useful features that libusb lacks, such as listing the serial number of a USB device without performing I/O or getting information about a USB device's virtual serial ports.
## Building from source on Windows with MSYS2
The recommended way to build this library on Windows is to use [MSYS2](http://msys2.github.io/).
After installing MSYS2, launch it by selecting "MSYS2 MinGW 32-bit" or "MSYS2 MinGW 64-bit" from the Start Menu. Then run this command to install the required packages:
pacman -S $MINGW_PACKAGE_PREFIX-{toolchain,cmake}
If pacman prompts you to enter a selection of packages to install, just press enter to install all of the packages.
Download the source code of this library and navigate to the top-level directory using `cd`. Then run these commands to build the library and install it:
mkdir build
cd build
MSYS2_ARG_CONV_EXCL=- cmake .. -G"MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX
make install DESTDIR=/
We currently do not provide any build files for Visual Studio. You can use CMake to generate Visual Studio build files, and the library and its examples will probably compile, but we have not tested the resulting library.
## Building from source on Linux
First, you will need to make sure that you have a suitable compiler installed, such as gcc. You can run `gcc -v` in a shell to make sure it is available.
You will also need to install CMake and libudev. On Ubuntu, Raspbian, and other Debian-based distributions, the command to do this is:
sudo apt-get install cmake libudev-dev
On Arch Linux, libudev should already be installed, and you can install CMake by running:
sudo pacman -S cmake
Download the source code of this library and navigate to the top-level directory using `cd`. Then run these commands to build the library and install it:
mkdir build
cd build
cmake ..
make
sudo make install
## Building from source on macOS
First, install [Homebrew](http://brew.sh/), a package manager for macOS. Then use Homebrew to install CMake by running the following command in a Terminal:
brew install cmake
Download the source code of this library and navigate to the top-level directory using `cd`. Then run these commands in a Terminal to build the library and install it:
mkdir build
cd build
cmake ..
make
sudo make install
## Incorporating libusbp into a C/C++ project
The first step to incorporating libusbp into another project is to add the libusbp header folder to your project's include search path.
The header folder is the folder that contains `libusbp.h` and `libusbp.hpp`, and it will typically be named `libusbp-1`.
On systems with `pkg-config`, assuming libusbp has been installed properly, you can run the following command to get the include directory:
pkg-config --cflags libusbp-1
The output of that command is formatted so that it can be directly added to the command-line arguments for most compilers.
Next, you should include the appropriate libusbp header in your source code by adding an include directive at the top of one of your source files.
To use the C API from a C or C++ project, you should write:
#include <libusbp.h>
To use the C++ API from a C++ project, you should write:
#include <libusbp.hpp>
After making the changes above, you should be able compile your project successfully.
If the compiler says that the libusbp header file cannot be found, make sure you have specified the include path correctly as described above.
If you add a call to any of the libusbp functions and rebuild, you will probably get an undefined reference error from the linker. To fix this, you need to add libusbp's linker settings to your project. To get the linker settings, run:
pkg-config --libs libusbp-1
If you are using GCC and a shell that supports Bash-like syntax, here is an example command that compiles a single-file C program using the correct compiler and linker settings:
gcc program.c `pkg-config --cflags --libs libusbp-1`
Here is an equivalent command for C++. Note that we use the `--std=gnu++11` option because the libusbp C++ API requires features from C++11:
g++ --std=gnu++11 program.cpp `pkg-config --cflags --libs libusbp-1`
The order of the arguments above matters: the user program must come before libusbp because it relies on symbols that are defined by libusbp.
The `examples` folder of this repository contains some example code that uses libusbp. These examples can serve as a starting point for your own project.
## Versioning
The version numbers used by this library follow the rules of [Semantic Versioning 2.0.0](http://semver.org/).
A backwards-incompatible version of this library might be released in the future.
If that happens, the new version will have a different major version number: its version number will be 2.0.0 and it will be known as libusbp-2 to `pkg-config`.
This library was designed to support having different major versions installed side-by-side on the same computer, so you could have both libusbp-1 and libusbp-2 installed at the same time.
However, you would not be able to use both versions from a single program.
If you write software that depends on libusbp, we recommend specifying which major version of libusbp your software uses in both the documentation of your software and in the scripts used to build it. Scripts or instructions for downloading the source code of libusbp should use a branch name to ensure that they downlod the latest version of the code for a given major version number. For example, the branch of this repository named "v1-latest" will always point to the latest release of libusbp-1.
## Documentation
For detailed documentation of this library, see the header files `libusb.h` and `libusbp.hpp` in the `include` directory, and see the `.md` files in this directory. You can also use [Doxygen](http://doxygen.org/) to generate documentation from those files.
## Version history
* 1.2.0 (2020-11-16):
* Linux: Made the library work with devices attached to the cp210x driver.
* Windows: Made the library work with devices that have lowercase letters in their hardware IDs.
* 1.1.0 (2018-11-23):
* Added `libusbp_write_pipe`.
* 1.0.4 (2017-08-29):
* Fixed a compilation error for macOS.
* Added the `lsport` example for listing multiple USB serial ports.
* 1.0.3 (2017-08-29):
* Compiler flags from libudev.pc are now used.
* For static builds, libusbp-1.pc now requires libudev.pc instead of copying its flags.
* Always build the install helper as a shared library (DLL).
* 1.0.2 (2017-06-29):
* Fixed some issues with using libusbp as a static library.
* Added the `drop_in` example, which shows how to use libusbp as a three-file library.
* 1.0.1 (2016-03-20): Fixed the `libusbp_broadcast_setting_change*` functions (a bonus feature for the Windows version).
* 1.0.0 (2016-03-02): Original release.

18
dep/libusbp/THREADS.md Normal file
View File

@@ -0,0 +1,18 @@
# Thread safety
If you are using multiple threads in your application, it is important to make sure that your calls to libusbp will be thread safe. This page covers everything you need to know about thread safety and libusbp.
We will only discuss the C API functions defined in libusbp.h. The C++ API defined in libusbp.hpp is just a simple wrapper around the C API and does not introduce or solve any thread safety issues.
This library does not create threads, use mutable global variables, use volatile variables, use mutexes, use memory barriers, or use reference counting.
On this page, two function calls are said to *conflict* with each other if there is no guarantee that executing the function calls concurrently on different threads will work as expected. To characterize the thread-safety of libusbp, we will specify which pairs of function calls conflict with each other. A function call consists of the name of a library function being called along with the values of its arguments.
Two function calls will not conflict if the memory areas pointed to by their arguments have no overlap. This is because the library does not use any mutable global variables. To determine whether the memory areas overlap, you will need to know which library objects hold pointers to other library objects. The rules for that are defined in the "Pointer rules" section below.
If there is an overlap in the memory areas pointed to by the arguments of two functions calls, the overlap will not cause a conflict as long as all of the parameters that are responsible for the overlap are marked with the `const` qualifier in the header. We use `const` as an indicator that the function will not modify the memory pointed to by that argument, and it will not call any API functions that might change the state of the underlying handles held by the object.
## Pointer rules
* Each ::libusbp_async_in_pipe object may hold a pointer to the ::libusbp_generic_handle that it was created from. Similarly, the ::libusbp_generic_handle may hold pointers to its ::libusbp_async_in_pipe objects.
* All other objects contain no pointers to each other.

2
dep/libusbp/UPSTREAM.md Normal file
View File

@@ -0,0 +1,2 @@
This was taken from https://github.com/pololu/libusbp on 2021-12-11.

View File

@@ -0,0 +1,4 @@
add_subdirectory(async_in)
add_subdirectory(lsport)
add_subdirectory(lsusb)
add_subdirectory(port_name)

View File

@@ -0,0 +1,9 @@
use_cxx11()
add_executable(async_in async_in.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(async_in usbp)

View File

@@ -0,0 +1,91 @@
/* This example shows how to read a constant stream of data from an IN endpoint.
* By queueing up a large number of asynchronous transfers, we ensure that the
* USB host controller is busy, and that it will retrieve data from the endpoint
* as fast as possible for an indefinite period of time.
*
* This example is designed to connect to Test Device A and read ADC data from
* endpoint 0x82. */
#include <libusbp.hpp>
#include <stdio.h>
#include <iostream>
#ifdef _MSC_VER
#define usleep(x) Sleep(((x) + 999) / 1000)
#else
#include <unistd.h>
#endif
const uint16_t vendor_id = 0x1FFB;
const uint16_t product_id = 0xDA01;
const uint8_t interface_number = 0;
const bool composite = true;
const uint8_t endpoint_address = 0x82;
const size_t transfer_size = 5;
const size_t transfer_count = 250;
// Prints the data in the given buffer to the standard output in HEX.
void print_data(uint8_t * buffer, size_t size)
{
for (uint8_t i = 0; i < size; i++)
{
printf("%02x", buffer[i]);
if (i < size - 1) { putchar(' '); }
}
printf("\n");
fflush(stdout);
}
int main_with_exceptions()
{
libusbp::device device = libusbp::find_device_with_vid_pid(vendor_id, product_id);
if (!device)
{
std::cerr << "Device not found." << std::endl;
return 1;
}
libusbp::generic_interface gi(device, interface_number, composite);
libusbp::generic_handle handle(gi);
libusbp::async_in_pipe pipe = handle.open_async_in_pipe(endpoint_address);
pipe.allocate_transfers(transfer_count, transfer_size);
pipe.start_endless_transfers();
while(true)
{
uint8_t buffer[transfer_size];
size_t transferred;
libusbp::error transfer_error;
while(pipe.handle_finished_transfer(buffer, &transferred, &transfer_error))
{
if (transfer_error) { throw transfer_error; }
print_data(buffer, transferred);
}
pipe.handle_events();
usleep(500);
}
// Note that closing an async_in_pipe cleanly without causing memory leaks
// can be difficult. For more information, see the documentation of
// libusbp_async_in_pipe_close in libusbp.h. In this example, we don't
// worry about it because we are only ever creating one pipe, so the amount
// of the memory leak is bounded.
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View File

@@ -0,0 +1,2 @@
/libusbp*
/lsusb*

View File

@@ -0,0 +1,38 @@
# This script converts libusbp into a format that can easily be dropped into
# other projects: one source file, one C header file, and one C++ header file.
#
# This is handy for people who want to use libusbp in their own C/C++ software
# projects but don't want to deal with the issues that come with having one more
# dependency.
#
# It also compiles the lsusb example program using the drop-in library.
set -eou pipefail
cd `dirname $0`
SRC=../../src
{
echo "#if 0"
cat $SRC/../LICENSE.txt
echo "#endif"
cat $SRC/libusbp_internal.h
cat $SRC/*.c
echo "#ifdef _WIN32"
cat $SRC/windows/*.c
echo "#endif"
echo "#ifdef __linux__"
cat $SRC/linux/*.c
echo "#endif"
echo "#ifdef __APPLE__"
cat $SRC/mac/*.c
echo "#endif"
} | sed 's/#include <libusbp_internal.h>//' > libusbp.c
cp ../lsusb/lsusb.cpp $SRC/../include/libusbp.h* .
# TODO: fix the library so we don't need -fpermissive here
g++ -std=gnu++11 -Wall -fpermissive -g -O2 -I. \
-DLIBUSBP_DROP_IN -DLIBUSBP_STATIC \
lsusb.cpp libusbp.c -ludev -o lsusb

View File

@@ -0,0 +1,9 @@
use_cxx11()
add_executable(lsport lsport.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(lsport usbp)

View File

@@ -0,0 +1,108 @@
// This example prints the names of all USB serial ports along with information
// about the USB devices they belong to.
//
// For each USB device, it prints the USB vendor ID, product ID, and serial
// number on a line. Then, on the following lines, it prints any serial port
// names it found, sorted by interface number, ascending.
//
// Note: This example is slow and ugly because libusbp does not yet have
// built-in support for listing serial ports; it only has support for finding
// a serial port if you already know what USB device it is connected to and what
// interface you expect the port to be on. This might be improved in the future.
#include <libusbp.hpp>
#include <iostream>
#include <iomanip>
std::string serial_number_or_default(const libusbp::device & device,
const std::string & def)
{
try
{
return device.get_serial_number();
}
catch (const libusbp::error & error)
{
if (error.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER))
{
return def;
}
throw;
}
}
bool try_print_port_name(const libusbp::device & device,
uint8_t interface_number, bool composite)
{
std::string port_name;
try
{
libusbp::serial_port port(device, interface_number, composite);
port_name = port.get_name();
}
catch (const libusbp::error & error)
{
return false;
}
std::cout << " " << port_name << std::endl;
return true;
}
int main_with_exceptions()
{
auto devices = libusbp::list_connected_devices();
for (const libusbp::device & device : devices)
{
bool success = false;
// Print the USB device info.
uint16_t vendor_id = device.get_vendor_id();
uint16_t product_id = device.get_product_id();
std::string serial_number = serial_number_or_default(device, "-");
std::ios::fmtflags flags(std::cout.flags());
std::cout
<< std::hex << std::setfill('0') << std::right
<< std::setw(4) << vendor_id
<< ':'
<< std::setw(4) << product_id
<< ' '
<< std::setfill(' ') << std::left << serial_number
<< std::endl;
std::cout.flags(flags);
// First, assume the device is composite and try the first 16 interfaces.
// Most devices don't have more interfaces than that. Trying all 255 possible
// interfaces slows the program down noticeably. This issue could be fixed if
// we added better serial port enumeration support to libusbp.
for (uint32_t i = 0; i < 16; i++)
{
success = try_print_port_name(device, i, true) || success;
}
// Try to find a port assuming the device is non-composite. Only do so if
// no ports were found earlier, to help avoid printing the same port twice.
if (!success)
{
try_print_port_name(device, 0, false);
}
}
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View File

@@ -0,0 +1,9 @@
use_cxx11()
add_executable(lsusb lsusb.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(lsusb usbp)

View File

@@ -0,0 +1,80 @@
/* This example shows how to gather information about the USB devices connected
* to the system and print it. */
#include <libusbp.hpp>
#include <iostream>
#include <iomanip>
std::string serial_number_or_default(const libusbp::device & device,
const std::string & def)
{
try
{
return device.get_serial_number();
}
catch (const libusbp::error & error)
{
if (error.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER))
{
return def;
}
throw;
}
}
void print_device(libusbp::device & device)
{
uint16_t vendor_id = device.get_vendor_id();
uint16_t product_id = device.get_product_id();
uint16_t revision = device.get_revision();
std::string serial_number = serial_number_or_default(device, "-");
std::string os_id = device.get_os_id();
// Note: The serial number might have spaces in it, so it should be the last
// field to avoid confusing programs that are looking for a field after the
// serial number.
std::ios::fmtflags flags(std::cout.flags());
std::cout
<< std::hex << std::setfill('0') << std::right
<< std::setw(4) << vendor_id
<< ':'
<< std::setw(4) << product_id
<< ' '
<< std::setfill(' ') << std::setw(2) << (revision >> 8)
<< '.'
<< std::setfill('0') << std::setw(2) << (revision & 0xFF)
<< ' '
<< os_id
<< ' '
<< std::setfill(' ') << std::left << serial_number
<< std::endl;
std::cout.flags(flags);
}
int main_with_exceptions()
{
std::vector<libusbp::device> list = libusbp::list_connected_devices();
for (auto it = list.begin(); it != list.end(); ++it)
{
print_device(*it);
}
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View File

@@ -0,0 +1,9 @@
use_cxx11()
add_executable(port_name port_name.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(port_name usbp)

View File

@@ -0,0 +1,44 @@
// This example shows how to get the name of a USB serial port (e.g. "COM6")
// for a single USB device.
#include <libusbp.hpp>
#include <iostream>
#include <iomanip>
const uint16_t vendor_id = 0x1FFB;
const uint16_t product_id = 0xDA01;
const uint8_t interface_number = 2;
const bool composite = true;
int main_with_exceptions()
{
libusbp::device device = libusbp::find_device_with_vid_pid(vendor_id, product_id);
if (!device)
{
std::cerr << "Device not found." << std::endl;
return 1;
}
libusbp::serial_port port(device, interface_number, composite);
std::string port_name = port.get_name();
std::cout << port_name << std::endl;
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View File

@@ -0,0 +1,737 @@
// Copyright (C) Pololu Corporation. See www.pololu.com for details.
/*! \file libusbp.h
*
* This header file provides the C API for libusbp.
*/
#pragma once
/*! The major component of libusbp's version number. In accordance with
* semantic versioning, this gets incremented every time there is a breaking
* change. */
#define LIBUSBP_VERSION_MAJOR 1
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifdef _WIN32
#include <wtypesbase.h>
#endif
#ifdef _WIN32
# define LIBUSBP_DLL_EXPORT __declspec(dllexport)
# define LIBUSBP_DLL_IMPORT __declspec(dllimport)
#else
# define LIBUSBP_DLL_IMPORT __attribute__((visibility ("default")))
# define LIBUSBP_DLL_EXPORT __attribute__((visibility ("default")))
#endif
#ifdef _MSC_VER
#define LIBUSBP_WARN_UNUSED _Check_return_
#else
#define LIBUSBP_WARN_UNUSED __attribute__((warn_unused_result))
#endif
#ifdef LIBUSBP_STATIC
# define LIBUSBP_API
#else
#error not static
# ifdef LIBUSBP_EXPORTS
# define LIBUSBP_API LIBUSBP_DLL_EXPORT
# else
# define LIBUSBP_API LIBUSBP_DLL_IMPORT
# endif
#endif
/*! Some functions in this library return strings to the caller via a char **
* argument. If the function call was successful, it is the caller's
* responsibility to free those strings by passing them to this function.
* Passing the NULL pointer to this function is OK. Do not pass any strings to
* this function unless they were previously returned by a call to this
* library. Do not free the same non-NULL string twice. */
LIBUSBP_API
void libusbp_string_free(char *);
/** libusbp_error **************************************************************/
/*! A libusbp_error object represents an error that occurred in the library.
* Many functions return a libusbp_error pointer as a return value. The
* convention is that a NULL pointer indicates success. If the pointer is not
* NULL, the caller needs to free it at some point by calling
* libusbp_error_free().
*
* NULL is a valid value for a libusbp_error pointer, and can be passed to any
* function in this library that takes a libusbp_error pointer. */
typedef struct libusbp_error
libusbp_error;
/*! Each ::libusbp_error can have 0 or more error codes that give additional
* information about the error that might help the caller take the right action
* when the error occurs. This enum defines which error codes are possible. */
enum libusbp_error_code
{
/*! There were problems allocating memory. A memory shortage might be the
* root cause of the error, or there might be another error that is masked
* by the memory problems. */
LIBUSBP_ERROR_MEMORY = 1,
/*! It is possible that the error was caused by a temporary condition, such
* as the operating system taking some time to initialize drivers. Any
* function that could return this error will say so explicitly in its
* documentation so you do not have to worry about handling it in too many
* places. */
LIBUSBP_ERROR_NOT_READY = 2,
/*! Access was denied. A common cause of this error on Windows is that
* another application has a handle open to the same device. */
LIBUSBP_ERROR_ACCESS_DENIED = 3,
/*! The device does not have a serial number. */
LIBUSBP_ERROR_NO_SERIAL_NUMBER = 4,
/*! The device took too long to respond to a request or transfer data. */
LIBUSBP_ERROR_TIMEOUT = 5,
/*! The error might have been caused by the device being disconnected, but
* it is possible it was caused by something else. */
LIBUSBP_ERROR_DEVICE_DISCONNECTED = 6,
/*! The error might have been caused by the host receiving a STALL packet
* from the device, but it is possible it was caused by something else. */
LIBUSBP_ERROR_STALL = 7,
/*! The error might have been caused by the transfer getting cancelled from
* the host side. Some data might have been transferred anyway. */
LIBUSBP_ERROR_CANCELLED = 8,
};
/*! Attempts to copy an error. If you copy a NULL ::libusbp_error
* pointer, the result will also be NULL. If you copy a non-NULL ::libusbp_error
* pointer, the result will be non-NULL, but if there are issues allocating
* memory, then the copied error might have different properties than the
* original error, and it will have the ::LIBUSBP_ERROR_MEMORY code.
*
* It is the caller's responsibility to free the copied error. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_error_copy(const libusbp_error *);
/*! Frees a returned error object. Passing the NULL pointer to this function is
* OK. Do not free the same non-NULL error twice. */
LIBUSBP_API
void libusbp_error_free(libusbp_error *);
/*! Returns true if the error has specified error code. The error codes are
* listed in the ::libusbp_error_code enum. */
LIBUSBP_API bool libusbp_error_has_code(const libusbp_error *, uint32_t code);
/*! Returns an English-language ASCII-encoded string describing the error. The
* message consists of one or more sentences. If there are multiple sentences,
* the earlier ones will typically explain the context that the error happened
* in.
*
* The returned pointer will be valid until the error is freed, at which point
* it might become invalid. Do not pass the returned pointer to
* libusbp_string_free(). */
LIBUSBP_API const char * libusbp_error_get_message(const libusbp_error *);
/** libusbp_async_in_pipe ******************************************************/
/*! A libusbp_async_in_pipe is an object that holds the memory and other data
* structures for a set of asynchronous USB requests to read data from a
* non-zero endpoint. It can be used to read data from a bulk or IN endpoint
* with high throughput. */
typedef struct libusbp_async_in_pipe
libusbp_async_in_pipe;
/*! Closes the pipe immediately. Note that if the pipe has any
* pending transfers, then it is possible that they cannot be freed
* by this function. Freeing a pipe with pending transfers could
* cause a memory leak, but is otherwise safe. */
LIBUSBP_API
void libusbp_async_in_pipe_close(libusbp_async_in_pipe *);
/*! Allocates buffers and other data structures for performing multiple
* concurrent transfers on the pipe.
*
* The @a transfer_count parameter specifies how many transfers to allocate. You
* can also think of this as the maximum number of concurrent transfers that can
* be active at the same time.
*
* The @a transfer_size parameter specifies how large each transfer's buffer should
* be, and is also the number of bytes that will be requested from the operating
* system when the transfer is submitted.
*
* It is best to set the transfer size to a multiple of the maximum packet size
* of the endpoint. Otherwise, you might get an error when the device sends
* more data than can fit in the transfer's buffer. This type of error is
* called an overflow.
*
* If you want to be reading the pipe every millisecond without gaps, you should
* set the transfer count high enough so that it would take about 100 ms to 250
* ms to finish all the transfers. As long as the operating system runs your
* process that often, you should be able to keep the USB host controller busy
* permanently. (Though we have observed gaps in the transfers when trying to
* do this inside a VirtualBox machine.)
*
* You should not set the transfer count too high, or else it might end up
* taking a long time to cancel the transfers when you are closing the pipe. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_async_in_pipe_allocate_transfers(
libusbp_async_in_pipe *,
size_t transfer_count,
size_t transfer_size);
/*! Starts reading data from the pipe. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_async_in_pipe_start_endless_transfers(
libusbp_async_in_pipe *);
/*! Checks for new events, such as a transfer completing. This
* function and libusbp_async_in_pipe_handle_finished_transfer() should
* be called regularly in order to get data from the pipe. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_async_in_pipe_handle_events(libusbp_async_in_pipe *);
/*! Retrieves a boolean saying whether there are any pending
* transfers. A pending transfer is a transfer that was submitted to
* the operating system, and it may have been completed, but it has
* not been passed to the caller yet via
* libusbp_async_in_pipe_handle_finished_transfer(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error *
libusbp_async_in_pipe_has_pending_transfers(
libusbp_async_in_pipe *,
bool * result);
/*! Checks to see if there is a finished transfer that can be handled.
* If there is one, then this function retrieves the data from the
* transfer, the number of bytes transferred, and any error that might
* have occurred related to the transfer.
*
* @param finished An optional output pointer used to return a pointer that
* indicates whether a transfer was finished. If the returned value is false,
* then no transfer was finished, and there is no transfer_error or data to
* handle.
*
* @param buffer An optional output pointer used to return the data
* from the transfer. The buffer must be at least as large as the
* transfer size specifed when
* libusbp_async_in_pipe_allocate_transfers was called.
*
* @param transferred An optional output pointer used to return the
* number of bytes transferred.
*
* @param transfer_error An optional pointer used to return an error
* related to the transfer, such as a timeout or a cancellation. If
* this pointer is provided, and a non-NULL error is returned via it,
* then the error must later be freed with libusbp_error_free(). There
* will never be a non-NULL transfer error if there is a regular error
* returned as the return value of this function. */
LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error *
libusbp_async_in_pipe_handle_finished_transfer(
libusbp_async_in_pipe *,
bool * finished,
void * buffer,
size_t * transferred,
libusbp_error ** transfer_error);
/*! Cancels all the transfers for this pipe. The cancellation is
* asynchronous, so it won't have an immediate effect. If you want
* to actually make sure that all the transfers get cancelled, you
* will need to call libusbp_async_in_pipe_handle_events() and
* libusbp_async_in_pipe_handle_finished_transfer() repeatedly until
* libusbp_async_in_pipe_has_pending_transfers() indicates there are
* no pending transfers left. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_async_in_pipe_cancel_transfers(libusbp_async_in_pipe *);
/** libusbp_device *************************************************************/
/*! Represents a single USB device. A composite device with multiple functions
* is represented by a single libusbp_device object.
*
* A NULL libusbp_device pointer is valid and can be passed to any function in
* this library that takes such pointers. */
typedef struct libusbp_device
libusbp_device;
/*! Finds all the USB devices connected to the computer and returns a list of them.
*
* The optional @a device_count parameter is used to return the number of
* devices in the list. The list is actually one element larger because it ends
* with a NULL pointer.
*
* If this function is successful (the returned error pointer is NULL), then you
* must later free each device by calling libusbp_device_free() and free the
* list by calling libusbp_list_free(). The order in which the retrieved
* objects are freed does not matter. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_list_connected_devices(
libusbp_device *** device_list,
size_t * device_count);
/*! Frees a device list returned by libusbp_list_connected_device(). */
LIBUSBP_API
void libusbp_list_free(libusbp_device ** list);
/*! Finds a device with the specified vendor ID and product ID and returns a
* pointer to it. If no device can be found, returns a NULL pointer. If the
* retrieved device pointer is not NULL, you must free it later by calling
* libusbp_device_free(). The retrieved device pointer will always be NULL if
* an error is returned. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_find_device_with_vid_pid(
uint16_t vendor_id,
uint16_t product_id,
libusbp_device ** device);
/*! Makes a copy of a device object. If this function is successful, you will
* need to free the copy by calling libusbp_device_free() at some point. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_copy(
const libusbp_device * source,
libusbp_device ** dest);
/*! Frees a device object. Passing a NULL pointer to this function is OK. Do
* not free the same non-NULL device twice. */
LIBUSBP_API void libusbp_device_free(libusbp_device *);
/*! Gets the USB vendor ID of the device (idVendor). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_get_vendor_id(
const libusbp_device *,
uint16_t * vendor_id);
/*! Gets the USB product ID of the device (idProduct). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_get_product_id(
const libusbp_device *,
uint16_t * product_id);
/*! Gets the USB revision code of the device (bcdDevice). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_get_revision(
const libusbp_device *,
uint16_t * revision);
/*! Gets the serial number of the device as an ASCII-encoded string.
*
* On Windows, this just returns the segment of the Device Instance ID after the
* last slash. If the device does not have a serial number, it will return some
* other type of identifier that contains andpersands (&). Windows ignores
* serial numbers with invalid characters in them. For more information, see:
*
* https://msdn.microsoft.com/en-us/library/windows/hardware/dn423379#usbsn
*
* On other systems, if the device does not have a serial number, then this
* function returns an error with the code ::LIBUSBP_ERROR_NO_SERIAL_NUMBER.
*
* (Most applications should only call this function on specific USB devices
* that are already known to have serial numbers, in which case the lack of a
* serial number really does indicate a failure.)
*
* You should free the returned string by calling libusbp_string_free(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_get_serial_number(
const libusbp_device *,
char ** serial_number);
/*! Gets an operating system-specific string that identifies the device.
*
* Note that the level of specificity provided by the ID depends on the system
* you are on, and whether your device has a USB serial number. As long as the
* device remains connected to the bus, this ID is not expected to change and
* there should be no other devices that have the same ID. However, if the
* device gets disconnected from the bus, it may be possible for the ID to be
* reused by another device.
*
* @b Windows: This will be a device instance ID, and it will look something
* like this:
*
* <pre>
* USB\\VID_1FFB&PID_DA01\6&11A23516&18&0000
* </pre>
*
* If your device has a serial number, the part after the slash will be the
* serial number. Otherwise, it will be a string with andpersands in it.
*
* @b Linux: This will be a sysfs path, and it will look like something like
* this:
*
* <pre>
* /sys/devices/pci0000:00/0000:00:06.0/usb1/1-2
* </pre>
*
* <b>macOS:</b> This will be an integer from
* IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number with
* no leading zeros. It will look something like this:
*
* <pre>
* 10000021a
* </pre>
*/
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_get_os_id(
const libusbp_device *,
char ** id);
/** libusbp_generic_interface **************************************************/
/*! Represents a generic or vendor-defined interface of a USB device. A null
* libusbp_generic_interface pointer is valid and can be passed to any function
* in this library that takes such pointers. */
typedef struct libusbp_generic_interface
libusbp_generic_interface;
/*! Creates a generic interface object for a specified interface of the
* specified USB device. This function does as many checks as possible to make
* sure that a handle to the interface could be opened, without actually opening
* it yet.
*
* On all platforms, if a record of the interface cannot be found, then an error
* is returned with the code LIBUSBP_ERROR_NOT_READY, because this could just be
* a temporary condition that happens right after the device is plugged in.
*
* On Windows, the generic interface must use the WinUSB driver, or this
* function will fail. If it is using no driver, that could be a temporary
* condition, and the error returned will use the LIBUSBP_ERROR_NOT_READY error
* code.
*
* On Linux, if the corresponding devnode file does not exist, an error with
* code LIBUSBP_ERROR_NOT_READY is returned. If the interface is assigned to a
* driver that is not "usbfs", an error is returned.
*
* On macOS, we do not have any additional checks beyond just making sure
* that an entry for the interface is found. For non-composite devices, that
* check is deferred until a handle is opened.
*
* @param interface_number The lowest @a bInterfaceNumber for the interfaces in
* the USB function you want to use.
*
* @param composite Should be true if the device is composite, and false
* otherwise.
*
* The returned object must be freed with libusbp_generic_interface_free(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_interface_create(
const libusbp_device *,
uint8_t interface_number,
bool composite,
libusbp_generic_interface **);
/*! Frees the specified generic interface object. Passing the NULL pointer to
* this function is OK. Do not free the same non-NULL pointer twice. */
LIBUSBP_API void libusbp_generic_interface_free(libusbp_generic_interface *);
/*! Makes a copy of the generic interface object. The copy must be freed with
* libusbp_generic_interface_free(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_interface_copy(
const libusbp_generic_interface * source,
libusbp_generic_interface ** dest);
/*! Returns an operating system-specific string that can be used to uniquely
* identify this generic interface.
*
* <b>Windows:</b> This will be a device instance ID specific to this interface, and
* it will look something like this:
*
* <pre>
* USB\\VID_1FFB&PID_DA01&MI_00\6&11A23516&18&0000
* </pre>
*
* <b>Linux:</b> This will be a sysfs path specific to this interface, and it will
* look like something like this:
*
* <pre>
* /sys/devices/pci0000:00/0000:00:06.0/usb1/1-2/1-2:1.0
* </pre>
*
* <b>macOS:</b> This will be an integer from
* IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number with
* no leading zeros. It will look something like this:
*
* <pre>
* 10000021a
* </pre>
*/
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_interface_get_os_id(
const libusbp_generic_interface *,
char ** id);
/*! Returns an operating system-specific filename corresponding to this
* interface.
*
* <b>Windows:</b> This will be the name of a file you can use with CreateFile to
* access the device, and it will look something like this:
*
* <pre>
* \\\\?\\usb#vid_1ffb&pid_da01&mi_00#6&11a23516&18&0000#{99c4bbb0-e925-4397-afee-981cd0702163}
* </pre>
*
* <b>Linux:</b> this will return a device node file name that represents the
* overall USB device. It will look something like:
*
* <pre>
* /dev/bus/usb/001/007
* </pre>
*
* <b>macOS:</b> This will be an integer from
* IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number with
* no leading zeros. It will look something like this:
*
* <pre>
* 10000021a
* </pre>
*/
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_interface_get_os_filename(
const libusbp_generic_interface *,
char ** filename);
/** libusbp_generic_handle *****************************************************/
/*! Represents a generic handle to a USB device. This handle can be used to
* perform operations such as control transfers and reading and writing data
* from non-zero endpoints.
*
* NULL is a valid value for a libusbp_generic_handle pointer, and can be passed
* in any functions of this library that take a libusbp_generic_handle
* pointer. */
typedef struct libusbp_generic_handle
libusbp_generic_handle;
/*! Opens a generic handle to the specified interface of a USB device which can
* be used to perform USB I/O operations.
*
* The handle must later be closed with libusbp_generic_handle_close().
*
* On Windows, for devices using WinUSB, if another application has a handle
* open already when this function is called, then this function will fail and
* the returned error will have code ::LIBUSBP_ERROR_ACCESS_DENIED.
*
* On macOS, this function will set the device's configuration to 1 as a
* side effect in case it is not already configured. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_handle_open(
const libusbp_generic_interface *,
libusbp_generic_handle **);
/*! Closes and frees the specified generic handle. It is OK to pass NULL to
* this function. Do not close the same non-NULL handle twice. All
* ::libusbp_async_in_pipe objects created by the handle must be closed before
* closing the handle. */
LIBUSBP_API
void libusbp_generic_handle_close(
libusbp_generic_handle *);
/*! Creates a new asynchronous pipe object for reading data in from the device
* on one of its bulk or interrupt IN endpoints.
*
* The behavior of this library is unspecified if you use both an asynchronous
* IN pipe and synchronous reads with libusbp_read_pipe() on the same pipe of
* the same generic handle. One reason for that is because for WinUSB devices,
* this function enables RAW_IO for the pipe, and it does not turn off RAW_IO
* again after the pipe is closed. So the behavior of libusbp_read_pipe() could
* change depending on whether an asynchronous IN pipe has been used. */
LIBUSBP_API
libusbp_error * libusbp_generic_handle_open_async_in_pipe(
libusbp_generic_handle *,
uint8_t pipe_id,
libusbp_async_in_pipe ** async_in_pipe);
/*! Sets a timeout for a particular pipe on the USB device.
*
* The @a pipe_id should either be 0 to specify control transfers on endpoint 0, or
* should be a bEndpointAddress value from one of the device's endpoint
* descriptors. Specifying an invalid pipe might result in an error.
*
* The timeout value is specified in milliseconds, and a value of 0 means no
* timeout (wait forever). If this function is not called, the default behavior
* of the handle is to have no timeout.
*
* The behavior of this function is unspecified if there is any data being
* transferred on the pipe while this function is running.
*
* It is unspecified whether this timeout has an effect on asynchronous
* transfers. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_handle_set_timeout(
libusbp_generic_handle *,
uint8_t pipe_id,
uint32_t timeout);
/*! Performs a synchronous (blocking) control transfer on endpoint 0.
*
* Under Linux, this blocking transfer unfortunately cannot be interrupted with
* Ctrl+C.
*
* The @a buffer parameter should point to a buffer that is at least @a wLength
* bytes long.
*
* The @a transferred pointer is optional, and is used to return the number of
* bytes that were actually transferred.
*
* The direction of the transfer is determined by the @a bmRequestType parameter.
*/
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_control_transfer(
libusbp_generic_handle *,
uint8_t bmRequestType,
uint8_t bRequest,
uint16_t wValue,
uint16_t wIndex,
void * buffer,
uint16_t wLength,
size_t * transferred);
/*! Performs a synchronous (blocking) write of data to a bulk or interrupt
* endpoint.
*
* Under Linux, this blocking transfer unfortunately cannot be interrupted with
* Ctrl+C.
*
* The @a pipe_id parameter specifies which endpoint to use. This argument
* should be bEndpointAddress value from one of the device's IN endpoint
* descriptors. (Its most significant bit must be 0.)
*
* The @a transferred parameter is an optional pointer to a variable that will
* receive the number of bytes transferred. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_write_pipe(
libusbp_generic_handle *,
uint8_t pipe_id,
const void * buffer,
size_t size,
size_t * transferred);
/*! Performs a synchronous (blocking) read of data from a bulk or interrupt
* endpoint.
*
* It is best to set the buffer size to a multiple of the maximum
* packet size of the endpoint. Otherwise, this function might return
* an error when the device sends more data than can fit in the
* buffer. This type of error is called an overflow.
*
* Under Linux, this blocking transfer unfortunately cannot be interrupted with
* Ctrl+C.
*
* The @a pipe_id parameter specifies which endpoint to use. This argument
* should be bEndpointAddress value from one of the device's IN endpoint
* descriptors. (Its most significant bit must be 1.)
*
* The @a transferred parameter is an optional pointer to a variable that will
* receive the number of bytes transferred. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_read_pipe(
libusbp_generic_handle *,
uint8_t pipe_id,
void * buffer,
size_t size,
size_t * transferred);
#ifdef __linux__
/*! Gets the underlying file descriptor of the generic handle. This function is
* only available on Linux, and is intended for advanced users. The returned
* file descriptor will remain open and valid as long as the handle is open and
* has not been closed. */
LIBUSBP_API
int libusbp_generic_handle_get_fd(libusbp_generic_handle *);
#endif
#ifdef _WIN32
/*! Gets the underlying WinUSB handle for the generic handle. This function is
* only available on Windows, and is intended for advanced users. The returned
* WinUSB handle will remain open and valid as long as the generic handle is
* open and has not been closed. */
LIBUSBP_API
HANDLE libusbp_generic_handle_get_winusb_handle(libusbp_generic_handle *);
#endif
#ifdef __APPLE__
/*! Gets the underlying IOCFPlugInInterface object representing the interface.
* You can cast the returned pointer to a `IOCFPlugInInterface **` and then use
* `QueryInterface` to get the corresponding `IOUSBInterfaceInterface **`.
* There is an example of this in generic_handle_test.cpp. */
LIBUSBP_API
void ** libusbp_generic_handle_get_cf_plug_in(libusbp_generic_handle *);
#endif
/** libusbp_serial_port ********************************************************/
/*! Represents a serial port. A null libusbp_serial_port pointer is valid and
* can be passed to any function in this library that takes such pointers. */
typedef struct libusbp_serial_port
libusbp_serial_port;
/*! Creates a serial port object for a specified interface of the
* specified USB device.
*
* On all platforms, if a record of the interface cannot be found, then an error
* is returned with the code LIBUSBP_ERROR_NOT_READY, because this could just be
* a temporary condition that happens right after the device is plugged in.
*
* On macOS, it is assumed that the interface with a @a bInterfaceNumber one
* greater than @a interface_number is the interface that the IOSerialBSDClient
* will attach to. This should be true if the device implements the USB CDC ACM
* class and has ordered its interfaces so that the control interface is right
* before the data interface.
*
* @param interface_number The lowest @a bInterfaceNumber for the USB interfaces
* that comprise the serial port.
*
* @param composite Should be true if the device is composite, and false
* otherwise.
*
* The returned object must be freed with libusbp_generic_interface_free(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_serial_port_create(
const libusbp_device *,
uint8_t interface_number,
bool composite,
libusbp_serial_port **);
/*! Frees the specified serial port object. Passing the NULL pointer to
* this function is OK. Do not free the same non-NULL pointer twice. */
LIBUSBP_API void libusbp_serial_port_free(libusbp_serial_port *);
/*! Makes a copy of the generic interface object. The copy must be freed with
* libusbp_generic_interface_free(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_serial_port_copy(
const libusbp_serial_port * source,
libusbp_serial_port ** dest);
/*! Gets the user-friendly name of the COM port
* that could be used to open a handle.
*
* On Windows, this will be something like "COM12".
*
* On Linux, it will be something like "/dev/ttyACM0".
*
* On macOS, it will be something like "/dev/cu.usbmodem012345".
* Specifically, it will be a call-out device, not a dial-in device.
*
* You should free the returned string by calling libusbp_string_free().
*/
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_serial_port_get_name(
const libusbp_serial_port *,
char ** name);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,569 @@
// Copyright (C) Pololu Corporation. See www.pololu.com for details.
/*! \file libusbp.hpp
*
* This header files provides the C++ API for libusbp. The classes and
* functions here are just thin wrappers around the C API, so you should see
* libusbp.h for full documentation. */
#pragma once
#include "libusbp.h"
#include <cstddef>
#include <utility>
#include <memory>
#include <string>
#include <vector>
/** Display a nice error if C++11 is not enabled (e.g. --std=c++11 or --std=gnu++11).
* The __GXX_EXPERIMENTAL_CXX0X__ check is needed for GCC 4.6, which defines __cplusplus as 1.
* The _MSC_VER check is needed for Visual Studio 2015. */
#if (!defined(__cplusplus) || (__cplusplus < 201103L)) && !defined(__GXX_EXPERIMENTAL_CXX0X__) && !defined(_MSC_VER)
#error This header requires features from C++11.
#endif
namespace libusbp
{
/*! \cond */
inline void throw_if_needed(libusbp_error * err);
/*! \endcond */
/*! Wrapper for libusbp_error_free(). */
inline void pointer_free(libusbp_error * p) noexcept
{
libusbp_error_free(p);
}
/*! Wrapper for libusbp_error_copy(). */
inline libusbp_error * pointer_copy(libusbp_error * p) noexcept
{
return libusbp_error_copy(p);
}
/*! Wrapper for libusbp_async_in_pipe_close(). */
inline void pointer_free(libusbp_async_in_pipe * p) noexcept
{
libusbp_async_in_pipe_close(p);
}
/*! Wrapper for libusbp_device_free(). */
inline void pointer_free(libusbp_device * p) noexcept
{
libusbp_device_free(p);
}
/*! Wrapper for libusbp_device_copy(). */
inline libusbp_device * pointer_copy(libusbp_device * pointer)
{
libusbp_device * copy;
throw_if_needed(libusbp_device_copy(pointer, &copy));
return copy;
}
/*! Wrapper for libusbp_generic_interface_free(). */
inline void pointer_free(libusbp_generic_interface * p) noexcept
{
libusbp_generic_interface_free(p);
}
/*! Wrapper for libusbp_generic_interface_copy(). */
inline libusbp_generic_interface * pointer_copy(libusbp_generic_interface * pointer)
{
libusbp_generic_interface * copy;
throw_if_needed(libusbp_generic_interface_copy(pointer, &copy));
return copy;
}
/*! Wrapper for libusbp_generic_handle_free(). */
inline void pointer_free(libusbp_generic_handle * p) noexcept
{
libusbp_generic_handle_close(p);
}
/*! Wrapper for libusbp_serial_port_copy(). */
inline libusbp_serial_port * pointer_copy(libusbp_serial_port * pointer)
{
libusbp_serial_port * copy;
throw_if_needed(libusbp_serial_port_copy(pointer, &copy));
return copy;
}
/*! Wrapper for libusbp_serial_port_free(). */
inline void pointer_free(libusbp_serial_port * p) noexcept
{
libusbp_serial_port_free(p);
}
/*! This class is not part of the public API of the library and you should
* not use it directly, but you can use the public methods it provides to
* the classes that inherit from it.
*
* For any type T, if you define pointer_free(T *), then
* unique_pointer_wrapper<T> will be a well-behaved C++ class that provides
* a constructor, implicit conversion to a bool, C++ move operations,
* pointer operations, and forbids C++ copy operations. */
template<class T>
class unique_pointer_wrapper
{
public:
/*! Constructor that takes a pointer. */
explicit unique_pointer_wrapper(T * p = nullptr) noexcept
: pointer(p)
{
}
/*! Move constructor. */
unique_pointer_wrapper(unique_pointer_wrapper && other) noexcept
{
pointer = other.pointer_release();
}
/*! Move assignment operator. */
unique_pointer_wrapper & operator=(unique_pointer_wrapper && other) noexcept
{
pointer_reset(other.pointer_release());
return *this;
}
/*! Destructor. */
~unique_pointer_wrapper() noexcept
{
pointer_reset();
}
/*! Implicit conversion to bool. Returns true if the underlying pointer
* is not NULL. */
explicit operator bool() const noexcept
{
return pointer != nullptr;
}
/*! Returns the underlying pointer. */
T * pointer_get() const noexcept
{
return pointer;
}
/*! Sets the underlying pointer to the specified value, freeing the
* previous pointer and taking ownership of the specified one. */
void pointer_reset(T * p = nullptr) noexcept
{
pointer_free(pointer);
pointer = p;
}
/*! Releases the pointer, transferring ownership of it to the caller and
* resetting the underlying pointer of this object to nullptr. The caller
* is responsible for freeing the returned pointer if it is not NULL. */
T * pointer_release() noexcept
{
T * p = pointer;
pointer = nullptr;
return p;
}
/*! Returns a pointer to the underlying pointer. */
T ** pointer_to_pointer_get() noexcept
{
return &pointer;
}
/*! Copy constructor: forbid. */
unique_pointer_wrapper(const unique_pointer_wrapper & other) = delete;
/*! Copy assignment operator: forbid. */
unique_pointer_wrapper & operator=(const unique_pointer_wrapper & other) = delete;
protected:
/*! The underlying pointer that is being wrapped. This pointer will be
* freed when the object is destroyed. */
T * pointer;
};
/*! This class is not part of the public API of the library and you should
* not use it directly, but you can use the public methods it provides to
* the classes that inherit from it.
*
* For any type T, if you define pointer_free(T *) and pointer_copy(T *), then
* unique_pointer_wrapper_with_copy<T> will be a well-behaved C++ class that provides
* a constructor, implicit conversion to a bool, C++ move operations, C++ copy operations,
* and pointer operations. */
template <class T>
class unique_pointer_wrapper_with_copy : public unique_pointer_wrapper<T>
{
public:
/*! Constructor that takes a pointer. */
explicit unique_pointer_wrapper_with_copy(T * p = nullptr) noexcept
: unique_pointer_wrapper<T>(p)
{
}
/*! Move constructor. */
unique_pointer_wrapper_with_copy(
unique_pointer_wrapper_with_copy && other) noexcept = default;
/*! Copy constructor */
unique_pointer_wrapper_with_copy(
const unique_pointer_wrapper_with_copy & other)
: unique_pointer_wrapper<T>()
{
this->pointer = pointer_copy(other.pointer);
}
/*! Copy assignment operator. */
unique_pointer_wrapper_with_copy & operator=(
const unique_pointer_wrapper_with_copy & other)
{
this->pointer_reset(pointer_copy(other.pointer));
return *this;
}
/*! Move assignment operator. */
unique_pointer_wrapper_with_copy & operator=(
unique_pointer_wrapper_with_copy && other) = default;
};
/*! Wrapper for a ::libusbp_error pointer. */
class error : public unique_pointer_wrapper_with_copy<libusbp_error>, public std::exception
{
public:
/*! Constructor that takes a pointer. */
explicit error(libusbp_error * p = nullptr) noexcept
: unique_pointer_wrapper_with_copy(p)
{
}
/*! Wrapper for libusbp_error_get_message(). */
const char * what() const noexcept override {
return libusbp_error_get_message(pointer);
}
/*! Wrapper for libusbp_error_get_message() that returns a
* std::string. */
std::string message() const
{
return what();
}
/*! Wrapper for libusbp_error_has_code(). */
bool has_code(uint32_t error_code) const noexcept
{
return libusbp_error_has_code(pointer, error_code);
}
};
/*! \cond */
inline void throw_if_needed(libusbp_error * err)
{
if (err != nullptr)
{
throw error(err);
}
}
/*! \endcond */
/*! Wrapper for a ::libusbp_async_in_pipe pointer. */
class async_in_pipe : public unique_pointer_wrapper<libusbp_async_in_pipe>
{
public:
/*! Constructor that takes a pointer. */
explicit async_in_pipe(libusbp_async_in_pipe * pointer = nullptr)
: unique_pointer_wrapper(pointer)
{
}
/*! Wrapper for libusbp_async_in_pipe_allocate_transfers(). */
void allocate_transfers(size_t transfer_count, size_t transfer_size)
{
throw_if_needed(libusbp_async_in_pipe_allocate_transfers(
pointer, transfer_count, transfer_size));
}
/*! Wrapper for libusbp_async_in_pipe_start_endless_transfers(). */
void start_endless_transfers()
{
throw_if_needed(libusbp_async_in_pipe_start_endless_transfers(pointer));
}
/*! Wrapper for libusbp_async_in_pipe_handle_events(). */
void handle_events()
{
throw_if_needed(libusbp_async_in_pipe_handle_events(pointer));
}
/*! Wrapper for libusbp_async_in_pipe_has_pending_transfers(). */
bool has_pending_transfers()
{
bool result;
throw_if_needed(libusbp_async_in_pipe_has_pending_transfers(pointer, &result));
return result;
}
/*! Wrapper for libusbp_async_in_pipe_handle_finished_transfer(). */
bool handle_finished_transfer(void * buffer, size_t * transferred,
error * transfer_error)
{
libusbp_error ** error_out = nullptr;
if (transfer_error != nullptr)
{
transfer_error->pointer_reset();
error_out = transfer_error->pointer_to_pointer_get();
}
bool finished;
throw_if_needed(libusbp_async_in_pipe_handle_finished_transfer(
pointer, &finished, buffer, transferred, error_out));
return finished;
}
/*! Wrapper for libusbp_async_in_pipe_cancel_transfers(). */
void cancel_transfers()
{
throw_if_needed(libusbp_async_in_pipe_cancel_transfers(pointer));
}
};
/*! Wrapper for a ::libusbp_device pointer. */
class device : public unique_pointer_wrapper_with_copy<libusbp_device>
{
public:
/*! Constructor that takes a pointer. */
explicit device(libusbp_device * pointer = nullptr) :
unique_pointer_wrapper_with_copy(pointer)
{
}
/*! Wrapper for libusbp_device_get_vendor_id(). */
uint16_t get_vendor_id() const
{
uint16_t id;
throw_if_needed(libusbp_device_get_vendor_id(pointer, &id));
return id;
}
/*! Wrapper for libusbp_device_get_product_id(). */
uint16_t get_product_id() const
{
uint16_t id;
throw_if_needed(libusbp_device_get_product_id(pointer, &id));
return id;
}
/*! Wrapper for libusbp_device_get_revision(). */
uint16_t get_revision() const
{
uint16_t r;
throw_if_needed(libusbp_device_get_revision(pointer, &r));
return r;
}
/*! Wrapper for libusbp_device_get_serial_number(). */
std::string get_serial_number() const
{
char * str;
throw_if_needed(libusbp_device_get_serial_number(pointer, &str));
std::string serial_number = str;
libusbp_string_free(str);
return serial_number;
}
/*! Wrapper for libusbp_device_get_os_id(). */
std::string get_os_id() const
{
char * str;
throw_if_needed(libusbp_device_get_os_id(pointer, &str));
std::string serial_number = str;
libusbp_string_free(str);
return serial_number;
}
};
/*! Wrapper for libusbp_list_connected_devices(). */
inline std::vector<libusbp::device> list_connected_devices()
{
libusbp_device ** device_list;
size_t size;
throw_if_needed(libusbp_list_connected_devices(&device_list, &size));
std::vector<device> vector;
for(size_t i = 0; i < size; i++)
{
vector.emplace_back(device_list[i]);
}
libusbp_list_free(device_list);
return vector;
}
/*! Wrapper for libusbp_find_device_with_vid_pid(). */
inline libusbp::device find_device_with_vid_pid(uint16_t vendor_id, uint16_t product_id)
{
libusbp_device * device_pointer;
throw_if_needed(libusbp_find_device_with_vid_pid(
vendor_id, product_id, &device_pointer));
return device(device_pointer);
}
/*! Wrapper for a ::libusbp_generic_interface pointer. */
class generic_interface : public unique_pointer_wrapper_with_copy<libusbp_generic_interface>
{
public:
/*! Constructor that takes a pointer. This object will free the pointer
* when it is destroyed. */
explicit generic_interface(libusbp_generic_interface * pointer = nullptr)
: unique_pointer_wrapper_with_copy(pointer)
{
}
/*! Wrapper for libusbp_generic_interface_create. */
explicit generic_interface(const device & device,
uint8_t interface_number = 0, bool composite = false)
{
throw_if_needed(libusbp_generic_interface_create(
device.pointer_get(), interface_number, composite, &pointer));
}
/*! Wrapper for libusbp_generic_interface_get_os_id(). */
std::string get_os_id() const
{
char * str;
throw_if_needed(libusbp_generic_interface_get_os_id(pointer, &str));
std::string id = str;
libusbp_string_free(str);
return id;
}
/*! Wrapper for libusbp_generic_interface_get_os_filename(). */
std::string get_os_filename() const
{
char * str;
throw_if_needed(libusbp_generic_interface_get_os_filename(pointer, &str));
std::string filename = str;
libusbp_string_free(str);
return filename;
}
};
/*! Wrapper for a ::libusbp_generic_handle pointer. */
class generic_handle : public unique_pointer_wrapper<libusbp_generic_handle>
{
public:
/*! Constructor that takes a pointer. This object will free the pointer
* when it is destroyed. */
explicit generic_handle(libusbp_generic_handle * pointer = nullptr) noexcept
: unique_pointer_wrapper(pointer)
{
}
/*! Wrapper for libusbp_generic_handle_open(). */
explicit generic_handle(const generic_interface & gi)
{
throw_if_needed(libusbp_generic_handle_open(gi.pointer_get(), &pointer));
}
/*! Wrapper for libusbp_generic_handle_close(). */
void close() noexcept
{
pointer_reset();
}
/*! Wrapper for libusbp_generic_handle_open_async_in_pipe(). */
async_in_pipe open_async_in_pipe(uint8_t pipe_id)
{
libusbp_async_in_pipe * pipe;
throw_if_needed(libusbp_generic_handle_open_async_in_pipe(
pointer, pipe_id, &pipe));
return async_in_pipe(pipe);
}
/*! Wrapper for libusbp_generic_handle_set_timeout(). */
void set_timeout(uint8_t pipe_id, uint32_t timeout)
{
throw_if_needed(libusbp_generic_handle_set_timeout(pointer, pipe_id, timeout));
}
/*! Wrapper for libusbp_control_transfer(). */
void control_transfer(
uint8_t bmRequestType,
uint8_t bRequest,
uint16_t wValue,
uint16_t wIndex,
void * buffer = nullptr,
uint16_t wLength = 0,
size_t * transferred = nullptr)
{
throw_if_needed(libusbp_control_transfer(pointer,
bmRequestType, bRequest, wValue, wIndex,
buffer, wLength, transferred));
}
/*! Wrapper for libusbp_write_pipe(). */
void write_pipe(uint8_t pipe_id, const void * buffer,
size_t size, size_t * transferred)
{
throw_if_needed(libusbp_write_pipe(pointer,
pipe_id, buffer, size, transferred));
}
/*! Wrapper for libusbp_read_pipe(). */
void read_pipe(uint8_t pipe_id, void * buffer,
size_t size, size_t * transferred)
{
throw_if_needed(libusbp_read_pipe(pointer,
pipe_id, buffer, size, transferred));
}
#ifdef _WIN32
/*! Wrapper for libusbp_generic_handle_get_winusb_handle(). */
HANDLE get_winusb_handle()
{
return libusbp_generic_handle_get_winusb_handle(pointer);
}
#endif
#ifdef __linux__
/*! Wrapper for libusbp_generic_handle_get_fd(). */
int get_fd()
{
return libusbp_generic_handle_get_fd(pointer);
}
#endif
#ifdef __APPLE__
/*! Wrapper for libusbp_generic_handle_get_cf_plug_in(). */
void ** get_cf_plug_in()
{
return libusbp_generic_handle_get_cf_plug_in(pointer);
}
#endif
};
/*! Wrapper for a ::libusbp_serial_port pointer. */
class serial_port : public unique_pointer_wrapper_with_copy<libusbp_serial_port>
{
public:
/*! Constructor that takes a pointer. This object will free the pointer
* when it is destroyed. */
explicit serial_port(libusbp_serial_port * pointer = nullptr)
: unique_pointer_wrapper_with_copy(pointer)
{
}
/*! Wrapper for libusbp_serial_port_create(). */
explicit serial_port(const device & device,
uint8_t interface_number = 0, bool composite = false)
{
throw_if_needed(libusbp_serial_port_create(
device.pointer_get(), interface_number, composite, &pointer));
}
/*! Wrapper for libusbp_serial_port_get_name(). */
std::string get_name() const
{
char * str;
throw_if_needed(libusbp_serial_port_get_name(pointer, &str));
std::string id = str;
libusbp_string_free(str);
return id;
}
};
}

View File

@@ -0,0 +1,8 @@
#pragma once
#define BUILD_SYSTEM_LIBUSBP_VERSION_MAJOR 1
#define LIBUSBP_STATIC
#undef LIBUSBP_LOG
#undef VBOX_LINUX_ON_WINDOWS
#undef USE_TEST_DEVICE_A
#undef USE_TEST_DEVICE_B

View File

@@ -0,0 +1,15 @@
use_c99()
add_library (install_helper SHARED install_helper_windows.c dll.def)
target_link_libraries (install_helper setupapi msi)
set_target_properties (install_helper PROPERTIES
OUTPUT_NAME usbp-install-helper-${LIBUSBP_VERSION_MAJOR}
LINK_FLAGS "-Wl,--enable-stdcall-fixup -static"
)
install (TARGETS install_helper
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)

View File

@@ -0,0 +1,3 @@
The files in this directory compile a separate, statically linked DLL named
`libusbp-install-helper-<x>.dll` that can be useful in installers of USB
software.

View File

@@ -0,0 +1,5 @@
EXPORTS
libusbp_install_inf
libusbp_install_infW
libusbp_broadcast_setting_change
libusbp_broadcast_setting_changeW

View File

@@ -0,0 +1,167 @@
/* This file contains special functions that can help install an application
* that uses USB. The functions can be used from an MSI custom action or from
* rundll32. */
#include <windows.h>
#include <devpropdef.h>
#include <msiquery.h>
#include <setupapi.h>
#include <strsafe.h>
#define LIBUSBP_UNUSED(param_name) (void)param_name;
typedef struct install_context
{
HWND owner;
MSIHANDLE install;
} install_context;
static void log_message(install_context * context, LPCWSTR message)
{
if (context->install == 0)
{
// The MSI handle is not available so just ignore log messages.
}
else
{
// Report the log message through MSI, which will put it in the log
// file.
MSIHANDLE record = MsiCreateRecord(1);
MsiRecordSetStringW(record, 0, message);
MsiProcessMessage(context->install, INSTALLMESSAGE_INFO, record);
MsiCloseHandle(record);
}
}
// Adds an error message to the Windows Installer log file and displays it
// to the user in a dialog box with an OK button.
static void error_message(install_context * context, LPCWSTR message)
{
if (context->install == 0)
{
// The MSIHANDLE is not available, so just display a dialog box.
MessageBoxW(context->owner, message, L"Installation Error", MB_ICONERROR);
}
else
{
// Report the error through MSI, which will in turn display the dialog
// box.
MSIHANDLE record = MsiCreateRecord(1);
MsiRecordSetStringW(record, 0, message);
MsiProcessMessage(context->install, INSTALLMESSAGE_ERROR, record);
MsiCloseHandle(record);
}
}
/* You might need to call this function after modifying the PATH in order to
* notify other programs about the change. This allows a newly launched Command
* Prompt to see that the PATH has changed and start using it. */
static void broadcast_setting_change_core(install_context * context)
{
DWORD_PTR result2 = 0;
LRESULT result = SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
(LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, &result2);
if (result == 0)
{
WCHAR message[1024];
StringCbPrintfW(message, sizeof(message),
L"SendMessageTimeout failed: Error code 0x%lx. Result %d",
GetLastError(), result2);
error_message(context, message);
}
}
// Usage: rundll32 libusbp*.dll libusbp_broadcast_setting_change
void __stdcall libusbp_broadcast_setting_changeW(
HWND owner, HINSTANCE hinst, LPWSTR args, int n)
{
LIBUSBP_UNUSED(hinst);
LIBUSBP_UNUSED(args);
LIBUSBP_UNUSED(n);
install_context context = {0};
context.owner = owner;
broadcast_setting_change_core(&context);
}
// Usage: make a Custom Action in an MSI with this function as the entry point.
UINT __stdcall libusbp_broadcast_setting_change(MSIHANDLE install)
{
install_context context = {0};
context.install = install;
log_message(&context, L"libusbp_broadcast_setting_change: Begin.");
broadcast_setting_change_core(&context);
log_message(&context, L"libusbp_broadcast_setting_change: End.");
return 0; // Always return success.
}
/* Calls SetupCopyOEMInf to install the specified INF file. The user may be
* prompted to accept the driver software, and if everything works then the file
* will be copied to the C:\Windows\inf directory. */
static void install_inf_core(install_context * context, LPWSTR filename)
{
BOOL success = SetupCopyOEMInfW(filename, NULL, SPOST_PATH, 0, NULL, 0, NULL, NULL);
if (!success)
{
WCHAR message[1024];
// NOTE: newlines do not show up in the MSI log, but they do show up in
// the MSI error dialog box.
StringCbPrintfW(message, sizeof(message),
L"There was an error installing the driver file %s. \n"
L"You might have to manually install this file by right-clicking it "
L"and selecting \"Install\". \n"
L"Error code 0x%lx.", filename, GetLastError());
error_message(context, message);
}
}
// Usage: rundll32 libusbp*.dll libusbp_install_inf path
void __stdcall libusbp_install_infW(HWND owner, HINSTANCE hinst, LPWSTR args, int n)
{
LIBUSBP_UNUSED(hinst);
LIBUSBP_UNUSED(args);
LIBUSBP_UNUSED(n);
install_context context = {0};
context.owner = owner;
install_inf_core(&context, args);
}
// Usage: make a Custom Action in your installer with a "CustomActionData"
// property set equal to the full path of the inf file.
UINT __stdcall libusbp_install_inf(MSIHANDLE install)
{
install_context context = {0};
context.install = install;
broadcast_setting_change_core(&context);
log_message(&context, L"libusbp_install_inf: Begin.");
WCHAR message[1024];
// Get the name of inf file.
WCHAR filename[1024];
DWORD length = 1024;
UINT result = MsiGetPropertyW(install, L"CustomActionData", filename, &length);
if (result != ERROR_SUCCESS)
{
StringCbPrintfW(message, sizeof(message),
L"libusbp_install_inf: Unable to get filename parameter. Error code %d.", result);
error_message(&context, message);
return 0; // Return success anyway.
}
StringCbPrintfW(message, sizeof(message), L"libusbp_install_inf: filename=%s", filename);
log_message(&context, message);
install_inf_core(&context, filename);
StringCbPrintfW(message, sizeof(message), L"libusbp_install_inf: End. result2=%d", result);
log_message(&context, message);
// Always return 0 even if there was an error, because we don't want to roll
// back the rest of the installation just because this part fails. The user
// can either manually install the INF files or try again.
return 0;
}

View File

@@ -0,0 +1,4 @@
add_subdirectory(test_async_in)
add_subdirectory(test_long_read)
add_subdirectory(test_long_write)
add_subdirectory(test_transitions)

View File

@@ -0,0 +1,9 @@
use_cxx11()
add_executable(test_async_in test_async_in.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(test_async_in usbp)

View File

@@ -0,0 +1,107 @@
/* Tests that libusbp is capable of reliably reading data from an IN endpoint on
* every frame using asynchronous transfers. It prints to the standard output
* as it successfully receives data, and prints to the standard error if there
* was a gap in the data (a USB frame where the device did not generate a new
* packet for the host).
*
* You can also check the CPU usage while running this function to make
* sure libusbp is not doing anything too inefficient.
*/
#include <libusbp.hpp>
#include <stdio.h>
#include <iostream>
#ifdef _MSC_VER
#define usleep(x) Sleep(((x) + 999) / 1000)
#else
#include <unistd.h>
#endif
const uint16_t vendor_id = 0x1FFB;
const uint16_t product_id = 0xDA01;
const uint8_t interface_number = 0;
const bool composite = true;
const uint8_t endpoint_address = 0x82;
const size_t packet_size = 5;
const size_t transfer_size = packet_size;
const size_t transfer_count = 250;
int main_with_exceptions()
{
libusbp::device device = libusbp::find_device_with_vid_pid(vendor_id, product_id);
if (!device)
{
std::cerr << "Device not found." << std::endl;
return 1;
}
libusbp::generic_interface gi(device, interface_number, composite);
libusbp::generic_handle handle(gi);
libusbp::async_in_pipe pipe = handle.open_async_in_pipe(endpoint_address);
pipe.allocate_transfers(transfer_count, transfer_size);
pipe.start_endless_transfers();
uint8_t last_f = 0;
uint32_t finish_count = 0;
while(true)
{
uint8_t buffer[transfer_size];
size_t transferred;
libusbp::error transfer_error;
while(pipe.handle_finished_transfer(buffer, &transferred, &transfer_error))
{
if (transfer_error)
{
fprintf(stderr, "Transfer error.\n");
throw transfer_error;
}
if (transferred != transfer_size)
{
fprintf(stderr, "Got %d bytes instead of %d.\n",
(int)transferred, (int)transfer_size);
}
uint8_t f = buffer[0];
if (f != (uint8_t)(last_f + transfer_size/packet_size))
{
// If this happens, it indicates there was a USB frame where the
// device did not generate a new packet for the host, which is
// bad. However, you should expect to see a few of these at the
// very beginning of the test because there will be some old
// packets queued up in the device from earlier, and because
// last_f always starts at 0.
fprintf(stderr, "Frame number gap: %d to %d\n", last_f, f);
}
last_f = f;
if ((++finish_count % 4096) == 0)
{
printf("Another 4096 transfers done.\n");
fflush(stdout);
}
}
pipe.handle_events();
usleep(20000);
}
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View File

@@ -0,0 +1,9 @@
use_cxx11()
add_executable(test_long_read test_long_read.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(test_long_read usbp)

View File

@@ -0,0 +1,90 @@
/* Tests what happens if we do a synchronous read from an IN endpoint that
* takes a long time to complete. This can be used to check that pressing
* Ctrl+C is able to interrupt the read. */
#include <libusbp.hpp>
#include <stdio.h>
#include <iostream>
const uint16_t vendor_id = 0x1FFB;
const uint16_t product_id = 0xDA01;
const uint8_t interface_number = 0;
const bool composite = true;
const uint8_t endpoint_address = 0x82;
const size_t packet_size = 5;
const size_t transfer_size = packet_size * 10000;
void long_read(libusbp::generic_handle & handle)
{
uint8_t buffer[transfer_size];
size_t transferred;
printf("Reading %d bytes...\n", (unsigned int)transfer_size);
fflush(stdout);
handle.read_pipe(endpoint_address, buffer, sizeof(buffer), &transferred);
if (transferred == transfer_size)
{
printf("Transfer successful.\n");
}
else
{
printf("Transferred only %d bytes out of %d.\n",
(unsigned int)transferred, (unsigned int)transfer_size);
}
fflush(stdout);
}
void long_control_read(libusbp::generic_handle & handle)
{
uint8_t buffer[5];
size_t transferred;
printf("Performing a slow control read...\n");
fflush(stdout);
handle.control_transfer(0xC0, 0x91, 10000, 5, buffer, sizeof(buffer), &transferred);
if (transferred == 5)
{
printf("Control read successful.\n");
}
else
{
printf("Transferred only %d bytes out of %d.\n",
(unsigned int)transferred, (unsigned int)sizeof(buffer));
}
fflush(stdout);
}
int main_with_exceptions()
{
libusbp::device device = libusbp::find_device_with_vid_pid(vendor_id, product_id);
if (!device)
{
std::cerr << "Device not found." << std::endl;
return 1;
}
libusbp::generic_interface gi(device, interface_number, composite);
libusbp::generic_handle handle(gi);
long_read(handle);
long_control_read(handle);
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View File

@@ -0,0 +1,9 @@
use_cxx11()
add_executable(test_long_write test_long_write.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(test_long_write usbp)

View File

@@ -0,0 +1,70 @@
/* Tests what happens if we do a synchronous write that takes a long time to
* complete. This can be used to check that pressing Ctrl+C is able to
* interrupt the write. */
#include <libusbp.hpp>
#include <stdio.h>
#include <iostream>
const uint16_t vendor_id = 0x1FFB;
const uint16_t product_id = 0xDA01;
const uint8_t interface_number = 0;
const bool composite = true;
const uint8_t endpoint_address = 0x03;
const size_t packet_size = 32;
const size_t transfer_size = packet_size * 4;
void long_write(libusbp::generic_handle & handle)
{
// First packet causes a delay of 4 seconds (0x0FA0 ms)
uint8_t buffer[transfer_size] = { 0xDE, 0xA0, 0x0F };
size_t transferred;
printf("Writing...\n");
fflush(stdout);
handle.write_pipe(endpoint_address, buffer, sizeof(buffer), &transferred);
if (transferred == transfer_size)
{
printf("Transfer successful.\n");
}
else
{
printf("Transferred only %d bytes out of %d.\n",
(unsigned int)transferred, (unsigned int)transfer_size);
}
}
int main_with_exceptions()
{
libusbp::device device = libusbp::find_device_with_vid_pid(
vendor_id, product_id);
if (!device)
{
std::cerr << "Device not found." << std::endl;
return 1;
}
libusbp::generic_interface gi(device, interface_number, composite);
libusbp::generic_handle handle(gi);
long_write(handle);
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View File

@@ -0,0 +1,9 @@
use_cxx11()
add_executable(test_transitions test_transitions.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(test_transitions usbp)

View File

@@ -0,0 +1,98 @@
/* This program helps us test the transitions that a USB device goes through as
* it gets connected or disconnected from a computer. It helps us identify
* errors that might occur so we can assign code to them such as
* LIBUSBP_ERROR_NOT_READY. */
#include <libusbp.hpp>
#include <iostream>
#include <iomanip>
#include <chrono>
#ifdef _MSC_VER
#define usleep(x) Sleep(((x) + 999) / 1000)
#else
#include <unistd.h>
#endif
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 6
typedef std::chrono::monotonic_clock clock_type;
#else
typedef std::chrono::steady_clock clock_type;
#endif
std::ostream & log()
{
return std::cout
<< clock_type::now().time_since_epoch().count()
<< ": ";
}
void check_test_device_a(libusbp::device device)
{
static std::string current_status;
std::string status;
if (device)
{
status = "Found " + device.get_serial_number() + ".";
// Try to connect to the generic interface.
try
{
libusbp::generic_interface gi(device, 0, true);
libusbp::generic_handle handle(gi);
status += " Interface 0 works.";
}
catch(const libusbp::error & error)
{
status += " Interface 0 error: " + error.message();
if (!error.has_code(LIBUSBP_ERROR_NOT_READY))
{
status += " Lacks code LIBUSBP_ERROR_NOT_READY!";
}
}
}
else
{
status = "Not found.";
}
if (current_status != status)
{
log() << "Test device A: " << status << std::endl;
current_status = status;
}
}
int main_with_exceptions()
{
std::cout
<< "Clock tick period: "
<< clock_type::period::num
<< "/"
<< clock_type::period::den
<< " seconds" << std::endl;
while(1)
{
check_test_device_a(libusbp::find_device_with_vid_pid(0x1FFB, 0xDA01));
usleep(10);
}
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View File

@@ -0,0 +1,116 @@
use_c99()
# Settings for GCC
if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
# By default, symbols are not visible outside of the library.
set (CMAKE_C_FLAGS "-fvisibility=hidden ${CMAKE_C_FLAGS}")
# Avoid tons of errors from strsafe.h.
set (CMAKE_C_FLAGS "-fgnu89-inline ${CMAKE_C_FLAGS}")
endif ()
# Define cross-platform source files.
set (sources
async_in_pipe.c
error.c
error_hresult.c
find_device.c
list.c
pipe_id.c
string.c)
# Define operating system-specific source files.
if (WIN32)
set (sources ${sources}
windows/error_windows.c
windows/device_windows.c
windows/interface_windows.c
windows/device_instance_id_windows.c
windows/generic_interface_windows.c
windows/list_windows.c
windows/generic_handle_windows.c
windows/async_in_transfer_windows.c
windows/serial_port_windows.c
${CMAKE_CURRENT_BINARY_DIR}/info.rc)
elseif (LINUX)
set (sources ${sources}
linux/list_linux.c
linux/device_linux.c
linux/generic_interface_linux.c
linux/generic_handle_linux.c
linux/error_linux.c
linux/udev_linux.c
linux/usbfd_linux.c
linux/async_in_transfer_linux.c
linux/serial_port_linux.c)
elseif (APPLE)
set (sources ${sources}
mac/list_mac.c
mac/device_mac.c
mac/error_mac.c
mac/generic_interface_mac.c
mac/generic_handle_mac.c
mac/async_in_transfer_mac.c
mac/serial_port_mac.c
mac/iokit_mac.c)
endif ()
add_library (usbp ${sources})
include_directories (
"${CMAKE_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}"
)
if (WIN32)
target_link_libraries (usbp setupapi winusb uuid ole32)
if (NOT BUILD_SHARED_LIBS)
set (PC_MORE_LIBS "-lsetupapi -lwinusb -luuid -lole32")
endif ()
elseif (LINUX)
pkg_check_modules(LIBUDEV REQUIRED libudev)
string (REPLACE ";" " " LIBUDEV_CFLAGS "${LIBUDEV_CFLAGS}")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${LIBUDEV_CFLAGS}")
target_link_libraries (usbp udev)
if (NOT BUILD_SHARED_LIBS)
set (PC_REQUIRES "libudev")
endif ()
elseif (APPLE)
set (link_flags "-framework IOKit -framework CoreFoundation ${link_flags}")
if (NOT BUILD_SHARED_LIBS)
set (PC_MORE_LIBS "${link_flags}")
endif ()
endif ()
set_target_properties(usbp PROPERTIES
OUTPUT_NAME usbp-${LIBUSBP_VERSION_MAJOR}
SOVERSION ${LIBUSBP_VERSION}
VERSION ${LIBUSBP_VERSION}
DEFINE_SYMBOL LIBUSBP_EXPORTS
LINK_FLAGS "${link_flags}"
)
configure_file (
"libusbp_config.h.in"
"libusbp_config.h"
)
configure_file (
"info.rc.in"
"info.rc"
)
configure_file (
"libusbp.pc.in"
"libusbp-${LIBUSBP_VERSION_MAJOR}.pc"
@ONLY
)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libusbp-${LIBUSBP_VERSION_MAJOR}.pc"
DESTINATION lib/pkgconfig)
install(TARGETS usbp
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)

View File

@@ -0,0 +1,343 @@
#include <libusbp_internal.h>
struct libusbp_async_in_pipe
{
libusbp_generic_handle * handle;
uint8_t pipe_id;
async_in_transfer ** transfer_array;
size_t transfer_size;
size_t transfer_count;
bool endless_transfers_enabled;
// The number of transfers that are pending, meaning that they were
// submitted (and possibly completed by the kernel) but not finished (handed
// to the user of the pipe) yet. This variable allows us to distinguish
// between the state where no transfers are pending and the state where all
// of them are pending. Note that this definition of pending is different
// than the definition of pending in the async_in_transfer struct.
size_t pending_count;
// The index of the transfer that should finish next. That transfer will be
// pending (and thus able to be checked) if pending_count > 0.
size_t next_finish;
// The index of the transfer that will be submitted next when we need to
// submit more transfers. That transfer can be submitted if pending_count <
// transfer_count.
size_t next_submit;
};
static inline size_t increment_and_wrap_size(size_t n, size_t bound)
{
n++;
return n >= bound ? 0 : n;
}
static void async_in_transfer_array_free(async_in_transfer ** array, size_t transfer_count)
{
if (array == NULL) { return; }
for (size_t i = 0; i < transfer_count; i++)
{
async_in_transfer_free(array[i]);
}
free(array);
}
void libusbp_async_in_pipe_close(libusbp_async_in_pipe * pipe)
{
if (pipe != NULL)
{
async_in_transfer_array_free(pipe->transfer_array, pipe->transfer_count);
free(pipe);
}
}
libusbp_error * async_in_pipe_create(libusbp_generic_handle * handle,
uint8_t pipe_id, libusbp_async_in_pipe ** pipe)
{
// Check the pipe output pointer.
if (pipe == NULL)
{
return error_create("Pipe output pointer is null.");
}
*pipe = NULL;
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
libusbp_error * error = NULL;
// Check the pipe_id parameter.
if (error == NULL)
{
error = check_pipe_id(pipe_id);
}
if (error == NULL && !(pipe_id & 0x80))
{
error = error_create("Asynchronous pipes for OUT endpoints are not supported.");
}
// Perform OS-specific setup.
if (error == NULL)
{
error = async_in_pipe_setup(handle, pipe_id);
}
libusbp_async_in_pipe * new_pipe = NULL;
if (error == NULL)
{
new_pipe = calloc(1, sizeof(libusbp_async_in_pipe));
if (new_pipe == NULL)
{
error = &error_no_memory;
}
}
if (error == NULL)
{
new_pipe->handle = handle;
new_pipe->pipe_id = pipe_id;
*pipe = new_pipe;
new_pipe = NULL;
}
free(new_pipe);
return error;
}
libusbp_error * libusbp_async_in_pipe_allocate_transfers(
libusbp_async_in_pipe * pipe,
size_t transfer_count,
size_t transfer_size)
{
if (pipe == NULL)
{
return error_create("Pipe argument is null.");
}
if (pipe->transfer_array != NULL)
{
return error_create("Transfers were already allocated for this pipe.");
}
if (transfer_count == 0)
{
return error_create("Transfer count cannot be zero.");
}
if (transfer_size == 0)
{
return error_create("Transfer size cannot be zero.");
}
libusbp_error * error = NULL;
async_in_transfer ** new_transfer_array = NULL;
if (error == NULL)
{
new_transfer_array = calloc(transfer_count, sizeof(async_in_transfer *));
if (new_transfer_array == NULL)
{
error = &error_no_memory;
}
}
for(size_t i = 0; error == NULL && i < transfer_count; i++)
{
error = async_in_transfer_create(pipe->handle, pipe->pipe_id,
transfer_size, &new_transfer_array[i]);
}
// Put the new array and the information about it into the pipe.
if (error == NULL)
{
pipe->transfer_array = new_transfer_array;
pipe->transfer_count = transfer_count;
pipe->transfer_size = transfer_size;
new_transfer_array = NULL;
}
async_in_transfer_array_free(new_transfer_array, transfer_count);
if (error != NULL)
{
error = error_add(error, "Failed to allocate transfers for asynchronous IN pipe.");
}
return error;
}
static void async_in_pipe_submit_next_transfer(libusbp_async_in_pipe * pipe)
{
assert(pipe != NULL);
assert(pipe->pending_count < pipe->transfer_count);
// Submit the next transfer.
async_in_transfer_submit(pipe->transfer_array[pipe->next_submit]);
// Update the counts and indices.
pipe->pending_count++;
pipe->next_submit = increment_and_wrap_size(pipe->next_submit, pipe->transfer_count);
}
libusbp_error * libusbp_async_in_pipe_start_endless_transfers(
libusbp_async_in_pipe * pipe)
{
if (pipe == NULL)
{
return error_create("Pipe argument is null.");
}
if (pipe->transfer_array == NULL)
{
return error_create("Pipe transfers have not been allocated yet.");
}
pipe->endless_transfers_enabled = true;
while(pipe->pending_count < pipe->transfer_count)
{
async_in_pipe_submit_next_transfer(pipe);
}
return NULL;
}
libusbp_error * libusbp_async_in_pipe_handle_events(libusbp_async_in_pipe * pipe)
{
if (pipe == NULL)
{
return error_create("Pipe argument is null.");
}
return generic_handle_events(pipe->handle);
}
libusbp_error * libusbp_async_in_pipe_has_pending_transfers(
libusbp_async_in_pipe * pipe,
bool * result)
{
libusbp_error * error = NULL;
if (error == NULL && result == NULL)
{
error = error_create("Boolean output pointer is null.");
}
if (error == NULL)
{
*result = false;
}
if (error == NULL && pipe == NULL)
{
error = error_create("Pipe argument is null.");
}
if (error == NULL)
{
*result = pipe->pending_count ? 1 : 0;
}
return error;
}
libusbp_error * libusbp_async_in_pipe_handle_finished_transfer(
libusbp_async_in_pipe * pipe,
bool * finished,
void * buffer,
size_t * transferred,
libusbp_error ** transfer_error)
{
if (finished != NULL)
{
*finished = false;
}
if (transferred != NULL)
{
*transferred = 0;
}
if (transfer_error != NULL)
{
*transfer_error = NULL;
}
if (pipe == NULL)
{
return error_create("Pipe argument is null.");
}
if (pipe->pending_count == 0)
{
// There are no pending transfers that we could check for completion.
return NULL;
}
async_in_transfer * transfer = pipe->transfer_array[pipe->next_finish];
if (async_in_transfer_pending(transfer))
{
// The next transfer we expect to finish is still pending;
// the kernel has not told us that it is done.
return NULL;
}
libusbp_error * error = async_in_transfer_get_results(transfer, buffer,
transferred, transfer_error);
if (error == NULL)
{
if (finished != NULL)
{
*finished = true;
}
pipe->pending_count--;
pipe->next_finish = increment_and_wrap_size(pipe->next_finish, pipe->transfer_count);
}
if (error == NULL && pipe->endless_transfers_enabled)
{
async_in_pipe_submit_next_transfer(pipe);
}
return error;
}
libusbp_error * libusbp_async_in_pipe_cancel_transfers(libusbp_async_in_pipe * pipe)
{
if (pipe == NULL)
{
return error_create("Pipe argument is null.");
}
pipe->endless_transfers_enabled = false;
libusbp_error * error = NULL;
#ifdef __linux__
// In Linux, transfers need to be cancelled individually.
for (size_t i = 0; error == NULL && i < pipe->transfer_count; i++)
{
error = async_in_transfer_cancel(pipe->transfer_array[i]);
// This doesn't help the performance issue in this function:
//if (error == NULL) { error = generic_handle_events(pipe->handle); }
}
#else
// On other platforms, any of the transfers has all the information needed
// to cancel the others.
error = async_in_transfer_cancel(pipe->transfer_array[0]);
#endif
return error;
}

223
dep/libusbp/src/error.c Normal file
View File

@@ -0,0 +1,223 @@
#include <libusbp_internal.h>
#ifdef _WIN32
#if (defined(__GNUC__) && !defined(__USE_MINGW_ANSI_STDIO)) || (defined(_MSC_VER) && _MSC_VER < 1900)
#error This code depends on vsnprintf returning a number of characters.
#endif
#endif
struct libusbp_error
{
bool do_not_free;
char * message;
size_t code_count;
uint32_t * code_array;
};
static uint32_t mem_error_code_array[1] = { LIBUSBP_ERROR_MEMORY };
static char error_no_memory_msg[] = "Failed to allocate memory.";
libusbp_error error_no_memory =
{
.do_not_free = true,
.message = error_no_memory_msg,
.code_count = 1,
.code_array = mem_error_code_array,
};
static char error_masked_by_no_memory_msg[] = "Failed to allocate memory for reporting an error.";
static libusbp_error error_masked_by_no_memory =
{
.do_not_free = true,
.message = error_masked_by_no_memory_msg,
.code_count = 1,
.code_array = mem_error_code_array,
};
static libusbp_error error_blank =
{
.do_not_free = true,
.message = NULL,
.code_count = 0,
.code_array = NULL,
};
void libusbp_error_free(libusbp_error * error)
{
if (error != NULL && !error->do_not_free)
{
free(error->message);
free(error->code_array);
free(error);
}
}
// Copies the error. If the input is not NULL, the output will always
// be not NULL, but it might be a immutable error (do_not_free=1).
libusbp_error * libusbp_error_copy(const libusbp_error * src_error)
{
if (src_error == NULL) { return NULL; }
const char * src_message = src_error->message;
if (src_message == NULL) { src_message = ""; }
size_t message_length = strlen(src_message);
size_t code_count = src_error->code_count;
if (src_error->code_array == NULL) { code_count = 0; }
// Allocate memory.
libusbp_error * new_error = malloc(sizeof(libusbp_error));
char * new_message = malloc(message_length + 1);
uint32_t * new_code_array = malloc(code_count * sizeof(uint32_t));
if (new_error == NULL || new_message == NULL ||
(code_count != 0 && new_code_array == NULL))
{
free(new_error);
free(new_message);
free(new_code_array);
return &error_masked_by_no_memory;
}
if (code_count != 0)
{
memcpy(new_code_array, src_error->code_array, code_count * sizeof(uint32_t));
}
strncpy(new_message, src_message, message_length + 1);
new_error->do_not_free = false;
new_error->message = new_message;
new_error->code_count = code_count;
new_error->code_array = new_code_array;
return new_error;
}
// Tries to make the error mutable. Always returns a non-NULL error,
// but it might return an immutable error (do_not_free == 1) if memory
// cannot be allocated.
static libusbp_error * error_make_mutable(libusbp_error * error)
{
if (error == NULL) { error = &error_blank; }
if (error->do_not_free)
{
error = libusbp_error_copy(error);
}
return error;
}
// Tries to add a message to the error. After calling this, you
// should not use the error you passed as an input, because it might
// have been freed.
libusbp_error * error_add_v(libusbp_error * error, const char * format, va_list ap)
{
if (format == NULL) { return error; }
error = error_make_mutable(error);
if (error == NULL || error->do_not_free) { return error; }
if (error->message == NULL) { error->message = ""; }
// Determine all the string lengths.
size_t outer_message_length = 0;
{
char x[1];
va_list ap2;
va_copy(ap2, ap);
int result = vsnprintf(x, 0, format, ap2);
if (result > 0)
{
outer_message_length = (size_t) result;
}
va_end(ap2);
}
size_t inner_message_length = strlen(error->message);
size_t separator_length = (inner_message_length && outer_message_length) ? 2 : 0;
size_t message_length = outer_message_length + separator_length + inner_message_length;
char * message = malloc(message_length + 1);
if (message == NULL)
{
libusbp_error_free(error);
return &error_masked_by_no_memory;
}
// Assemble the message.
vsnprintf(message, outer_message_length + 1, format, ap);
if (separator_length)
{
strncpy(message + outer_message_length, " ", separator_length + 1);
}
strncpy(message + outer_message_length + separator_length,
error->message, inner_message_length + 1);
message[message_length] = 0;
free(error->message);
error->message = message;
return error;
}
// Tries to add the specified code to the error.
// This is just like error_add_v in terms of pointer ownership.
libusbp_error * error_add_code(libusbp_error * error, uint32_t code)
{
error = error_make_mutable(error);
if (error == NULL || error->do_not_free) { return error; }
if (error->code_count >= SIZE_MAX / sizeof(uint32_t)) { return error; }
size_t size = (error->code_count + 1) * sizeof(uint32_t);
uint32_t * new_array = realloc(error->code_array, size);
if (new_array == NULL)
{
libusbp_error_free(error);
return &error_masked_by_no_memory;
}
error->code_array = new_array;
error->code_array[error->code_count++] = code;
return error;
}
// Variadic version of error_add_v.
libusbp_error * error_add(libusbp_error * error, const char * format, ...)
{
va_list ap;
va_start(ap, format);
error = error_add_v(error, format, ap);
va_end(ap);
return error;
}
// Creates a new error.
libusbp_error * error_create(const char * format, ...)
{
va_list ap;
va_start(ap, format);
libusbp_error * error = error_add_v(NULL, format, ap);
va_end(ap);
return error;
}
bool libusbp_error_has_code(const libusbp_error * error, uint32_t code)
{
if (error == NULL) { return false; }
for (size_t i = 0; i < error->code_count; i++)
{
if (error->code_array[i] == code)
{
return true;
}
}
return false;
}
const char * libusbp_error_get_message(const libusbp_error * error)
{
if (error == NULL)
{
return "No error.";
}
if (error->message == NULL)
{
return "";
}
return error->message;
}

View File

@@ -0,0 +1,21 @@
#include <libusbp_internal.h>
#if defined(_WIN32) || defined(__APPLE__)
libusbp_error * error_create_hr(HRESULT hr, const char * format, ...)
{
// HRESULT should be an int32_t on the systems we care about (Mac OS X,
// Win32, Win64), but let's assert it here in case that ever changes.
assert(sizeof(HRESULT) == 4);
assert((HRESULT)-1 < (HRESULT)0);
libusbp_error * error = error_create("HRESULT error code 0x%x.", (int32_t)hr);
va_list ap;
va_start(ap, format);
error = error_add_v(error, format, ap);
va_end(ap);
return error;
}
#endif

View File

@@ -0,0 +1,70 @@
#include <libusbp_internal.h>
static libusbp_error * check_device_vid_pid(const libusbp_device * device,
uint16_t vendor_id, uint16_t product_id, bool * matches)
{
assert(matches != NULL);
*matches = false;
libusbp_error * error;
uint16_t device_vid;
error = libusbp_device_get_vendor_id(device, &device_vid);
if (error != NULL) { return error; }
if (device_vid != vendor_id) { return NULL; }
uint16_t device_pid;
error = libusbp_device_get_product_id(device, &device_pid);
if (error != NULL) { return error; }
if (device_pid != product_id) { return NULL; }
*matches = true;
return NULL;
}
libusbp_error * libusbp_find_device_with_vid_pid(
uint16_t vendor_id, uint16_t product_id, libusbp_device ** device)
{
if (device == NULL)
{
return error_create("Device output pointer is null.");
}
*device = NULL;
libusbp_error * error = NULL;
libusbp_device ** new_list = NULL;
size_t size = 0;
error = libusbp_list_connected_devices(&new_list, &size);
assert(error != NULL || new_list != NULL);
for(size_t i = 0; error == NULL && i < size; i++)
{
// Each iteration of this loop checks one candidate device
// and either passes it to the caller or frees it.
libusbp_device * candidate = new_list[i];
if (*device == NULL)
{
bool matches;
error = check_device_vid_pid(candidate, vendor_id, product_id, &matches);
if (error != NULL) { break; }
if (matches)
{
// Return the device to the caller.
*device = candidate;
candidate = NULL;
}
}
libusbp_device_free(candidate);
}
libusbp_list_free(new_list);
return error;
}

View File

@@ -0,0 +1,28 @@
#define VERSION ${LIBUSBP_VERSION_MAJOR},${LIBUSBP_VERSION_MINOR},${LIBUSBP_VERSION_PATCH},0
#define VERSION_STR "${LIBUSBP_VERSION_MAJOR}.${LIBUSBP_VERSION_MINOR}.${LIBUSBP_VERSION_PATCH}"
#include <winver.h>
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION
PRODUCTVERSION VERSION
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "Pololu Corporation"
VALUE "FileDescription", "Pololu USB Library"
VALUE "FileVersion", VERSION_STR
VALUE "ProductName", "Pololu USB Library (${CMAKE_BUILD_TYPE})"
VALUE "ProductVersion", VERSION_STR
VALUE "LegalCopyright", "Copyright (C) ${YEAR} Pololu Corporation"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END

View File

@@ -0,0 +1,10 @@
prefix=@CMAKE_INSTALL_PREFIX@
libdir=${prefix}/lib
includedir=${prefix}/include
Name: libusbp
Version: @LIBUSBP_VERSION@
Description: Library for accessing USB devices.
Requires: @PC_REQUIRES@
Libs: -L${libdir} -lusbp-@LIBUSBP_VERSION_MAJOR@ @PC_MORE_LIBS@
Cflags: -I${includedir}/libusbp-@LIBUSBP_VERSION_MAJOR@ @PC_MORE_CFLAGS@

View File

@@ -0,0 +1,11 @@
#pragma once
#define BUILD_SYSTEM_LIBUSBP_VERSION_MAJOR @LIBUSBP_VERSION_MAJOR@
#cmakedefine LIBUSBP_LOG
#cmakedefine VBOX_LINUX_ON_WINDOWS
#cmakedefine USE_TEST_DEVICE_A
#cmakedefine USE_TEST_DEVICE_B

View File

@@ -0,0 +1,367 @@
#ifdef LIBUSBP_DROP_IN
#include "libusbp.h"
#else
#pragma once
#include <libusbp_config.h>
#include <libusbp.h>
#if BUILD_SYSTEM_LIBUSBP_VERSION_MAJOR != LIBUSBP_VERSION_MAJOR
#error Major version in libusbp.h disagrees with build system.
#endif
#endif
// Don't warn about zero-length format strings, which we sometimes use when
// constructing error objects.
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wformat-zero-length"
#endif
// Silence some warnings from the Microsoft C Compiler.
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#define strdup _strdup
#endif
#include <assert.h>
#include <stdlib.h>
#include <limits.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#include <devpropdef.h>
#include <setupapi.h>
#include <cfgmgr32.h>
#include <usbiodef.h>
#include <usbioctl.h>
#include <stringapiset.h>
#include <winusb.h>
#endif
#ifdef __linux__
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <libudev.h>
#include <linux/usbdevice_fs.h>
#include <linux/usb/ch9.h>
#include <sys/ioctl.h>
#endif
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <mach/error.h>
#include <mach/mach_error.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFBundle.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/serial/IOSerialKeys.h>
#include <IOKit/serial/ioss.h>
#endif
// For debug builds (NDEBUG undefined), functions and variables declared with
// LIBUSBP_TEST_API will be exported in the library so that they can be unit
// tested, but they are not part of the public API and they might change at any
// time. Those symbols can cause name collisions with symbols defined by the
// user, so this type of build is not suitable for making a general release.
#ifdef NDEBUG
#define LIBUSBP_TEST_API
#else
#define LIBUSBP_TEST_API LIBUSBP_API
#endif
#ifdef _MSC_VER
#define LIBUSBP_PRINTF(f, a)
#else
#define LIBUSBP_PRINTF(f, a) __attribute__((format (printf, f, a)))
#endif
// Suppresses unused parameter warnings.
#define LIBUSBP_UNUSED(param_name) (void)param_name;
#define MAX_ENDPOINT_NUMBER 15
typedef struct libusbp_setup_packet
{
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} libusbp_setup_packet;
LIBUSBP_TEST_API extern libusbp_error error_no_memory;
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED libusbp_error * error_create(
const char * format, ...) LIBUSBP_PRINTF(1, 2);
LIBUSBP_WARN_UNUSED libusbp_error * error_add_v(
libusbp_error * error, const char * format, va_list ap);
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED libusbp_error * error_add(
libusbp_error * error, const char * format, ...)
LIBUSBP_PRINTF(2, 3);
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED libusbp_error * error_add_code(
libusbp_error * error, uint32_t code);
LIBUSBP_WARN_UNUSED libusbp_error * string_copy(
const char * input_string,
char ** output_string);
LIBUSBP_WARN_UNUSED
libusbp_error * check_pipe_id(uint8_t pipe_id);
LIBUSBP_WARN_UNUSED
libusbp_error * check_pipe_id_in(uint8_t pipe_id);
LIBUSBP_WARN_UNUSED
libusbp_error * check_pipe_id_out(uint8_t pipe_id);
LIBUSBP_WARN_UNUSED
libusbp_error * device_list_create(libusbp_device *** device_list);
LIBUSBP_WARN_UNUSED
libusbp_error * device_list_append(libusbp_device *** device_list,
size_t * device_count, libusbp_device * device);
void free_devices_and_list(libusbp_device ** device_list);
typedef struct async_in_transfer
async_in_transfer;
void async_in_transfer_handle_completion(async_in_transfer *);
void async_in_transfer_free(async_in_transfer * transfer);
libusbp_error * async_in_transfer_create(
libusbp_generic_handle * handle,
uint8_t pipe_id,
size_t transfer_size,
async_in_transfer ** transfer);
LIBUSBP_WARN_UNUSED
libusbp_error * async_in_pipe_create(
libusbp_generic_handle * handle,
uint8_t pipe_id,
libusbp_async_in_pipe ** pipe);
LIBUSBP_WARN_UNUSED
libusbp_error * async_in_pipe_setup(libusbp_generic_handle * gh, uint8_t pipe_id);
void async_in_transfer_submit(async_in_transfer * transfer);
LIBUSBP_WARN_UNUSED
libusbp_error * async_in_transfer_get_results(async_in_transfer * transfer,
void * buffer, size_t * transferred, libusbp_error ** transfer_error);
LIBUSBP_WARN_UNUSED
libusbp_error * async_in_transfer_cancel(async_in_transfer * transfer);
bool async_in_transfer_pending(async_in_transfer * transfer);
LIBUSBP_WARN_UNUSED
libusbp_error * generic_handle_events(libusbp_generic_handle * handle);
#ifdef _WIN32
LIBUSBP_WARN_UNUSED libusbp_error * create_device(HDEVINFO list,
PSP_DEVINFO_DATA info, libusbp_device ** device);
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(1, 2)
libusbp_error * error_create_winapi(const char * format, ...);
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(1, 2)
libusbp_error * error_create_overlapped(const char * format, ...);
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(2, 3)
libusbp_error * error_create_cr(CONFIGRET, const char * format, ...);
LIBUSBP_WARN_UNUSED libusbp_error * get_interface(
const char * device_instance_id,
uint8_t interface_number,
bool composite,
HDEVINFO * list,
PSP_DEVINFO_DATA info);
LIBUSBP_WARN_UNUSED libusbp_error * create_id_string(
HDEVINFO list, PSP_DEVINFO_DATA info, char ** id);
LIBUSBP_WARN_UNUSED libusbp_error * get_filename_from_devinst_and_guid(
DEVINST devinst,
const GUID * guid,
char ** filename);
#endif
#ifdef __linux__
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(1, 2)
libusbp_error * error_create_errno(const char * format, ...);
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(2, 3)
libusbp_error * error_create_udev(int error_code, const char * format, ...);
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED
libusbp_error * error_from_urb_status(struct usbdevfs_urb * urb);
LIBUSBP_WARN_UNUSED
libusbp_error * device_create(struct udev_device * dev, libusbp_device ** device);
LIBUSBP_WARN_UNUSED
libusbp_error * generic_interface_get_device_copy(
const libusbp_generic_interface * gi, libusbp_device ** device);
/** udevw **********************************************************************/
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_create_context(struct udev **);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_create_usb_list(struct udev * context, struct udev_enumerate ** list);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_device_from_syspath(struct udev *,
const char * syspath, struct udev_device ** dev);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_device_type(struct udev_device *, const char ** devtype);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_sysattr_uint8(
struct udev_device * dev, const char * name, uint8_t * value);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_sysattr_uint16(
struct udev_device * dev, const char * name, uint16_t * value);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_sysattr_if_exists_copy(
struct udev_device * dev, const char * name, char ** value);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_interface(
struct udev * udev,
const char * device_syspath,
uint8_t interface_number,
struct udev_device ** device);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_tty(
struct udev * udev,
struct udev_device * parent,
struct udev_device ** device);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_syspath(struct udev_device * device, const char ** syspath);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_syspath_copy(struct udev_device * device, char ** devnode);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_devnode(struct udev_device * device, const char ** devnode);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_devnode_copy(struct udev_device * device, char ** devnode);
LIBUSBP_WARN_UNUSED
libusbp_error * udevw_get_devnode_copy_from_syspath(const char * syspath, char ** devnode);
/** usbfd **********************************************************************/
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED
libusbp_error * usbfd_check_existence(const char * filename);
LIBUSBP_WARN_UNUSED
libusbp_error * usbfd_open(const char * path, int * fd);
LIBUSBP_WARN_UNUSED
libusbp_error * usbfd_get_device_descriptor(int fd, struct usb_device_descriptor * desc);
LIBUSBP_WARN_UNUSED
libusbp_error * usbfd_control_transfer(int fd, libusbp_setup_packet setup,
uint32_t timeout, void * data, size_t * transferred);
LIBUSBP_WARN_UNUSED
libusbp_error * usbfd_control_transfer_async(int fd, void * combined_buffer,
size_t size, uint32_t timeout, void * user_context);
LIBUSBP_WARN_UNUSED
libusbp_error * usbfd_bulk_or_interrupt_transfer(int fd, uint8_t pipe, uint32_t timeout,
void * buffer, size_t size, size_t * transferred);
LIBUSBP_WARN_UNUSED
libusbp_error * usbfd_submit_urb(int fd, struct usbdevfs_urb * urb);
LIBUSBP_WARN_UNUSED
libusbp_error * usbfd_reap_urb(int fd, struct usbdevfs_urb ** urb);
LIBUSBP_WARN_UNUSED
libusbp_error * usbfd_discard_urb(int fd, struct usbdevfs_urb * urb);
#endif
#ifdef __APPLE__
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(2, 3)
libusbp_error * error_create_mach(kern_return_t error_code, const char * format, ...);
LIBUSBP_WARN_UNUSED
libusbp_error * create_device(io_service_t service, libusbp_device ** device);
uint64_t device_get_id(const libusbp_device * device);
bool generic_interface_get_interface_id(const libusbp_generic_interface * gi, uint64_t * id);
uint64_t generic_interface_get_device_id(const libusbp_generic_interface * gi);
uint8_t generic_interface_get_interface_number(const libusbp_generic_interface * gi);
IOUSBInterfaceInterface182 ** generic_handle_get_ioh(const libusbp_generic_handle * handle);
uint8_t generic_handle_get_pipe_index(const libusbp_generic_handle * handle, uint8_t pipe_id);
LIBUSBP_WARN_UNUSED
libusbp_error * iokit_id_to_string(uint64_t id, char ** str);
LIBUSBP_WARN_UNUSED
libusbp_error * service_get_from_id(uint64_t id, io_service_t *);
LIBUSBP_WARN_UNUSED
libusbp_error * service_get_usb_interface(io_service_t service,
uint8_t interface_number, io_service_t * interface_service);
LIBUSBP_WARN_UNUSED
libusbp_error * service_get_child_by_class(io_service_t service,
const char * class_name, io_service_t * interface_service);
LIBUSBP_WARN_UNUSED
libusbp_error * service_to_interface(io_service_t, CFUUIDRef pluginType, REFIID rid,
void **, IOCFPlugInInterface ***);
LIBUSBP_WARN_UNUSED
libusbp_error * get_id(io_registry_entry_t entry, uint64_t * id);
LIBUSBP_WARN_UNUSED
libusbp_error * get_string(io_registry_entry_t entry,
CFStringRef name, char ** value);
LIBUSBP_WARN_UNUSED
libusbp_error * get_int32(io_registry_entry_t entry,
CFStringRef name, int32_t * value);
LIBUSBP_WARN_UNUSED
libusbp_error * get_uint16(io_registry_entry_t entry,
CFStringRef name, uint16_t * value);
#endif
#if defined(_WIN32) || defined(__APPLE__)
LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(2, 3)
libusbp_error * error_create_hr(HRESULT, const char * format, ...);
#endif

View File

@@ -0,0 +1,187 @@
#include <libusbp_internal.h>
struct async_in_transfer
{
struct usbdevfs_urb urb;
bool pending;
libusbp_error * error;
int fd;
};
libusbp_error * async_in_pipe_setup(libusbp_generic_handle * handle, uint8_t pipe_id)
{
LIBUSBP_UNUSED(handle);
LIBUSBP_UNUSED(pipe_id);
return NULL;
}
libusbp_error * async_in_transfer_create(
libusbp_generic_handle * handle, uint8_t pipe_id, size_t transfer_size,
async_in_transfer ** transfer)
{
assert(transfer_size != 0);
assert(transfer != NULL);
if (transfer_size > INT_MAX)
{
// usbdevfs_urb uses ints to represent sizes.
return error_create("Transfer size is too large.");
}
libusbp_error * error = NULL;
// Allocate the transfer struct.
async_in_transfer * new_transfer = NULL;
if (error == NULL)
{
new_transfer = calloc(1, sizeof(async_in_transfer));
if (new_transfer == NULL)
{
error = &error_no_memory;
}
}
// Allocate the buffer for the transfer.
void * new_buffer = NULL;
if (error == NULL)
{
new_buffer = malloc(transfer_size);
if (new_buffer == NULL)
{
error = &error_no_memory;
}
}
// Assemble the transfer and pass it to the caller.
if (error == NULL)
{
new_transfer->fd = libusbp_generic_handle_get_fd(handle);
new_transfer->urb.usercontext = new_transfer;
new_transfer->urb.buffer_length = transfer_size;
new_transfer->urb.type = USBDEVFS_URB_TYPE_BULK;
new_transfer->urb.endpoint = pipe_id;
new_transfer->urb.buffer = new_buffer;
new_buffer = NULL;
*transfer = new_transfer;
new_transfer = NULL;
}
free(new_buffer);
free(new_transfer);
return error;
}
void async_in_transfer_free(async_in_transfer * transfer)
{
if (transfer == NULL) { return; }
if (transfer->pending)
{
// Unfortunately, this transfer is still pending, so we cannot free it;
// the kernel needs to be able to write to this transfer's memory when
// it completes. We could just abort the process here, but instead we
// we choose to have a memory leak, since it can be easily detected and
// might be small enough to be harmless.
return;
}
libusbp_error_free(transfer->error);
free(transfer->urb.buffer);
free(transfer);
}
void async_in_transfer_submit(async_in_transfer * transfer)
{
assert(transfer != NULL);
assert(transfer->pending == false);
libusbp_error_free(transfer->error);
transfer->error = NULL;
transfer->pending = true;
libusbp_error * error = usbfd_submit_urb(transfer->fd, &transfer->urb);
if (error != NULL)
{
transfer->pending = false;
transfer->error = error;
}
}
void async_in_transfer_handle_completion(async_in_transfer * transfer)
{
assert(transfer != NULL);
#ifdef LIBUSBP_LOG
fprintf(stderr, "URB completed: %p, status=%d, actual_length=%d\n",
transfer, transfer->urb.status, transfer->urb.actual_length);
#endif
libusbp_error * error = NULL;
if (error == NULL)
{
error = error_from_urb_status(&transfer->urb);
}
if (error == NULL && transfer->urb.error_count != 0)
{
error = error_create("Non-zero error count for USB request: %d.",
transfer->urb.error_count);
}
if (error != NULL)
{
error = error_add(error, "Asynchronous IN transfer failed.");
}
transfer->pending = false;
transfer->error = error;
}
libusbp_error * async_in_transfer_get_results(async_in_transfer * transfer,
void * buffer, size_t * transferred, libusbp_error ** transfer_error)
{
assert(transfer != NULL);
assert(!transfer->pending);
size_t tmp_transferred = transfer->urb.actual_length;
// Make sure we don't overflow the user's buffer.
if (tmp_transferred > (size_t)transfer->urb.buffer_length)
{
tmp_transferred = transfer->urb.buffer_length;
}
if (buffer != NULL)
{
memcpy(buffer, transfer->urb.buffer, tmp_transferred);
}
if (transferred != NULL)
{
*transferred = tmp_transferred;
}
if (transfer_error != NULL)
{
*transfer_error = libusbp_error_copy(transfer->error);
}
return NULL;
}
libusbp_error * async_in_transfer_cancel(async_in_transfer * transfer)
{
if (transfer == NULL) { return NULL; }
return usbfd_discard_urb(transfer->fd, &transfer->urb);
}
bool async_in_transfer_pending(async_in_transfer * transfer)
{
assert(transfer != NULL);
return transfer->pending;
}

View File

@@ -0,0 +1,256 @@
#include <libusbp_internal.h>
struct libusbp_device
{
char * syspath;
char * serial_number; // may be NULL
uint16_t product_id;
uint16_t vendor_id;
uint16_t revision;
};
libusbp_error * device_create(struct udev_device * dev, libusbp_device ** device)
{
assert(dev != NULL);
assert(device != NULL);
libusbp_error * error = NULL;
// Allocate memory for the device.
libusbp_device * new_device = NULL;
if (error == NULL)
{
new_device = malloc(sizeof(libusbp_device));
if (new_device == NULL)
{
error = &error_no_memory;
}
}
// Get the syspath.
char * new_syspath = NULL;
if (error == NULL)
{
error = udevw_get_syspath_copy(dev, &new_syspath);
}
// Get the vendor ID.
uint16_t vendor_id;
if (error == NULL)
{
error = udevw_get_sysattr_uint16(dev, "idVendor", &vendor_id);
}
// Get the product ID.
uint16_t product_id;
if (error == NULL)
{
error = udevw_get_sysattr_uint16(dev, "idProduct", &product_id);
}
// Get the revision.
uint16_t revision;
if (error == NULL)
{
error = udevw_get_sysattr_uint16(dev, "bcdDevice", &revision);
}
// Get the serial number.
char * new_serial_number = NULL;
if (error == NULL)
{
error = udevw_get_sysattr_if_exists_copy(dev, "serial", &new_serial_number);
}
// Populate the new device and give it to the caller.
if (error == NULL)
{
new_device->syspath = new_syspath;
new_device->serial_number = new_serial_number;
new_device->vendor_id = vendor_id;
new_device->product_id = product_id;
new_device->revision = revision;
*device = new_device;
new_syspath = NULL;
new_serial_number = NULL;
new_device = NULL;
}
free(new_serial_number);
free(new_syspath);
free(new_device);
return error;
}
void libusbp_device_free(libusbp_device * device)
{
if (device != NULL)
{
free(device->serial_number);
free(device->syspath);
free(device);
}
}
libusbp_error * libusbp_device_copy(
const libusbp_device * source, libusbp_device ** dest)
{
if (dest == NULL)
{
return error_create("Device output pointer is null.");
}
*dest = NULL;
if (source == NULL) { return NULL; }
libusbp_error * error = NULL;
// Allocate memory for the device.
libusbp_device * new_device = NULL;
if (error == NULL)
{
new_device = malloc(sizeof(libusbp_device));
if (new_device == NULL) { error = &error_no_memory; }
}
// Copy the syspath.
char * new_syspath = NULL;
if (error == NULL)
{
assert(source->syspath != NULL);
error = string_copy(source->syspath, &new_syspath);
}
// Copy the serial number if it is not NULL.
char * new_serial_number = NULL;
if (error == NULL && source->serial_number != NULL)
{
error = string_copy(source->serial_number, &new_serial_number);
}
// Assemble the new device and give it to the caller.
if (error == NULL)
{
memcpy(new_device, source, sizeof(libusbp_device));
new_device->syspath = new_syspath;
new_device->serial_number = new_serial_number;
*dest = new_device;
new_device = NULL;
new_syspath = NULL;
new_serial_number = NULL;
}
// Clean up and return.
free(new_device);
free(new_syspath);
free(new_serial_number);
return error;
}
libusbp_error * libusbp_device_get_vendor_id(
const libusbp_device * device,
uint16_t * vendor_id)
{
if (vendor_id == NULL)
{
return error_create("Vendor ID output pointer is null.");
}
*vendor_id = 0;
if (device == NULL)
{
return error_create("Device is null.");
}
*vendor_id = device->vendor_id;
return NULL;
}
libusbp_error * libusbp_device_get_product_id(
const libusbp_device * device,
uint16_t * product_id)
{
if (product_id == NULL)
{
return error_create("Product ID output pointer is null.");
}
*product_id = 0;
if (device == NULL)
{
return error_create("Device is null.");
}
*product_id = device->product_id;
return NULL;
}
libusbp_error * libusbp_device_get_revision(
const libusbp_device * device,
uint16_t * revision)
{
if (revision == NULL)
{
return error_create("Device revision output pointer is null.");
}
*revision = 0;
if (device == NULL)
{
return error_create("Device is null.");
}
*revision = device->revision;
return NULL;
}
libusbp_error * libusbp_device_get_serial_number(
const libusbp_device * device,
char ** serial_number)
{
if (serial_number == NULL)
{
return error_create("Serial number output pointer is null.");
}
*serial_number = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
if (device->serial_number == NULL)
{
libusbp_error * error = error_create("Device does not have a serial number.");
error = error_add_code(error, LIBUSBP_ERROR_NO_SERIAL_NUMBER);
return error;
}
return string_copy(device->serial_number, serial_number);
}
libusbp_error * libusbp_device_get_os_id(
const libusbp_device * device,
char ** id)
{
if (id == NULL)
{
return error_create("Device OS ID output pointer is null.");
}
*id = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
return string_copy(device->syspath, id);
}

View File

@@ -0,0 +1,123 @@
/* This file has functions for converting Windows error codes libusbp_error
* objects. See error_message_test.cpp for documentation/justification of the
* conversions that are performed. */
#include <libusbp_internal.h>
libusbp_error * error_create_errno(const char * format, ...)
{
int error_code = errno;
libusbp_error * error = error_create("Error code %d.", error_code);
bool skip_standard_message = false;
switch(error_code)
{
case EACCES:
error = error_add_code(error, LIBUSBP_ERROR_ACCESS_DENIED);
break;
case ENOMEM:
error = error_add_code(error, LIBUSBP_ERROR_MEMORY);
break;
case EPIPE:
skip_standard_message = true;
error = error_add(error,
"The request was invalid or there was an I/O problem.");
error = error_add_code(error, LIBUSBP_ERROR_STALL);
error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
break;
case ENODEV:
case ESHUTDOWN:
skip_standard_message = true;
error = error_add(error, "The device was removed.");
error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
break;
case EPROTO:
case ETIME:
error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
break;
case ETIMEDOUT:
skip_standard_message = true;
error = error_add(error, "The operation timed out.");
error = error_add_code(error, LIBUSBP_ERROR_TIMEOUT);
break;
case EOVERFLOW:
skip_standard_message = true;
error = error_add(error, "The transfer overflowed.");
break;
case EILSEQ:
skip_standard_message = true;
error = error_add(error,
"Illegal byte sequence: the device may have been disconnected or the request may have been cancelled.");
error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
error = error_add_code(error, LIBUSBP_ERROR_CANCELLED);
break;
}
if (!skip_standard_message)
{
// We use strerror_r because strerror is not guaranteed to be
// thread-safe. Also note that strerror_r does depend on the
// locale.
char buffer[256];
int result = strerror_r(error_code, buffer, sizeof(buffer) - 1);
if (result == 0)
{
error = error_add(error, "%s.", buffer);
}
}
// Finally, add the context message provided by the caller.
va_list ap;
va_start(ap, format);
error = error_add_v(error, format, ap);
va_end(ap);
return error;
}
libusbp_error * error_from_urb_status(struct usbdevfs_urb * urb)
{
libusbp_error * error = NULL;
int error_code = -urb->status;
switch(error_code)
{
case 0: // Success
break;
case ENOENT:
error = error_create("Error code %d.", error_code);
error = error_add(error, "The operation was cancelled.");
error = error_add_code(error, LIBUSBP_ERROR_CANCELLED);
break;
default:
errno = error_code;
error = error_create_errno("");
break;
}
return error;
}
libusbp_error * error_create_udev(int error_code, const char * format, ...)
{
libusbp_error * error = error_create("Error from libudev: %d.", error_code);
va_list ap;
va_start(ap, format);
error = error_add_v(error, format, ap);
va_end(ap);
return error;
}

View File

@@ -0,0 +1,389 @@
#include <libusbp_internal.h>
struct libusbp_generic_handle
{
libusbp_device * device;
int fd;
// Timeouts are stored in milliseconds. 0 is forever.
uint32_t in_timeout[MAX_ENDPOINT_NUMBER + 1];
uint32_t out_timeout[MAX_ENDPOINT_NUMBER + 1];
};
// Allocates memory structures and opens the device file, but does read or write
// anything.
static libusbp_error * generic_handle_setup(
const libusbp_generic_interface * gi,
libusbp_generic_handle ** handle)
{
assert(gi != NULL);
assert(handle != NULL);
libusbp_error * error = NULL;
// Allocate memory for the handle.
libusbp_generic_handle * new_handle = NULL;
if (error == NULL)
{
new_handle = calloc(1, sizeof(libusbp_generic_handle));
if (handle == NULL) { error = &error_no_memory; }
}
// Copy the device.
libusbp_device * new_device = NULL;
if (error == NULL)
{
error = generic_interface_get_device_copy(gi, &new_device);
}
// Get the filename.
char * new_filename = NULL;
if (error == NULL)
{
error = libusbp_generic_interface_get_os_filename(gi, &new_filename);
}
// Open the file.
int new_fd = -1;
if (error == NULL)
{
error = usbfd_open(new_filename, &new_fd);
}
// Assemble the handle and pass it to the caller.
if (error == NULL)
{
new_handle->fd = new_fd;
new_fd = -1;
new_handle->device = new_device;
new_device = NULL;
*handle = new_handle;
new_handle = NULL;
}
if (new_fd != -1) { close(new_fd); }
libusbp_string_free(new_filename);
libusbp_device_free(new_device);
free(new_handle);
return error;
}
// Reads the device descriptor from the device and makes sure that certain
// fields in it match what we were expecting. This should help detect the
// situation where devices were changed between the time that the libusbp_device
// object was created and the time that this handle was created.
static libusbp_error * check_device_descriptor(
libusbp_generic_handle * handle)
{
assert(handle != NULL);
libusbp_error * error = NULL;
struct usb_device_descriptor desc;
if (error == NULL)
{
error = usbfd_get_device_descriptor(handle->fd, &desc);
}
uint16_t vendor_id;
if (error == NULL)
{
error = libusbp_device_get_vendor_id(handle->device, &vendor_id);
}
if (error == NULL && desc.idVendor != vendor_id)
{
error = error_create("Vendor ID mismatch: 0x%04x != 0x%04x.",
desc.idVendor, vendor_id);
}
uint16_t product_id;
if (error == NULL)
{
error = libusbp_device_get_product_id(handle->device, &product_id);
}
if (error == NULL && desc.idProduct != product_id)
{
error = error_create("Product ID mismatch: 0x%04x != 0x%04x.",
desc.idProduct, product_id);
}
uint16_t revision;
if (error == NULL)
{
error = libusbp_device_get_revision(handle->device, &revision);
}
if (error == NULL && desc.bcdDevice != revision)
{
error = error_create("Device revision mismatch: 0x%04x != 0x%04x.",
desc.bcdDevice, revision);
}
return error;
}
libusbp_error * libusbp_generic_handle_open(
const libusbp_generic_interface * gi,
libusbp_generic_handle ** handle)
{
if (handle == NULL)
{
return error_create("Generic handle output pointer is null.");
}
*handle = NULL;
if (gi == NULL)
{
return error_create("Generic interface is null.");
}
libusbp_error * error = NULL;
// Set up the memory structures and open the file handle.
libusbp_generic_handle * new_handle = NULL;
if (error == NULL)
{
error = generic_handle_setup(gi, &new_handle);
}
// Check that the device descriptor is consistent.
if (error == NULL)
{
error = check_device_descriptor(new_handle);
}
// Pass the handle to the caller.
if (error == NULL)
{
*handle = new_handle;
new_handle = NULL;
}
libusbp_generic_handle_close(new_handle);
return error;
}
void libusbp_generic_handle_close(libusbp_generic_handle * handle)
{
if (handle != NULL)
{
close(handle->fd);
libusbp_device_free(handle->device);
free(handle);
}
}
libusbp_error * libusbp_generic_handle_open_async_in_pipe(
libusbp_generic_handle * handle,
uint8_t pipe_id,
libusbp_async_in_pipe ** pipe)
{
return async_in_pipe_create(handle, pipe_id, pipe);
}
libusbp_error * libusbp_generic_handle_set_timeout(
libusbp_generic_handle * handle,
uint8_t pipe_id,
uint32_t timeout)
{
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
libusbp_error * error = NULL;
if (error == NULL)
{
error = check_pipe_id(pipe_id);
}
if (error == NULL)
{
uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER;
if (pipe_id & 0x80)
{
handle->in_timeout[endpoint_number] = timeout;
}
else
{
handle->out_timeout[endpoint_number] = timeout;
}
}
return error;
}
libusbp_error * libusbp_control_transfer(
libusbp_generic_handle * handle,
uint8_t bmRequestType,
uint8_t bRequest,
uint16_t wValue,
uint16_t wIndex,
void * data,
uint16_t wLength,
size_t * transferred)
{
if (transferred != NULL)
{
*transferred = 0;
}
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
struct libusbp_setup_packet setup;
setup.bmRequestType = bmRequestType;
setup.bRequest = bRequest;
setup.wValue = wValue;
setup.wIndex = wIndex;
setup.wLength = wLength;
return usbfd_control_transfer(handle->fd, setup,
handle->out_timeout[0], data, transferred);
}
libusbp_error * libusbp_read_pipe(
libusbp_generic_handle * handle,
uint8_t pipe_id,
void * data,
size_t size,
size_t * transferred)
{
if (transferred != NULL)
{
*transferred = 0;
}
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
libusbp_error * error = NULL;
if (error == NULL)
{
error = check_pipe_id_in(pipe_id);
}
if (error == NULL)
{
uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER;
uint32_t timeout = handle->in_timeout[endpoint_number];
error = usbfd_bulk_or_interrupt_transfer(
handle->fd, pipe_id, timeout, data, size, transferred);
}
if (error != NULL)
{
error = error_add(error, "Failed to read from pipe.");
}
return error;
}
libusbp_error * libusbp_write_pipe(
libusbp_generic_handle * handle,
uint8_t pipe_id,
const void * data,
size_t size,
size_t * transferred)
{
if (transferred != NULL)
{
*transferred = 0;
}
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
libusbp_error * error = NULL;
if (error == NULL)
{
error = check_pipe_id_out(pipe_id);
}
if (error == NULL)
{
uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER;
uint32_t timeout = handle->out_timeout[endpoint_number];
error = usbfd_bulk_or_interrupt_transfer(
handle->fd, pipe_id, timeout, (void *)data, size, transferred);
}
if (error != NULL)
{
error = error_add(error, "Failed to write to pipe.");
}
return error;
}
LIBUSBP_WARN_UNUSED
static libusbp_error * handle_completed_urb(struct usbdevfs_urb * urb)
{
if (urb->usercontext == NULL)
{
return error_create("A completed USB request block has a NULL usercontext.");
}
if (urb->type == USBDEVFS_URB_TYPE_BULK && (urb->endpoint & 0x80))
{
async_in_transfer * transfer = urb->usercontext;
async_in_transfer_handle_completion(transfer);
return NULL;
}
else
{
return error_create("A completed USB request block was unrecognized.");
}
}
libusbp_error * generic_handle_events(libusbp_generic_handle * handle)
{
if (handle == NULL)
{
return error_create("Generic handle argument is null.");
}
while(true)
{
struct usbdevfs_urb * urb;
libusbp_error * error = usbfd_reap_urb(handle->fd, &urb);
if (error != NULL)
{
// There was some problem, like the device being disconnected.
return error;
}
if (urb == NULL)
{
// No more URBs left to reap.
return NULL;
}
error = handle_completed_urb(urb);
if (error != NULL)
{
return error;
}
}
return NULL;
}
int libusbp_generic_handle_get_fd(libusbp_generic_handle * handle)
{
if (handle == NULL)
{
return -1;
}
return handle->fd;
}

View File

@@ -0,0 +1,276 @@
#include <libusbp_internal.h>
struct libusbp_generic_interface
{
libusbp_device * device;
uint8_t interface_number;
// A sysfs path like "/sys/devices/pci0000:00/0000:00:06.0/usb1/1-2/1-2:1.0"
char * syspath;
// A filename like "/dev/bus/usb/001/007"
char * filename;
};
libusbp_error * check_driver_installation(struct udev_device * device)
{
assert(device != NULL);
const char * driver_name = udev_device_get_driver(device);
if (driver_name != NULL && strcmp(driver_name, "usbfs") && strcmp(driver_name, "cp210x"))
{
return error_create("Device is attached to an incorrect driver: %s.", driver_name);
}
return NULL;
}
libusbp_error * libusbp_generic_interface_create(
const libusbp_device * device,
uint8_t interface_number,
bool composite __attribute__((unused)),
libusbp_generic_interface ** gi)
{
if (gi == NULL)
{
return error_create("Generic interface output pointer is null.");
}
*gi = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
libusbp_error * error = NULL;
// Allocate memory for the interface.
libusbp_generic_interface * new_gi = NULL;
if (error == NULL)
{
new_gi = malloc(sizeof(libusbp_generic_interface));
if (new_gi == NULL) { error = &error_no_memory; }
}
// Make a copy of the device (since the original device could be freed
// before this generic interface is freed.)
libusbp_device * new_device = NULL;
if (error == NULL)
{
error = libusbp_device_copy(device, &new_device);
}
// Get the syspath of the device.
char * new_device_syspath = NULL;
if (error == NULL)
{
error = libusbp_device_get_os_id(new_device, &new_device_syspath);
}
// Get a udev context.
struct udev * new_udev = NULL;
if (error == NULL)
{
error = udevw_create_context(&new_udev);
}
// Get the device for the interface.
struct udev_device * new_dev = NULL;
if (error == NULL)
{
error = udevw_get_interface(new_udev, new_device_syspath,
interface_number, &new_dev);
}
// Make sure it is not attached to a kernel driver.
// Note: This step might be inappropriate, since libusbp can operate
// on some devices that are attached to a kernel driver, like the cp210x
// driver.
if (error == NULL)
{
error = check_driver_installation(new_dev);
}
// Get the syspath of the interface.
char * new_interface_syspath = NULL;
if (error == NULL)
{
error = udevw_get_syspath_copy(new_dev, &new_interface_syspath);
}
// Get the filename.
char * new_filename = NULL;
if (error == NULL)
{
error = udevw_get_devnode_copy_from_syspath(new_device_syspath, &new_filename);
}
// Check that the file exists yet, but don't check to see if we have permission
// to open it. This behavior was chosen to be as similar to the Windows
// behavior as possible.
if (error == NULL)
{
error = usbfd_check_existence(new_filename);
}
// Assemble the new generic interface and pass it to the caller.
if (error == NULL)
{
new_gi->interface_number = interface_number;
new_gi->device = new_device;
new_device = NULL;
new_gi->syspath = new_interface_syspath;
new_interface_syspath = NULL;
new_gi->filename = new_filename;
new_filename = NULL;
*gi = new_gi;
new_gi = NULL;
}
if (error != NULL)
{
error = error_add(error, "Failed to initialize generic interface.");
}
libusbp_string_free(new_filename);
libusbp_string_free(new_interface_syspath);
libusbp_string_free(new_device_syspath);
libusbp_device_free(new_device);
free(new_gi);
if (new_dev != NULL) { udev_device_unref(new_dev); }
if (new_udev != NULL) { udev_unref(new_udev); }
return error;
}
void libusbp_generic_interface_free(libusbp_generic_interface * gi)
{
if (gi != NULL)
{
libusbp_device_free(gi->device);
libusbp_string_free(gi->syspath);
libusbp_string_free(gi->filename);
free(gi);
}
}
libusbp_error * libusbp_generic_interface_copy(
const libusbp_generic_interface * source,
libusbp_generic_interface ** dest)
{
if (dest == NULL)
{
return error_create("Generic interface output pointer is null.");
}
*dest = NULL;
if (source == NULL)
{
return NULL;
}
libusbp_error * error = NULL;
// Allocate memory for the generic interface.
libusbp_generic_interface * new_gi = NULL;
if (error == NULL)
{
new_gi = malloc(sizeof(libusbp_generic_interface));
if (new_gi == NULL)
{
error = &error_no_memory;
}
}
// Copy the device.
libusbp_device * new_device = NULL;
if (error == NULL)
{
error = libusbp_device_copy(source->device, &new_device);
}
// Copy the syspath.
char * new_syspath = NULL;
if (error == NULL)
{
error = string_copy(source->syspath, &new_syspath);
}
// Copy the filename.
char * new_filename = NULL;
if (error == NULL)
{
error = string_copy(source->filename, &new_filename);
}
// Assemble the new interface and return it to the caller.
if (error == NULL)
{
memcpy(new_gi, source, sizeof(libusbp_generic_interface));
new_gi->device = new_device;
new_gi->syspath = new_syspath;
new_gi->filename = new_filename;
*dest = new_gi;
new_filename = NULL;
new_syspath = NULL;
new_device = NULL;
new_gi = NULL;
}
libusbp_string_free(new_filename);
libusbp_string_free(new_syspath);
libusbp_device_free(new_device);
free(new_gi);
return NULL;
}
libusbp_error * libusbp_generic_interface_get_os_id(
const libusbp_generic_interface * gi, char ** id)
{
if (id == NULL)
{
return error_create("String output pointer is null.");
}
*id = NULL;
if (gi == NULL)
{
return error_create("Generic interface is null.");
}
assert(gi->syspath != NULL);
return string_copy(gi->syspath, id);
}
libusbp_error * libusbp_generic_interface_get_os_filename(
const libusbp_generic_interface * gi, char ** filename)
{
if (filename == NULL)
{
return error_create("String output pointer is null.");
}
*filename = NULL;
if (gi == NULL)
{
return error_create("Generic interface is null.");
}
assert(gi->filename != NULL);
return string_copy(gi->filename, filename);
}
libusbp_error * generic_interface_get_device_copy(
const libusbp_generic_interface * gi, libusbp_device ** device)
{
assert(gi != NULL);
assert(device != NULL);
return libusbp_device_copy(gi->device, device);
}

View File

@@ -0,0 +1,165 @@
#include <libusbp_internal.h>
// Adds a device to the list, while maintaining the loop invariants for the loop
// in libusbp_list_connected_devices.
static libusbp_error * add_udev_device_to_list(
struct udev_device * dev, libusbp_device *** device_list, size_t * device_count)
{
assert(dev != NULL);
assert(device_list != NULL);
assert(device_count != NULL);
libusbp_error * error = NULL;
// Create a new device.
libusbp_device * new_device;
error = device_create(dev, &new_device);
if (error != NULL) { return error; }
// Add the device to the list.
error = device_list_append(device_list, device_count, new_device);
if (error != NULL)
{
libusbp_device_free(new_device);
return error;
}
return NULL;
}
static libusbp_error * add_udev_device_to_list_if_needed(
struct udev * udev, const char * syspath,
libusbp_device *** device_list, size_t * device_count
)
{
assert(udev != NULL);
assert(syspath != NULL);
assert(device_list != NULL);
assert(device_count != NULL);
libusbp_error * error = NULL;
// Get the udev device.
struct udev_device * dev = NULL;
if (error == NULL)
{
error = udevw_get_device_from_syspath(udev, syspath, &dev);
}
// Get the devtype, which is typically usb_device or usb_interface.
const char * devtype = NULL;
if (error == NULL)
{
error = udevw_get_device_type(dev, &devtype);
}
bool skip = false;
// Skip the interfaces, just add the actual USB devices.
if (error == NULL && !skip && strcmp(devtype, "usb_device") != 0)
{
skip = true;
}
// Skip devices that have not been initialized yet, because the udev rules
// might not be fully applied. They might have the wrong permissions.
if (error == NULL && !skip && !udev_device_get_is_initialized(dev))
{
skip = true;
}
if (error == NULL && !skip)
{
// This is a USB device, so we do want to add it to the list.
error = add_udev_device_to_list(dev, device_list, device_count);
}
if (dev != NULL)
{
udev_device_unref(dev);
}
return error;
}
libusbp_error * libusbp_list_connected_devices(
libusbp_device *** device_list, size_t * device_count)
{
if (device_count != NULL)
{
*device_count = 0;
}
if (device_list == NULL)
{
return error_create("Device list output pointer is null.");
}
libusbp_error * error = NULL;
// Create a udev context.
struct udev * udev = NULL;
if (error == NULL)
{
error = udevw_create_context(&udev);
}
// Create a list of USB devices and interfaces.
struct udev_enumerate * enumerate = NULL;
if (error == NULL)
{
error = udevw_create_usb_list(udev, &enumerate);
}
// Allocate a new list.
libusbp_device ** new_list = NULL;
size_t count = 0;
if (error == NULL)
{
error = device_list_create(&new_list);
}
// Iterate over the devices and add them to the list.
if (error == NULL)
{
struct udev_list_entry * first_entry = udev_enumerate_get_list_entry(enumerate);
struct udev_list_entry * list_entry;
udev_list_entry_foreach(list_entry, first_entry)
{
const char * path = udev_list_entry_get_name(list_entry);
assert(path != NULL);
error = add_udev_device_to_list_if_needed(udev, path, &new_list, &count);
if (error != NULL)
{
// Something went wrong when getting information about the
// device. When unplugging a device, we often see
// udev_device_new_from_syspath return NULL, which could cause
// this error. To make the library more robust and usable, we
// ignore this error and continue.
#ifdef LIBUSBP_LOG
fprintf(stderr, "Problem adding device to list: %s\n",
libusbp_error_get_message(error));
#endif
libusbp_error_free(error);
error = NULL;
}
}
}
// Pass the list and the count to the caller.
if (error == NULL)
{
*device_list = new_list;
new_list = NULL;
if (device_count != NULL)
{
*device_count = count;
}
}
// Clean up everything we used.
if (enumerate != NULL) { udev_enumerate_unref(enumerate); }
if (udev != NULL) { udev_unref(udev); }
free_devices_and_list(new_list);
return error;
}

View File

@@ -0,0 +1,191 @@
#include <libusbp_internal.h>
struct libusbp_serial_port
{
// A sysfs path like "/sys/devices/pci0000:00/0000:00:06.0/usb1/1-2/1-2:1.0/tty/ttyACM0".
// It corresponds to a udev device with subsystem "tty".
char * syspath;
// A port filename like "/dev/ttyACM0".
char * port_name;
};
libusbp_error * libusbp_serial_port_create(
const libusbp_device * device,
uint8_t interface_number,
bool composite,
libusbp_serial_port ** port)
{
LIBUSBP_UNUSED(composite);
if (port == NULL)
{
return error_create("Serial port output pointer is null.");
}
*port = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
libusbp_error * error = NULL;
libusbp_serial_port * new_port = NULL;
if (error == NULL)
{
new_port = calloc(1, sizeof(libusbp_serial_port));
if (new_port == NULL)
{
error = &error_no_memory;
}
}
// Get the syspath of the physical device.
char * new_device_syspath = NULL;
if (error == NULL)
{
error = libusbp_device_get_os_id(device, &new_device_syspath);
}
// Get a udev context.
struct udev * new_udev = NULL;
if (error == NULL)
{
error = udevw_create_context(&new_udev);
}
// Get the USB interface device.
struct udev_device * new_interface_dev = NULL;
if (error == NULL)
{
error = udevw_get_interface(new_udev, new_device_syspath,
interface_number, &new_interface_dev);
}
// Get the tty device.
struct udev_device * new_tty_dev = NULL;
if (error == NULL)
{
error = udevw_get_tty(new_udev, new_interface_dev, &new_tty_dev);
}
// Get the syspath of the tty device.
if (error == NULL)
{
error = udevw_get_syspath_copy(new_tty_dev, &new_port->syspath);
}
// Get the port name (e.g. /dev/ttyACM0)
const char * port_name = NULL;
if (error == NULL)
{
port_name = udev_device_get_property_value(new_tty_dev, "DEVNAME");
if (port_name == NULL)
{
error = error_create("The DEVNAME property does not exist.");
}
}
// Copy the port name to the new serial port object.
if (error == NULL)
{
error = string_copy(port_name, &new_port->port_name);
}
// Pass the new object to the caller.
if (error == NULL)
{
*port = new_port;
new_port = NULL;
}
if (new_tty_dev != NULL) { udev_device_unref(new_tty_dev); }
if (new_interface_dev != NULL) { udev_device_unref(new_interface_dev); }
if (new_udev != NULL) { udev_unref(new_udev); }
libusbp_string_free(new_device_syspath);
libusbp_serial_port_free(new_port);
return error;
}
void libusbp_serial_port_free(libusbp_serial_port * port)
{
if (port != NULL)
{
libusbp_string_free(port->syspath);
libusbp_string_free(port->port_name);
free(port);
}
}
libusbp_error * libusbp_serial_port_copy(const libusbp_serial_port * source,
libusbp_serial_port ** dest)
{
if (dest == NULL)
{
return error_create("Serial port output pointer is null.");
}
*dest = NULL;
if (source == NULL)
{
return NULL;
}
libusbp_error * error = NULL;
// Allocate memory for the new object.
libusbp_serial_port * new_port = NULL;
if (error == NULL)
{
new_port = calloc(1, sizeof(libusbp_serial_port));
if (new_port == NULL)
{
error = &error_no_memory;
}
}
// Copy the syspath.
if (error == NULL)
{
error = string_copy(source->syspath, &new_port->syspath);
}
// Copy the port name.
if (error == NULL)
{
error = string_copy(source->port_name, &new_port->port_name);
}
// Pass the new object to the caller.
if (error == NULL)
{
*dest = new_port;
new_port = NULL;
}
libusbp_serial_port_free(new_port);
return error;
}
libusbp_error * libusbp_serial_port_get_name(
const libusbp_serial_port * port,
char ** name)
{
if (name == NULL)
{
return error_create("String output pointer is null.");
}
*name = NULL;
if (port == NULL)
{
return error_create("Serial port is null.");
}
return string_copy(port->port_name, name);
}

View File

@@ -0,0 +1,540 @@
/* Wrapper functions for libudev that group together certain operations and
* provide error handling. */
#include <libusbp_internal.h>
// Creates a new udev context (struct udev *). If there is no error, the caller
// must call udev_unref at some point.
libusbp_error * udevw_create_context(struct udev ** context)
{
assert(context != NULL);
*context = udev_new();
if (*context == NULL)
{
return error_create("Failed to create a udev context.");
}
return NULL;
}
// Creates a list (struct udev_enumerate *) of all devices
// that are children of a given parent device.
static libusbp_error * udevw_create_child_list(
struct udev * udev,
struct udev_device * parent,
struct udev_enumerate ** list)
{
assert(udev != NULL);
assert(parent != NULL);
assert(list != NULL);
*list = NULL;
libusbp_error * error = NULL;
struct udev_enumerate * new_list = NULL;
if (error == NULL)
{
new_list = udev_enumerate_new(udev);
if (new_list == NULL)
{
error = error_create("Failed to create a udev enumeration context.");
}
}
if (error == NULL)
{
int result = udev_enumerate_add_match_parent(new_list, parent);
if (result != 0)
{
error = error_create_udev(result, "Failed to match by parent device.");
}
}
if (error == NULL)
{
int result = udev_enumerate_scan_devices(new_list);
if (result != 0)
{
error = error_create_udev(result, "Failed to scan devices.");
}
}
if (error == NULL)
{
*list = new_list;
new_list = NULL;
}
if (new_list != NULL) { udev_enumerate_unref(new_list); }
return error;
}
// Creates a list (struct udev_enumerate *) of all devices in the "usb"
// subsystem. This includes overall USB devices (devtype == "usb_device") and
// also interfaces (devtype == "usb_interface"). If there is no error, the
// caller must use udev_enumerate_unref at some point.
libusbp_error * udevw_create_usb_list(struct udev * udev, struct udev_enumerate ** list)
{
assert(udev != NULL);
assert(list != NULL);
*list = NULL;
libusbp_error * error = NULL;
struct udev_enumerate * new_list = NULL;
if (error == NULL)
{
new_list = udev_enumerate_new(udev);
if (new_list == NULL)
{
error = error_create("Failed to create a udev enumeration context.");
}
}
if (error == NULL)
{
int result = udev_enumerate_add_match_subsystem(new_list, "usb");
if (result != 0)
{
error = error_create_udev(result, "Failed to add a subsystem match.");
}
}
if (error == NULL)
{
int result = udev_enumerate_scan_devices(new_list);
if (result != 0)
{
error = error_create_udev(result, "Failed to scan devices.");
}
}
if (error == NULL)
{
*list = new_list;
new_list = NULL;
}
if (new_list != NULL) { udev_enumerate_unref(new_list); }
return error;
}
// Gets a udev device corresponding to the given syspath. The syspath is the
// unique identifier that we store in order to refer to devices.
libusbp_error * udevw_get_device_from_syspath(
struct udev * context, const char * syspath, struct udev_device ** dev)
{
assert(context != NULL);
assert(dev != NULL);
assert(syspath != NULL);
*dev = udev_device_new_from_syspath(context, syspath);
if (*dev == NULL)
{
// This error can happen when the device is being unplugged.
return error_create("Failed to get udev device from syspath: %s.", syspath);
}
return NULL;
}
// Get the device type of a device, typically "usb_device" or "usb_interface".
// The device type is returned as a string that you cannot modify and you do not
// have to free; it is owned by the device.
libusbp_error * udevw_get_device_type(struct udev_device * dev, const char ** devtype)
{
assert(dev != NULL);
assert(devtype != NULL);
*devtype = udev_device_get_devtype(dev);
if (*devtype == NULL)
{
return error_create("Failed to get device type.");
}
return NULL;
}
// Gets a sysattr with the specified name and parses it as a hex uint8_t.
libusbp_error * udevw_get_sysattr_uint8(
struct udev_device * dev, const char * name, uint8_t * value)
{
assert(dev != NULL);
assert(name != NULL);
assert(value != NULL);
const char * str = udev_device_get_sysattr_value(dev, name);
if (str == NULL)
{
return error_create("Device does not have sysattr %s.", name);
}
int result = sscanf(str, "%4hhx\n", value);
if (result != 1)
{
return error_create("Failed to parse sysattr %s.", name);
}
return NULL;
}
// Gets a sysattr with the specified name and parses it as a hex uint16_t.
libusbp_error * udevw_get_sysattr_uint16(
struct udev_device * dev, const char * name, uint16_t * value)
{
assert(dev != NULL);
assert(name != NULL);
assert(value != NULL);
const char * str = udev_device_get_sysattr_value(dev, name);
if (str == NULL)
{
return error_create("Device does not have sysattr %s.", name);
}
int result = sscanf(str, "%4hx\n", value);
if (result != 1)
{
return error_create("Failed to parse sysattr %s.", name);
}
return NULL;
}
// Gets a sysattr string and makes a copy of it. The string must be freed with
// libusbp_string_free(). If the sysattr does not exists, returns a NULL string
// instead of raising an error.
libusbp_error * udevw_get_sysattr_if_exists_copy(
struct udev_device * dev, const char * name, char ** value)
{
assert(dev != NULL);
assert(name != NULL);
assert(value != NULL);
*value = NULL;
const char * str = udev_device_get_sysattr_value(dev, name);
if (str == NULL) { return NULL; }
return string_copy(str, value);
}
// Get the USB device of which this device is a child. This is intended to be
// run on devices with devtype == "usb_interface" in order to get information
// about the overall USB device.
static libusbp_error * udevw_get_parent_usb_device(
struct udev_device * dev, struct udev_device ** parent)
{
assert(dev != NULL);
assert(parent != NULL);
*parent = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
if (*parent == NULL)
{
return error_create("Failed to get parent USB device.");
}
return NULL;
}
// Helper function for udevw_get_interface.
static libusbp_error * udevw_check_if_device_is_specific_interface(
struct udev_device * dev, const char * device_syspath,
uint8_t interface_number, bool * result)
{
assert(dev != NULL);
assert(device_syspath != NULL);
assert(result != NULL);
*result = false;
libusbp_error * error;
// Check that the devtype is equal to "usb_interface"
const char * devtype;
error = udevw_get_device_type(dev, &devtype);
if (error != NULL) { return error; }
if (strcmp(devtype, "usb_interface")) { return NULL; }
// Check that bInterfaceNumber matches our interface number.
uint8_t actual_interface_number;
error = udevw_get_sysattr_uint8(dev, "bInterfaceNumber", &actual_interface_number);
if (error != NULL) { return error; }
if (actual_interface_number != interface_number) { return NULL; }
// Get the overall USB device.
struct udev_device * parent;
error = udevw_get_parent_usb_device(dev, &parent);
if (error != NULL) { return error; }
// Check that the overall USB device is the one we are interested in.
const char * parent_syspath = udev_device_get_syspath(parent);
assert(parent_syspath != NULL);
if (strcmp(parent_syspath, device_syspath)) { return NULL; }
*result = true;
return NULL;
}
// Finds a udev device of type "usb_interface" that is a child of the specified
// device and has the specified bInterfaceNumber.
libusbp_error * udevw_get_interface(
struct udev * udev,
const char * device_syspath,
uint8_t interface_number,
struct udev_device ** device)
{
assert(udev != NULL);
assert(device_syspath != NULL);
assert(device != NULL);
*device = NULL;
libusbp_error * error = NULL;
struct udev_enumerate * list = NULL;
if (error == NULL)
{
// Note: It would probably be better to create a list using
// udev_enumerate_add_match_parent so the list is smaller.
error = udevw_create_usb_list(udev, &list);
}
// Loop over the list to find the device.
struct udev_device * found_device = NULL;
if (error == NULL)
{
struct udev_list_entry * first_entry = udev_enumerate_get_list_entry(list);
struct udev_list_entry * list_entry;
udev_list_entry_foreach(list_entry, first_entry)
{
const char * path = udev_list_entry_get_name(list_entry);
assert(path != NULL);
// Get the udev device.
struct udev_device * dev = NULL;
error = udevw_get_device_from_syspath(udev, path, &dev);
if (error != NULL) { break; }
// See if it is the right device.
bool correct_device = false;
error = udevw_check_if_device_is_specific_interface(dev, device_syspath,
interface_number, &correct_device);
if (error != NULL)
{
udev_device_unref(dev);
break;
}
// If it is the right device, stop looping.
if (correct_device)
{
found_device = dev;
break;
}
udev_device_unref(dev);
}
}
// Make sure we found the device.
if (error == NULL && found_device == NULL)
{
// We did not find it. Maybe the interface is just not ready yet and it
// would be ready in a few milliseconds. (Note: We have not seen this
// happen, but adding the error code here makes the behavior consistent
// with how the Windows code behaves.)
error = error_create("Could not find interface %d.", interface_number);
error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
}
// Pass the device back to the caller.
if (error == NULL)
{
*device = found_device;
}
if (list != NULL) { udev_enumerate_unref(list); }
return error;
}
// Finds a udev device of type "usb_interface" that is a child of the specified
// device and has the specified bInterfaceNumber.
libusbp_error * udevw_get_tty(
struct udev * udev,
struct udev_device * parent,
struct udev_device ** device)
{
assert(udev != NULL);
assert(parent != NULL);
assert(device != NULL);
*device = NULL;
libusbp_error * error = NULL;
struct udev_enumerate * list = NULL;
if (error == NULL)
{
error = udevw_create_child_list(udev, parent, &list);
}
// Loop over the list to find the device.
struct udev_device * found_device = NULL;
if (error == NULL)
{
struct udev_list_entry * first_entry = udev_enumerate_get_list_entry(list);
struct udev_list_entry * list_entry;
udev_list_entry_foreach(list_entry, first_entry)
{
const char * path = udev_list_entry_get_name(list_entry);
assert(path != NULL);
// Get the udev device.
struct udev_device * dev = NULL;
error = udevw_get_device_from_syspath(udev, path, &dev);
if (error != NULL) { break; }
// See if it is in the "tty" subsystem.
bool correct_device = false;
const char * subsystem = udev_device_get_subsystem(dev);
if (subsystem != NULL && 0 == strcmp(subsystem, "tty"))
{
correct_device = true;
}
// If it is the right device, stop looping.
if (correct_device)
{
found_device = dev;
break;
}
udev_device_unref(dev);
}
}
// Make sure we found the device.
if (error == NULL && found_device == NULL)
{
// We did not find it. Maybe the interface is just not ready yet and it
// would be ready in a few milliseconds. (Note: We have not seen this
// happen, but adding the error code here makes the behavior consistent
// with how the Windows code behaves.)
error = error_create("Could not find tty device.");
error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
}
// Pass the device back to the caller.
if (error == NULL)
{
*device = found_device;
}
if (list != NULL) { udev_enumerate_unref(list); }
return error;
}
// Gets the devnode path of the specified udev device. The returned string is
// owned by the device.
libusbp_error * udevw_get_syspath(struct udev_device * device, const char ** syspath)
{
assert(device != NULL);
assert(syspath != NULL);
*syspath = udev_device_get_syspath(device);
assert(*syspath != NULL);
return NULL;
}
// Gets the syspath of the specified udev device. The returned string must
// be freed with libusbp_string_free.
libusbp_error * udevw_get_syspath_copy(struct udev_device * device, char ** syspath)
{
assert(device != NULL);
assert(syspath != NULL);
*syspath = NULL;
libusbp_error * error = NULL;
const char * tmp = NULL;
if (error == NULL)
{
error = udevw_get_syspath(device, &tmp);
}
if (error == NULL)
{
error = string_copy(tmp, syspath);
}
return error;
}
// Gets the devnode path of the specified udev device. The returned string is
// owned by the device.
libusbp_error * udevw_get_devnode(struct udev_device * device, const char ** devnode)
{
assert(device != NULL);
assert(devnode != NULL);
*devnode = udev_device_get_devnode(device);
if (*devnode == NULL)
{
return error_create("No device node exists.");
}
return NULL;
}
// Gets the devnode of the specified udev device. The returned string must
// be freed with libusbp_string_free.
libusbp_error * udevw_get_devnode_copy(struct udev_device * device, char ** devnode)
{
assert(device != NULL);
assert(devnode != NULL);
*devnode = NULL;
libusbp_error * error = NULL;
const char * tmp = NULL;
if (error == NULL)
{
error = udevw_get_devnode(device, &tmp);
}
if (error == NULL)
{
error = string_copy(tmp, devnode);
}
return error;
}
// Takes a syspath as input and gets the corresponding devpath. The returned
// string must be freed with libusbp_string_free if there were no errors.
libusbp_error * udevw_get_devnode_copy_from_syspath(const char * syspath, char ** devnode)
{
assert(syspath != NULL);
assert(devnode != NULL);
*devnode = NULL;
libusbp_error * error = NULL;
struct udev * context = NULL;
if (error == NULL)
{
error = udevw_create_context(&context);
}
struct udev_device * device = NULL;
if (error == NULL)
{
error = udevw_get_device_from_syspath(context, syspath, &device);
}
if (error == NULL)
{
error = udevw_get_devnode_copy(device, devnode);
}
if (device != NULL) { udev_device_unref(device); }
if (context != NULL) { udev_unref(context); }
return error;
}

View File

@@ -0,0 +1,222 @@
/* Functions for working with a USB device node file
* (e.g. "/dev/bus/usb/001/002"). */
#include <libusbp_internal.h>
libusbp_error * usbfd_check_existence(const char * filename)
{
libusbp_error * error = NULL;
int result = access(filename, F_OK);
if (result != 0)
{
if (errno == ENOENT)
{
// The file does not exist. This might just be a temporary
// condition, so use the LIBUSBP_ERROR_NOT_READY code.
error = error_create("File does not exist: %s.", filename);
error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
}
else
{
// Something actually went wrong when checking if the file
// exists.
error = error_create_errno("Failed to check file: %s.", filename);
}
}
return error;
}
libusbp_error * usbfd_open(const char * filename, int * fd)
{
assert(filename != NULL);
assert(fd != NULL);
*fd = open(filename, O_RDWR | O_CLOEXEC);
if (*fd == -1)
{
libusbp_error * error = error_create_errno("Failed to open USB device %s.", filename);
return error;
}
return NULL;
}
// Reads the device descriptor. This is not thread-safe, because it is possible
// that another thread might change the position of the file descriptor after
// lseek() and before read().
//
// The kernel code that provides the device descriptor can be found in
// usbdev_read() in devio.c.
libusbp_error * usbfd_get_device_descriptor(int fd, struct usb_device_descriptor * desc)
{
assert(desc != NULL);
// Seek to the beginning of the file, where the device descriptor lives.
off_t offset = lseek(fd, 0, SEEK_SET);
if (offset == -1)
{
return error_create_errno("Failed to go to beginning of USB device file.");
}
ssize_t expected_size = sizeof(struct usb_device_descriptor);
ssize_t size = read(fd, desc, expected_size);
if (size == -1)
{
return error_create_errno("Failed to read device descriptor.");
}
if (size != expected_size)
{
return error_create("Failed to read device descriptor. "
"Expected %zd-byte device descriptor, read %zd bytes.",
expected_size, size);
}
return NULL;
}
libusbp_error * usbfd_control_transfer(int fd, libusbp_setup_packet setup,
uint32_t timeout, void * data, size_t * transferred)
{
struct usbdevfs_ctrltransfer transfer = {0};
transfer.bRequestType = setup.bmRequestType;
transfer.bRequest = setup.bRequest;
transfer.wValue = setup.wValue;
transfer.wIndex = setup.wIndex;
transfer.wLength = setup.wLength;
transfer.timeout = timeout;
transfer.data = data;
if (transferred != NULL)
{
*transferred = 0;
}
int result = ioctl(fd, USBDEVFS_CONTROL, &transfer);
if (result < 0)
{
return error_create_errno("Control transfer failed.");
}
if (transferred != NULL)
{
*transferred = result;
}
return NULL;
}
/* Performs a bulk or interrupt transfer on the specified endpoint.
*
* Despite the name, USBDEVFS_BULK does actually work for interrupt endpoints.
* The function usbdev_do_ioctl in devio.c calls proc_bulk in devio.c, which
* calls usb_bulk_msg in message.c, which explicitly detects if it was called on
* an interrupt endpoint and handle that situation properly. */
libusbp_error * usbfd_bulk_or_interrupt_transfer(int fd, uint8_t pipe,
uint32_t timeout, void * buffer, size_t size, size_t * transferred)
{
if (transferred != NULL)
{
*transferred = 0;
}
// A buffer size of 0 for IN transfers (or at least interrupt IN
// transfers in a VirtualBox Linux guest running on a Windows
// host) seems to put the Linux USB drivers in some weird state
// where every subsequent request times out.
if (size == 0 && (pipe & 0x80))
{
return error_create("Transfer size 0 is not allowed.");
}
// A size greater than UINT_MAX will not fit into the struct below.
if (size > UINT_MAX)
{
return error_create("Transfer size is too large.");
}
if (buffer == NULL && size)
{
return error_create("Buffer is null.");
}
struct usbdevfs_bulktransfer transfer = {0};
transfer.ep = pipe;
transfer.len = size;
transfer.timeout = timeout;
transfer.data = buffer;
int result = ioctl(fd, USBDEVFS_BULK, &transfer);
if (result < 0)
{
return error_create_errno("");
}
if (transferred != NULL)
{
*transferred = result;
}
return NULL;
}
libusbp_error * usbfd_submit_urb(int fd, struct usbdevfs_urb * urb)
{
assert(urb != NULL);
int result = ioctl(fd, USBDEVFS_SUBMITURB, urb);
if (result < 0)
{
return error_create_errno("Submitting USB request block failed.");
}
return NULL;
}
/*! Checks to see if there is a finished asynchronous request. If there is,
* this function "reaps" the request and retrieves a pointer to its URB. The
* kernel writes to the URB and its associated buffer when you call this
* function, allowing you to get the results of the operation.
*
* If nothing is available to be reaped at the moment, the retrieved URB pointer
* will be NULL.
*
* Note: For Linux kernels older than 4.0, this function will return an error
* if the USB device happens to be disconnected. In 4.0 and later, you will be
* able to reap URBs from disconnected devices thanks to commit 3f2cee73b from
* Alan Stern on 2015-01-29. */
libusbp_error * usbfd_reap_urb(int fd, struct usbdevfs_urb ** urb)
{
assert(urb != NULL);
int result = ioctl(fd, USBDEVFS_REAPURBNDELAY, urb);
if (result < 0)
{
*urb = NULL;
if (errno == EAGAIN)
{
// No URBs are available to be reaped right now.
return NULL;
}
return error_create_errno("Failed to reap an asynchronous transfer.");
}
return NULL;
}
/*! Cancels an URB that was already submitted. */
libusbp_error * usbfd_discard_urb(int fd, struct usbdevfs_urb * urb)
{
assert(urb != NULL);
int result = ioctl(fd, USBDEVFS_DISCARDURB, urb);
if (result < 0)
{
if (errno == EINVAL)
{
// This error code happens if the URB was already completed. This
// is not an error.
return NULL;
}
return error_create_errno("Failed to cancel asynchronous transfer.");
}
return NULL;
}

72
dep/libusbp/src/list.c Normal file
View File

@@ -0,0 +1,72 @@
/** This file provides libusbp_list_free as well as internal functions that help
* construct lists of devices.
*
* At all times, a list maintained by these functions will be NULL terminated.
*/
#include <libusbp_internal.h>
libusbp_error * device_list_create(libusbp_device *** device_list)
{
assert(device_list != NULL);
*device_list = NULL;
libusbp_device ** new_list = malloc(sizeof(libusbp_device *));
if (new_list == NULL)
{
return &error_no_memory;
}
new_list[0] = NULL;
*device_list = new_list;
return NULL;
}
libusbp_error * device_list_append(libusbp_device *** device_list,
size_t * count, libusbp_device * device)
{
assert(device_list != NULL);
assert(count != NULL);
assert(device != NULL);
size_t new_count = *count + 1;
libusbp_device ** expanded_list = realloc(*device_list,
(new_count + 1) * sizeof(libusbp_device *));
if (expanded_list == NULL)
{
// Expanding the list failed, so we return an error and leave the
// list in its original state.
return &error_no_memory;
}
expanded_list[new_count - 1] = device;
expanded_list[new_count] = NULL;
*count = new_count;
*device_list = expanded_list;
return NULL;
}
void free_devices_and_list(libusbp_device ** device_list)
{
if (device_list != NULL)
{
libusbp_device ** device = device_list;
while(*device != NULL)
{
libusbp_device_free(*device);
device++;
}
libusbp_list_free(device_list);
}
}
void libusbp_list_free(libusbp_device ** device_list)
{
if (device_list == NULL) { return; }
free(device_list);
}

View File

@@ -0,0 +1,188 @@
#include <libusbp_internal.h>
struct async_in_transfer
{
IOUSBInterfaceInterface182 ** ioh;
uint8_t pipe_index;
void * buffer;
uint32_t size;
bool pending;
size_t transferred;
libusbp_error * error;
};
static void async_in_transfer_callback(void * context, kern_return_t kr, void * arg0)
{
#ifdef LIBUSBP_LOG
printf("async_in_transfer_callback (%p): %p, %#x, %p, %s\n",
async_in_transfer_callback, context, kr, arg0, mach_error_string(kr));
#endif
async_in_transfer * transfer = (async_in_transfer *)context;
assert(transfer != NULL);
assert(transfer->pending);
assert(transfer->error == NULL);
assert(transfer->transferred == 0);
libusbp_error * error = NULL;
if (kr != KERN_SUCCESS)
{
error = error_create_mach(kr, "Asynchronous IN transfer failed.");
}
transfer->transferred = (size_t)arg0;
transfer->pending = false;
transfer->error = error;
}
libusbp_error * async_in_transfer_create(
libusbp_generic_handle * handle, uint8_t pipe_id, size_t transfer_size,
async_in_transfer ** transfer)
{
assert(handle != NULL);
if (transfer_size > UINT32_MAX)
{
return error_create("Transfer size is too large.");
}
libusbp_error * error = NULL;
// Allocate memory for the transfer struct.
async_in_transfer * new_transfer = calloc(1, sizeof(async_in_transfer));
if (new_transfer == NULL)
{
error = &error_no_memory;
}
// Allocate memory for the buffer.
if (error == NULL)
{
new_transfer->size = transfer_size;
new_transfer->buffer = malloc(transfer_size);
if (new_transfer->buffer == NULL)
{
error = &error_no_memory;
}
}
// Get needed information from the generic handle.
if (error == NULL)
{
new_transfer->ioh = generic_handle_get_ioh(handle);
new_transfer->pipe_index = generic_handle_get_pipe_index(handle, pipe_id);
}
// Pass the transfer to the caller.
if (error == NULL)
{
*transfer = new_transfer;
new_transfer = NULL;
}
async_in_transfer_free(new_transfer);
return error;
}
libusbp_error * async_in_pipe_setup(libusbp_generic_handle * gh, uint8_t pipe_id)
{
LIBUSBP_UNUSED(gh);
LIBUSBP_UNUSED(pipe_id);
return NULL;
}
void async_in_transfer_free(async_in_transfer * transfer)
{
if (transfer == NULL) { return; }
if (transfer->pending)
{
// Unfortunately, this transfer is still pending, so we cannot free it;
// the kernel needs to be able to write to this transfer's memory when
// it completes. We could just abort the process here, but instead we
// we choose to have a memory leak.
return;
}
libusbp_error_free(transfer->error);
free(transfer->buffer);
free(transfer);
}
void async_in_transfer_submit(async_in_transfer * transfer)
{
assert(transfer != NULL);
assert(transfer->pending == false);
libusbp_error_free(transfer->error);
transfer->error = NULL;
transfer->transferred = 0;
transfer->pending = true;
kern_return_t kr = (*transfer->ioh)->ReadPipeAsync(transfer->ioh,
transfer->pipe_index, transfer->buffer, transfer->size,
async_in_transfer_callback, transfer);
if (kr != KERN_SUCCESS)
{
transfer->pending = false;
transfer->error = error_create_mach(kr, "Failed to submit asynchronous read transfer.");
}
}
libusbp_error * async_in_transfer_get_results(async_in_transfer * transfer,
void * buffer, size_t * transferred, libusbp_error ** transfer_error)
{
assert(transfer != NULL);
assert(transfer->pending == false);
size_t tmp_transferred = transfer->transferred;
// Make sure we don't overflow the user's buffer.
if (tmp_transferred > transfer->size)
{
assert(0);
tmp_transferred = transfer->size;
}
if (buffer != NULL)
{
memcpy(buffer, transfer->buffer, tmp_transferred);
}
if (transferred != NULL)
{
*transferred = tmp_transferred;
}
if (transfer_error != NULL)
{
*transfer_error = libusbp_error_copy(transfer->error);
}
return NULL;
}
// This cancels all of the transfers for the whole pipe. It is not possible to
// cancel an individual transfer on Mac OS X, which is one of the reasons
// individual transfers are not provided as first-class objects by the libusbp
// API.
libusbp_error * async_in_transfer_cancel(async_in_transfer * transfer)
{
if (transfer == NULL) { return NULL; }
kern_return_t kr = (*transfer->ioh)->AbortPipe(transfer->ioh, transfer->pipe_index);
if (kr != KERN_SUCCESS)
{
return error_create_mach(kr, "Failed to cancel transfers.");
}
return NULL;
}
bool async_in_transfer_pending(async_in_transfer * transfer)
{
assert(transfer != NULL);
return transfer->pending;
}

View File

@@ -0,0 +1,233 @@
#include <libusbp_internal.h>
struct libusbp_device
{
uint64_t id;
uint16_t product_id;
uint16_t vendor_id;
uint16_t revision;
char * serial_number;
};
static libusbp_error * device_allocate(libusbp_device ** device)
{
assert(device != NULL);
*device = calloc(1, sizeof(libusbp_device));
if (*device == NULL)
{
return &error_no_memory;
}
return NULL;
}
libusbp_error * create_device(io_service_t service, libusbp_device ** device)
{
assert(service != MACH_PORT_NULL);
assert(device != NULL);
// Allocate the device.
libusbp_device * new_device = NULL;
libusbp_error * error = device_allocate(&new_device);
// Get the numeric IDs.
if (error == NULL)
{
error = get_uint16(service, CFSTR(kUSBVendorID), &new_device->vendor_id);
}
if (error == NULL)
{
error = get_uint16(service, CFSTR(kUSBProductID), &new_device->product_id);
}
if (error == NULL)
{
error = get_uint16(service, CFSTR(kUSBDeviceReleaseNumber), &new_device->revision);
}
// Get the serial number.
if (error == NULL)
{
error = get_string(service, CFSTR(kUSBSerialNumberString), &new_device->serial_number);
}
// Get the ID.
if (error == NULL)
{
error = get_id(service, &new_device->id);
}
// Pass the device to the caller.
if (error == NULL)
{
*device = new_device;
new_device = NULL;
}
libusbp_device_free(new_device);
return error;
}
libusbp_error * libusbp_device_copy(const libusbp_device * source, libusbp_device ** dest)
{
if (dest == NULL)
{
return error_create("Device output pointer is null.");
}
*dest = NULL;
if (source == NULL)
{
return NULL;
}
libusbp_error * error = NULL;
// Allocate the device.
libusbp_device * new_device = NULL;
error = device_allocate(&new_device);
// Copy the simple fields, while leaving the pointers owned by the
// device NULL so that libusbp_device_free is still OK to call.
if (error == NULL)
{
memcpy(new_device, source, sizeof(libusbp_device));
new_device->serial_number = NULL;
}
// Copy the serial number.
if (error == NULL && source->serial_number != NULL)
{
error = string_copy(source->serial_number, &new_device->serial_number);
}
// Pass the device to the caller.
if (error == NULL)
{
*dest = new_device;
new_device = NULL;
}
libusbp_device_free(new_device);
return error;
}
void libusbp_device_free(libusbp_device * device)
{
if (device != NULL)
{
libusbp_string_free(device->serial_number);
free(device);
}
}
uint64_t device_get_id(const libusbp_device * device)
{
assert(device != NULL);
return device->id;
}
libusbp_error * libusbp_device_get_vendor_id(
const libusbp_device * device,
uint16_t * vendor_id)
{
if (vendor_id == NULL)
{
return error_create("Vendor ID output pointer is null.");
}
*vendor_id = 0;
if (device == NULL)
{
return error_create("Device is null.");
}
*vendor_id = device->vendor_id;
return NULL;
}
libusbp_error * libusbp_device_get_product_id(
const libusbp_device * device,
uint16_t * product_id)
{
if (product_id == NULL)
{
return error_create("Product ID output pointer is null.");
}
*product_id = 0;
if (device == NULL)
{
return error_create("Device is null.");
}
*product_id = device->product_id;
return NULL;
}
libusbp_error * libusbp_device_get_revision(
const libusbp_device * device,
uint16_t * revision)
{
if (revision == NULL)
{
return error_create("Device revision output pointer is null.");
}
*revision = 0;
if (device == NULL)
{
return error_create("Device is null.");
}
*revision = device->revision;
return NULL;
}
libusbp_error * libusbp_device_get_serial_number(
const libusbp_device * device,
char ** serial_number)
{
if (serial_number == NULL)
{
return error_create("Serial number output pointer is null.");
}
*serial_number = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
if (device->serial_number == NULL)
{
libusbp_error * error = error_create("Device does not have a serial number.");
error = error_add_code(error, LIBUSBP_ERROR_NO_SERIAL_NUMBER);
return error;
}
return string_copy(device->serial_number, serial_number);
}
libusbp_error * libusbp_device_get_os_id(
const libusbp_device * device,
char ** id)
{
if (id == NULL)
{
return error_create("Device OS ID output pointer is null.");
}
*id = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
return iokit_id_to_string(device->id, id);
}

View File

@@ -0,0 +1,61 @@
#include <libusbp_internal.h>
libusbp_error * error_create_mach(kern_return_t error_code, const char * format, ...)
{
// Add the numerical error code.
libusbp_error * error = error_create("Error code 0x%x.", error_code);
bool skip_standard_message = false;
switch(error_code)
{
case kIOUSBPipeStalled:
skip_standard_message = true;
error = error_add(error, "The request was invalid or there was an I/O problem.");
error = error_add_code(error, LIBUSBP_ERROR_STALL);
error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
break;
case kIOReturnNoDevice:
error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
break;
case kIOUSBTransactionTimeout:
skip_standard_message = true;
error = error_add(error, "The operation timed out.");
error = error_add_code(error, LIBUSBP_ERROR_TIMEOUT);
break;
case kIOReturnExclusiveAccess:
skip_standard_message = true;
error = error_add(error,
"Access is denied. Try closing all other programs that are using the device.");
error = error_add_code(error, LIBUSBP_ERROR_ACCESS_DENIED);
break;
case kIOReturnOverrun:
skip_standard_message = true;
error = error_add(error, "The transfer overflowed.");
break;
case kIOReturnAborted:
skip_standard_message = true;
error = error_add(error, "The operation was cancelled.");
error = error_add_code(error, LIBUSBP_ERROR_CANCELLED);
break;
}
if (!skip_standard_message)
{
// Add a message from the system.
error = error_add(error, "%s.", mach_error_string(error_code));
}
// Finally, add the context provided by the caller.
va_list ap;
va_start(ap, format);
error = error_add_v(error, format, ap);
va_end(ap);
return error;
}

View File

@@ -0,0 +1,600 @@
#include <libusbp_internal.h>
#include <mach/mach_time.h>
struct libusbp_generic_handle
{
// ioh is short for "IOKit Handle"
IOUSBInterfaceInterface182 ** ioh;
IOCFPlugInInterface ** plug_in;
mach_port_t async_port;
// Timeouts are stored in milliseconds. 0 is forever.
uint32_t out_timeout[MAX_ENDPOINT_NUMBER + 1];
uint32_t in_timeout[MAX_ENDPOINT_NUMBER + 1];
// These arrays allow us to convert from a normal USB endpoint address like
// 0x82 (Endpoint 2 IN) to the pipe index needed by IOUSBInterface functions.
uint8_t out_pipe_index[MAX_ENDPOINT_NUMBER + 1];
uint8_t in_pipe_index[MAX_ENDPOINT_NUMBER + 1];
};
#ifdef LIBUSBP_LOG
void log_mach_msg(mach_msg_return_t mr, mach_msg_header_t * header)
{
uint64_t t = mach_absolute_time();
size_t main_payload_size = header->msgh_size - sizeof(mach_msg_header_t);
assert(main_payload_size < 1024); // avoid buffer overruns
uint8_t * payload = (uint8_t *)(header + 1);
mach_msg_trailer_t * trailer = (mach_msg_trailer_t *)(payload + main_payload_size);
size_t payload_size = main_payload_size + trailer->msgh_trailer_size;
assert(payload_size < 1024); // avoid buffer overruns
printf("mach_msg at %lld: %#x, %s, %d\n", t, mr, mach_error_string(mr), header->msgh_size);
printf(" bits: %#x\n", header->msgh_bits);
printf(" ports: %d, %d, %d\n", header->msgh_remote_port,
header->msgh_local_port, header->msgh_voucher_port);
printf(" id: %d\n", header->msgh_id);
for (size_t i = 0; i < payload_size; i++)
{
if ((i % 8) == 0) { printf(" "); }
printf("%02x ", payload[i]);
if ((i % 8) == 7 || i == payload_size - 1) { printf("\n"); }
}
}
#endif
// Gets the properties of all the pipes and uses that to populate the
// out_pipe_index and in_pipe_index arrays.
static libusbp_error * process_pipe_properties(libusbp_generic_handle * handle)
{
uint8_t endpoint_count;
kern_return_t kr = (*handle->ioh)->GetNumEndpoints(handle->ioh, &endpoint_count);
if (kr != KERN_SUCCESS)
{
return error_create_mach(kr, "Failed to get number of endpoints.");
}
for(uint32_t i = 1; i <= endpoint_count; i++)
{
uint8_t direction;
uint8_t endpoint_number;
uint8_t transfer_type;
uint16_t max_packet_size;
uint8_t interval;
kr = (*handle->ioh)->GetPipeProperties(handle->ioh, (UInt8) i,
&direction, &endpoint_number, &transfer_type, &max_packet_size, &interval);
if (kr != KERN_SUCCESS)
{
return error_create_mach(kr, "Failed to get pipe properties for pipe %d.", i);
}
if (endpoint_number <= MAX_ENDPOINT_NUMBER)
{
if (direction)
{
handle->in_pipe_index[endpoint_number] = (uint8_t) i;
}
else
{
handle->out_pipe_index[endpoint_number] = (uint8_t) i;
}
}
}
return NULL;
}
// Sets the configruation of a device to 1 if it is not configured.
static libusbp_error * set_configuration(io_service_t service)
{
assert(service != MACH_PORT_NULL);
libusbp_error * error = NULL;
// Turn io_service_t into something we can actually use.
IOUSBDeviceInterface ** dev_handle = NULL;
IOCFPlugInInterface ** plug_in = NULL;
error = service_to_interface(service,
kIOUSBDeviceUserClientTypeID,
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID197),
(void **)&dev_handle,
&plug_in);
uint8_t config_num = 0;
if (error == NULL)
{
kern_return_t kr = (*dev_handle)->GetConfiguration(dev_handle, &config_num);
if (kr != KERN_SUCCESS)
{
// We failed to get the current configuration. The documentation of
// GetConfiguration doesn't state whether it actually does I/O on
// the device or not, but if it does do I/O then one possible reason
// for GetConfiguration to fail is that the device simply doesn't
// support the request. Let's just assume the device is not
// configured and set it to configuration 1.
config_num = 0;
}
}
// Open the device for exclusive access.
if (error == NULL && config_num == 0)
{
kern_return_t kr = (*dev_handle)->USBDeviceOpen(dev_handle);
if (kr != KERN_SUCCESS)
{
error = error_create_mach(kr, "Failed to open handle to device.");
}
}
// Set the configuration.
if (error == NULL && config_num == 0)
{
uint8_t new_config_num = 1;
kern_return_t kr = (*dev_handle)->SetConfiguration(dev_handle, new_config_num);
if (kr != KERN_SUCCESS)
{
error = error_create_mach(kr, "Failed to set configuration to 1.");
}
}
// Clean up.
if (dev_handle != NULL)
{
(*dev_handle)->USBDeviceClose(dev_handle);
(*dev_handle)->Release(dev_handle);
dev_handle = NULL;
(*plug_in)->Release(plug_in);
plug_in = NULL;
}
return error;
}
// Sets the configruation of the device to 1 if it is not set, and then
// retrieves the io_service_t representing the specific interface we want
// to talk to.
static libusbp_error * set_configuration_and_get_service(
const libusbp_generic_interface * gi,
io_service_t * service)
{
assert(gi != NULL);
assert(service != NULL);
*service = MACH_PORT_NULL;
uint64_t device_id = generic_interface_get_device_id(gi);
uint8_t interface_number = generic_interface_get_interface_number(gi);
libusbp_error * error = NULL;
// Get an io_service_t for the physical device.
io_service_t device_service = MACH_PORT_NULL;
error = service_get_from_id(device_id, &device_service);
// Set the configruation to 1 if it is not set.
if (error == NULL)
{
error = set_configuration(device_service);
}
// Get the io_service_t for the interface.
if (error == NULL)
{
error = service_get_usb_interface(device_service, interface_number, service);
}
if (device_service != MACH_PORT_NULL) { IOObjectRelease(device_service); }
return error;
}
libusbp_error * libusbp_generic_handle_open(
const libusbp_generic_interface * gi,
libusbp_generic_handle ** handle)
{
if (handle == NULL)
{
return error_create("Generic handle output pointer is null.");
}
*handle = NULL;
if (gi == NULL)
{
return error_create("Generic interface is null.");
}
libusbp_error * error = NULL;
// Allocate memory for the handle.
libusbp_generic_handle * new_handle = NULL;
new_handle = calloc(1, sizeof(libusbp_generic_handle));
if (new_handle == NULL)
{
error = &error_no_memory;
}
// Get the io_service_t representing the IOUSBInterface.
io_service_t service = MACH_PORT_NULL;
if (error == NULL)
{
uint64_t interface_id;
bool has_interface_id = generic_interface_get_interface_id(gi, &interface_id);
if (has_interface_id)
{
// This generic interface has an I/O Registry ID for a specific USB interface,
// so lets just get the corresponding io_service_t.
error = service_get_from_id(interface_id, &service);
}
else
{
// This generic interface does not have an ID for the specific USB interface yet,
// probably because it is a non-composite device and we need to put it into the
// right configuration.
error = set_configuration_and_get_service(gi, &service);
}
}
// Get the IOInterfaceInterface
if (error == NULL)
{
error = service_to_interface(service,
kIOUSBInterfaceUserClientTypeID,
CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID182),
(void **)&new_handle->ioh,
&new_handle->plug_in);
}
// Open the interface for exclusive access.
// (Otherwise, we can't read from non-zero pipes.)
if (error == NULL)
{
kern_return_t kr = (*new_handle->ioh)->USBInterfaceOpen(new_handle->ioh);
if (kr != KERN_SUCCESS)
{
error = error_create_mach(kr, "Failed to open generic handle.");
}
}
// Retrieve and store important information from the pipe properties.
if (error == NULL)
{
error = process_pipe_properties(new_handle);
}
// Create the async port.
if (error == NULL)
{
kern_return_t kr = (*new_handle->ioh)->CreateInterfaceAsyncPort(new_handle->ioh,
&new_handle->async_port);
if (kr != KERN_SUCCESS)
{
error = error_create_mach(kr, "Failed to create asynchronous port.");
}
}
// Pass the handle to the caller.
if (error == NULL)
{
*handle = new_handle;
new_handle = NULL;
}
// Clean up.
libusbp_generic_handle_close(new_handle);
if (service != MACH_PORT_NULL) { IOObjectRelease(service); }
return error;
}
void libusbp_generic_handle_close(libusbp_generic_handle * handle)
{
if (handle != NULL)
{
if (handle->ioh != NULL)
{
(*handle->ioh)->USBInterfaceClose(handle->ioh);
(*handle->ioh)->Release(handle->ioh);
(*handle->plug_in)->Release(handle->plug_in);
}
free(handle);
}
}
libusbp_error * libusbp_generic_handle_open_async_in_pipe(
libusbp_generic_handle * handle,
uint8_t pipe_id,
libusbp_async_in_pipe ** pipe)
{
return async_in_pipe_create(handle, pipe_id, pipe);
}
libusbp_error * libusbp_generic_handle_set_timeout(
libusbp_generic_handle * handle,
uint8_t pipe_id,
uint32_t timeout)
{
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
libusbp_error * error = NULL;
error = check_pipe_id(pipe_id);
if (error == NULL)
{
uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER;
if (pipe_id & 0x80)
{
handle->in_timeout[endpoint_number] = timeout;
}
else
{
handle->out_timeout[endpoint_number] = timeout;
}
}
return error;
}
libusbp_error * libusbp_control_transfer(
libusbp_generic_handle * handle,
uint8_t bmRequestType,
uint8_t bRequest,
uint16_t wValue,
uint16_t wIndex,
void * buffer,
uint16_t wLength,
size_t * transferred)
{
if (transferred != NULL)
{
*transferred = 0;
}
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
IOUSBDevRequestTO request;
request.bmRequestType = bmRequestType;
request.bRequest = bRequest;
request.wValue = wValue;
request.wIndex = wIndex;
request.wLength = wLength;
request.pData = buffer;
request.completionTimeout = handle->out_timeout[0];
request.wLenDone = 0;
kern_return_t kr = (*handle->ioh)->ControlRequestTO(handle->ioh, 0, &request);
if (transferred != NULL) { *transferred = request.wLenDone; }
if (kr != KERN_SUCCESS)
{
return error_create_mach(kr, "Control transfer failed.");
}
return NULL;
}
libusbp_error * libusbp_read_pipe(
libusbp_generic_handle * handle,
uint8_t pipe_id,
void * buffer,
size_t size,
size_t * transferred)
{
if (transferred != NULL)
{
*transferred = 0;
}
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
libusbp_error * error = NULL;
if (size == 0)
{
error = error_create("Transfer size 0 is not allowed.");
}
if (error == NULL && size > UINT32_MAX)
{
error = error_create("Transfer size is too large.");
}
if (error == NULL && buffer == NULL)
{
error = error_create("Buffer is null.");
}
if (error == NULL)
{
error = check_pipe_id_in(pipe_id);
}
if (error == NULL)
{
uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER;
uint32_t no_data_timeout = 0;
uint32_t completion_timeout = handle->in_timeout[endpoint_number];
uint32_t iokit_size = (uint32_t) size;
uint32_t pipe_index = handle->in_pipe_index[endpoint_number];
kern_return_t kr = (*handle->ioh)->ReadPipeTO(handle->ioh, (UInt8) pipe_index,
buffer, &iokit_size, no_data_timeout, completion_timeout);
if (transferred != NULL) { *transferred = iokit_size; }
if (kr != KERN_SUCCESS)
{
error = error_create_mach(kr, "");
}
}
if (error != NULL)
{
error = error_add(error, "Failed to read from pipe.");
}
return error;
}
libusbp_error * libusbp_write_pipe(
libusbp_generic_handle * handle,
uint8_t pipe_id,
const void * buffer,
size_t size,
size_t * transferred)
{
if (transferred != NULL)
{
*transferred = 0;
}
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
libusbp_error * error = NULL;
if (size > UINT32_MAX)
{
error = error_create("Transfer size is too large.");
}
if (error == NULL && buffer == NULL && size)
{
error = error_create("Buffer is null.");
}
if (error == NULL)
{
error = check_pipe_id_out(pipe_id);
}
if (error == NULL)
{
uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER;
uint32_t no_data_timeout = 0;
uint32_t completion_timeout = handle->out_timeout[endpoint_number];
uint32_t pipe_index = handle->out_pipe_index[endpoint_number];
kern_return_t kr = (*handle->ioh)->WritePipeTO(handle->ioh, (UInt8) pipe_index,
(void *)buffer, (UInt32) size, no_data_timeout, completion_timeout);
if (kr != KERN_SUCCESS)
{
error = error_create_mach(kr, "");
}
}
if (error == NULL && transferred != NULL)
{
// There was no error, so just assume the entire amount was transferred.
// WritePipeTO does not give us a number.
*transferred = size;
}
if (error != NULL)
{
error = error_add(error, "Failed to write to pipe.");
}
return error;
}
#pragma pack(4)
typedef struct
{
mach_msg_header_t header;
uint8_t buffer[1024];
} our_msg;
libusbp_error * generic_handle_events(libusbp_generic_handle * handle)
{
assert(handle != NULL);
while(1)
{
// Check for messages on this handle's async port using mach_msg().
our_msg msg;
mach_msg_option_t option = MACH_RCV_MSG | MACH_RCV_TIMEOUT;
option |= MACH_RCV_LARGE; // tmphax to receive large messages
mach_msg_size_t send_size = 0;
mach_msg_size_t rcv_size = sizeof(msg); // might need to make this bigger
mach_msg_timeout_t timeout = 0; // non-blocking
mach_port_t notify = MACH_PORT_NULL;
mach_msg_return_t mr = mach_msg(&msg.header, option, send_size, rcv_size,
handle->async_port, timeout, notify);
if (mr != MACH_MSG_SUCCESS)
{
if (mr == MACH_RCV_TIMED_OUT)
{
// There was no message available on the port.
return NULL;
}
else if (mr == MACH_RCV_TOO_LARGE)
{
return error_create("Mach message received was too large: %d > %d\n",
msg.header.msgh_size, (unsigned int)sizeof(msg));
}
else
{
return error_create_mach(mr, "Failed to receive mach message.");
}
}
#ifdef LIBUSBP_LOG
log_mach_msg(mr, &msg.header);
#endif
// We have received a message from the mach port that contains a blob of
// binary data. The blob seems to include a pointer to the
// async_in_transfer, a kern_return_t code for the transfer, and a pointer
// to the callback we specified when we submitted the transfer. We aren't
// supposed to process that data ourselves; we are supposed to call
// IODispatchCalloutFromMessage.
//
// The third argument to IODispatchCalloutFromMessage is supposed to be a
// IONotificationPortRef, but we don't have access to the
// IONotificationPortRef, which is a protected member of IOUSBInterface
// class with no accessors. Passing NULL (or any arbitary pointer) seems to
// work. IODispatchCalloutFromMessage has no return value.
IODispatchCalloutFromMessage(0, &msg.header, NULL);
}
}
void ** libusbp_generic_handle_get_cf_plug_in(libusbp_generic_handle * handle)
{
if (handle == NULL)
{
return NULL;
}
return (void **)handle->plug_in;
}
IOUSBInterfaceInterface182 ** generic_handle_get_ioh(const libusbp_generic_handle * handle)
{
assert(handle != NULL);
return handle->ioh;
}
uint8_t generic_handle_get_pipe_index(const libusbp_generic_handle * handle, uint8_t pipe_id)
{
uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER;
if (pipe_id & 0x80)
{
return handle->in_pipe_index[endpoint_number];
}
else
{
return handle->out_pipe_index[endpoint_number];
}
}

View File

@@ -0,0 +1,224 @@
#include <libusbp_internal.h>
/* Composite devices on all OSes and non-composite vendor-defined devices on
* Windows and Linux will automatically be set to configuration 1, which means
* we can find device nodes for the specific interface we are interested in
* using in libusbp_generic_interface_create, and return error code
* LIBUSBP_ERROR_NOT_READY if that node is not found. However, non-composite
* devices on Mac OS X will not automatically get configured unless someone
* tells them what configuration to use.
*
* For composite devices on Mac OS X:
* libusbp_generic_interface_create() finds the correct device node.
* libusbp_generic_handle_open() simply opens it.
*
* For non-composite devices on Mac OS X:
* libusbp_generic_interface_create() just records information from the user.
* libusbp_generic_handle_open() ensures the device is set to configuration 1,
* then finds the correct interface and opens it.
*/
struct libusbp_generic_interface
{
uint64_t device_id;
uint8_t interface_number;
bool has_interface_id;
uint64_t interface_id;
};
libusbp_error * generic_interface_allocate(libusbp_generic_interface ** gi)
{
*gi = calloc(1, sizeof(libusbp_generic_interface));
if (*gi == NULL)
{
return &error_no_memory;
}
return NULL;
}
libusbp_error * libusbp_generic_interface_create(
const libusbp_device * device,
uint8_t interface_number,
bool composite,
libusbp_generic_interface ** gi)
{
if (gi == NULL)
{
return error_create("Generic interface output pointer is null.");
}
*gi = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
libusbp_error * error = NULL;
libusbp_generic_interface * new_gi = NULL;
if (error == NULL)
{
error = generic_interface_allocate(&new_gi);
}
// Record the I/O registry ID for the device.
if (error == NULL)
{
new_gi->device_id = device_get_id(device);
}
// Record the interface number.
if (error == NULL)
{
new_gi->interface_number = interface_number;
}
// If this is a composite device, get the id for the specific interface we
// are interested in. If it is non-composite, we wait until later becuase
// the device might be unconfigured and its interface registry entries might
// not even exist yet.
if (error == NULL && composite)
{
// Get an io_service_t for the physical device.
io_service_t device_service = MACH_PORT_NULL;
error = service_get_from_id(new_gi->device_id, &device_service);
// Get the io_service_t for the interface.
io_service_t interface_service = MACH_PORT_NULL;
if (error == NULL)
{
error = service_get_usb_interface(device_service, interface_number, &interface_service);
}
// Get the registry entry ID for the interface.
if (error == NULL)
{
assert(interface_service != MACH_PORT_NULL);
error = get_id(interface_service, &new_gi->interface_id);
}
// Record the fact that we have an ID for the interface.
if (error == NULL)
{
assert(new_gi->interface_id);
assert(new_gi->interface_id != new_gi->device_id);
new_gi->has_interface_id = true;
}
if (device_service != MACH_PORT_NULL) { IOObjectRelease(device_service); }
if (interface_service != MACH_PORT_NULL) { IOObjectRelease(interface_service); }
}
// Pass the new generic interface to the caller.
if (error == NULL)
{
*gi = new_gi;
new_gi = NULL;
}
libusbp_generic_interface_free(new_gi);
return error;
}
void libusbp_generic_interface_free(libusbp_generic_interface * gi)
{
free(gi);
}
libusbp_error * libusbp_generic_interface_copy(
const libusbp_generic_interface * source,
libusbp_generic_interface ** dest)
{
if (dest == NULL)
{
return error_create("Generic interface output pointer is null.");
}
*dest = NULL;
if (source == NULL)
{
return NULL;
}
libusbp_error * error = NULL;
// Allocate the generic interface.
libusbp_generic_interface * new_gi = NULL;
error = generic_interface_allocate(&new_gi);
// Copy the simple fields.
if (error == NULL)
{
memcpy(new_gi, source, sizeof(libusbp_generic_interface));
}
// Pass the generic interface to the caller.
if (error == NULL)
{
*dest = new_gi;
new_gi = NULL;
}
libusbp_generic_interface_free(new_gi);
return error;
}
uint64_t generic_interface_get_device_id(const libusbp_generic_interface * gi)
{
assert(gi != NULL);
return gi->device_id;
}
uint8_t generic_interface_get_interface_number(const libusbp_generic_interface * gi)
{
assert(gi != NULL);
return gi->interface_number;
}
bool generic_interface_get_interface_id(const libusbp_generic_interface * gi, uint64_t * id)
{
assert(gi != NULL);
if (gi->has_interface_id)
{
*id = gi->interface_id;
return 1;
}
else
{
*id = 0;
return 0;
}
}
libusbp_error * libusbp_generic_interface_get_os_id(
const libusbp_generic_interface * gi,
char ** id)
{
if (id == NULL)
{
return error_create("String output pointer is null.");
}
*id = NULL;
if (gi == NULL)
{
return error_create("Generic interface is null.");
}
// Some information is being lost here, unfortunately.
uint64_t idnum = gi->has_interface_id ? gi->interface_id : gi->device_id;
return iokit_id_to_string(idnum, id);
}
libusbp_error * libusbp_generic_interface_get_os_filename(
const libusbp_generic_interface * gi,
char ** filename)
{
return libusbp_generic_interface_get_os_id(gi, filename);
}

View File

@@ -0,0 +1,306 @@
#include <libusbp_internal.h>
libusbp_error * iokit_id_to_string(uint64_t id, char ** str)
{
char buffer[17];
snprintf(buffer, sizeof(buffer), "%" PRIx64, id);
return string_copy(buffer, str);
}
libusbp_error * service_get_from_id(uint64_t id, io_service_t * service)
{
assert(service != NULL);
// Create a dictionary specifying this ID. This dictionary will be
// CFReleased by IOServiceGetMatchingService.
CFMutableDictionaryRef dict = IORegistryEntryIDMatching(id);
if (dict == NULL)
{
return error_create("Failed to create a dictionary matching the ID.");
}
*service = IOServiceGetMatchingService(kIOMasterPortDefault, dict);
if (*service == MACH_PORT_NULL)
{
return error_create("Failed to find service with ID 0x%" PRIx64 ".", id);
}
return NULL;
}
// Takes a service representing a physical, composite USB device.
// Returns a service representing the specified USB interface of the device.
// The returned service will conform to the class IOUSBInterface.
libusbp_error * service_get_usb_interface(io_service_t service,
uint8_t interface_number, io_service_t * interface_service)
{
assert(service != MACH_PORT_NULL);
assert(interface_service != NULL);
*interface_service = MACH_PORT_NULL;
libusbp_error * error = NULL;
io_iterator_t iterator = MACH_PORT_NULL;
kern_return_t result = IORegistryEntryGetChildIterator(
service, kIOServicePlane, &iterator);
if (result != KERN_SUCCESS)
{
error = error_create_mach(result, "Failed to get child iterator.");
}
// Loop through the devices to find the right one.
while (error == NULL)
{
io_service_t candidate = IOIteratorNext(iterator);
if (candidate == MACH_PORT_NULL) { break; }
// Filter out candidates that are not of class IOUSBInterface.
bool conforms = (bool) IOObjectConformsTo(candidate, kIOUSBInterfaceClassName);
if (!conforms)
{
IOObjectRelease(candidate);
continue;
}
// Get bInterfaceNumber.
int32_t actual_num;
error = get_int32(candidate, CFSTR("bInterfaceNumber"), &actual_num);
if (error == NULL && actual_num == interface_number)
{
// This is the right one. Pass it to the caller.
*interface_service = candidate;
break;
}
IOObjectRelease(candidate);
}
if (error == NULL && *interface_service == MACH_PORT_NULL)
{
error = error_create("Could not find interface %d.", interface_number);
error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
}
if (iterator != MACH_PORT_NULL) { IOObjectRelease(iterator); }
return error;
}
libusbp_error * service_get_child_by_class(io_service_t service,
const char * class_name, io_service_t * interface_service)
{
assert(service != MACH_PORT_NULL);
assert(interface_service != NULL);
*interface_service = MACH_PORT_NULL;
libusbp_error * error = NULL;
io_iterator_t iterator = MACH_PORT_NULL;
kern_return_t result = IORegistryEntryCreateIterator(
service, kIOServicePlane, kIORegistryIterateRecursively, &iterator);
if (result != KERN_SUCCESS)
{
error = error_create_mach(result, "Failed to get recursive iterator.");
}
// Loop through the devices to find the right one.
while (error == NULL)
{
io_service_t candidate = IOIteratorNext(iterator);
if (candidate == MACH_PORT_NULL) { break; }
// Filter out candidates that are not the right class.
bool conforms = (bool) IOObjectConformsTo(candidate, class_name);
if (!conforms)
{
IOObjectRelease(candidate);
continue;
}
// This is the right one. Pass it to the caller.
*interface_service = candidate;
break;
}
if (error == NULL && *interface_service == MACH_PORT_NULL)
{
error = error_create("Could not find entry with class %s.", class_name);
error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
}
if (iterator != MACH_PORT_NULL) { IOObjectRelease(iterator); }
return error;
}
libusbp_error * service_to_interface(
io_service_t service,
CFUUIDRef pluginType,
REFIID rid,
void ** object,
IOCFPlugInInterface *** plug_in)
{
assert(service != MACH_PORT_NULL);
assert(object != NULL);
*object = NULL;
int32_t score;
libusbp_error * error = NULL;
// Create the plug-in interface.
IOCFPlugInInterface ** new_plug_in = NULL;
kern_return_t kr = IOCreatePlugInInterfaceForService(service,
pluginType, kIOCFPlugInInterfaceID,
&new_plug_in, &score);
if (kr != KERN_SUCCESS)
{
error = error_create_mach(kr, "Failed to create plug-in interface.");
}
// Create the device interface and pass it to the caller.
if (error == NULL)
{
HRESULT hr = (*new_plug_in)->QueryInterface(new_plug_in, rid, object);
if (hr)
{
error = error_create_hr(hr, "Failed to query interface.");
}
}
// Also pass the plug-in interface to the caller if they want it.
if (error == NULL && plug_in != NULL)
{
*plug_in = new_plug_in;
new_plug_in = NULL;
}
// Clean up.
if (new_plug_in != NULL)
{
(*new_plug_in)->Release(new_plug_in);
}
return error;
}
libusbp_error * get_id(io_registry_entry_t entry, uint64_t * id)
{
assert(entry != MACH_PORT_NULL);
assert(id != NULL);
*id = 0;
kern_return_t result = IORegistryEntryGetRegistryEntryID(entry, id);
if (result != KERN_SUCCESS)
{
return error_create_mach(result, "Failed to get registry entry ID.");
}
return NULL;
}
// Returns NULL if the string is not present.
// The returned string should be freed with libusbp_string_free.
libusbp_error * get_string(io_registry_entry_t entry, CFStringRef name, char ** value)
{
assert(entry != MACH_PORT_NULL);
assert(name != NULL);
assert(value != NULL);
*value = NULL;
CFTypeRef cf_value = IORegistryEntryCreateCFProperty(entry, name, kCFAllocatorDefault, 0);
if (cf_value == NULL)
{
// The string probably does not exist, so just return.
return NULL;
}
libusbp_error * error = NULL;
if (CFGetTypeID(cf_value) != CFStringGetTypeID())
{
error = error_create("Property is not a string.");
}
char buffer[256];
if (error == NULL)
{
bool success = CFStringGetCString(cf_value, buffer, sizeof(buffer),
kCFStringEncodingASCII);
if (!success)
{
error = error_create("Failed to convert property to C string.");
}
}
if (error == NULL)
{
error = string_copy(buffer, value);
}
CFRelease(cf_value);
return error;
}
libusbp_error * get_int32(io_registry_entry_t entry, CFStringRef name, int32_t * value)
{
assert(entry != MACH_PORT_NULL);
assert(name != NULL);
assert(value != NULL);
*value = 0;
libusbp_error * error = NULL;
CFTypeRef cf_value = IORegistryEntryCreateCFProperty(entry, name, kCFAllocatorDefault, 0);
if (cf_value == NULL)
{
error = error_create("Failed to get int32 property from IORegistryEntry.");
}
if (error == NULL && CFGetTypeID(cf_value) != CFNumberGetTypeID())
{
error = error_create("Property is not a number.");
}
if (error == NULL)
{
bool success = CFNumberGetValue(cf_value, kCFNumberSInt32Type, value);
if (!success)
{
error = error_create("Failed to convert property to C integer.");
}
}
if (cf_value != NULL) { CFRelease(cf_value); }
return error;
}
libusbp_error * get_uint16(io_registry_entry_t entry, CFStringRef name, uint16_t * value)
{
assert(entry != MACH_PORT_NULL);
assert(name != NULL);
assert(value != NULL);
libusbp_error * error = NULL;
int32_t tmp;
error = get_int32(entry, name, &tmp);
if (error == NULL)
{
// There is an unchecked conversion of an int32_t to a uint16_t here but
// we don't expect any data to be lost.
*value = (uint16_t) tmp;
}
return error;
}

View File

@@ -0,0 +1,103 @@
#include <libusbp_internal.h>
static void try_create_device(io_service_t service, libusbp_device ** device)
{
libusbp_error * error = create_device(service, device);
if (error != NULL)
{
assert(*device == NULL);
// Something went wrong. To make the library more robust and usable, we
// ignore this error and continue.
#ifdef LIBUSBP_LOG
fprintf(stderr, "Problem creating device: %s\n",
libusbp_error_get_message(error));
#endif
libusbp_error_free(error);
}
}
libusbp_error * libusbp_list_connected_devices(
libusbp_device *** device_list,
size_t * device_count)
{
if (device_count != NULL)
{
*device_count = 0;
}
if (device_list == NULL)
{
return error_create("Device list output pointer is null.");
}
libusbp_error * error = NULL;
// Create a dictionary that says "IOProviderClass" => "IOUSBDevice"
// This dictionary is CFReleased by IOServiceGetMatchingServices.
CFMutableDictionaryRef dict = NULL;
dict = IOServiceMatching("IOUSBHostDevice");
if (dict == NULL)
{
error = error_create("IOServiceMatching returned null.");
}
// Create an iterator for all the connected USB devices.
io_iterator_t iterator = MACH_PORT_NULL;
if (error == NULL)
{
// IOServiceGetMatchingServices consumes one reference to dict,
// so we don't have to CFRelease it.
kern_return_t result = IOServiceGetMatchingServices(
kIOMasterPortDefault, dict, &iterator);
dict = NULL;
if (result != KERN_SUCCESS)
{
error = error_create_mach(result, "Failed to get matching services.");
}
}
// Allocate a new list.
libusbp_device ** new_list = NULL;
size_t count = 0;
if (error == NULL)
{
error = device_list_create(&new_list);
}
// Loop through the devices and add them to our list.
while(error == NULL)
{
io_service_t service = IOIteratorNext(iterator);
if (service == MACH_PORT_NULL) { break; }
libusbp_device * device = NULL;
try_create_device(service, &device);
if (device != NULL)
{
error = device_list_append(&new_list, &count, device);
}
IOObjectRelease(service);
}
// Pass the list and the count to the caller.
if (error == NULL)
{
*device_list = new_list;
new_list = NULL;
if (device_count != NULL)
{
*device_count = count;
}
}
// Clean up.
assert(dict == NULL);
if (dict != NULL) { CFRelease(dict); } // makes the code less brittle
if (iterator != MACH_PORT_NULL) { IOObjectRelease(iterator); }
free_devices_and_list(new_list);
return error;
}

View File

@@ -0,0 +1,169 @@
#include <libusbp_internal.h>
struct libusbp_serial_port
{
// The I/O Registry ID of the IOBSDSerialClient.
uint64_t id;
// A port filename like "/dev/cu.usbmodemFD123".
char * port_name;
};
libusbp_error * libusbp_serial_port_create(
const libusbp_device * device,
uint8_t interface_number,
bool composite,
libusbp_serial_port ** port)
{
LIBUSBP_UNUSED(composite);
if (port == NULL)
{
return error_create("Serial port output pointer is null.");
}
*port = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
// Add one to the interface number because that is what we need for the
// typical case: The user specifies the lower of the two interface numbers,
// which corresponds to the control interface of a CDC ACM device. We
// actually need the data interface because that is the one that the
// IOSerialBSDClient lives under. If this +1 causes any problems, it is
// easy for the user to address it using an an ifdef. Also, we might make
// this function more flexible in the future if we need to handle different
// types of serial devices with different drivers or interface layouts.
interface_number += 1;
libusbp_error * error = NULL;
libusbp_serial_port * new_port = calloc(1, sizeof(libusbp_serial_port));
if (new_port == NULL)
{
error = &error_no_memory;
}
// Get the ID for the physical device.
uint64_t device_id;
if (error == NULL)
{
device_id = device_get_id(device);
}
// Get an io_service_t for the physical device.
io_service_t device_service = MACH_PORT_NULL;
if (error == NULL)
{
error = service_get_from_id(device_id, &device_service);
}
// Get an io_service_t for the interface.
io_service_t interface_service = MACH_PORT_NULL;
if (error == NULL)
{
error = service_get_usb_interface(device_service, interface_number, &interface_service);
}
// Get an io_service_t for the IOSerialBSDClient
io_service_t serial_service = MACH_PORT_NULL;
if (error == NULL)
{
error = service_get_child_by_class(interface_service,
kIOSerialBSDServiceValue, &serial_service);
}
// Get the port name.
if (error == NULL)
{
error = get_string(serial_service, CFSTR(kIOCalloutDeviceKey), &new_port->port_name);
}
// Pass the new object to the caller.
if (error == NULL)
{
*port = new_port;
new_port = NULL;
}
if (serial_service != MACH_PORT_NULL) { IOObjectRelease(serial_service); }
if (interface_service != MACH_PORT_NULL) { IOObjectRelease(interface_service); }
if (device_service != MACH_PORT_NULL) { IOObjectRelease(device_service); }
libusbp_serial_port_free(new_port);
return error;
}
void libusbp_serial_port_free(libusbp_serial_port * port)
{
if (port != NULL)
{
libusbp_string_free(port->port_name);
free(port);
}
}
libusbp_error * libusbp_serial_port_copy(const libusbp_serial_port * source,
libusbp_serial_port ** dest)
{
if (dest == NULL)
{
return error_create("Serial port output pointer is null.");
}
*dest = NULL;
if (source == NULL)
{
return NULL;
}
libusbp_error * error = NULL;
// Allocate memory for the new object.
libusbp_serial_port * new_port = calloc(1, sizeof(libusbp_serial_port));
if (new_port == NULL)
{
error = &error_no_memory;
}
// Copy the port name.
if (error == NULL)
{
error = string_copy(source->port_name, &new_port->port_name);
}
// Pass the new object to the caller.
if (error == NULL)
{
*dest = new_port;
new_port = NULL;
}
libusbp_serial_port_free(new_port);
return error;
}
libusbp_error * libusbp_serial_port_get_name(
const libusbp_serial_port * port,
char ** name)
{
if (name == NULL)
{
return error_create("String output pointer is null.");
}
*name = NULL;
if (port == NULL)
{
return error_create("Serial port is null.");
}
return string_copy(port->port_name, name);
}

33
dep/libusbp/src/pipe_id.c Normal file
View File

@@ -0,0 +1,33 @@
#include <libusbp_internal.h>
static libusbp_error * error_invalid_pipe_id(uint8_t pipe_id)
{
return error_create("Invalid pipe ID 0x%02x.", pipe_id);
}
libusbp_error * check_pipe_id(uint8_t pipe_id)
{
if ((pipe_id & ~0x80) > MAX_ENDPOINT_NUMBER || pipe_id == 0x80)
{
return error_invalid_pipe_id(pipe_id);
}
return NULL;
}
libusbp_error * check_pipe_id_in(uint8_t pipe_id)
{
if (!(pipe_id & 0x80))
{
return error_invalid_pipe_id(pipe_id);
}
return check_pipe_id(pipe_id);
}
libusbp_error * check_pipe_id_out(uint8_t pipe_id)
{
if (pipe_id & 0x80)
{
return error_invalid_pipe_id(pipe_id);
}
return check_pipe_id(pipe_id);
}

25
dep/libusbp/src/string.c Normal file
View File

@@ -0,0 +1,25 @@
#include <libusbp_internal.h>
// Simple wrapper around strdup to make a copy of a string, either for internal
// use or for returning the string to the user.
libusbp_error * string_copy(const char * input_string, char ** output_string)
{
assert(input_string != NULL);
assert(output_string != NULL);
*output_string = NULL;
char * new_string = strdup(input_string);
if (new_string == NULL)
{
return &error_no_memory;
}
*output_string = new_string;
return NULL;
}
void libusbp_string_free(char * string)
{
free(string);
}

View File

@@ -0,0 +1,239 @@
#include <libusbp_internal.h>
struct async_in_transfer
{
HANDLE winusb_handle;
uint8_t pipe_id;
void * buffer;
size_t buffer_size;
OVERLAPPED overlapped;
ULONG transferred;
bool pending;
libusbp_error * error;
};
libusbp_error * async_in_pipe_setup(libusbp_generic_handle * handle, uint8_t pipe_id)
{
assert(handle != NULL);
HANDLE winusb_handle = libusbp_generic_handle_get_winusb_handle(handle);
// Turn on raw I/O, because without it the transfers will not be efficient
// enough in Windows Vista and Windows 7, as tested by test_async_in.
UCHAR raw_io = 1;
BOOL success = WinUsb_SetPipePolicy(winusb_handle, pipe_id, RAW_IO,
sizeof(raw_io), &raw_io);
if (!success)
{
return error_create_winapi("Failed to enable raw I/O for pipe 0x%x.", pipe_id);
}
return NULL;
}
void async_in_transfer_free(async_in_transfer * transfer)
{
if (transfer == NULL) { return; }
if (transfer->pending)
{
// Unfortunately, this transfer is still pending, so we cannot free it;
// the kernel needs to be able to write to this transfer's memory when
// it completes. We could just abort the process here, but instead we
// we choose to have a memory leak, since it can be easily detected and
// might be small enough to be harmless.
return;
}
libusbp_error_free(transfer->error);
CloseHandle(transfer->overlapped.hEvent);
free(transfer->buffer);
free(transfer);
}
void async_in_transfer_submit(async_in_transfer * transfer)
{
assert(transfer != NULL);
assert(transfer->pending == false);
libusbp_error_free(transfer->error);
transfer->error = NULL;
transfer->pending = true;
transfer->transferred = 0;
BOOL success = WinUsb_ReadPipe(
transfer->winusb_handle,
transfer->pipe_id,
transfer->buffer,
transfer->buffer_size,
&transfer->transferred,
&transfer->overlapped);
if (success)
{
// The transfer completed immediately.
transfer->pending = false;
}
else if (GetLastError() == ERROR_IO_PENDING)
{
// The transfer is pending.
}
else
{
// An error happened.
transfer->error = error_create_winapi("Failed to submit asynchronous in transfer.");
transfer->pending = false;
}
}
libusbp_error * async_in_transfer_create(
libusbp_generic_handle * handle, uint8_t pipe_id, size_t transfer_size,
async_in_transfer ** transfer)
{
assert(transfer_size != 0);
assert(transfer != NULL);
if (transfer_size > ULONG_MAX)
{
// WinUSB uses ULONGs to represent sizes.
return error_create("Transfer size is too large.");
}
libusbp_error * error = NULL;
// Allocate the transfer struct.
async_in_transfer * new_transfer = NULL;
if (error == NULL)
{
new_transfer = calloc(1, sizeof(async_in_transfer));
if (new_transfer == NULL)
{
error = &error_no_memory;
}
}
// Allocate the buffer for the transfer.
void * new_buffer = NULL;
if (error == NULL)
{
new_buffer = malloc(transfer_size);
if (new_buffer == NULL)
{
error = &error_no_memory;
}
}
// Create an event.
HANDLE new_event = INVALID_HANDLE_VALUE;
if (error == NULL)
{
new_event = CreateEvent(NULL, false, false, NULL);
if (new_event == NULL)
{
error = error_create_winapi(
"Failed to create event for asynchronous in transfer.");
}
}
// Assemble the transfer and pass it to the caller.
if (error == NULL)
{
new_transfer->winusb_handle = libusbp_generic_handle_get_winusb_handle(handle);
new_transfer->buffer_size = transfer_size;
new_transfer->pipe_id = pipe_id;
new_transfer->buffer = new_buffer;
new_buffer = NULL;
new_transfer->overlapped.hEvent = new_event;
new_event = INVALID_HANDLE_VALUE;
*transfer = new_transfer;
new_transfer = NULL;
}
if (new_event != INVALID_HANDLE_VALUE) { CloseHandle(new_event); }
free(new_buffer);
free(new_transfer);
return error;
}
libusbp_error * async_in_transfer_get_results(async_in_transfer * transfer,
void * buffer, size_t * transferred, libusbp_error ** transfer_error)
{
assert(transfer != NULL);
assert(!transfer->pending);
size_t tmp_transferred = transfer->transferred;
// Make sure we don't overflow the user's buffer.
if (tmp_transferred > transfer->buffer_size)
{
tmp_transferred = transfer->buffer_size;
}
if (buffer != NULL)
{
memcpy(buffer, transfer->buffer, tmp_transferred);
}
if (transferred != NULL)
{
*transferred = tmp_transferred;
}
if (transfer_error != NULL)
{
*transfer_error = libusbp_error_copy(transfer->error);
}
return NULL;
}
// Cancels all of the transfers on this pipe, given one of the transfers.
libusbp_error * async_in_transfer_cancel(async_in_transfer * transfer)
{
if (transfer == NULL)
{
return error_create("Transfer to cancel is null.");
}
BOOL success = WinUsb_AbortPipe(transfer->winusb_handle, transfer->pipe_id);
if (!success)
{
return error_create_winapi("Failed to cancel transfers on pipe 0x%x.",
transfer->pipe_id);
}
return NULL;
}
bool async_in_transfer_pending(async_in_transfer * transfer)
{
assert(transfer != NULL);
if (!transfer->pending)
{
return false;
}
DWORD transferred = 0;
BOOL success = WinUsb_GetOverlappedResult(
transfer->winusb_handle, &transfer->overlapped, &transferred, false);
transfer->transferred = transferred;
if (success)
{
transfer->pending = false;
}
else if (GetLastError() == ERROR_IO_INCOMPLETE)
{
// The transfer is still pending.
}
else
{
transfer->error = error_create_overlapped("Asynchronous IN transfer failed.");
transfer->pending = false;
}
return transfer->pending;
}

View File

@@ -0,0 +1,25 @@
#include <libusbp_internal.h>
libusbp_error * create_id_string(HDEVINFO list, PSP_DEVINFO_DATA info, char ** id)
{
assert(list != INVALID_HANDLE_VALUE);
assert(info != NULL);
assert(id != NULL);
DWORD size = MAX_DEVICE_ID_LEN + 1;
char * new_id = malloc(size);
if (new_id == NULL)
{
return &error_no_memory;
}
bool success = SetupDiGetDeviceInstanceId(list, info, new_id, size, NULL);
if (!success)
{
free(new_id);
return error_create_winapi("Error getting device instance ID.");
}
*id = new_id;
return NULL;
}

View File

@@ -0,0 +1,362 @@
#include <libusbp_internal.h>
struct libusbp_device
{
// A string like: USB\VID_xxxx&PID_xxxx\idthing
char * device_instance_id;
uint16_t product_id;
uint16_t vendor_id;
uint16_t revision;
};
static libusbp_error * device_allocate(libusbp_device ** device)
{
assert(device != NULL);
*device = calloc(1, sizeof(libusbp_device));
if (*device == NULL)
{
return &error_no_memory;
}
return NULL;
}
// Gets the hardware IDs of the device, in ASCII REG_MULTI_SZ format. If there
// is no error, the returned IDs must be freed with libusbp_string_free.
// This function converts the IDs to uppercase before returning them.
static libusbp_error * device_get_hardware_ids(
HDEVINFO list, PSP_DEVINFO_DATA info, char ** ids)
{
assert(list != INVALID_HANDLE_VALUE);
assert(info != NULL);
assert(ids != NULL);
*ids = NULL;
libusbp_error * error = NULL;
// Get the size of the hardware IDs.
DWORD data_type = 0;
DWORD size = 0;
if (error == NULL)
{
BOOL success = SetupDiGetDeviceRegistryProperty(list, info,
SPDRP_HARDWAREID, &data_type, NULL, 0, &size);
if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
error = error_create_winapi("Failed to get size of hardware IDs.");
}
}
// Allocate memory.
char * new_ids = NULL;
if (error == NULL)
{
new_ids = malloc(size);
if (new_ids == NULL)
{
error = &error_no_memory;
}
}
// Get the actual hardware IDs.
if (error == NULL)
{
BOOL success = SetupDiGetDeviceRegistryProperty(list, info,
SPDRP_HARDWAREID, &data_type, (unsigned char *)new_ids, size, NULL);
if (!success)
{
error = error_create_winapi("Failed to get hardware IDs.");
}
}
// Check the data type.
if (error == NULL && data_type != REG_MULTI_SZ)
{
error = error_create("Hardware IDs are wrong data type: %ld.", data_type);
}
if (error == NULL && (size < 2 || new_ids[size - 2] != 0 || new_ids[size - 1] != 0))
{
error = error_create("Hardware IDs are empty or not terminated correctly.");
}
// Capitalize the hardware IDs because some drivers create USB IDs with the
// wrong capitalization (i.e. "Vid" instead of "VID").
if (error == NULL)
{
for (DWORD i = 0; i < size; i++)
{
if (new_ids[i] >= 'a' && new_ids[i] <= 'z')
{
new_ids[i] -= 'a' - 'A';
}
}
}
// Pass the IDs to the caller.
if (error == NULL)
{
*ids = new_ids;
new_ids = NULL;
}
free(new_ids);
return error;
}
// This function extracts the product ID, vendor ID, and revision code
// from the hardware IDs.
// The "ids" parmaeter must be a pointer to a REG_MULTI_SZ data structure.
// (null-terminated ASCII strings, ending with an empty string).
static libusbp_error * device_take_info_from_hardware_ids(
libusbp_device * device, const char * ids)
{
assert(ids != NULL);
assert(device != NULL);
device->vendor_id = 0;
device->product_id = 0;
device->revision = 0;
for(; *ids; ids += strlen(ids) + 1)
{
uint16_t vendor_id, product_id, revision;
int result = sscanf(ids, "USB\\VID_%4hx&PID_%4hx&REV_%4hx",
&vendor_id, &product_id, &revision);
if (result == 3)
{
device->vendor_id = vendor_id;
device->product_id = product_id;
device->revision = revision;
return NULL;
}
}
return error_create("Device has no hardware ID with the correct format.");
}
libusbp_error * create_device(HDEVINFO list, PSP_DEVINFO_DATA info, libusbp_device ** device)
{
assert(list != INVALID_HANDLE_VALUE);
assert(info != NULL);
assert(info->cbSize == sizeof(SP_DEVINFO_DATA));
assert(device != NULL);
*device = NULL;
libusbp_error * error = NULL;
// Allocate memory for the device itself.
libusbp_device * new_device = NULL;
if (error == NULL)
{
error = device_allocate(&new_device);
}
// Get the device instance ID.
if (error == NULL)
{
error = create_id_string(list, info, &new_device->device_instance_id);
}
// Get the vendor ID, product ID, and revision from the hardware IDs.
char * new_ids = NULL;
if (error == NULL)
{
error = device_get_hardware_ids(list, info, &new_ids);
}
if (error == NULL)
{
error = device_take_info_from_hardware_ids(new_device, new_ids);
}
// Return the device to the caller.
if (error == NULL)
{
*device = new_device;
new_device = NULL;
}
libusbp_string_free(new_ids);
libusbp_device_free(new_device);
return error;
}
libusbp_error * libusbp_device_copy(const libusbp_device * source, libusbp_device ** dest)
{
if (dest == NULL)
{
return error_create("Device output pointer is null.");
}
*dest = NULL;
if (source == NULL)
{
return NULL;
}
libusbp_error * error = NULL;
// Allocate memory for the device itself.
libusbp_device * new_device = NULL;
if (error == NULL)
{
error = device_allocate(&new_device);
}
// Copy the device instance ID.
if (error == NULL)
{
assert(source->device_instance_id != NULL);
size_t size = strlen(source->device_instance_id) + 1;
char * new_id = malloc(size);
if (new_id == NULL)
{
error = &error_no_memory;
}
else
{
memcpy(new_id, source->device_instance_id, size);
new_device->device_instance_id = new_id;
}
}
if (error == NULL)
{
new_device->vendor_id = source->vendor_id;
new_device->product_id = source->product_id;
new_device->revision = source->revision;
}
// Pass the device to the caller.
if (error == NULL)
{
*dest = new_device;
new_device = NULL;
}
libusbp_device_free(new_device);
return error;
}
// Note: Some pointers in the device might be null when we are
// freeing, because this can be called from the library to clean up a
// failed device creation.
void libusbp_device_free(libusbp_device * device)
{
if (device != NULL)
{
free(device->device_instance_id);
free(device);
}
}
libusbp_error * libusbp_device_get_vendor_id(
const libusbp_device * device, uint16_t * vendor_id)
{
if (vendor_id == NULL)
{
return error_create("Vendor ID output pointer is null.");
}
*vendor_id = 0;
if (device == NULL)
{
return error_create("Device is null.");
}
*vendor_id = device->vendor_id;
return NULL;
}
libusbp_error * libusbp_device_get_product_id(
const libusbp_device * device, uint16_t * product_id)
{
if (product_id == NULL)
{
return error_create("Product ID output pointer is null.");
}
*product_id = 0;
if (device == NULL)
{
return error_create("Device is null.");
}
*product_id = device->product_id;
return NULL;
}
libusbp_error * libusbp_device_get_revision(
const libusbp_device * device, uint16_t * revision)
{
if (revision == NULL)
{
return error_create("Device revision output pointer is null.");
}
*revision = 0;
if (device == NULL)
{
return error_create("Device is null.");
}
*revision = device->revision;
return NULL;
}
libusbp_error * libusbp_device_get_serial_number(
const libusbp_device * device, char ** serial_number)
{
if (serial_number == NULL)
{
return error_create("Serial number output pointer is null.");
}
*serial_number = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
libusbp_error * error = NULL;
if (error == NULL && strlen(device->device_instance_id) < 22)
{
error = error_create("Device instance ID is too short.");
}
if (error == NULL)
{
error = string_copy(device->device_instance_id + 22, serial_number);
}
if (error != NULL)
{
error = error_add(error, "Failed to get serial number.");
}
return error;
}
libusbp_error * libusbp_device_get_os_id(
const libusbp_device * device, char ** id)
{
if (id == NULL)
{
return error_create("Device OS ID output pointer is null.");
}
*id = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
return string_copy(device->device_instance_id, id);
}

View File

@@ -0,0 +1,111 @@
/* This file has functions for converting Windows error codes libusbp_error
* objects. See error_message_test.cpp for documentation/justification of the
* conversions that are performed. */
#include <libusbp_internal.h>
static libusbp_error * error_create_winapi_v(
DWORD error_code, const char * format, va_list ap)
{
libusbp_error * error = error_create("Windows error code 0x%lx.", error_code);
bool skip_windows_message = false;
// Convert certain Windows error codes into libusbp error codes.
switch(error_code)
{
case ERROR_ACCESS_DENIED:
error = error_add(error, "Try closing all other programs that are using the device.");
error = error_add_code(error, LIBUSBP_ERROR_ACCESS_DENIED);
break;
case ERROR_OUTOFMEMORY:
case ERROR_NOT_ENOUGH_MEMORY:
error = error_add_code(error, LIBUSBP_ERROR_MEMORY);
break;
case ERROR_GEN_FAILURE:
skip_windows_message = true;
error = error_add(error, "The request was invalid or there was an I/O problem.");
error = error_add_code(error, LIBUSBP_ERROR_STALL);
error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
break;
case ERROR_SEM_TIMEOUT:
skip_windows_message = true;
error = error_add(error, "The operation timed out.");
error = error_add_code(error, LIBUSBP_ERROR_TIMEOUT);
break;
case ERROR_OPERATION_ABORTED:
skip_windows_message = true;
error = error_add(error, "The operation was cancelled.");
error = error_add_code(error, LIBUSBP_ERROR_CANCELLED);
break;
}
// Get an English sentence from Windows to help describe the error.
if (!skip_windows_message)
{
char buffer[256];
DWORD size = FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_MAX_WIDTH_MASK,
NULL, error_code,
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), buffer,
sizeof(buffer), NULL);
if (size != 0)
{
// Remove the leading space and then add the message to the error.
if (buffer[size - 1] == ' ') { buffer[--size] = 0; }
error = error_add(error, "%s", buffer);
}
}
// Finally, add the context message provided by the caller.
return error_add_v(error, format, ap);
}
libusbp_error * error_create_winapi(const char * format, ...)
{
DWORD error_code = GetLastError();
va_list ap;
va_start(ap, format);
libusbp_error * error = error_create_winapi_v(error_code, format, ap);
va_end(ap);
return error;
}
// This is for reporting an error from WinUsb_GetOverlappedResult. This
// function is necessary because ERROR_FILE_NOT_FOUND has a special meaning if
// it is the result of an asynchronous USB transfer.
libusbp_error * error_create_overlapped(const char * format, ...)
{
DWORD error_code = GetLastError();
va_list ap;
va_start(ap, format);
libusbp_error * error = NULL;
switch(error_code)
{
case ERROR_FILE_NOT_FOUND:
error = error_create("Windows error code 0x%lx.", error_code);
error = error_add(error, "The device was disconnected.");
error = error_add_code(error, LIBUSBP_ERROR_DEVICE_DISCONNECTED);
error = error_add_v(error, format, ap);
break;
default:
error = error_create_winapi_v(error_code, format, ap);
break;
}
va_end(ap);
return error;
}
libusbp_error * error_create_cr(CONFIGRET cr, const char * format, ...)
{
libusbp_error * error = error_create("CONFIGRET error code 0x%lx.", cr);
va_list ap;
va_start(ap, format);
error = error_add_v(error, format, ap);
va_end(ap);
return error;
}

View File

@@ -0,0 +1,316 @@
#include <libusbp_internal.h>
// TODO: add a test for the issue where WinUSB cannot send data from read-only
// memory, then add a note about it to the documentation for
// libusbp_control_transfer and PLATFORM_NOTES.md.
struct libusbp_generic_handle
{
HANDLE file_handle;
WINUSB_INTERFACE_HANDLE winusb_handle;
};
libusbp_error * libusbp_generic_handle_open(
const libusbp_generic_interface * gi,
libusbp_generic_handle ** gh
)
{
if (gh == NULL)
{
return error_create("Generic handle output pointer is null.");
}
*gh = NULL;
if (gi == NULL)
{
return error_create("Generic interface is null.");
}
libusbp_error * error = NULL;
// Allocate memory for the struct.
libusbp_generic_handle * new_gh = malloc(sizeof(libusbp_generic_handle));
if (new_gh == NULL)
{
error = &error_no_memory;
}
// Initialize the handles so we can clean up properly in case an error
// happens.
if (error == NULL)
{
new_gh->file_handle = INVALID_HANDLE_VALUE;
new_gh->winusb_handle = INVALID_HANDLE_VALUE;
}
// Get the filename.
char * filename = NULL;
if (error == NULL)
{
error = libusbp_generic_interface_get_os_filename(gi, &filename);
}
// Open the file. We have observed this step failing with error code
// ERROR_NOT_FOUND if the device was recently unplugged.
if (error == NULL)
{
new_gh->file_handle = CreateFile(filename,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (new_gh->file_handle == INVALID_HANDLE_VALUE)
{
error = error_create_winapi("Failed to open generic handle.");
}
}
// Initialize WinUSB.
if (error == NULL)
{
BOOL success = WinUsb_Initialize(new_gh->file_handle, &new_gh->winusb_handle);
if (!success)
{
error = error_create_winapi("Failed to initialize WinUSB.");
}
}
if (error == NULL)
{
// Success.
*gh = new_gh;
new_gh = NULL;
}
libusbp_string_free(filename);
libusbp_generic_handle_close(new_gh);
return error;
}
void libusbp_generic_handle_close(libusbp_generic_handle * gh)
{
if (gh != NULL)
{
if (gh->winusb_handle != INVALID_HANDLE_VALUE)
{
WinUsb_Free(gh->winusb_handle);
}
if (gh->file_handle != INVALID_HANDLE_VALUE)
{
CloseHandle(gh->file_handle);
}
free(gh);
}
}
libusbp_error * libusbp_generic_handle_open_async_in_pipe(
libusbp_generic_handle * handle,
uint8_t pipe_id,
libusbp_async_in_pipe ** pipe)
{
return async_in_pipe_create(handle, pipe_id, pipe);
}
libusbp_error * libusbp_generic_handle_set_timeout(
libusbp_generic_handle * gh,
uint8_t pipe_id,
uint32_t timeout)
{
if (gh == NULL)
{
return error_create("Generic handle is null.");
}
ULONG winusb_timeout = timeout;
BOOL success = WinUsb_SetPipePolicy(gh->winusb_handle, pipe_id,
PIPE_TRANSFER_TIMEOUT, sizeof(winusb_timeout), &winusb_timeout);
if (!success)
{
return error_create_winapi("Failed to set timeout.");
}
return NULL;
}
libusbp_error * libusbp_control_transfer(
libusbp_generic_handle * gh,
uint8_t bmRequestType,
uint8_t bRequest,
uint16_t wValue,
uint16_t wIndex,
void * data,
uint16_t wLength,
size_t * transferred)
{
if (transferred != NULL)
{
*transferred = 0;
}
if (gh == NULL)
{
return error_create("Generic handle is null.");
}
if (wLength != 0 && data == NULL)
{
return error_create("Control transfer buffer was null while wLength was non-zero.");
}
WINUSB_SETUP_PACKET packet;
packet.RequestType = bmRequestType;
packet.Request = bRequest;
packet.Value = wValue;
packet.Index = wIndex;
packet.Length = wLength;
ULONG winusb_transferred;
BOOL success = WinUsb_ControlTransfer(gh->winusb_handle, packet, data,
wLength, &winusb_transferred, NULL);
if (!success)
{
return error_create_winapi("Control transfer failed.");
}
if (transferred)
{
*transferred = winusb_transferred;
}
return NULL;
}
libusbp_error * libusbp_write_pipe(
libusbp_generic_handle * handle,
uint8_t pipe_id,
const void * data,
size_t size,
size_t * transferred)
{
if (transferred != NULL)
{
*transferred = 0;
}
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
libusbp_error * error = NULL;
if (error == NULL)
{
error = check_pipe_id_out(pipe_id);
}
if (error == NULL && size > ULONG_MAX)
{
error = error_create("Transfer size is too large.");
}
if (error == NULL && data == NULL && size)
{
error = error_create("Buffer is null.");
}
ULONG winusb_transferred = 0;
if (error == NULL)
{
BOOL success = WinUsb_WritePipe(handle->winusb_handle, pipe_id,
(uint8_t *)data, size, &winusb_transferred, NULL);
if (!success)
{
error = error_create_winapi("");
}
}
if (transferred)
{
*transferred = winusb_transferred;
}
if (error != NULL)
{
error = error_add(error, "Failed to write to pipe.");
}
return error;
}
libusbp_error * libusbp_read_pipe(
libusbp_generic_handle * handle,
uint8_t pipe_id,
void * data,
size_t size,
size_t * transferred)
{
if (transferred != NULL)
{
*transferred = 0;
}
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
libusbp_error * error = NULL;
if (error == NULL)
{
error = check_pipe_id_in(pipe_id);
}
if (error == NULL && size == 0)
{
error = error_create("Transfer size 0 is not allowed.");
}
if (error == NULL && size > ULONG_MAX)
{
error = error_create("Transfer size is too large.");
}
if (error == NULL && data == NULL)
{
error = error_create("Buffer is null.");
}
ULONG winusb_transferred = 0;
if (error == NULL)
{
BOOL success = WinUsb_ReadPipe(handle->winusb_handle, pipe_id, data,
size, &winusb_transferred, NULL);
if (!success)
{
error = error_create_winapi("");
}
}
if (transferred)
{
*transferred = winusb_transferred;
}
if (error != NULL)
{
error = error_add(error, "Failed to read from pipe.");
}
return error;
}
HANDLE libusbp_generic_handle_get_winusb_handle(libusbp_generic_handle * handle)
{
if (handle == NULL) { return INVALID_HANDLE_VALUE; }
return handle->winusb_handle;
}
libusbp_error * generic_handle_events(libusbp_generic_handle * handle)
{
LIBUSBP_UNUSED(handle);
return NULL;
}

View File

@@ -0,0 +1,366 @@
#include <libusbp_internal.h>
struct libusbp_generic_interface
{
uint8_t interface_number;
char * device_instance_id;
char * filename;
};
// Returns NULL via the driver_name parameter if no driver is installed.
// Otherwise returns the driver name.
static libusbp_error * get_driver_name(
HDEVINFO list, PSP_DEVINFO_DATA info, char ** driver_name)
{
assert(list != INVALID_HANDLE_VALUE);
assert(info != NULL);
assert(driver_name != NULL);
*driver_name = NULL;
// Get the "Service" key from the registry, which will say what
// Windows service is assigned to this device.
CHAR service[64];
DWORD data_type;
bool success = SetupDiGetDeviceRegistryProperty(list, info, SPDRP_SERVICE,
&data_type, (BYTE *)&service, sizeof(service), NULL);
if (!success)
{
if (GetLastError() == ERROR_INVALID_DATA)
{
// The registry key isn't present, so the device does not
// have have a driver.
return NULL;
}
else
{
return error_create_winapi("Failed to get the service name.");
}
}
if (data_type != REG_SZ)
{
return error_create("Service name is the wrong data type: %ld.", data_type);
}
return string_copy(service, driver_name);
}
// Checks to see if a suitable driver for a USB generic interface is installed
// by reading the Service registry key.
static libusbp_error * check_driver_installation(
HDEVINFO list, PSP_DEVINFO_DATA info,
bool * good_driver_installed, bool * bad_driver_installed,
char ** driver_name)
{
assert(list != INVALID_HANDLE_VALUE);
assert(info != NULL);
assert(good_driver_installed != NULL);
assert(bad_driver_installed != NULL);
assert(driver_name != NULL);
*good_driver_installed = false;
*bad_driver_installed = false;
*driver_name = NULL;
libusbp_error * error;
error = get_driver_name(list, info, driver_name);
if (error != NULL)
{
return error;
}
if (*driver_name == NULL)
{
// No driver installed.
return NULL;
}
// We really don't want this comparison to be affected by the user's locale,
// since that will probably just mess things up.
int result = CompareString(LOCALE_INVARIANT, NORM_IGNORECASE,
*driver_name, -1, "winusb", -1);
if (result == CSTR_EQUAL)
{
*good_driver_installed = true;
return NULL;
}
*bad_driver_installed = true;
return NULL;
}
static libusbp_error * get_first_device_interface_guid(HDEVINFO list,
PSP_DEVINFO_DATA info, GUID * guid)
{
assert(list != INVALID_HANDLE_VALUE);
assert(info != NULL);
assert(guid != NULL);
HANDLE key = SetupDiOpenDevRegKey(list, info, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
if (key == INVALID_HANDLE_VALUE)
{
return error_create_winapi(
"Failed to get device registry key in order to find its device interface GUIDs.");
}
// Get the size of the DeviceInterfaceGUIDs key.
// (Partial reads are not allowed.)
DWORD size;
LONG reg_result;
reg_result = RegQueryValueExW(key, L"DeviceInterfaceGUIDs", NULL, NULL, NULL, &size);
if (reg_result != ERROR_SUCCESS)
{
if (reg_result == ERROR_FILE_NOT_FOUND)
{
RegCloseKey(key);
return error_create("DeviceInterfaceGUIDs key does not exist.");
}
else
{
RegCloseKey(key);
SetLastError(reg_result);
return error_create_winapi("Failed to get DeviceInterfaceGUIDs key size.");
}
}
WCHAR * guids = malloc(size);
if (guids == NULL)
{
return &error_no_memory;
}
DWORD reg_type;
reg_result = RegQueryValueExW(key, L"DeviceInterfaceGUIDs",
NULL, &reg_type, (BYTE *)guids, &size);
RegCloseKey(key);
if (reg_result)
{
free(guids);
SetLastError(reg_result);
return error_create_winapi("Failed to get DeviceInterfaceGUIDs key.");
}
if (reg_type != REG_MULTI_SZ)
{
free(guids);
return error_create(
"Expected DeviceInterfaceGUIDs key to be a REG_MULTI_SZ (0x%x), got 0x%lx.",
REG_MULTI_SZ, reg_type);
}
HRESULT hr = IIDFromString(guids, guid);
free(guids);
if (FAILED(hr))
{
return error_create_hr(hr, "Failed to parse device interface GUID.");
}
return NULL;
}
static libusbp_error * generic_interface_initialize(libusbp_generic_interface * gi,
const libusbp_device * device, uint8_t interface_number, bool composite)
{
assert(gi != NULL);
assert(device != NULL);
// First, initialize everything so that the struct can safely be
// freed if something goes wrong.
gi->interface_number = interface_number;
gi->device_instance_id = NULL;
gi->filename = NULL;
libusbp_error * error = NULL;
// Get the device instance ID of the overall USB device.
char * usb_device_id;
if (error == NULL)
{
error = libusbp_device_get_os_id(device, &usb_device_id);
}
// Access the generic interface device node with SetupAPI.
HDEVINFO list = INVALID_HANDLE_VALUE;
SP_DEVINFO_DATA device_info_data;
if (error == NULL)
{
error = get_interface(usb_device_id, interface_number,
composite, &list, &device_info_data);
}
// Record the device instance ID.
if (error == NULL)
{
error = create_id_string(list, &device_info_data, &gi->device_instance_id);
}
// Check the driver situation.
char * driver_name = NULL;
if (error == NULL)
{
bool good_driver_installed = false;
bool bad_driver_installed = false;
error = check_driver_installation(list, &device_info_data,
&good_driver_installed, &bad_driver_installed, &driver_name);
if (error == NULL && bad_driver_installed)
{
error = error_create("Device is attached to an incorrect driver: %s.", driver_name);
}
if (error == NULL && !good_driver_installed)
{
error = error_create("Device is not using any driver.");
error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
}
}
// Get the first device interface GUID.
GUID guid;
if (error == NULL)
{
error = get_first_device_interface_guid(list, &device_info_data, &guid);
}
// Use that GUID to get an actual filename that we can later open
// with CreateFile to access the device.
if (error == NULL)
{
error = get_filename_from_devinst_and_guid(device_info_data.DevInst,
&guid, &gi->filename);
}
// Clean up.
if (list != INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(list);
}
libusbp_string_free(driver_name);
libusbp_string_free(usb_device_id);
if (error != NULL)
{
error = error_add(error, "Failed to initialize generic interface.");
}
return error;
}
libusbp_error * libusbp_generic_interface_create(
const libusbp_device * device,
uint8_t interface_number,
bool composite,
libusbp_generic_interface ** gi)
{
if (gi == NULL)
{
return error_create("Generic interface output pointer is null.");
}
*gi = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
libusbp_generic_interface * new_gi = malloc(sizeof(libusbp_generic_interface));
if (new_gi == NULL)
{
return &error_no_memory;
}
libusbp_error * error = generic_interface_initialize(new_gi, device,
interface_number, composite);
if (error)
{
libusbp_generic_interface_free(new_gi);
return error;
}
*gi = new_gi;
return NULL;
}
libusbp_error * libusbp_generic_interface_copy(
const libusbp_generic_interface * source,
libusbp_generic_interface ** dest
)
{
if (dest == NULL)
{
return error_create("Generic interface output pointer is null.");
}
*dest = NULL;
if (source == NULL)
{
return NULL;
}
assert(source->device_instance_id);
assert(source->filename);
libusbp_generic_interface * new_gi = calloc(1, sizeof(libusbp_generic_interface));
char * id = strdup(source->device_instance_id);
char * filename = strdup(source->filename);
if (new_gi == NULL || id == NULL || filename == NULL)
{
free(new_gi);
free(id);
free(filename);
return &error_no_memory;
}
new_gi->interface_number = source->interface_number;
new_gi->device_instance_id = id;
new_gi->filename = filename;
*dest = new_gi;
return NULL;
}
void libusbp_generic_interface_free(libusbp_generic_interface * gi)
{
if (gi != NULL)
{
free(gi->device_instance_id);
free(gi->filename);
free(gi);
}
}
libusbp_error * libusbp_generic_interface_get_os_id(
const libusbp_generic_interface * gi,
char ** id)
{
if (id == NULL)
{
return error_create("String output pointer is null.");
}
*id = NULL;
if (gi == NULL)
{
return error_create("Generic interface is null.");
}
return string_copy(gi->device_instance_id, id);
}
libusbp_error * libusbp_generic_interface_get_os_filename(
const libusbp_generic_interface * gi,
char ** filename)
{
if (filename == NULL)
{
return error_create("String output pointer is null.");
}
*filename = NULL;
if (gi == NULL)
{
return error_create("Generic interface is null.");
}
return string_copy(gi->filename, filename);
}

View File

@@ -0,0 +1,322 @@
// If we are compiling under MSVC, this is the file that will define GUID
// symbols we need such as GUID_DEVINTERFACE_USB_DEVICE. This prevents
// an unresolved symbol error at link time.
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#include <initguid.h>
#endif
#include <libusbp_internal.h>
static libusbp_error * get_interface_non_composite(
const char * device_instance_id,
HDEVINFO * list,
PSP_DEVINFO_DATA info)
{
*list = INVALID_HANDLE_VALUE;
// This is a non-composite device, so we just want to find a device
// with the same device instance ID.
libusbp_error * error = NULL;
char id[MAX_DEVICE_ID_LEN + 1];
HDEVINFO new_list = INVALID_HANDLE_VALUE;
if (error == NULL)
{
new_list = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0,
DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
if (list == INVALID_HANDLE_VALUE)
{
error = error_create_winapi("Failed to list all USB devices.");
}
}
if (error == NULL)
{
for(DWORD i = 0; ; i++)
{
SP_DEVINFO_DATA device_info_data;
device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
bool success = SetupDiEnumDeviceInfo(new_list, i, &device_info_data);
if (!success)
{
if (GetLastError() == ERROR_NO_MORE_ITEMS)
{
error = error_create("Failed to find device node.");
}
else
{
error = error_create_winapi("Failed to enumerate device info.");
}
break;
}
success = SetupDiGetDeviceInstanceId(new_list, &device_info_data,
id, sizeof(id), NULL);
if (!success)
{
error = error_create_winapi("Failed to get device instance ID.");
break;
}
if (strcmp(id, device_instance_id) == 0)
{
// We found the device; success.
*list = new_list;
new_list = INVALID_HANDLE_VALUE;
*info = device_info_data;
break;
}
}
}
if (new_list != INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(new_list);
}
if (error != NULL)
{
error = error_add(error, "Failed to find non-composite device node.");
}
return error;
}
static libusbp_error * get_interface_composite(
const char * device_instance_id,
uint8_t interface_number,
HDEVINFO * list,
PSP_DEVINFO_DATA info)
{
// This is a composite device, and we need to find a device
// whose parent has the specified device_instance_id, and
// whose own device instance ID has "MI_xx" where xx is the
// hex representation of interface_number.
*list = INVALID_HANDLE_VALUE;
// Get the DEVINST for this device.
CONFIGRET cr;
DEVINST dev_inst;
cr = CM_Locate_DevNode(&dev_inst, (char *)device_instance_id, CM_LOCATE_DEVNODE_NORMAL);
if (cr != CR_SUCCESS)
{
libusbp_error * error = error_create_cr(cr,
"Failed to get device node in order to find an interface.");
// NOTE: if cr == CR_NO_SUCH_DEVNODE, that means
// The device instance ID has a valid format, but either
// the device it was referring to is unplugged or it was
// never plugged into the computer in the first place.
// This error has been seen when unplugging a USB device.
return error;
}
// Get a list of all the USB-related devices.
HDEVINFO new_list = SetupDiGetClassDevs(NULL, "USB", NULL,
DIGCF_ALLCLASSES | DIGCF_PRESENT);
if (new_list == INVALID_HANDLE_VALUE)
{
return error_create_winapi(
"Failed to get list of all USB devices while finding an interface.");
}
// Iterate through the list until we find a device whose
// parent device is ours and which controls the interface
// specified by the caller.
for (DWORD i = 0; ; i++)
{
SP_DEVINFO_DATA device_info_data;
device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
bool success = SetupDiEnumDeviceInfo(new_list, i, &device_info_data);
if (!success)
{
libusbp_error * error;
if (GetLastError() == ERROR_NO_MORE_ITEMS)
{
// Could not find the child interface. This could be
// a temporary condition.
error = error_create("Could not find interface %d.",
interface_number);
error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
}
else
{
error = error_create_winapi(
"Failed to get device info while finding an interface.");
}
SetupDiDestroyDeviceInfoList(new_list);
return error;
}
DEVINST parent_dev_inst;
cr = CM_Get_Parent(&parent_dev_inst, device_info_data.DevInst, 0);
if (cr != CR_SUCCESS)
{
SetupDiDestroyDeviceInfoList(new_list);
return error_create_cr(cr, "Failed to get parent of an interface.");
}
if (parent_dev_inst != dev_inst)
{
// This device is not a child of our device.
continue;
}
// Get the device instance ID.
char device_id[MAX_DEVICE_ID_LEN + 1];
cr = CM_Get_Device_ID(device_info_data.DevInst, device_id, sizeof(device_id), 0);
if (cr != CR_SUCCESS)
{
libusbp_error * error = error_create_cr(cr,
"Failed to get device instance ID while finding an interface.");
SetupDiDestroyDeviceInfoList(new_list);
return error;
}
unsigned int actual_interface_number;
int result = sscanf(device_id, "USB\\VID_%*4x&PID_%*4x&MI_%2x\\",
&actual_interface_number);
if (result != 1 || actual_interface_number != interface_number)
{
// This is not the right interface.
continue;
}
// Found the interface.
*list = new_list;
*info = device_info_data;
return NULL;
}
}
libusbp_error * get_interface(
const char * device_instance_id,
uint8_t interface_number,
bool composite,
HDEVINFO * list,
PSP_DEVINFO_DATA info)
{
assert(device_instance_id != NULL);
assert(list != NULL);
assert(info != NULL);
if (composite)
{
return get_interface_composite(device_instance_id, interface_number,
list, info);
}
else
{
return get_interface_non_composite(device_instance_id, list, info);
}
}
libusbp_error * get_filename_from_devinst_and_guid(
DEVINST devinst,
const GUID * guid,
char ** filename
)
{
assert(guid != NULL);
assert(filename != NULL);
BOOL success;
// Make a list of devices that have the specified device interface GUID.
HDEVINFO list = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
if (list == INVALID_HANDLE_VALUE)
{
return error_create_winapi(
"Failed to create a list of devices to find a device filename.");
}
// Iterate through the list looking for one that matches the given DEVINST.
SP_DEVINFO_DATA device_info_data;
for(DWORD index = 0; ; index++)
{
device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
success = SetupDiEnumDeviceInfo(list, index, &device_info_data);
if (!success)
{
if (GetLastError() == ERROR_NO_MORE_ITEMS)
{
// We reached the end of the list.
SetupDiDestroyDeviceInfoList(list);
libusbp_error * error = error_create(
"Could not find matching device in order to get its filename.");
// This is one of the errors we see when plugging in a device,
// so it could indicate that the devce is just not ready and
// Windows is still setting it up.
error = error_add_code(error, LIBUSBP_ERROR_NOT_READY);
return error;
}
libusbp_error * error = error_create_winapi(
"Failed to enumerate list item to find matching device.");
SetupDiDestroyDeviceInfoList(list);
return error;
}
if (device_info_data.DevInst == devinst)
{
// We found the matching device, so break.
break;
}
}
// Get the DeviceInterfaceData struct.
SP_DEVICE_INTERFACE_DATA device_interface_data;
device_interface_data.cbSize = sizeof(device_interface_data);
success = SetupDiEnumDeviceInterfaces(list, &device_info_data, guid, 0, &device_interface_data);
if (!success)
{
libusbp_error * error = error_create_winapi("Failed to get device interface data.");
SetupDiDestroyDeviceInfoList(list);
return error;
}
// Get the DeviceInterfaceDetailData struct size.
DWORD size;
success = SetupDiGetDeviceInterfaceDetail(list, &device_interface_data, NULL, 0, &size, NULL);
if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
libusbp_error * error = error_create_winapi("Failed to get the size of the device interface details.");
SetupDiDestroyDeviceInfoList(list);
return error;
}
// Get the DeviceInterfaceDetailData struct data
SP_DEVICE_INTERFACE_DETAIL_DATA_A * device_interface_detail_data = malloc(size);
if (device_interface_detail_data == NULL)
{
return &error_no_memory;
}
device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
success = SetupDiGetDeviceInterfaceDetail(list, &device_interface_data,
device_interface_detail_data, size, NULL, NULL);
if (!success)
{
libusbp_error * error = error_create_winapi("Failed to get the device interface details.");
free(device_interface_detail_data);
SetupDiDestroyDeviceInfoList(list);
return error;
}
char * new_string = strdup(device_interface_detail_data->DevicePath);
if (new_string == NULL)
{
free(device_interface_detail_data);
SetupDiDestroyDeviceInfoList(list);
return &error_no_memory;
}
*filename = new_string;
free(device_interface_detail_data);
SetupDiDestroyDeviceInfoList(list);
return NULL;
}

View File

@@ -0,0 +1,109 @@
#include <libusbp_internal.h>
#if SIZE_MAX < DWORD_MAX
#error The code in get_list_length assumes a size_t can hold any DWORD.
#endif
static void try_create_device(HDEVINFO list, PSP_DEVINFO_DATA info,
libusbp_device ** device)
{
libusbp_error * error = create_device(list, info, device);
if (error != NULL)
{
assert(*device == NULL);
// Something went wrong. For example, one of the devices might have
// lacked a hardware ID with the proper format. To make the library
// more robust and usable, we ignore this error and continue.
#ifdef LIBUSBP_LOG
fprintf(stderr, "Problem creating device: %s\n",
libusbp_error_get_message(error));
#endif
libusbp_error_free(error);
}
}
libusbp_error * libusbp_list_connected_devices(libusbp_device *** device_list,
size_t * device_count)
{
// Return 0 for the device count by default, just to be safe.
if (device_count != NULL)
{
*device_count = 0;
}
if (device_list == NULL)
{
return error_create("Device list output pointer is null.");
}
libusbp_error * error = NULL;
// Get a list of all USB devices from Windows.
HDEVINFO handle = INVALID_HANDLE_VALUE;
if (error == NULL)
{
handle = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0,
DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
if (handle == INVALID_HANDLE_VALUE)
{
error = error_create_winapi("Failed to list all USB devices.");
}
}
// Start a new device list and keep track of how many devices are in it.
libusbp_device ** new_list = NULL;
size_t count = 0;
if (error == NULL)
{
error = device_list_create(&new_list);
}
// Each iteration of this loop attempts to set up a new device.
DWORD index = 0;
while(error == NULL)
{
// See if we have reached the end of the list.
SP_DEVINFO_DATA info;
info.cbSize = sizeof(info);
BOOL success = SetupDiEnumDeviceInfo(handle, index, &info);
if (!success)
{
if (GetLastError() == ERROR_NO_MORE_ITEMS)
{
// We have reached the end of the list.
break;
}
// An unexpected error happened.
error = error_create_winapi("Failed to test for the end of the USB device list.");
break;
}
libusbp_device * device = NULL;
try_create_device(handle, &info, &device);
if (device != NULL)
{
error = device_list_append(&new_list, &count, device);
}
index++;
}
// Give the list and count to the caller.
if (error == NULL)
{
*device_list = new_list;
new_list = NULL;
if (device_count != NULL)
{
*device_count = count;
}
}
// Clean up.
if (handle != INVALID_HANDLE_VALUE) { SetupDiDestroyDeviceInfoList(handle); }
if (new_list != NULL) { free_devices_and_list(new_list); }
return error;
}

View File

@@ -0,0 +1,207 @@
#include <libusbp_internal.h>
struct libusbp_serial_port
{
char * device_instance_id;
char * port_name; // e.g. "COM4"
};
libusbp_error * libusbp_serial_port_create(
const libusbp_device * device,
uint8_t interface_number,
bool composite,
libusbp_serial_port ** port)
{
if (port == NULL)
{
return error_create("Serial port output pointer is null.");
}
*port = NULL;
if (device == NULL)
{
return error_create("Device is null.");
}
libusbp_error * error = NULL;
libusbp_serial_port * new_sp = NULL;
if (error == NULL)
{
new_sp = calloc(1, sizeof(libusbp_serial_port));
if (new_sp == NULL)
{
error = &error_no_memory;
}
}
// Get the device instance ID of the overall USB device.
char * usb_device_id = NULL;
if (error == NULL)
{
error = libusbp_device_get_os_id(device, &usb_device_id);
}
// Access the serial port interface device node with SetupAPI.
HDEVINFO list = INVALID_HANDLE_VALUE;
SP_DEVINFO_DATA device_info_data;
if (error == NULL)
{
error = get_interface(usb_device_id, interface_number,
composite, &list, &device_info_data);
}
// Record the device instance ID.
if (error == NULL)
{
error = create_id_string(list, &device_info_data, &new_sp->device_instance_id);
}
// Open the registry key for device-specific configuration information.
HANDLE keyDev = INVALID_HANDLE_VALUE;
if (error == NULL)
{
keyDev = SetupDiOpenDevRegKey(list, &device_info_data,
DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
if (keyDev == INVALID_HANDLE_VALUE)
{
error = error_create_winapi(
"Failed to get device registry key in order to find its COM port name.");
}
}
// Get the port name from the registry (e.g. "COM8").
char portName[128];
if (error == NULL)
{
DWORD size = sizeof(portName);
DWORD reg_type;
LONG reg_result = RegQueryValueEx(keyDev, "PortName",
NULL, &reg_type, (BYTE *)portName, &size);
if (reg_result != ERROR_SUCCESS)
{
if (reg_result == ERROR_FILE_NOT_FOUND)
{
error = error_create("The PortName key was not found.");
}
else
{
SetLastError(reg_result);
error = error_create_winapi("Failed to get PortName key.");
}
}
else if (reg_type != REG_SZ)
{
error = error_create(
"Expected PortName key to be a REG_SZ (0x%x), got 0x%lx.",
REG_SZ, reg_type);
}
}
// Copy the port name into the serial port object.
if (error == NULL)
{
error = string_copy(portName, &new_sp->port_name);
}
// Give the new serial port to the caller.
if (error == NULL)
{
*port = new_sp;
new_sp = NULL;
}
// Clean up.
if (keyDev != INVALID_HANDLE_VALUE)
{
RegCloseKey(keyDev);
}
if (list != INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(list);
}
libusbp_string_free(usb_device_id);
libusbp_serial_port_free(new_sp);
return error;
}
void libusbp_serial_port_free(libusbp_serial_port * port)
{
if (port == NULL) { return; }
libusbp_string_free(port->device_instance_id);
libusbp_string_free(port->port_name);
free(port);
}
libusbp_error * libusbp_serial_port_copy(const libusbp_serial_port * source,
libusbp_serial_port ** dest)
{
if (dest == NULL)
{
return error_create("Serial port output pointer is null.");
}
*dest = NULL;
if (source == NULL)
{
return NULL;
}
assert(source->device_instance_id);
assert(source->port_name);
libusbp_error * error = NULL;
libusbp_serial_port * new_sp = NULL;
if (error == NULL)
{
new_sp = calloc(1, sizeof(libusbp_serial_port));
if (new_sp == NULL)
{
error = &error_no_memory;
}
}
if (error == NULL)
{
error = string_copy(source->device_instance_id, &new_sp->device_instance_id);
}
if (error == NULL)
{
error = string_copy(source->port_name, &new_sp->port_name);
}
if (error == NULL)
{
*dest = new_sp;
new_sp = NULL;
}
libusbp_serial_port_free(new_sp);
return error;
}
libusbp_error * libusbp_serial_port_get_name(
const libusbp_serial_port * port,
char ** name)
{
if (name == NULL)
{
return error_create("String output pointer is null.");
}
*name = NULL;
if (port == NULL)
{
return error_create("Serial port is null.");
}
return string_copy(port->port_name, name);
}

View File

@@ -0,0 +1,41 @@
INCLUDE (CheckIncludeFileCXX)
# If catch.hpp is not present, we want to simply skip compiling the tests. This
# allows someone to compile and install libusbp without having catch installed.
# The header can either be installed in this directory or in a standard system
# location.
set (CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}")
CHECK_INCLUDE_FILE_CXX (catch.hpp HAVE_CATCH_FRAMEWORK)
if (NOT HAVE_CATCH_FRAMEWORK)
message (STATUS "The test suite will not be built.")
return ()
endif ()
use_cxx11 ()
set(USE_TEST_DEVICE_A FALSE CACHE BOOL
"Run tests that require Test Device A.")
set(USE_TEST_DEVICE_B FALSE CACHE BOOL
"Run tests that require Test Device B.")
file(GLOB test_sources *.cpp)
if (APPLE)
set (link_flags "-framework CoreFoundation ${link_flags}")
endif ()
add_executable(run_test ${test_sources})
set_target_properties(run_test PROPERTIES
LINK_FLAGS "${link_flags}"
)
include_directories (
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_SOURCE_DIR}/include"
"${CMAKE_SOURCE_DIR}/src"
"${CMAKE_BINARY_DIR}/src"
)
target_link_libraries(run_test usbp)

View File

@@ -0,0 +1,749 @@
#include <test_helper.h>
// Note: A lot of these tests make specific assumptions about the timing of
// various operations. If the tests fail intermittently, some parameters may
// need to be adjusted.
const uint8_t pipe_id = 0x82;
#ifdef USE_TEST_DEVICE_A
static void check_error_for_cancelled_transfer(const libusbp::error & error)
{
if (!error)
{
// The transfer actually completed successfully.
return;
}
if (error.has_code(LIBUSBP_ERROR_CANCELLED))
{
// This is expected.
}
else
{
// Some other error happened that we didn't expect.
throw error;
}
}
static void clean_up_async_in_pipe(libusbp::async_in_pipe & pipe)
{
pipe.cancel_transfers();
test_timeout timeout(500);
while(pipe.has_pending_transfers())
{
pipe.handle_events();
libusbp::error transfer_error;
while(pipe.handle_finished_transfer(NULL, NULL, &transfer_error))
{
check_error_for_cancelled_transfer(transfer_error);
}
timeout.check();
sleep_quick();
}
}
static void clean_up_async_in_pipe_and_expect_a_success(libusbp::async_in_pipe & pipe)
{
pipe.cancel_transfers();
test_timeout timeout(500);
uint32_t success_count = 0;
while(pipe.has_pending_transfers())
{
pipe.handle_events();
libusbp::error transfer_error;
uint8_t buffer[64] = {0};
size_t transferred;
while(pipe.handle_finished_transfer(buffer, &transferred, &transfer_error))
{
check_error_for_cancelled_transfer(transfer_error);
if (!transfer_error)
{
REQUIRE(buffer[4] == 0xAB);
REQUIRE(transferred == 5);
success_count++;
}
}
timeout.check();
sleep_quick();
}
REQUIRE(success_count > 0);
}
#endif
TEST_CASE("async_in_pipe traits")
{
libusbp::async_in_pipe pipe, pipe2;
SECTION("is not copy-constructible")
{
REQUIRE(std::is_copy_constructible<libusbp::async_in_pipe>::value == false);
// Should not compile:
// libusbp::async_in_pipe pipe3(pipe);
}
SECTION("is not copy-assignable")
{
REQUIRE(std::is_copy_assignable<libusbp::async_in_pipe>::value == false);
// Should not compile:
// pipe2 = pipe;
}
}
#ifdef USE_TEST_DEVICE_A
TEST_CASE("async_in_pipe basic properties")
{
libusbp::device device = find_test_device_a();
libusbp::generic_interface gi(device, 0, true);
libusbp::generic_handle handle(gi);
libusbp::async_in_pipe pipe = handle.open_async_in_pipe(pipe_id);
SECTION("is present")
{
REQUIRE(pipe);
}
SECTION("is movable")
{
libusbp::async_in_pipe pipe2 = std::move(pipe);
REQUIRE(pipe2);
REQUIRE_FALSE(pipe);
}
SECTION("is move-assignable")
{
libusbp::async_in_pipe pipe2;
pipe2 = std::move(pipe);
REQUIRE(pipe2);
REQUIRE_FALSE(pipe);
}
}
TEST_CASE("null async_in_pipe")
{
libusbp::async_in_pipe pipe;
std::string expected_message = "Pipe argument is null.";
SECTION("is not present")
{
CHECK_FALSE(pipe);
}
SECTION("cannot allocate transfers")
{
try
{
pipe.allocate_transfers(4, 5);
REQUIRE(0);
}
catch(const libusbp::error & error)
{
REQUIRE(error.message() == expected_message);
}
}
SECTION("cannot start endless transfers")
{
try
{
pipe.start_endless_transfers();
REQUIRE(0);
}
catch(const libusbp::error & error)
{
REQUIRE(error.message() == expected_message);
}
}
SECTION("cannot handle events")
{
try
{
pipe.handle_events();
REQUIRE(0);
}
catch(const libusbp::error & error)
{
REQUIRE(error.message() == expected_message);
}
}
SECTION("cannot say if it has pending transfers")
{
// Test it this way to make sure the C++ wrapper throws an exception
// instead of ignoring it.
try
{
pipe.has_pending_transfers();
REQUIRE(0);
}
catch(const libusbp::error & error)
{
REQUIRE(error.message() == expected_message);
}
// Also test it this way so we can check the "result" output parameter
// in the C API.
bool result = true;
libusbp::error error(libusbp_async_in_pipe_has_pending_transfers(NULL, &result));
REQUIRE(error.message() == expected_message);
REQUIRE_FALSE(result);
}
SECTION("cannot handle a finished transfer")
{
uint8_t buffer[] = "hi there";
size_t transferred = 10;
libusbp::error error = get_some_error();
try
{
pipe.handle_finished_transfer(buffer, &transferred, &error);
}
catch(const libusbp::error & error)
{
REQUIRE(error.message() == expected_message);
}
CHECK(buffer[0] == 'h');
CHECK(transferred == 0);
CHECK_FALSE(error);
}
SECTION("cannot cancel all transfers")
{
try
{
pipe.cancel_transfers();
REQUIRE(0);
}
catch(const libusbp::error & error)
{
REQUIRE(error.message() == expected_message);
}
}
}
TEST_CASE("async_in_pipe parameter validation and state checks")
{
libusbp::device device = find_test_device_a();
libusbp::generic_interface gi(device, 0, true);
libusbp::generic_handle handle(gi);
libusbp::async_in_pipe pipe = handle.open_async_in_pipe(0x82);
SECTION("allocate_transfers")
{
SECTION("cannot be called twice on the same pipe")
{
pipe.allocate_transfers(1, 64);
try
{
pipe.allocate_transfers(1, 64);
}
catch(const libusbp::error & error)
{
REQUIRE(error.message() == "Transfers were already allocated for this pipe.");
}
}
SECTION("does not allow transfer_count to be 0")
{
// Set the size to 0 also so we can test that the count is checked
// before the size (it's nice if the little details stay consistent
// over time and across platforms).
try
{
pipe.allocate_transfers(0, 0);
}
catch(const libusbp::error & error)
{
REQUIRE(error.message() == "Transfer count cannot be zero.");
}
}
SECTION("does not allow transfer_size to be 0")
{
try
{
pipe.allocate_transfers(64, 0);
}
catch(const libusbp::error & error)
{
REQUIRE(error.message() == "Transfer size cannot be zero.");
}
}
SECTION("rejects transfer sizes too large for the underlying APIs")
{
#ifdef _WIN32
// On Windows, URB buffer sizes are represented by ULONGs.
if (SIZE_MAX <= ULONG_MAX) { return; }
size_t too_large_size = (size_t)ULONG_MAX + 1;
#endif
#ifdef __linux__
// On Linux, URB buffer sizes are represented by ints.
if (SIZE_MAX <= INT_MAX) { return; }
size_t too_large_size = (size_t)INT_MAX + 1;
#endif
#ifdef __APPLE__
size_t too_large_size = (size_t)UINT32_MAX + 1;
#endif
try
{
pipe.allocate_transfers(1, too_large_size);
REQUIRE(0);
}
catch(const libusbp::error & error)
{
REQUIRE(error.message() ==
"Failed to allocate transfers for asynchronous IN pipe. "
"Transfer size is too large.");
}
}
}
SECTION("start_endless_transfers")
{
SECTION("complains if transfers were not allocated")
{
try
{
pipe.start_endless_transfers();
}
catch(const libusbp::error & error)
{
REQUIRE(error.message() == "Pipe transfers have not been allocated yet.");
}
}
}
SECTION("has_pending_transfers")
{
SECTION("works even if transfers were not allocated")
{
REQUIRE_FALSE(pipe.has_pending_transfers());
}
SECTION("complains if the output pointer is NULL")
{
libusbp::error error(libusbp_async_in_pipe_has_pending_transfers(
pipe.pointer_get(), NULL));
REQUIRE(error.message() == "Boolean output pointer is null.");
}
}
}
TEST_CASE("async_in_pipe for an interrupt endpoint")
{
// If it works for an IN endpoint, it should work for a bulk endpoint too
// because the underlying APIs that libusbp uses allow us to treat those
// types of endpoints the same.
libusbp::device device = find_test_device_a();
libusbp::generic_interface gi(device, 0, true);
libusbp::generic_handle handle(gi);
libusbp::async_in_pipe pipe = handle.open_async_in_pipe(0x82);
const size_t transfer_size = 5;
test_timeout timeout(500);
SECTION("can do continuous transfers and then cancel them")
{
pipe.allocate_transfers(5, transfer_size);
pipe.start_endless_transfers();
size_t finish_count = 0;
while(finish_count < 12)
{
// Don't use REQUIRE or CATCH here because then the number of
// assertions printed at the end of the tests will be unpredictable.
if (!pipe.has_pending_transfers()) { throw "No pending transfers."; }
uint8_t buffer[transfer_size] = {0};
size_t transferred;
libusbp::error transfer_error;
if (pipe.handle_finished_transfer(buffer, &transferred, &transfer_error))
{
if (transfer_error) { throw transfer_error; }
REQUIRE(buffer[4] == 0xAB);
finish_count++;
}
pipe.handle_events();
timeout.check();
sleep_quick();
}
REQUIRE(pipe.has_pending_transfers());
clean_up_async_in_pipe(pipe);
}
SECTION("cancelling a transfer before it is completed")
{
// Event order tested here: submit, cancel, "reap"
// Make a lot of transfers and cancel then immediately to make sure at
// least one was cancelled before it was completed.
#ifdef __linux__
// On a normal Linux machine it takes about 1 ms per transfer to cancel
// transfers. (Maybe that is because we are using an endpoint with a 1
// ms polling interval.) On a VirtualBox machine running on a Windows
// guest, it takes about much longer: 20 ms per transfer. To work
// around this slowness, we only allocate 20 transfers at a time in
// Linux.
// Maybe later we should we provide a workaround that lets
// people close the generic_handle and all its pipes at the same time,
// thus saving their users from this painful wait, without causing
// memory leaks. If the generic handle fd gets closed and replaced with
// -1, then it would be safe to just free all the resources for the URBs
// without actually cancelling them.
// We would like to make the transfer count larger (500) to make this
// test more resilient to lag caused by the operating system, but that
// slows down the development process too much.
const size_t transfer_count = 20;
#else
const size_t transfer_count = 500;
#endif
pipe.allocate_transfers(transfer_count, transfer_size);
pipe.start_endless_transfers();
//printf("Cancelling %d transfers\n", (int)transfer_count);
test_timeout cancel_timer(10000);
pipe.cancel_transfers();
//printf("Done cancelling, took %d ms\n", cancel_timer.get_milliseconds());
size_t cancel_count = 0;
test_timeout timeout(10000);
while(pipe.has_pending_transfers())
{
pipe.handle_events();
libusbp::error transfer_error;
while(pipe.handle_finished_transfer(NULL, NULL, &transfer_error))
{
check_error_for_cancelled_transfer(transfer_error);
if (transfer_error.has_code(LIBUSBP_ERROR_CANCELLED))
{
cancel_count++;
}
}
timeout.check();
sleep_quick();
}
// Make sure at least one error from the cancelled transfers had the
// right error code.
//printf("Cancel count: %d\n", (int)cancel_count);
REQUIRE(cancel_count > 5);
}
SECTION("cancelling a transfer after it is completed but before the libusbp knows")
{
// Event order tested here: submit, complete, cancel, "reap"
pipe.allocate_transfers(1, transfer_size);
pipe.start_endless_transfers();
sleep_ms(30);
clean_up_async_in_pipe_and_expect_a_success(pipe);
}
SECTION("cancelling a transfer after it is completed and the event is handled")
{
// Event order tested here: submit, complete, "reap", cancel
pipe.allocate_transfers(1, transfer_size);
pipe.start_endless_transfers();
sleep_ms(30);
pipe.handle_events();
clean_up_async_in_pipe_and_expect_a_success(pipe);
}
SECTION("cancelling a partially completed transfer")
{
// Note: This test seems to always fail on Windows Vista and Windows 7
// because WinUSB reports that 0 bytes have been transferred instead of
// 10. It might be that older versions of WinUSB or of the USB stack
// didn't support returning data from a cancelled, partially completed
// transfer.
// Assumption: there will be two packets queued up by Test Device A in
// its ping-pong buffers. So when we tell it to pause the ADC for 100
// ms, a three-packet transfer will quickly receive those two packets
// and then keep waiting for more.
// Pause the ADC for 100 ms.
handle.control_transfer(0x40, 0xA0, 100, 0);
pipe.allocate_transfers(1, transfer_size * 3);
pipe.start_endless_transfers();
sleep_ms(20);
pipe.cancel_transfers();
test_timeout timeout(500);
while(pipe.has_pending_transfers())
{
pipe.handle_events();
libusbp::error transfer_error;
uint8_t buffer[transfer_size * 3];
size_t transferred;
while(pipe.handle_finished_transfer(buffer, &transferred, &transfer_error))
{
CHECK(transfer_error);
check_error_for_cancelled_transfer(transfer_error);
#if defined(VBOX_LINUX_ON_WINDOWS)
CHECK(transferred == 0);
#elif defined(__APPLE__)
CHECK(transferred == transfer_size);
CHECK(buffer[4] == 0xAB);
#else
CHECK(transferred == transfer_size * 2);
CHECK(buffer[4] == 0xAB);
CHECK(buffer[9] == 0xAB);
#endif
}
timeout.check();
sleep_quick();
}
// Unpause the ADC.
handle.control_transfer(0x40, 0xA0, 0, 0);
}
SECTION("cancelling transfers twice is okay")
{
pipe.allocate_transfers(1, transfer_size);
pipe.start_endless_transfers();
pipe.cancel_transfers();
clean_up_async_in_pipe(pipe);
}
SECTION("might respect the time out from set_timeout")
{
#ifdef __linux__
// On Linux, asynchronous requests cannot have a timeout, so this test
// will not pass. If we want to implement a timeout feature, we would
// need to make some kind of timer like libusb does, and cancel the
// requests when the time is up. It's probably fine for the user to
// just implement a timeout themselves using a separate time library.
return;
#endif
#ifdef __APPLE__
// On Mac OS X, interrupt endpoints cannot have a timeout, and our
// library simply doesn't use a timeout (we use ReadPipeAsync instead of
// ReadPipeAsyncTO).
return;
#endif
// Pause the ADC for 200 ms.
handle.control_transfer(0x40, 0xA0, 200, 0);
handle.set_timeout(pipe_id, 10);
pipe.allocate_transfers(5, transfer_size);
pipe.start_endless_transfers();
uint32_t timeout_count = 0;
uint32_t success_count = 0;
test_timeout timeout(500);
while(timeout_count < 8)
{
pipe.handle_events();
libusbp::error transfer_error;
while(pipe.handle_finished_transfer(NULL, NULL, &transfer_error))
{
// We expect there to be 0 to 2 successes, and the rest of the
// transfers will be timeouts.
if (transfer_error == false)
{
// Successful transfer.
success_count++;
if (timeout_count != 0)
{
throw "Got a success after a timeout.";
}
if (success_count > 2)
{
throw "Got too many successes.";
}
}
else if (transfer_error.has_code(LIBUSBP_ERROR_TIMEOUT))
{
// Timeout.
const char * expected =
"Asynchronous IN transfer failed. "
"The operation timed out. "
#ifdef _WIN32
"Windows error code 0x79."
#endif
;
REQUIRE(transfer_error.message() == expected);
timeout_count++;
}
else
{
// Unexpected error.
throw transfer_error;
}
}
timeout.check();
sleep_quick();
}
// Clean up the pipe, while accepting that there might be more transfers
// finishing with a cancellation error.
pipe.cancel_transfers();
test_timeout timeout_cleanup(500);
while(pipe.has_pending_transfers())
{
pipe.handle_events();
libusbp::error transfer_error;
while(pipe.handle_finished_transfer(NULL, NULL, &transfer_error))
{
if (transfer_error
&& !transfer_error.has_code(LIBUSBP_ERROR_CANCELLED)
&& !transfer_error.has_code(LIBUSBP_ERROR_TIMEOUT))
{
throw transfer_error;
}
}
timeout_cleanup.check();
sleep_quick();
}
// Unpause the ADC.
handle.control_transfer(0x40, 0xA0, 0, 0);
}
SECTION("overflow")
{
#ifdef VBOX_LINUX_ON_WINDOWS
// This test fails and then puts the USB device into a weird state
// if run on Linux inside VirtualBox on a Windows host.
std::cerr << "Skipping asynchronous IN pipe overflow test.\n";
return;
#endif
pipe.allocate_transfers(1, transfer_size + 1);
pipe.start_endless_transfers();
sleep_ms(10);
pipe.handle_events();
uint8_t buffer[transfer_size] = {0};
size_t transferred;
libusbp::error transfer_error;
bool finished = pipe.handle_finished_transfer(buffer,
&transferred, &transfer_error);
REQUIRE(finished);
std::string expected_message;
size_t expected_transferred;
#ifdef _WIN32
// This request is an error in WinUSB since we are using RAW_IO. The
// error is detected before any data is transferred.
expected_transferred = 0;
expected_message = "Asynchronous IN transfer failed. "
"Incorrect function. Windows error code 0x1.";
#elif defined(__linux__)
// This request results in an error in Linux but it is only detected
// after some data is transferred.
expected_transferred = transfer_size + 1;
expected_message = "Asynchronous IN transfer failed. "
"The transfer overflowed. Error code 75.";
#elif defined(__APPLE__)
// On Mac OS X, this results in an error after some data is transferred.
expected_transferred = transfer_size;
expected_message = "Asynchronous IN transfer failed. "
"The transfer overflowed. Error code 0xe00002e8.";
#else
REQUIRE(0);
#endif
CHECK(transferred == expected_transferred);
CHECK(transfer_error.message() == expected_message);
// We can't just call clean_up_async_in_pipe because we might be running
// on a slower computer and the next transfer has actually already been
// queued.
pipe.cancel_transfers();
test_timeout timeout(500);
while(pipe.has_pending_transfers())
{
pipe.handle_events();
libusbp::error transfer_error;
while(pipe.handle_finished_transfer(NULL, NULL, &transfer_error))
{
if (!transfer_error)
{
#if defined(__linux__)
// Overflowing is always an error, none of the transfers
// should have succeeded.
throw "Expected to get a transfer error.";
#endif
}
else if (transfer_error.message() == expected_message)
{
// This is fine; the error indicates an overflow.
}
#ifdef __APPLE__
else if (transfer_error.has_code(LIBUSBP_ERROR_STALL))
{
// Mac OS X considers the pipe to be "stalled" after an
// overflow happens and we cannot actually submit any more
// transfers.
}
#endif
else if (transfer_error.has_code(LIBUSBP_ERROR_CANCELLED))
{
// This is fine; the transfer was cancelled.
}
else
{
// Unexpected error.
throw transfer_error;
}
}
timeout.check();
sleep_quick();
}
clean_up_async_in_pipe(pipe);
}
#ifdef __linux__
SECTION("does not prevent the creation of another generic_interface object")
{
// It seems that the "usbfs" driver gets attached to the device
// while we are doing asynchronous transfers. This tests that our
// code is OK with that and we can still open up other handles
// to the device, at least in Linux.
pipe.allocate_transfers(1, transfer_size);
pipe.start_endless_transfers();
libusbp::generic_interface gi2(device, 0, true);
libusbp::generic_interface gi3(device, 0, true);
libusbp::generic_handle handle2(gi3);
handle2.control_transfer(0x40, 0x90, 1, 0);
clean_up_async_in_pipe(pipe);
}
#endif
}
#endif

View File

@@ -0,0 +1,204 @@
#include <test_helper.h>
TEST_CASE("control transfer corner cases")
{
SECTION("sets transferred to zero if possible")
{
size_t transferred = 1;
libusbp::error error(libusbp_control_transfer(NULL,
0, 0, 0, 0, NULL, 0, &transferred));
REQUIRE(transferred == 0);
}
}
#ifdef USE_TEST_DEVICE_A
TEST_CASE("control transfers (synchronous) for Test Device A", "[ctstda]")
{
libusbp::device device = find_test_device_a();
libusbp::generic_interface gi(device, 0, true);
libusbp::generic_handle gih(gi);
size_t transferred = 0xF12F;
gih.set_timeout(0, 300);
SECTION("request without a data stage")
{
// Turn on the LED.
gih.control_transfer(0x40, 0x90, 1, 0, NULL, 0, &transferred);
REQUIRE(transferred == 0);
}
SECTION("writing and reading data")
{
char buffer1[40] = "hello there";
size_t size = strlen(buffer1) + 1;
// Transfer data to the device.
gih.control_transfer(0x40, 0x92, 0, 0, buffer1, size, &transferred);
REQUIRE(transferred == size);
// Read the data back.
char buffer2[40];
gih.control_transfer(0xC0, 0x91, 0, 12, buffer2, 20, &transferred);
REQUIRE(transferred == size);
REQUIRE(std::string(buffer2) == buffer1);
}
SECTION("invalid request")
{
try
{
gih.control_transfer(0x40, 0x48, 0, 0);
REQUIRE(0);
}
catch(const libusbp::error & error)
{
const char * expected =
"Control transfer failed. "
"The request was invalid or there was an I/O problem. "
#if defined(_WIN32)
"Windows error code 0x1f."
#elif defined(__linux__)
"Error code 32."
#elif defined(__APPLE__)
"Error code 0xe000404f."
#endif
;
REQUIRE(std::string(error.what()) == expected);
REQUIRE(error.has_code(LIBUSBP_ERROR_STALL));
}
}
}
TEST_CASE("control transfers that time out for Test Device A")
{
libusbp::device device = find_test_device_a();
libusbp::generic_interface gi(device, 0, true);
libusbp::generic_handle gih(gi);
size_t transferred = 0xFFFF;
gih.set_timeout(0, 1);
// Figure out what delay is necessary to trigger a control transfer timeout.
#ifdef __APPLE__
// Mac OS X seems to have really inaccurate timers and it does not detect a
// timeout unless the device delays for over roughly one second. To detect it
// reliably, the delay needs to be even longer, definitely slows down the tests
// and makes our device not compliant with the USB specification during that
// time, because it cannot response to SETUP packets.
const uint32_t required_delay = 2000;
#else
const uint32_t required_delay = 100;
#endif
std::string timeout_message = "Control transfer failed. "
"The operation timed out. "
#if defined(_WIN32)
"Windows error code 0x79."
#elif defined(__linux__)
"Error code 110."
#elif defined(__APPLE__)
"Error code 0xe0004051."
#endif
;
SECTION("write request that times out")
{
size_t transferred = 0xFFFF;
char buffer[] = "hi";
try
{
gih.control_transfer(0x40, 0x92, required_delay, 0,
buffer, sizeof(buffer), &transferred);
REQUIRE(0);
}
catch(const libusbp::error & error)
{
REQUIRE(std::string(error.what()) == timeout_message);
REQUIRE(error.has_code(LIBUSBP_ERROR_TIMEOUT));
REQUIRE(transferred == 0);
}
}
SECTION("read request that times out")
{
size_t transferred = 0xFFFF;
char buffer[3];
try
{
gih.control_transfer(0xC0, 0x91, required_delay, 0,
buffer, sizeof(buffer), &transferred);
REQUIRE(0);
}
catch(const libusbp::error & error)
{
REQUIRE(std::string(error.what()) == timeout_message);
REQUIRE(error.has_code(LIBUSBP_ERROR_TIMEOUT));
REQUIRE(transferred == 0);
}
}
#ifdef _WIN32
SECTION("write request with no data stage that times out (no error raised)")
{
// For some reason, WinUSB does not seem to report timeouts for a
// control transfer with no data stage that times out, which is bad. I
// am not sure why, because it used to work.
gih.control_transfer(0x40, 0x92, required_delay, 0, NULL, 0, &transferred);
}
#else
SECTION("write request with no data stage that times out (error raised)")
{
try
{
gih.control_transfer(0x40, 0x92, required_delay, 0, NULL, 0, &transferred);
REQUIRE(0);
}
catch(const libusbp::error & error)
{
REQUIRE(std::string(error.what()) == timeout_message);
REQUIRE(error.has_code(LIBUSBP_ERROR_TIMEOUT));
REQUIRE(transferred == 0);
}
}
#endif
}
#endif
#ifdef USE_TEST_DEVICE_B
TEST_CASE("control transfers (synchronous) for Test Device B", "[ctstda]")
{
libusbp::device device = find_test_device_b();
libusbp::generic_interface gi(device, 0, false);
libusbp::generic_handle gih(gi);
size_t transferred;
gih.set_timeout(0, 300);
SECTION("request without a data stage")
{
// Turn on the LED.
gih.control_transfer(0x40, 0x90, 1, 0, NULL, 0, &transferred);
REQUIRE(transferred == 0);
}
SECTION("writing and reading data")
{
char buffer1[40] = "hello there";
size_t size = strlen(buffer1) + 1;
// Transfer data to the device.
gih.control_transfer(0x40, 0x92, 0, 0, buffer1, size, &transferred);
REQUIRE(transferred == size);
// Read the data back.
char buffer2[40];
gih.control_transfer(0xC0, 0x91, 0, 12, buffer2, 20, &transferred);
REQUIRE(transferred == size);
REQUIRE(std::string(buffer2) == buffer1);
}
}
#endif

View File

@@ -0,0 +1,251 @@
#include <test_helper.h>
static void check_null_device_error(const libusbp::error & error)
{
REQUIRE(error.message() == "Device is null.");
}
TEST_CASE("null device")
{
libusbp::device device;
SECTION("is not present")
{
REQUIRE_FALSE(device);
}
SECTION("can be copied")
{
libusbp::device device2 = device;
REQUIRE_FALSE(device2);
}
SECTION("cannot return a vendor ID")
{
try
{
device.get_vendor_id();
}
catch(const libusbp::error & error)
{
check_null_device_error(error);
}
}
SECTION("cannot return a product ID")
{
try
{
device.get_product_id();
}
catch(const libusbp::error & error)
{
check_null_device_error(error);
}
}
SECTION("cannot return a revision")
{
try
{
device.get_revision();
}
catch(const libusbp::error & error)
{
check_null_device_error(error);
}
}
SECTION("cannot return a serial number")
{
try
{
device.get_serial_number();
}
catch(const libusbp::error & error)
{
check_null_device_error(error);
}
}
SECTION("cannot return an OS id")
{
try
{
device.get_os_id();
}
catch(const libusbp::error & error)
{
check_null_device_error(error);
}
}
}
TEST_CASE("device parameter checks and corner cases")
{
SECTION("libusbp_device_copy complains about a null output pointer")
{
libusbp::error error(libusbp_device_copy(NULL, NULL));
REQUIRE(error.message() == "Device output pointer is null.");
}
SECTION("libusbp_device_copy sets the output pointer to 0 by default")
{
void * p = &p;
libusbp::error error(libusbp_device_copy(NULL, (libusbp_device**)&p));
REQUIRE((p == NULL));
}
SECTION("libusbp_device_get_vendor_id complains about a null output pointer")
{
libusbp::error error(libusbp_device_get_vendor_id(NULL, NULL));
REQUIRE(error.message() == "Vendor ID output pointer is null.");
}
SECTION("libusbp_device_get_vendor_id sets the output to 0 by default")
{
uint16_t x = 1;
libusbp::error error(libusbp_device_get_vendor_id(NULL, &x));
REQUIRE(x == 0);
}
SECTION("libusbp_device_get_product_id complains about a null output pointer")
{
libusbp::error error(libusbp_device_get_product_id(NULL, NULL));
REQUIRE(error.message() == "Product ID output pointer is null.");
}
SECTION("libusbp_device_get_product_id sets the output to 0 by default")
{
uint16_t x = 1;
libusbp::error error(libusbp_device_get_product_id(NULL, &x));
REQUIRE(x == 0);
}
SECTION("libusbp_device_get_revision complains about a null output pointer")
{
libusbp::error error(libusbp_device_get_revision(NULL, NULL));
REQUIRE(error.message() == "Device revision output pointer is null.");
}
SECTION("libusbp_device_get_revision sets the output to 0 by default")
{
uint16_t x = 1;
libusbp::error error(libusbp_device_get_revision(NULL, &x));
REQUIRE(x == 0);
}
SECTION("libusbp_device_get_serial_number complains about a null output pointer")
{
libusbp::error error(libusbp_device_get_serial_number(NULL, NULL));
REQUIRE(error.message() == "Serial number output pointer is null.");
}
SECTION("libusbp_device_get_serial_number sets the output to NULL by default")
{
void * p = &p;
libusbp::error error(libusbp_device_get_serial_number(NULL, (char **)&p));
REQUIRE((p == NULL));
}
SECTION("libusbp_device_get_os_id complains about a null output pointer")
{
libusbp::error error(libusbp_device_get_os_id(NULL, NULL));
REQUIRE(error.message() == "Device OS ID output pointer is null.");
}
SECTION("libusbp_device_get_os_id sets the output to NULL by default")
{
void * p = &p;
libusbp::error error(libusbp_device_get_os_id(NULL, (char **)&p));
REQUIRE((p == NULL));
}
}
TEST_CASE("basic checks on all devices", "[device_basic]")
{
const bool print_devices = false;
std::vector<libusbp::device> list = libusbp::list_connected_devices();
for (auto it = list.begin(); it != list.end(); ++it)
{
libusbp::device device = *it;
uint16_t vendor_id = device.get_vendor_id();
uint16_t product_id = device.get_product_id();
uint16_t revision = device.get_revision();
std::string serial;
try
{
serial = device.get_serial_number();
}
catch(const libusbp::error & error)
{
if (!error.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER)) { throw; }
}
std::string id = device.get_os_id();
CHECK_FALSE(id.empty());
if (print_devices)
{
printf("Device: %04x:%04x:%04x %-32s %s\n",
vendor_id, product_id, revision, serial.c_str(), id.c_str());
}
}
}
#ifdef USE_TEST_DEVICE_A
TEST_CASE("Test Device A", "[tda]")
{
libusbp::device device = find_test_device_a();
SECTION("present")
{
REQUIRE(device);
}
SECTION("revision code")
{
// If this test fails, you should probably update
// your Test Device A with the latest firmware.
REQUIRE(device.get_revision() == 0x0007);
}
SECTION("device instance id")
{
std::string id = device.get_os_id();
REQUIRE_FALSE(id.empty());
#ifdef _WIN32
REQUIRE(id.find("USB") == 0);
#endif
}
SECTION("serial number")
{
std::string serial_number = device.get_serial_number();
CHECK(serial_number.size() == 11);
CHECK(serial_number[2] == '-');
}
}
#endif
#ifdef USE_TEST_DEVICE_B
TEST_CASE("Test Device B", "[tdb]")
{
libusbp::device device = find_test_device_b();
SECTION("present")
{
REQUIRE(device);
}
SECTION("revision code")
{
// If this test fails, you should probably update
// your Test Device B with the latest firmware.
REQUIRE(device.get_revision() == 0x0007);
}
}
#endif

View File

Binary file not shown.

View File

@@ -0,0 +1,56 @@
; Copyright (C) 2015 Pololu Corporation
; This driver file is not needed on Windows 8.1 and later because each device
; implements Microsoft OS 2.0 Descriptors.
[Strings]
DriverPackageDisplayName="USB Test Device A Native USB Interface Driver"
ManufacturerName="Pololu Corporation"
ClassName="Universal Serial Bus devices"
DeviceInterfaceGUID="{99c4bbb0-e925-4397-afee-981cd0702163}"
pDA01="USB Test Device A Interface 0"
[DefaultInstall]
CopyINF=usb_test_a_native.inf
[Version]
DriverVer=12/23/2015,2.0.5
Signature=$Windows NT$
Class=USBDevice
ClassGuid={88BAE032-5A81-49F0-BC3D-A4FF138216D6}
Provider=%ManufacturerName%
CatalogFile=pololu.cat
DriverPackageDisplayName=%DriverPackageDisplayName%
[Manufacturer]
%ManufacturerName%=Models,NTamd64
[ClassInstall32]
AddReg=ClassInstall_AddReg
[ClassInstall_AddReg]
HKR,,,0,%ClassName%
HKR,,IconPath,%REG_MULTI_SZ%,"%systemroot%\system32\setupapi.dll,-20"
HKR,,NoInstallClass,,1
HKR,,BootCritical,,0
HKR,,Configurable,,1
[Models]
%pDA01%=USB_Install, USB\VID_1FFB&PID_DA01&MI_00
[Models.NTamd64]
%pDA01%=USB_Install, USB\VID_1FFB&PID_DA01&MI_00
[USB_Install]
Include = Winusb.inf
Needs = WINUSB.NT
[USB_Install.Services]
Include = Winusb.inf
Needs = WINUSB.NT.Services
[USB_Install.HW]
AddReg = Dev_AddReg
[Dev_AddReg]
HKR,,DeviceInterfaceGUIDs,0x00010000,%DeviceInterfaceGUID%

View File

@@ -0,0 +1,54 @@
; Copyright 2015 Pololu Corporation
[Strings]
DriverPackageDisplayName="USB Test Device A Serial Port Driver"
ManufacturerName="Pololu Corporation"
ServiceName="USB RS-232 Emulation Driver"
pDA01.Port.Name="USB Test Device A Port"
[DefaultInstall]
CopyINF=usb_test_a_serial.inf
[Version]
Class=Ports
ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318}
Signature="$Windows NT$"
Provider=%ManufacturerName%
CatalogFile=pololu.cat
DriverVer=07/09/2015,1.0.1.0
DriverPackageDisplayName=%DriverPackageDisplayName%
[Manufacturer]
%ManufacturerName%=DeviceList, NTamd64
[DestinationDirs]
FakeModemCopyFileSection=12
DefaultDestDir=12
[DeviceList]
%pDA01.Port.Name%=DriverInstall, USB\VID_1FFB&PID_DA01&MI_02
[DeviceList.NTamd64]
%pDA01.Port.Name%=DriverInstall, USB\VID_1FFB&PID_DA01&MI_02
[DriverInstall]
include=mdmcpq.inf,usb.inf
CopyFiles = FakeModemCopyFileSection
AddReg=DriverAddReg
[DriverAddReg]
HKR,,DevLoader,,*ntkern
HKR,,NTMPDriver,,usbser.sys
HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider"
[DriverInstall.Services]
include=mdmcpq.inf
AddService=usbser, 0x00000002, DriverService
[DriverService]
DisplayName=%ServiceName%
ServiceType=1
StartType=3
ErrorControl=1
ServiceBinary=%12%\usbser.sys
LoadOrderGroup=Base

View File

@@ -0,0 +1,56 @@
; Copyright (C) 2015 Pololu Corporation
; This driver file is not needed on Windows 8.1 and later because each device
; implements Microsoft OS 2.0 Descriptors.
[Strings]
DriverPackageDisplayName="USB Test Device B Driver"
ManufacturerName="Pololu Corporation"
ClassName="Universal Serial Bus devices"
DeviceInterfaceGUID="{99c4bbb0-e925-4397-afee-981cd0702163}"
pDA02="USB Test Device B"
[DefaultInstall]
CopyINF=usb_test_b_native.inf
[Version]
DriverVer=12/23/2015,1.0.1
Signature=$Windows NT$
Class=USBDevice
ClassGuid={88BAE032-5A81-49F0-BC3D-A4FF138216D6}
Provider=%ManufacturerName%
CatalogFile=pololu.cat
DriverPackageDisplayName=%DriverPackageDisplayName%
[Manufacturer]
%ManufacturerName%=Models,NTamd64
[ClassInstall32]
AddReg=ClassInstall_AddReg
[ClassInstall_AddReg]
HKR,,,0,%ClassName%
HKR,,IconPath,%REG_MULTI_SZ%,"%systemroot%\system32\setupapi.dll,-20"
HKR,,NoInstallClass,,1
HKR,,BootCritical,,0
HKR,,Configurable,,1
[Models]
%pDA02%=USB_Install, USB\VID_1FFB&PID_DA02
[Models.NTamd64]
%pDA02%=USB_Install, USB\VID_1FFB&PID_DA02
[USB_Install]
Include = Winusb.inf
Needs = WINUSB.NT
[USB_Install.Services]
Include = Winusb.inf
Needs = WINUSB.NT.Services
[USB_Install.HW]
AddReg = Dev_AddReg
[Dev_AddReg]
HKR,,DeviceInterfaceGUIDs,0x00010000,%DeviceInterfaceGUID%

View File

@@ -0,0 +1,456 @@
/* This file tests the conversion of errors from libusbp's underlying APIs to
* libusbp_error objects. It also documents and justifies the explicit mappings
* that are in the error handling code. */
#include <test_helper.h>
#ifndef NDEBUG
// Here is a list of the messages we want to use consistently across platforms
// to describe certain types of errors:
#define STR_TIMEOUT "The operation timed out."
#define STR_CANCELLED "The operation was cancelled."
#define STR_GENERAL_FAILURE "The request was invalid or there was an I/O problem."
#define STR_REMOVED "The device was removed."
#define STR_OVERFLOW "The transfer overflowed."
#ifdef _WIN32
TEST_CASE("error_create_winapi", "[error_create_winapi]")
{
libusbp::error error;
SECTION("includes the error code from GetLastError and its message")
{
SetLastError(0x1F4);
error.pointer_reset(error_create_winapi("Something failed."));
REQUIRE(error.message() == "Something failed. "
"User profile cannot be loaded. "
"Windows error code 0x1f4.");
}
SECTION("still works if Windows does not have a message")
{
SetLastError(0x1F431892);
error.pointer_reset(error_create_winapi("Something failed."));
REQUIRE(error.message() == "Something failed. "
"Windows error code 0x1f431892.");
}
SECTION("access denied errors")
{
// ERROR_ACCESS_DENIED can happen when running CreateFile to open a
// handle to a WinUSB device node that is already being used by another
// application. This is one of the few cases where a libusbp error
// message actually contains a troubleshooting step for the user to try.
// It is included in the library because it is a universally useful piece
// of advice for any WinUSB device.
//
// This feature is part of error_create_winapi, which means we are
// assuming that we will only ERROR_ACCESS_DENIED as a result of trying
// to open a device that is being used. If we expand the library to do
// other things that might have their access denied, this feature should
// maybe move to a different function.
SetLastError(ERROR_ACCESS_DENIED);
error.pointer_reset(error_create_winapi("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
CHECK(error.message() ==
"Hi. "
"Access is denied. "
"Try closing all other programs that are using the device. "
"Windows error code 0x5.");
}
SECTION("out of memory errors")
{
// We haven't specifically seen these errors happen before, but it seems
// like either of them might be used to indicate that the system is out
// memory.
SetLastError(ERROR_OUTOFMEMORY);
error.pointer_reset(error_create_winapi("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_MEMORY));
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
error.pointer_reset(error_create_winapi("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_MEMORY));
}
SECTION("general failure")
{
// We have seen ERROR_GEN_FAILURE occur in multiple different
// situations.
//
// It could mean that the device was disconnected from the computer
// in the middle of or during a synchronous USB transfer.
//
// It could mean that the host just sent a control transfer
// request that the device does not support, and the device has returned
// a STALL packet, which is a perfectly valid way for the device to
// behave.
//
// It probably also applies to STALL packets during IN and OUT
// transfers.
//
// The default message is "A device attached to the system is not
// functioning." This places too much blame on the USB device, so we
// definitely want libusbp to use STR_GENERAL_FAILURE instead.
SetLastError(ERROR_GEN_FAILURE);
error.pointer_reset(error_create_winapi("Hey."));
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
CHECK(error.has_code(LIBUSBP_ERROR_STALL));
CHECK(error.message() == "Hey. "
STR_GENERAL_FAILURE
" Windows error code 0x1f.");
}
SECTION("timeout error")
{
// The have seen WinUSB return ERROR_SEM_TIMEOUT for both synchronous
// and asynchronous operations that time out.
SetLastError(ERROR_SEM_TIMEOUT);
error.pointer_reset(error_create_winapi("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_TIMEOUT));
CHECK(error.message() == "Hi. " STR_TIMEOUT " Windows error code 0x79.");
}
SECTION("cancelled error")
{
// GetOverlappedResult returns this if the asynchronous operation was
// cancelled.
SetLastError(ERROR_OPERATION_ABORTED);
error.pointer_reset(error_create_winapi("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_CANCELLED));
CHECK(error.message() == "Hi. " STR_CANCELLED " Windows error code 0x3e3.");
}
}
TEST_CASE("error_create_overlapped")
{
libusbp::error error;
SECTION("usually just calls error_create_winapi")
{
SetLastError(ERROR_SEM_TIMEOUT);
error.pointer_reset(error_create_overlapped("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_TIMEOUT));
}
SECTION("device disconnect error")
{
// WinUsb_GetOverlappedResult (and presumably also GetOverlappedResult)
// returns this error code when you are checking the status of an
// asynchronous operation for a device that has been disconnected from
// the system. This is inconsistent with the synchronous operations,
// which return ERROR_GEN_FAILURE, but it makes sense because
// GetOverlappedResult is a more general thing that applies to any type
// of file that can have asynchronous operations performed on it.
//
// It might be OK to put this feature into error_create_winapi, but in
// general, ERROR_FILE_NOT_FOUND does not mean a device was
// disconnected, so we have put this behavior in a special function
// named error_create_overlapped (instead of error_create_winapi).
SetLastError(ERROR_FILE_NOT_FOUND);
error.pointer_reset(error_create_overlapped("Hey."));
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
CHECK(error.message() == "Hey. "
"The device was disconnected. "
"Windows error code 0x2.");
}
}
TEST_CASE("error_create_cr", "[error_create_cr]")
{
SECTION("returns the right message")
{
libusbp::error error(error_create_cr(CR_NO_SUCH_DEVNODE, "Hi."));
REQUIRE(error.message() == "Hi. CONFIGRET error code 0xd.");
}
}
#endif
#ifdef __linux__
TEST_CASE("error_create_errno", "[error_create_errno]")
{
libusbp::error error;
SECTION("returns the right message")
{
errno = ENOENT;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.message() ==
"Hi. No such file or directory. Error code 2.");
}
SECTION("still works when no message is available")
{
errno = -122344;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.message() == "Hi. Error code -122344.");
}
SECTION("access denied error")
{
// EACCES is the error we see when calling open() on a USB device file
// that we don't have read-write permissions for.
errno = EACCES;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
// One reason we might not have permissions is that the udev rules might
// not have been applied yet, so usbfd_open adds the
// LIBUSBP_ERROR_NOT_READY code to EACCES errors. (That is NOT tested
// here though.)
CHECK_FALSE(error.has_code(LIBUSBP_ERROR_NOT_READY));
}
SECTION("EPERM error")
{
// EPERM is described in errno-base.h as "operation not permitted". The
// file error-codes.txt says "submission failed because urb->reject was
// set". libusb does not do anything special with this error, and I am
// not sure when we would receive it, so for now let's NOT map it to the
// LIBUSBP_ERROR_ACCESS_DENIED code.
errno = EPERM;
error.pointer_reset(error_create_errno("Hi."));
CHECK_FALSE(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
}
SECTION("memory errors")
{
// This is described by error-codes.txt as "no memory for allocation of
// internal structures", and it will probably be used by other non-USB
// system calls as well.
errno = ENOMEM;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_MEMORY));
}
SECTION("general failure")
{
// error-codes.txt says that EPIPE (broken pipe) can either mean
// "endpoint stalled", a device disconnect, or the pipe type specified
// in the URB doesn't match the endpoint's actual type.
//
// It's interesting that both Windows and Linux return just a single
// error code which could either mean a STALL packet or a device
// disconnect. Maybe it's hard to tell the difference between those two
// cases because of the design of the host controller interface.
errno = EPIPE;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_STALL));
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
CHECK(error.message() == "Hi. " STR_GENERAL_FAILURE " Error code 32.");
}
SECTION("device disconnects")
{
// error-codes.txt documents ENODEV (no such device) to mean "specified
// USB-device or bus doesn't exist" or "device was removed". In
// devio.c, we can see that most ioctls will return ENODEV if the device
// is not connected. Also read() will return ENODEV if the device is
// not connected, which might be a handy way to check if the device is
// connected.
errno = ENODEV;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
CHECK(error.message() == "Hi. " STR_REMOVED " Error code 19.");
// error-codes.txt says ESHUTDOWN (cannot send after transport endpoint
// shutdown) means "The device or host controller has been disabled due
// to some problem that could not be worked around, such as a physical
// disconnect." libusb has four places where it handles ESHUTDOWN, and
// all four of them say that the device was removed. ESHUTDOWN is the
// error we typically see from a synchronous operation when the device
// is removed in the middle of the operation.
errno = ESHUTDOWN;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
CHECK(error.message() == "Hi. " STR_REMOVED " Error code 108.");
// error-codes.txt says EPROTO (protocol error) and ETIME (timer
// expired) (and EPIPE and EILSEQ) are codes that different kinds of
// host controller use to indicate a transfer has failed because of
// device disconnect.
errno = EPROTO;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
errno = ETIME;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
}
SECTION("timeout errors")
{
// error-codes.txt says that ETIMEDOUT indicates a timeout in a
// synchronous USB message.
errno = ETIMEDOUT;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_TIMEOUT));
CHECK(error.message() == "Hi. " STR_TIMEOUT " Error code 110.");
}
SECTION("overflow errors")
{
// error-codes.txt says that EOVERFLOW (Value too large for defined data
// type) means the amount of data reutnred by the endpoint was greater
// than either the max packet size or the remaining buffer size. We
// don't have a libusbp error code for that, but we want to fix the
// misleading message from Linux.
errno = EOVERFLOW;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.message() == "Hi. " STR_OVERFLOW " Error code 75.");
}
SECTION("EILSEQ")
{
// error-codes.txt says that EILSEQ is one of "several codes that
// different kinds of host controller use to indicate a transfer has
// failed because of device disconnect".
//
// We have also seen EILSEQ happen sometimes in VirtualBox when
// cancelling an URB, or when removing a device.
//
// The default message for EILSEQ is "Invalid or incomplete multibyte or
// wide character." which is atrocious because it makes me think there
// is some bug in the software, instead of this being a USB hardware
// thing.
errno = EILSEQ;
error.pointer_reset(error_create_errno("Hi."));
CHECK(error.has_code(LIBUSBP_ERROR_CANCELLED));
CHECK(error.has_code(LIBUSBP_ERROR_DEVICE_DISCONNECTED));
CHECK(error.message() == "Hi. Illegal byte sequence: "
"the device may have been disconnected or "
"the request may have been cancelled. "
"Error code 84.");
}
}
TEST_CASE("error_from_urb_status")
{
struct usbdevfs_urb urb;
memset(&urb, 0, sizeof(urb));
libusbp::error error;
SECTION("gives a good message for cancellation (ENOENT)")
{
// error-codes.txt says the ENOENT in an URB status means that the URB
// was synchronously unlinked with usb_unlink_urb. This is the error we
// typically see for a cancelled asynchronous request (which actually
// uses usb_kill_urb, which is similar but probably slower).
urb.status = -ENOENT;
error.pointer_reset(error_from_urb_status(&urb));
REQUIRE(error.message() == STR_CANCELLED " Error code 2.");
}
SECTION("does report an error for EREMOTEIO")
{
// error-codes.txt says that EREMOTEIO means that the data read from the
// endpoint did not fill the specified buffer, and URB_SHORT_NOT_OK was
// set in urb->transfer_flags. We don't indend to set that flag, and if
// we did set that flag, then we should probably interpret a short
// transfer as an error.
//
// libusb does NOT treat EREMOTEIO as an error but that is probably
// because libusb could actually submit multiple URBs per transfer and
// some of them might not be used.
urb.status = -EREMOTEIO;
error.pointer_reset(error_from_urb_status(&urb));
REQUIRE(error.message() == "Remote I/O error. Error code 121.");
}
}
TEST_CASE("error_create_udev", "[error_create_errno]")
{
SECTION("returns the right message")
{
libusbp::error error(error_create_udev(123, "Hi."));
REQUIRE(error.message() == "Hi. Error from libudev: 123.");
}
}
#endif
#ifdef __APPLE__
TEST_CASE("error_create_mach")
{
SECTION("returns the right message")
{
libusbp::error error(error_create_mach(1, "Hi."));
REQUIRE(error.message() == "Hi. (os/kern) invalid address. Error code 0x1.");
}
SECTION("stall error")
{
// We encounter this error if a control transfer ends with a STALL
// packet. This is tested for in control_sync_test.cpp.
kern_return_t kr = kIOUSBPipeStalled;
libusbp::error error(error_create_mach(kr, "Hey."));
REQUIRE(error.message() == "Hey. " STR_GENERAL_FAILURE " Error code 0xe000404f.");
REQUIRE(error.has_code(LIBUSBP_ERROR_STALL));
}
SECTION("timeout")
{
// We encounter this error if a control transfer times out.
// This is tested for in control_sync_test.cpp.
kern_return_t kr = kIOUSBTransactionTimeout;
libusbp::error error(error_create_mach(kr, "Hey."));
REQUIRE(error.message() == "Hey. " STR_TIMEOUT " Error code 0xe0004051.");
REQUIRE(error.has_code(LIBUSBP_ERROR_TIMEOUT));
}
SECTION("access denied")
{
// We encounter this error if an IOUSBInterface is already open for
// exclusive access when we try to open it for exclusive access.
// This is tested for in generic_handle_test.cpp.
kern_return_t kr = kIOReturnExclusiveAccess;
libusbp::error error(error_create_mach(kr, "Hey."));
REQUIRE(error.message() == "Hey. Access is denied. "
"Try closing all other programs that are using the device. "
"Error code 0xe00002c5.");
REQUIRE(error.has_code(LIBUSBP_ERROR_ACCESS_DENIED));
}
SECTION("overflow")
{
// This is an error we see when we read from a pipe using an
// IOUSBInterface and the device returns more data than can fit in our
// buffer.
kern_return_t kr = kIOReturnOverrun;
libusbp::error error(error_create_mach(kr, "Hi."));
REQUIRE(error.message() == "Hi. " STR_OVERFLOW " Error code 0xe00002e8.");
}
SECTION("cancellation")
{
// This is an error we see when we call ReadPipeAsync and
// then cancel it with AbortPipe().
kern_return_t kr = kIOReturnAborted;
libusbp::error error(error_create_mach(kr, "Hi."));
REQUIRE(error.message() == "Hi. " STR_CANCELLED " Error code 0xe00002eb.");
REQUIRE(error.has_code(LIBUSBP_ERROR_CANCELLED));
}
}
#endif
#if defined(_WIN32) || defined(__APPLE__)
TEST_CASE("error_create_hr", "[error_create_hr]")
{
SECTION("returns the right message")
{
libusbp::error error(error_create_hr(0x80070057, "Hi."));
REQUIRE(error.message() == "Hi. HRESULT error code 0x80070057.");
}
}
#endif
#endif // !NDEBUG

Some files were not shown because too many files have changed in this diff Show More